From 0b58d862ca166c0c17ce298b6770c9b636aa26fe Mon Sep 17 00:00:00 2001 From: Ala-Mansouri Date: Mon, 29 Aug 2022 02:25:18 +0100 Subject: [PATCH 01/24] GET_NAME_FIELD_BY_TAG: Added get name of field from it's json tag, added converting a map to a struct --- reflections.go | 39 +++++++++++++++++++++++++++++++++++++++ reflections_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/reflections.go b/reflections.go index b25b423..6988ac9 100644 --- a/reflections.go +++ b/reflections.go @@ -91,6 +91,26 @@ func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) { return field.Tag.Get(tagKey), nil } +// Function to get the the Field from it's json tag +func GetNameFieldByTag(obj interface{}, tag string, key string) (string, error) { + if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + return "", errors.New("Cannot use GetFieldByTag on a non-struct interface") + } + + objValue := reflectValue(obj) + objType := objValue.Type() + fieldsCount := objType.NumField() + + for i := 0; i < fieldsCount; i++ { + structField := objType.Field(i) + if structField.Tag.Get(key) == tag { + return structField.Name, nil + } + } + + return "", errors.New("tag doesn't exist in the given struct") +} + // SetField sets the provided obj field with provided value. obj param has // to be a pointer to a struct, otherwise it will soundly fail. Provided // value type should match with the struct field you're trying to set. @@ -177,6 +197,25 @@ func fields(obj interface{}, deep bool) ([]string, error) { return allFields, nil } +// Converts a map datatype to struct datatype +// obj must be a pointer to a structure +func MapToStruct(obj map[string]interface{}, result interface{}) error { + if !hasValidType(obj, []reflect.Kind{reflect.Map}) { + return errors.New("Cannot use MapToStruct on a non-map interface") + } + + if !hasValidType(result, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + return errors.New("result must be a pointer to a struct") + } + + t := reflect.ValueOf(result).Elem() + for k, v := range obj { + val := t.FieldByName(k) + val.Set(reflect.ValueOf(v)) + } + return nil +} + // Items returns the field - value struct pairs as a map. obj can whether // be a structure or pointer to structure. func Items(obj interface{}) (map[string]interface{}, error) { diff --git a/reflections_test.go b/reflections_test.go index 5546c46..e587d6b 100644 --- a/reflections_test.go +++ b/reflections_test.go @@ -443,6 +443,33 @@ func TestItems_deep(t *testing.T) { assert.Equal(t, itemsDeep["Number"], 17) } +func TestGetNameFieldByTag(t *testing.T) { + + dummyStruct := TestStruct{ + Dummy: "test", + Yummy: 123, + } + + tagJson := "dummytag" + field, err := GetNameFieldByTag(dummyStruct, tagJson, "test") + + assert.NoError(t, err) + assert.Equal(t, field, "Dummy") +} + +func TestGetNameFieldByTag_on_non_existing_tag(t *testing.T) { + + dummyStruct := TestStruct{ + Dummy: "test", + Yummy: 123, + } + + tagJson := "tag" + _, err := GetNameFieldByTag(dummyStruct, tagJson, "test") + + assert.Error(t, err) +} + func TestTags_deep(t *testing.T) { type Address struct { Street string `tag:"be"` @@ -504,3 +531,20 @@ func TestFields_deep(t *testing.T) { assert.Equal(t, fieldsDeep[1], "Street") assert.Equal(t, fieldsDeep[2], "Number") } + +func TestMapToStruct_on_map(t *testing.T) { + testMap := make(map[string]interface{}) + testMap["Dummy"] = "test" + testMap["Yummy"] = 123 + + dummyStruct := TestStruct{ + Dummy: "test", + Yummy: 123, + } + + var convertedMap TestStruct + err := MapToStruct(testMap, &convertedMap) + + assert.NoError(t, err) + assert.Equal(t, convertedMap, dummyStruct) +} From 1b6a64f337efcdc6c7eb413deb3b62859b21c4d6 Mon Sep 17 00:00:00 2001 From: Ala-Mansouri Date: Mon, 29 Aug 2022 02:42:29 +0100 Subject: [PATCH 02/24] GET_NAME_FIELD_BY_TAG: Typos in comment --- reflections.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflections.go b/reflections.go index 6988ac9..1bba256 100644 --- a/reflections.go +++ b/reflections.go @@ -198,7 +198,7 @@ func fields(obj interface{}, deep bool) ([]string, error) { } // Converts a map datatype to struct datatype -// obj must be a pointer to a structure +// result must be a pointer to a structure func MapToStruct(obj map[string]interface{}, result interface{}) error { if !hasValidType(obj, []reflect.Kind{reflect.Map}) { return errors.New("Cannot use MapToStruct on a non-map interface") From 461d22e2e44c3163aed469ecc5bfeac06c7151f8 Mon Sep 17 00:00:00 2001 From: oleiade Date: Sun, 4 Sep 2022 10:08:53 +0200 Subject: [PATCH 03/24] Refresh the README with links, grammar, and formatting --- README.md | 326 ++++++++++++++++++++++++++---------------------------- 1 file changed, 158 insertions(+), 168 deletions(-) diff --git a/README.md b/README.md index 8f6b38d..db58a6d 100644 --- a/README.md +++ b/README.md @@ -1,235 +1,225 @@ -Reflections -=========== +# Reflections -Package reflections provides high level abstractions above the golang reflect library. +[![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/) +[![Build Status](https://github.com/oleiade/reflections/actions/workflows/go.yml/badge.svg)](https://github.com/oleiade/reflections/actions/workflows/go.yml) +[![Go Documentation](https://pkg.go.dev/badge/github.com/oleiade/reflections)](https://pkg.go.dev/github.com/oleiade/reflections) +[![Go Report Card](https://goreportcard.com/badge/github.com/oleiade/reflections)](https://goreportcard.com/report/github.com/oleiade/reflections) +![Go Version](https://img.shields.io/github/go-mod/go-version/oleiade/reflections) -Reflect library is very low-level and can be quite complex when it comes to do simple things like accessing a structure field value, a field tag... +The `reflections` library provides high-level abstractions on top of the go language standard `reflect` library. -The purpose of reflections package is to make developers life easier when it comes to introspect structures at runtime. -Its API is inspired from python language (getattr, setattr, hasattr...) and provides a simplified access to structure fields and tags. +My experience of the `reflect` library's API is that it's somewhat low-level and unintuitive. Using it can rapidly become pretty complex, daunting, and scary, especially when doing simple things like accessing a structure field value, a field tag, etc. -*Reflections is an open source library under the MIT license. Any hackers are welcome to supply ideas, features requests, patches, pull requests and so on: see [Contribute]()* +The `reflections` package aims to make developers' life easier when it comes to introspect struct values at runtime. Its API is inspired by the python language `getattr,` `setattr,` and `hasattr` set of methods and provides simplified access to structure fields and tags. -#### Documentation +## Documentation -Documentation is available at http://godoc.org/github.com/oleiade/reflections +Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) to get more details on the library's API. ## Usage -#### Accessing structure fields +## Accessing structure fields -##### GetField +### GetField -*GetField* returns the content of a structure field. It can be very usefull when -you'd wanna iterate over a struct specific fields values for example. You can whether -provide *GetField* a structure or a pointer to structure as first argument. +`GetField` returns the content of a structure field. For example, it can be beneficial when you want to iterate over struct-specific field values. You can provide `GetField` a structure or a pointer to a struct as the first argument. ```go - s := MyStruct { - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } - - fieldsToExtract := []string{"FirstField", "ThirdField"} - - for _, fieldName := range fieldsToExtract { - value, err := reflections.GetField(s, fieldName) - DoWhatEverWithThatValue(value) - } + s := MyStruct { + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", + } + + fieldsToExtract := []string{"FirstField", "ThirdField"} + + for _, fieldName := range fieldsToExtract { + value, err := reflections.GetField(s, fieldName) + DoWhatEverWithThatValue(value) + } ``` -##### GetFieldKind +### GetFieldKind -*GetFieldKind* returns the [reflect.Kind](http://golang.org/src/pkg/reflect/type.go?s=6916:6930#L189) of a structure field. It can be used to operate type assertion over a structure fields at runtime. You can whether provide *GetFieldKind* a structure or a pointer to structure as first argument. +`GetFieldKind` returns the [`reflect.Kind`](http://golang.org/src/pkg/reflect/type.go?s=6916:6930#L189) of a structure field. You can use it to operate type assertion over a structure field at runtime. You can provide `GetFieldKind` a structure or a pointer to structure as the first argument. ```go - s := MyStruct{ - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } - - var firstFieldKind reflect.String - var secondFieldKind reflect.Int - var err error - - firstFieldKind, err = GetFieldKind(s, "FirstField") - if err != nil { - log.Fatal(err) - } - - secondFieldKind, err = GetFieldKind(s, "SecondField") - if err != nil { - log.Fatal(err) - } + s := MyStruct{ + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", + } + + var firstFieldKind reflect.String + var secondFieldKind reflect.Int + var err error + + firstFieldKind, err = GetFieldKind(s, "FirstField") + if err != nil { + log.Fatal(err) + } + + secondFieldKind, err = GetFieldKind(s, "SecondField") + if err != nil { + log.Fatal(err) + } ``` -##### GetFieldType +### GetFieldType -*GetFieldType* returns the string literal of a structure field type. It can be used to operate type assertion over a structure fields at runtime. You can whether provide *GetFieldType* a structure or a pointer to structure as first argument. +`GetFieldType` returns the string literal of a structure field type. You can use it to operate type assertion over a structure field at runtime. You can provide `GetFieldType` a structure or a pointer to structure as the first argument. ```go - s := MyStruct{ - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } - - var firstFieldKind string - var secondFieldKind string - var err error - - firstFieldKind, err = GetFieldType(s, "FirstField") - if err != nil { - log.Fatal(err) - } - - secondFieldKind, err = GetFieldType(s, "SecondField") - if err != nil { - log.Fatal(err) - } + s := MyStruct{ + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", + } + + var firstFieldKind string + var secondFieldKind string + var err error + + firstFieldKind, err = GetFieldType(s, "FirstField") + if err != nil { + log.Fatal(err) + } + + secondFieldKind, err = GetFieldType(s, "SecondField") + if err != nil { + log.Fatal(err) + } ``` -##### GetFieldTag - -*GetFieldTag* extracts a specific structure field tag. You can whether provide *GetFieldTag* a structure or a pointer to structure as first argument. +### GetFieldTag +`GetFieldTag` extracts a specific structure field tag. You can provide `GetFieldTag` a structure or a pointer to structure as the first argument. ```go - s := MyStruct{} - - tag, err := reflections.GetFieldTag(s, "FirstField", "matched") - if err != nil { - log.Fatal(err) - } - fmt.Println(tag) - - tag, err = reflections.GetFieldTag(s, "ThirdField", "unmatched") - if err != nil { - log.Fatal(err) - } - fmt.Println(tag) + s := MyStruct{} + + tag, err := reflections.GetFieldTag(s, "FirstField", "matched") + if err != nil { + log.Fatal(err) + } + fmt.Println(tag) + + tag, err = reflections.GetFieldTag(s, "ThirdField", "unmatched") + if err != nil { + log.Fatal(err) + } + fmt.Println(tag) ``` -##### HasField - -*HasField* asserts a field exists through structure. You can whether provide *HasField* a structure or a pointer to structure as first argument. +### HasField +`HasField` asserts a field exists through the structure. You can provide `HasField` a struct or a pointer to a struct as the first argument. ```go - s := MyStruct { - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } + s := MyStruct { + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", + } - // has == true - has, _ := reflections.HasField(s, "FirstField") + // has == true + has, _ := reflections.HasField(s, "FirstField") - // has == false - has, _ := reflections.HasField(s, "FourthField") + // has == false + has, _ := reflections.HasField(s, "FourthField") ``` -##### Fields - -*Fields* returns the list of a structure field names, so you can access or modify them later on. You can whether provide *Fields* a structure or a pointer to structure as first argument. +### Fields +`Fields` returns the list of structure field names so that you can access or modify them later. You can provide `Fields` with a struct or a pointer to a struct as the first argument. ```go - s := MyStruct { - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } - - var fields []string - - // Fields will list every structure exportable fields. - // Here, it's content would be equal to: - // []string{"FirstField", "SecondField", "ThirdField"} - fields, _ = reflections.Fields(s) + s := MyStruct { + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", + } + + var fields []string + + // Fields will list every structure exportable fields. + // Here, it's content would be equal to: + // []string{"FirstField", "SecondField", "ThirdField"} + fields, _ = reflections.Fields(s) ``` -##### Items - -*Items* returns the structure's field name to values map. You can whether provide *Items* a structure or a pointer to structure as first argument. +### Items +`Items` returns the structure's field name to the values map. You can provide `Items` with a struct or a pointer to structure as the first argument. ```go - s := MyStruct { - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } + s := MyStruct { + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", + } - var structItems map[string]interface{} + var structItems map[string]interface{} - // Items will return a field name to - // field value map - structItems, _ = reflections.Items(s) + // Items will return a field name to + // field value map + structItems, _ = reflections.Items(s) ``` -##### Tags - -*Tags* returns the structure's fields tag with the provided key. You can whether provide *Tags* a structure or a pointer to structure as first argument. +### Tags +`Tags` returns the structure's fields tag with the provided key. You can provide `Tags` with a struct or a pointer to a struct as the first argument. ```go - s := MyStruct { - FirstField: "first value", `matched:"first tag"` - SecondField: 2, `matched:"second tag"` - ThirdField: "third value", `unmatched:"third tag"` - } - - var structTags map[string]string - - // Tags will return a field name to tag content - // map. Nota that only field with the tag name - // you've provided which will be matched. - // Here structTags will contain: - // { - // "FirstField": "first tag", - // "SecondField": "second tag", - // } - structTags, _ = reflections.Tags(s, "matched") + s := MyStruct { + FirstField: "first value", `matched:"first tag"` + SecondField: 2, `matched:"second tag"` + ThirdField: "third value", `unmatched:"third tag"` + } + + var structTags map[string]string + + // Tags will return a field name to tag content + // map. N.B that only field with the tag name + // you've provided will be matched. + // Here structTags will contain: + // { + // "FirstField": "first tag", + // "SecondField": "second tag", + // } + structTags, _ = reflections.Tags(s, "matched") ``` -#### Set a structure field value +### Set a structure field value -*SetField* update's a structure's field value with the one provided. Note that -unexported fields cannot be set, and that field type and value type have to match. +`SetField` update's a structure's field value with the one provided. Note that you can't set unexported fields and that the field and value types must match. ```go - s := MyStruct { - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } - - // In order to be able to set the structure's values, - // a pointer to it has to be passed to it. - _ := reflections.SetField(&s, "FirstField", "new value") - - // If you try to set a field's value using the wrong type, - // an error will be returned - err := reflection.SetField(&s, "FirstField", 123) // err != nil + s := MyStruct { + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", + } + + //To be able to set the structure's values, + // it must be passed as a pointer. + _ := reflections.SetField(&s, "FirstField", "new value") + + // If you try to set a field's value using the wrong type, + // an error will be returned + err := reflection.SetField(&s, "FirstField", 123) // err != nil ``` ## Important notes -* **unexported fields** cannot be accessed or set using reflections library: the golang reflect library intentionaly prohibits unexported fields values access or modifications. - +- **unexported fields** cannot be accessed nor set using the `reflections` library. The Go lang standard `reflect` library intentionally prohibits unexported fields values access or modifications. ## Contribute -* Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. -* Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it). -* Write tests which shows that the bug was fixed or that the feature works as expected. -* Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_. +- Check for open issues or open a new issue to start a discussion around a feature idea or a bug. +- Fork `the repository`\_ on GitHub to start making your changes to the **master** branch (or branch off of it). +- Write tests showing that the bug was fixed or the feature works as expected. +- Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS\_. [the repository](http://github.com/oleiade/reflections) [AUTHORS](https://github.com/oleiade/reflections/blob/master/AUTHORS.md) - - -[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/oleiade/reflections/trend.png)](https://bitdeli.com/free "Bitdeli Badge") From 556d82a10ced74cb0b93e18b1b627fd57a74c1c8 Mon Sep 17 00:00:00 2001 From: oleiade Date: Sun, 4 Sep 2022 10:27:20 +0200 Subject: [PATCH 04/24] Add golangci linter config --- .golangci.yml | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..c0057a4 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,84 @@ +# v1.47.2 +# Please don't remove the first line. It uses in CI to determine the golangci version +run: + deadline: 5m + +issues: + # Maximum issues count per one linter. Set to 0 to disable. Default is 50. + max-issues-per-linter: 0 + # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. + max-same-issues: 0 + + # We want to try and improve the comments in the k6 codebase, so individual + # non-golint items from the default exclusion list will gradually be addded + # to the exclude-rules below + exclude-use-default: false + + exclude-rules: + # Exclude duplicate code and function length and complexity checking in test + # files (due to common repeats and long functions in test code) + - path: _(test|gen)\.go + linters: + - cyclop + - dupl + - gocognit + - funlen + - lll + - linters: + - paralleltest # false positive: https://github.com/kunwardeep/paralleltest/issues/8. + text: "does not use range value in test Run" + +linters-settings: + nolintlint: + # Disable to ensure that nolint directives don't have a leading space. Default is true. + allow-leading-space: false + exhaustive: + default-signifies-exhaustive: true + govet: + check-shadowing: true + cyclop: + max-complexity: 25 + maligned: + suggest-new: true + dupl: + threshold: 150 + goconst: + min-len: 10 + min-occurrences: 4 + funlen: + lines: 80 + statements: 60 + +linters: + enable-all: true + disable: + - nlreturn + - gci + - gochecknoinits + - godot + - godox + - gomodguard + - testpackage + - wsl + - gomnd + - goerr113 # most of the errors here are meant for humans + - goheader + - exhaustivestruct + - thelper + - gocyclo # replaced by cyclop since it also calculates the package complexity + - maligned # replaced by govet 'fieldalignment' + - interfacer # deprecated + - scopelint # deprecated, replaced by exportloopref + - wrapcheck # a little bit too much for k6, maybe after https://github.com/tomarrell/wrapcheck/issues/2 is fixed + - golint # this linter is deprecated + - varnamelen + - ireturn + - tagliatelle + - exhaustruct + - execinquery + - maintidx + - grouper + - decorder + - nonamedreturns + - nosnakecase + fast: false \ No newline at end of file From 20547cf18a5f58f3ebabf9112b56cc828a8e59c6 Mon Sep 17 00:00:00 2001 From: oleiade Date: Sun, 4 Sep 2022 10:27:36 +0200 Subject: [PATCH 05/24] Improve package top-level documentation --- reflections.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/reflections.go b/reflections.go index b25b423..c8e9935 100644 --- a/reflections.go +++ b/reflections.go @@ -4,12 +4,19 @@ /* Package reflections provides high level abstractions above the -reflect library. +Go standard [reflect] library. -Reflect library is very low-level and as can be quite complex when it comes to do simple things like accessing a structure field value, a field tag... +My experience of the `reflect` library's API is that it's somewhat low-level +and unintuitive. Using it can rapidly become pretty complex, +daunting, and scary, especially when doing simple things like +accessing a structure field value, a field tag, etc. -The purpose of reflections package is to make developers life easier when it comes to introspect structures at runtime. -It's API is freely inspired from python language (getattr, setattr, hasattr...) and provides a simplified access to structure fields and tags. +The `reflections` package aims to make developers' life easier when it comes to +introspect struct values at runtime. +Its API is inspired by the python language `getattr,` `setattr,` and `hasattr` set +of methods and provides simplified access to structure fields and tags. + +[reflect]: http://golang.org/pkg/reflect/ */ package reflections From c26d521fd3b05b51e8714bac8c451fbfe7199321 Mon Sep 17 00:00:00 2001 From: oleiade Date: Sun, 4 Sep 2022 10:29:44 +0200 Subject: [PATCH 06/24] Rename hasValidType to isSupportedType --- reflections.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/reflections.go b/reflections.go index c8e9935..6271f97 100644 --- a/reflections.go +++ b/reflections.go @@ -29,7 +29,7 @@ import ( // GetField returns the value of the provided obj field. obj can whether // be a structure or pointer to structure. func GetField(obj interface{}, name string) (interface{}, error) { - if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, errors.New("Cannot use GetField on a non-struct interface") } @@ -45,7 +45,7 @@ func GetField(obj interface{}, name string) (interface{}, error) { // GetFieldKind returns the kind of the provided obj field. obj can whether // be a structure or pointer to structure. func GetFieldKind(obj interface{}, name string) (reflect.Kind, error) { - if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return reflect.Invalid, errors.New("Cannot use GetField on a non-struct interface") } @@ -62,7 +62,7 @@ func GetFieldKind(obj interface{}, name string) (reflect.Kind, error) { // GetFieldType returns the kind of the provided obj field. obj can whether // be a structure or pointer to structure. func GetFieldType(obj interface{}, name string) (string, error) { - if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return "", errors.New("Cannot use GetField on a non-struct interface") } @@ -79,7 +79,7 @@ func GetFieldType(obj interface{}, name string) (string, error) { // GetFieldTag returns the provided obj field tag value. obj can whether // be a structure or pointer to structure. func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) { - if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return "", errors.New("Cannot use GetField on a non-struct interface") } @@ -129,7 +129,7 @@ func SetField(obj interface{}, name string, value interface{}) error { // HasField checks if the provided field name is part of a struct. obj can whether // be a structure or pointer to structure. func HasField(obj interface{}, name string) (bool, error) { - if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return false, errors.New("Cannot use GetField on a non-struct interface") } @@ -156,7 +156,7 @@ func FieldsDeep(obj interface{}) ([]string, error) { } func fields(obj interface{}, deep bool) ([]string, error) { - if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, errors.New("Cannot use GetField on a non-struct interface") } @@ -197,7 +197,7 @@ func ItemsDeep(obj interface{}) (map[string]interface{}, error) { } func items(obj interface{}, deep bool) (map[string]interface{}, error) { - if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, errors.New("Cannot use GetField on a non-struct interface") } @@ -241,7 +241,7 @@ func TagsDeep(obj interface{}, key string) (map[string]string, error) { } func tags(obj interface{}, key string, deep bool) (map[string]string, error) { - if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, errors.New("Cannot use GetField on a non-struct interface") } @@ -289,7 +289,7 @@ func isExportableField(field reflect.StructField) bool { return field.PkgPath == "" } -func hasValidType(obj interface{}, types []reflect.Kind) bool { +func isSupportedType(obj interface{}, types []reflect.Kind) bool { for _, t := range types { if reflect.TypeOf(obj).Kind() == t { return true From 1d0516300f65a648d576270cebba431bc799b25e Mon Sep 17 00:00:00 2001 From: oleiade Date: Sun, 4 Sep 2022 10:40:24 +0200 Subject: [PATCH 07/24] Improve error management according to golangci-lint recommendations --- reflections.go | 112 ++++++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/reflections.go b/reflections.go index 6271f97..0289b6b 100644 --- a/reflections.go +++ b/reflections.go @@ -26,17 +26,25 @@ import ( "reflect" ) +// ErrUnsupportedType indicates that the provided type is not supported +// by the requested reflection operation. +var ErrUnsupportedType = errors.New("unsupported type") + +// ErrUnexportedField indicates that an operation failed as a result of +// being applied on a non-exported struct field. +var ErrUnexportedField = errors.New("unexported field") + // GetField returns the value of the provided obj field. obj can whether // be a structure or pointer to structure. func GetField(obj interface{}, name string) (interface{}, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return nil, errors.New("Cannot use GetField on a non-struct interface") + return nil, fmt.Errorf("cannot use GetField on a non-struct object: %w", ErrUnsupportedType) } objValue := reflectValue(obj) field := objValue.FieldByName(name) if !field.IsValid() { - return nil, fmt.Errorf("No such field: %s in obj", name) + return nil, fmt.Errorf("no such field: %s in obj", name) } return field.Interface(), nil @@ -46,14 +54,14 @@ func GetField(obj interface{}, name string) (interface{}, error) { // be a structure or pointer to structure. func GetFieldKind(obj interface{}, name string) (reflect.Kind, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return reflect.Invalid, errors.New("Cannot use GetField on a non-struct interface") + return reflect.Invalid, fmt.Errorf("cannot use GetFieldKind on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) field := objValue.FieldByName(name) if !field.IsValid() { - return reflect.Invalid, fmt.Errorf("No such field: %s in obj", name) + return reflect.Invalid, fmt.Errorf("no such field: %s in obj", name) } return field.Type().Kind(), nil @@ -63,14 +71,14 @@ func GetFieldKind(obj interface{}, name string) (reflect.Kind, error) { // be a structure or pointer to structure. func GetFieldType(obj interface{}, name string) (string, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return "", errors.New("Cannot use GetField on a non-struct interface") + return "", fmt.Errorf("cannot use GetFieldType on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) field := objValue.FieldByName(name) if !field.IsValid() { - return "", fmt.Errorf("No such field: %s in obj", name) + return "", fmt.Errorf("no such field: %s in obj", name) } return field.Type().String(), nil @@ -80,7 +88,7 @@ func GetFieldType(obj interface{}, name string) (string, error) { // be a structure or pointer to structure. func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return "", errors.New("Cannot use GetField on a non-struct interface") + return "", fmt.Errorf("cannot use GetFieldTag on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) @@ -88,11 +96,11 @@ func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) { field, ok := objType.FieldByName(fieldName) if !ok { - return "", fmt.Errorf("No such field: %s in obj", fieldName) + return "", fmt.Errorf("no such field: %s in obj", fieldName) } if !isExportableField(field) { - return "", errors.New("Cannot GetFieldTag on a non-exported struct field") + return "", fmt.Errorf("cannot GetFieldTag on a non-exported struct field: %w", ErrUnexportedField) } return field.Tag.Get(tagKey), nil @@ -107,18 +115,18 @@ func SetField(obj interface{}, name string, value interface{}) error { structFieldValue := structValue.FieldByName(name) if !structFieldValue.IsValid() { - return fmt.Errorf("No such field: %s in obj", name) + return fmt.Errorf("no such field: %s in obj", name) } // If obj field value is not settable an error is thrown if !structFieldValue.CanSet() { - return fmt.Errorf("Cannot set %s field value", name) + return fmt.Errorf("cannot set %s field value", name) } structFieldType := structFieldValue.Type() val := reflect.ValueOf(value) if structFieldType != val.Type() { - invalidTypeError := errors.New("Provided value type didn't match obj field type") + invalidTypeError := fmt.Errorf("provided value type didn't match obj field type") return invalidTypeError } @@ -130,7 +138,7 @@ func SetField(obj interface{}, name string, value interface{}) error { // be a structure or pointer to structure. func HasField(obj interface{}, name string) (bool, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return false, errors.New("Cannot use GetField on a non-struct interface") + return false, fmt.Errorf("cannot use HasField on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) @@ -157,7 +165,7 @@ func FieldsDeep(obj interface{}) ([]string, error) { func fields(obj interface{}, deep bool) ([]string, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return nil, errors.New("Cannot use GetField on a non-struct interface") + return nil, fmt.Errorf("cannot use fields on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) @@ -168,16 +176,17 @@ func fields(obj interface{}, deep bool) ([]string, error) { for i := 0; i < fieldsCount; i++ { field := objType.Field(i) if isExportableField(field) { - if deep && field.Anonymous { - fieldValue := objValue.Field(i) - subFields, err := fields(fieldValue.Interface(), deep) - if err != nil { - return nil, fmt.Errorf("Cannot get fields in %s: %s", field.Name, err.Error()) - } - allFields = append(allFields, subFields...) - } else { + if !deep || !field.Anonymous { allFields = append(allFields, field.Name) + continue } + + fieldValue := objValue.Field(i) + subFields, err := fields(fieldValue.Interface(), deep) + if err != nil { + return nil, fmt.Errorf("cannot get fields in %s: %w", field.Name, err) + } + allFields = append(allFields, subFields...) } } @@ -190,7 +199,7 @@ func Items(obj interface{}) (map[string]interface{}, error) { return items(obj, false) } -// FieldsDeep returns "flattened" items (fields from anonymous +// ItemsDeep returns "flattened" items (fields from anonymous // inner structs are treated as normal fields) func ItemsDeep(obj interface{}) (map[string]interface{}, error) { return items(obj, true) @@ -198,7 +207,7 @@ func ItemsDeep(obj interface{}) (map[string]interface{}, error) { func items(obj interface{}, deep bool) (map[string]interface{}, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return nil, errors.New("Cannot use GetField on a non-struct interface") + return nil, fmt.Errorf("cannot use items on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) @@ -210,17 +219,20 @@ func items(obj interface{}, deep bool) (map[string]interface{}, error) { for i := 0; i < fieldsCount; i++ { field := objType.Field(i) fieldValue := objValue.Field(i) + if isExportableField(field) { - if deep && field.Anonymous { - if m, err := items(fieldValue.Interface(), deep); err == nil { - for k, v := range m { - allItems[k] = v - } - } else { - return nil, fmt.Errorf("Cannot get items in %s: %s", field.Name, err.Error()) - } - } else { + if !deep || !field.Anonymous { allItems[field.Name] = fieldValue.Interface() + continue + } + + m, err := items(fieldValue.Interface(), deep) + if err != nil { + return nil, fmt.Errorf("cannot get items in %s: %w", field.Name, err) + } + + for k, v := range m { + allItems[k] = v } } } @@ -234,7 +246,7 @@ func Tags(obj interface{}, key string) (map[string]string, error) { return tags(obj, key, false) } -// FieldsDeep returns "flattened" tags (fields from anonymous +// TagsDeep returns "flattened" tags (fields from anonymous // inner structs are treated as normal fields) func TagsDeep(obj interface{}, key string) (map[string]string, error) { return tags(obj, key, true) @@ -242,7 +254,7 @@ func TagsDeep(obj interface{}, key string) (map[string]string, error) { func tags(obj interface{}, key string, deep bool) (map[string]string, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return nil, errors.New("Cannot use GetField on a non-struct interface") + return nil, fmt.Errorf("cannot use tags on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) @@ -254,17 +266,19 @@ func tags(obj interface{}, key string, deep bool) (map[string]string, error) { for i := 0; i < fieldsCount; i++ { structField := objType.Field(i) if isExportableField(structField) { - if deep && structField.Anonymous { - fieldValue := objValue.Field(i) - if m, err := tags(fieldValue.Interface(), key, deep); err == nil { - for k, v := range m { - allTags[k] = v - } - } else { - return nil, fmt.Errorf("Cannot get items in %s: %s", structField.Name, err.Error()) - } - } else { + if !deep || !structField.Anonymous { allTags[structField.Name] = structField.Tag.Get(key) + continue + } + + fieldValue := objValue.Field(i) + m, err := tags(fieldValue.Interface(), key, deep) + if err != nil { + return nil, fmt.Errorf("cannot get items in %s: %w", structField.Name, err) + } + + for k, v := range m { + allTags[k] = v } } } @@ -298,11 +312,3 @@ func isSupportedType(obj interface{}, types []reflect.Kind) bool { return false } - -func isStruct(obj interface{}) bool { - return reflect.TypeOf(obj).Kind() == reflect.Struct -} - -func isPointer(obj interface{}) bool { - return reflect.TypeOf(obj).Kind() == reflect.Ptr -} From dc685858f097c5b4ba787abcf1738d3cd6bf19a4 Mon Sep 17 00:00:00 2001 From: oleiade Date: Sun, 4 Sep 2022 10:43:02 +0200 Subject: [PATCH 08/24] Address linting issues in tests --- reflections_test.go | 92 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/reflections_test.go b/reflections_test.go index 5546c46..4b094d4 100644 --- a/reflections_test.go +++ b/reflections_test.go @@ -18,6 +18,8 @@ type TestStruct struct { } func TestGetField_on_struct(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", } @@ -28,6 +30,8 @@ func TestGetField_on_struct(t *testing.T) { } func TestGetField_on_struct_pointer(t *testing.T) { + t.Parallel() + dummyStruct := &TestStruct{ Dummy: "test", } @@ -38,6 +42,8 @@ func TestGetField_on_struct_pointer(t *testing.T) { } func TestGetField_on_non_struct(t *testing.T) { + t.Parallel() + dummy := "abc 123" _, err := GetField(dummy, "Dummy") @@ -45,6 +51,8 @@ func TestGetField_on_non_struct(t *testing.T) { } func TestGetField_non_existing_field(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", } @@ -54,17 +62,21 @@ func TestGetField_non_existing_field(t *testing.T) { } func TestGetField_unexported_field(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ unexported: 12345, Dummy: "test", } assert.Panics(t, func() { - GetField(dummyStruct, "unexported") + GetField(dummyStruct, "unexported") //nolint:errcheck,gosec }) } func TestGetFieldKind_on_struct(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, @@ -80,6 +92,8 @@ func TestGetFieldKind_on_struct(t *testing.T) { } func TestGetFieldKind_on_struct_pointer(t *testing.T) { + t.Parallel() + dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, @@ -95,6 +109,8 @@ func TestGetFieldKind_on_struct_pointer(t *testing.T) { } func TestGetFieldKind_on_non_struct(t *testing.T) { + t.Parallel() + dummy := "abc 123" _, err := GetFieldKind(dummy, "Dummy") @@ -102,6 +118,8 @@ func TestGetFieldKind_on_non_struct(t *testing.T) { } func TestGetFieldKind_non_existing_field(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, @@ -112,6 +130,8 @@ func TestGetFieldKind_non_existing_field(t *testing.T) { } func TestGetFieldType_on_struct(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, @@ -127,6 +147,8 @@ func TestGetFieldType_on_struct(t *testing.T) { } func TestGetFieldType_on_struct_pointer(t *testing.T) { + t.Parallel() + dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, @@ -142,6 +164,8 @@ func TestGetFieldType_on_struct_pointer(t *testing.T) { } func TestGetFieldType_on_non_struct(t *testing.T) { + t.Parallel() + dummy := "abc 123" _, err := GetFieldType(dummy, "Dummy") @@ -149,6 +173,8 @@ func TestGetFieldType_on_non_struct(t *testing.T) { } func TestGetFieldType_non_existing_field(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, @@ -159,6 +185,8 @@ func TestGetFieldType_non_existing_field(t *testing.T) { } func TestGetFieldTag_on_struct(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{} tag, err := GetFieldTag(dummyStruct, "Dummy", "test") @@ -171,6 +199,8 @@ func TestGetFieldTag_on_struct(t *testing.T) { } func TestGetFieldTag_on_struct_pointer(t *testing.T) { + t.Parallel() + dummyStruct := &TestStruct{} tag, err := GetFieldTag(dummyStruct, "Dummy", "test") @@ -183,6 +213,8 @@ func TestGetFieldTag_on_struct_pointer(t *testing.T) { } func TestGetFieldTag_on_non_struct(t *testing.T) { + t.Parallel() + dummy := "abc 123" _, err := GetFieldTag(dummy, "Dummy", "test") @@ -190,6 +222,8 @@ func TestGetFieldTag_on_non_struct(t *testing.T) { } func TestGetFieldTag_non_existing_field(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{} _, err := GetFieldTag(dummyStruct, "obladioblada", "test") @@ -197,6 +231,8 @@ func TestGetFieldTag_non_existing_field(t *testing.T) { } func TestGetFieldTag_unexported_field(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ unexported: 12345, Dummy: "test", @@ -207,6 +243,8 @@ func TestGetFieldTag_unexported_field(t *testing.T) { } func TestSetField_on_struct_with_valid_value_type(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", } @@ -216,14 +254,9 @@ func TestSetField_on_struct_with_valid_value_type(t *testing.T) { assert.Equal(t, dummyStruct.Dummy, "abc") } -// func TestSetField_on_non_struct(t *testing.T) { -// dummy := "abc 123" - -// err := SetField(&dummy, "Dummy", "abc") -// assert.Error(t, err) -// } - func TestSetField_non_existing_field(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", } @@ -233,6 +266,8 @@ func TestSetField_non_existing_field(t *testing.T) { } func TestSetField_invalid_value_type(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", } @@ -242,6 +277,8 @@ func TestSetField_invalid_value_type(t *testing.T) { } func TestSetField_non_exported_field(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", } @@ -250,6 +287,8 @@ func TestSetField_non_exported_field(t *testing.T) { } func TestFields_on_struct(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, @@ -261,6 +300,8 @@ func TestFields_on_struct(t *testing.T) { } func TestFields_on_struct_pointer(t *testing.T) { + t.Parallel() + dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, @@ -272,6 +313,8 @@ func TestFields_on_struct_pointer(t *testing.T) { } func TestFields_on_non_struct(t *testing.T) { + t.Parallel() + dummy := "abc 123" _, err := Fields(dummy) @@ -279,6 +322,8 @@ func TestFields_on_non_struct(t *testing.T) { } func TestFields_with_non_exported_fields(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ unexported: 6789, Dummy: "test", @@ -291,6 +336,8 @@ func TestFields_with_non_exported_fields(t *testing.T) { } func TestHasField_on_struct_with_existing_field(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, @@ -302,6 +349,8 @@ func TestHasField_on_struct_with_existing_field(t *testing.T) { } func TestHasField_on_struct_pointer_with_existing_field(t *testing.T) { + t.Parallel() + dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, @@ -313,6 +362,8 @@ func TestHasField_on_struct_pointer_with_existing_field(t *testing.T) { } func TestHasField_non_existing_field(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, @@ -324,6 +375,8 @@ func TestHasField_non_existing_field(t *testing.T) { } func TestHasField_on_non_struct(t *testing.T) { + t.Parallel() + dummy := "abc 123" _, err := HasField(dummy, "Test") @@ -331,6 +384,8 @@ func TestHasField_on_non_struct(t *testing.T) { } func TestHasField_unexported_field(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ unexported: 7890, Dummy: "test", @@ -343,6 +398,8 @@ func TestHasField_unexported_field(t *testing.T) { } func TestTags_on_struct(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, @@ -357,6 +414,8 @@ func TestTags_on_struct(t *testing.T) { } func TestTags_on_struct_pointer(t *testing.T) { + t.Parallel() + dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, @@ -371,6 +430,8 @@ func TestTags_on_struct_pointer(t *testing.T) { } func TestTags_on_non_struct(t *testing.T) { + t.Parallel() + dummy := "abc 123" _, err := Tags(dummy, "test") @@ -378,6 +439,8 @@ func TestTags_on_non_struct(t *testing.T) { } func TestItems_on_struct(t *testing.T) { + t.Parallel() + dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, @@ -392,6 +455,8 @@ func TestItems_on_struct(t *testing.T) { } func TestItems_on_struct_pointer(t *testing.T) { + t.Parallel() + dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, @@ -406,13 +471,18 @@ func TestItems_on_struct_pointer(t *testing.T) { } func TestItems_on_non_struct(t *testing.T) { + t.Parallel() + dummy := "abc 123" _, err := Items(dummy) assert.Error(t, err) } +//nolint:unused func TestItems_deep(t *testing.T) { + t.Parallel() + type Address struct { Street string `tag:"be"` Number int `tag:"bi"` @@ -443,7 +513,10 @@ func TestItems_deep(t *testing.T) { assert.Equal(t, itemsDeep["Number"], 17) } +//nolint:unused func TestTags_deep(t *testing.T) { + t.Parallel() + type Address struct { Street string `tag:"be"` Number int `tag:"bi"` @@ -474,7 +547,10 @@ func TestTags_deep(t *testing.T) { assert.Equal(t, tagsDeep["Number"], "bi") } +//nolint:unused func TestFields_deep(t *testing.T) { + t.Parallel() + type Address struct { Street string `tag:"be"` Number int `tag:"bi"` From 3682b3e76a4ea2375e2d08fbf579e8cfd78d3a6a Mon Sep 17 00:00:00 2001 From: oleiade Date: Sun, 4 Sep 2022 11:03:27 +0200 Subject: [PATCH 09/24] Add linting stage to Github actions --- .github/workflows/go.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 1c5b486..e54a9df 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -12,7 +12,6 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - name: Set up Go 1.x uses: actions/setup-go@v2 with: @@ -21,16 +20,11 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@v2 - - name: Get dependencies - run: | - go get -v -t -d ./... - if [ -f Gopkg.toml ]; then - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh - dep ensure - fi - - name: Build run: go build -v . - name: Test run: go test -v . + + - name: Lint + uses: golangci/golangci-lint-action@v3.2.0 From f703b021fac72871d92f15ad8191638830912c4e Mon Sep 17 00:00:00 2001 From: oleiade Date: Sun, 4 Sep 2022 11:10:55 +0200 Subject: [PATCH 10/24] Fix README's examples indentation --- README.md | 246 +++++++++++++++++++++++++++--------------------------- 1 file changed, 123 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index db58a6d..c7ef71f 100644 --- a/README.md +++ b/README.md @@ -25,18 +25,18 @@ Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) t `GetField` returns the content of a structure field. For example, it can be beneficial when you want to iterate over struct-specific field values. You can provide `GetField` a structure or a pointer to a struct as the first argument. ```go - s := MyStruct { - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } - - fieldsToExtract := []string{"FirstField", "ThirdField"} - - for _, fieldName := range fieldsToExtract { - value, err := reflections.GetField(s, fieldName) - DoWhatEverWithThatValue(value) - } +s := MyStruct { + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", +} + +fieldsToExtract := []string{"FirstField", "ThirdField"} + +for _, fieldName := range fieldsToExtract { + value, err := reflections.GetField(s, fieldName) + DoWhatEverWithThatValue(value) +} ``` ### GetFieldKind @@ -44,25 +44,25 @@ Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) t `GetFieldKind` returns the [`reflect.Kind`](http://golang.org/src/pkg/reflect/type.go?s=6916:6930#L189) of a structure field. You can use it to operate type assertion over a structure field at runtime. You can provide `GetFieldKind` a structure or a pointer to structure as the first argument. ```go - s := MyStruct{ - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } - - var firstFieldKind reflect.String - var secondFieldKind reflect.Int - var err error - - firstFieldKind, err = GetFieldKind(s, "FirstField") - if err != nil { - log.Fatal(err) - } - - secondFieldKind, err = GetFieldKind(s, "SecondField") - if err != nil { - log.Fatal(err) - } +s := MyStruct{ + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", +} + +var firstFieldKind reflect.String +var secondFieldKind reflect.Int +var err error + +firstFieldKind, err = GetFieldKind(s, "FirstField") +if err != nil { + log.Fatal(err) +} + +secondFieldKind, err = GetFieldKind(s, "SecondField") +if err != nil { + log.Fatal(err) +} ``` ### GetFieldType @@ -70,25 +70,25 @@ Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) t `GetFieldType` returns the string literal of a structure field type. You can use it to operate type assertion over a structure field at runtime. You can provide `GetFieldType` a structure or a pointer to structure as the first argument. ```go - s := MyStruct{ - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } - - var firstFieldKind string - var secondFieldKind string - var err error - - firstFieldKind, err = GetFieldType(s, "FirstField") - if err != nil { - log.Fatal(err) - } - - secondFieldKind, err = GetFieldType(s, "SecondField") - if err != nil { - log.Fatal(err) - } +s := MyStruct{ + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", +} + +var firstFieldKind string +var secondFieldKind string +var err error + +firstFieldKind, err = GetFieldType(s, "FirstField") +if err != nil { + log.Fatal(err) +} + +secondFieldKind, err = GetFieldType(s, "SecondField") +if err != nil { + log.Fatal(err) +} ``` ### GetFieldTag @@ -96,19 +96,19 @@ Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) t `GetFieldTag` extracts a specific structure field tag. You can provide `GetFieldTag` a structure or a pointer to structure as the first argument. ```go - s := MyStruct{} - - tag, err := reflections.GetFieldTag(s, "FirstField", "matched") - if err != nil { - log.Fatal(err) - } - fmt.Println(tag) - - tag, err = reflections.GetFieldTag(s, "ThirdField", "unmatched") - if err != nil { - log.Fatal(err) - } - fmt.Println(tag) +s := MyStruct{} + +tag, err := reflections.GetFieldTag(s, "FirstField", "matched") +if err != nil { + log.Fatal(err) +} +fmt.Println(tag) + +tag, err = reflections.GetFieldTag(s, "ThirdField", "unmatched") +if err != nil { + log.Fatal(err) +} +fmt.Println(tag) ``` ### HasField @@ -116,17 +116,17 @@ Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) t `HasField` asserts a field exists through the structure. You can provide `HasField` a struct or a pointer to a struct as the first argument. ```go - s := MyStruct { - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } +s := MyStruct { + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", +} - // has == true - has, _ := reflections.HasField(s, "FirstField") +// has == true +has, _ := reflections.HasField(s, "FirstField") - // has == false - has, _ := reflections.HasField(s, "FourthField") +// has == false +has, _ := reflections.HasField(s, "FourthField") ``` ### Fields @@ -134,18 +134,18 @@ Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) t `Fields` returns the list of structure field names so that you can access or modify them later. You can provide `Fields` with a struct or a pointer to a struct as the first argument. ```go - s := MyStruct { - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } - - var fields []string - - // Fields will list every structure exportable fields. - // Here, it's content would be equal to: - // []string{"FirstField", "SecondField", "ThirdField"} - fields, _ = reflections.Fields(s) +s := MyStruct { + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", +} + +var fields []string + +// Fields will list every structure exportable fields. +// Here, it's content would be equal to: +// []string{"FirstField", "SecondField", "ThirdField"} +fields, _ = reflections.Fields(s) ``` ### Items @@ -153,17 +153,17 @@ Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) t `Items` returns the structure's field name to the values map. You can provide `Items` with a struct or a pointer to structure as the first argument. ```go - s := MyStruct { - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } +s := MyStruct { + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", +} - var structItems map[string]interface{} +var structItems map[string]interface{} - // Items will return a field name to - // field value map - structItems, _ = reflections.Items(s) +// Items will return a field name to +// field value map +structItems, _ = reflections.Items(s) ``` ### Tags @@ -171,23 +171,23 @@ Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) t `Tags` returns the structure's fields tag with the provided key. You can provide `Tags` with a struct or a pointer to a struct as the first argument. ```go - s := MyStruct { - FirstField: "first value", `matched:"first tag"` - SecondField: 2, `matched:"second tag"` - ThirdField: "third value", `unmatched:"third tag"` - } - - var structTags map[string]string - - // Tags will return a field name to tag content - // map. N.B that only field with the tag name - // you've provided will be matched. - // Here structTags will contain: - // { - // "FirstField": "first tag", - // "SecondField": "second tag", - // } - structTags, _ = reflections.Tags(s, "matched") +s := MyStruct { + FirstField: "first value", `matched:"first tag"` + SecondField: 2, `matched:"second tag"` + ThirdField: "third value", `unmatched:"third tag"` +} + +var structTags map[string]string + +// Tags will return a field name to tag content +// map. N.B that only field with the tag name +// you've provided will be matched. +// Here structTags will contain: +// { +// "FirstField": "first tag", +// "SecondField": "second tag", +// } +structTags, _ = reflections.Tags(s, "matched") ``` ### Set a structure field value @@ -195,19 +195,19 @@ Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) t `SetField` update's a structure's field value with the one provided. Note that you can't set unexported fields and that the field and value types must match. ```go - s := MyStruct { - FirstField: "first value", - SecondField: 2, - ThirdField: "third value", - } - - //To be able to set the structure's values, - // it must be passed as a pointer. - _ := reflections.SetField(&s, "FirstField", "new value") - - // If you try to set a field's value using the wrong type, - // an error will be returned - err := reflection.SetField(&s, "FirstField", 123) // err != nil +s := MyStruct { + FirstField: "first value", + SecondField: 2, + ThirdField: "third value", +} + +//To be able to set the structure's values, +// it must be passed as a pointer. +_ := reflections.SetField(&s, "FirstField", "new value") + +// If you try to set a field's value using the wrong type, +// an error will be returned +err := reflection.SetField(&s, "FirstField", 123) // err != nil ``` ## Important notes From 1740dfd6aaa7b59197159b8ed1c7d1cc91a01158 Mon Sep 17 00:00:00 2001 From: Ala-Mansouri Date: Sun, 4 Sep 2022 19:49:39 +0100 Subject: [PATCH 11/24] GetFieldNameByTagValue: added example in example_test.go, removed MapToStruct and it's test, updated function name, added more test cases in reflections_test.go, added short example in Readme file --- README.md | 20 ++++++++++++++++++++ example_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ reflections.go | 26 ++++---------------------- reflections_test.go | 38 ++++++++++++++++---------------------- 4 files changed, 84 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 8f6b38d..09a99df 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,26 @@ unexported fields cannot be set, and that field type and value type have to matc err := reflection.SetField(&s, "FirstField", 123) // err != nil ``` +##### GetFieldNameByTagValue + +*GetFieldNameByTagValue* looks up a field with a matching `{tagKey}:"{tagValue}"` tag in the provided `obj` item. +If `obj` is not a `struct`, nor a `pointer`, or it does not have a field tagged with the `tagKey`, and the matching `tagValue`, this function returns an error. + +```go + s := MyStruct { + FirstField: "first value", `matched:"first tag"` + SecondField: 2, `matched:"second tag"` + ThirdField: "third value", `unmatched:"third tag"` + } + + // Getting field name from external source as json would be headache to convert it manually, so we get it directly from struct tag + // returns fieldName = "FirstField" + fieldName, _ = reflections.GetFieldNameByTagValue(s, "first tag", "matched"); + + // later we can do GetField(s, fieldName) +``` + + ## Important notes * **unexported fields** cannot be accessed or set using reflections library: the golang reflect library intentionaly prohibits unexported fields values access or modifications. diff --git a/example_test.go b/example_test.go index c715b43..56d5a88 100644 --- a/example_test.go +++ b/example_test.go @@ -1,6 +1,7 @@ package reflections_test import ( + "encoding/json" "fmt" "log" "reflect" @@ -213,3 +214,46 @@ func ExampleSetField() { log.Fatal(err) } } + +func ExampleGetFieldNameByTagValue() { + type Order struct { + Step string `json:"order_step"` + Id string `json:"id"` + Category string `json:"category"` + } + type Condition struct { + Field string `json:"field"` + Value string `json:"value"` + Next string `json:"next"` + } + + // JSON data from external source + orderJson := `{ + "order_step": "cooking", + "id": "45457-fv54f54", + "category": "Pizzas" + }` + + conditionJson := `{ + "field": "order_step", + "value": "cooking", + "next": "serve" + }` + + // Storing Json in corresponding Variables + var order Order + json.Unmarshal([]byte(orderJson), &order) + + var condition Condition + json.Unmarshal([]byte(conditionJson), &condition) + + // example + // condition.Field = "order_step" + // we need to get this value order[condition.Field] + // but condition.Field in go needs to be "Step" not "order_step" + // this is what GetFieldNameByTagValue is about + // returns fieldName = "Step" + fieldName, _ := reflections.GetFieldNameByTagValue(condition, condition.Field, "json") + fieldValue, _ := reflections.GetField(order, fieldName) + fmt.Println(fieldValue) +} diff --git a/reflections.go b/reflections.go index 1bba256..b8170da 100644 --- a/reflections.go +++ b/reflections.go @@ -91,8 +91,9 @@ func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) { return field.Tag.Get(tagKey), nil } -// Function to get the the Field from it's json tag -func GetNameFieldByTag(obj interface{}, tag string, key string) (string, error) { +// GetFieldNameByTagValue looks up a field with a matching `{tagKey}:"{tagValue}"` tag in the provided `obj` item. +// If `obj` is not a `struct`, nor a `pointer`, or it does not have a field tagged with the `tagKey`, and the matching `tagValue`, this function returns an error. +func GetFieldNameByTagValue(obj interface{}, tagValue string, tagKey string) (string, error) { if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return "", errors.New("Cannot use GetFieldByTag on a non-struct interface") } @@ -103,7 +104,7 @@ func GetNameFieldByTag(obj interface{}, tag string, key string) (string, error) for i := 0; i < fieldsCount; i++ { structField := objType.Field(i) - if structField.Tag.Get(key) == tag { + if structField.Tag.Get(tagKey) == tagValue { return structField.Name, nil } } @@ -197,25 +198,6 @@ func fields(obj interface{}, deep bool) ([]string, error) { return allFields, nil } -// Converts a map datatype to struct datatype -// result must be a pointer to a structure -func MapToStruct(obj map[string]interface{}, result interface{}) error { - if !hasValidType(obj, []reflect.Kind{reflect.Map}) { - return errors.New("Cannot use MapToStruct on a non-map interface") - } - - if !hasValidType(result, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return errors.New("result must be a pointer to a struct") - } - - t := reflect.ValueOf(result).Elem() - for k, v := range obj { - val := t.FieldByName(k) - val.Set(reflect.ValueOf(v)) - } - return nil -} - // Items returns the field - value struct pairs as a map. obj can whether // be a structure or pointer to structure. func Items(obj interface{}) (map[string]interface{}, error) { diff --git a/reflections_test.go b/reflections_test.go index e587d6b..afb3ed3 100644 --- a/reflections_test.go +++ b/reflections_test.go @@ -446,12 +446,12 @@ func TestItems_deep(t *testing.T) { func TestGetNameFieldByTag(t *testing.T) { dummyStruct := TestStruct{ - Dummy: "test", + Dummy: "dummy", Yummy: 123, } tagJson := "dummytag" - field, err := GetNameFieldByTag(dummyStruct, tagJson, "test") + field, err := GetFieldNameByTagValue(dummyStruct, tagJson, "test") assert.NoError(t, err) assert.Equal(t, field, "Dummy") @@ -460,14 +460,25 @@ func TestGetNameFieldByTag(t *testing.T) { func TestGetNameFieldByTag_on_non_existing_tag(t *testing.T) { dummyStruct := TestStruct{ - Dummy: "test", + Dummy: "dummy", Yummy: 123, } + // non existing tag value with an existing tag key tagJson := "tag" - _, err := GetNameFieldByTag(dummyStruct, tagJson, "test") + _, errTagValue := GetFieldNameByTagValue(dummyStruct, tagJson, "test") + assert.Error(t, errTagValue) + + // non existing tag key with an existing tag value + tagJson = "dummytag" + _, errTagKey := GetFieldNameByTagValue(dummyStruct, tagJson, "json") + assert.Error(t, errTagKey) + + // non existing tag key and value + tagJson = "tag" + _, errTagKeyValue := GetFieldNameByTagValue(dummyStruct, tagJson, "json") + assert.Error(t, errTagKeyValue) - assert.Error(t, err) } func TestTags_deep(t *testing.T) { @@ -531,20 +542,3 @@ func TestFields_deep(t *testing.T) { assert.Equal(t, fieldsDeep[1], "Street") assert.Equal(t, fieldsDeep[2], "Number") } - -func TestMapToStruct_on_map(t *testing.T) { - testMap := make(map[string]interface{}) - testMap["Dummy"] = "test" - testMap["Yummy"] = 123 - - dummyStruct := TestStruct{ - Dummy: "test", - Yummy: 123, - } - - var convertedMap TestStruct - err := MapToStruct(testMap, &convertedMap) - - assert.NoError(t, err) - assert.Equal(t, convertedMap, dummyStruct) -} From ee7c56ab01769bdadd7c3725bab92b2393b57c7d Mon Sep 17 00:00:00 2001 From: Ala-Mansouri Date: Sun, 4 Sep 2022 19:53:31 +0100 Subject: [PATCH 12/24] GetFieldNameByTagValue: fixed some Readme Typos --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 09a99df..f365f27 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,8 @@ If `obj` is not a `struct`, nor a `pointer`, or it does not have a field tagged ThirdField: "third value", `unmatched:"third tag"` } - // Getting field name from external source as json would be headache to convert it manually, so we get it directly from struct tag + // Getting field name from external source as json would be a headache to convert it manually, + // so we get it directly from struct tag // returns fieldName = "FirstField" fieldName, _ = reflections.GetFieldNameByTagValue(s, "first tag", "matched"); From 4110f635de2c54215061f725ad277c7a648b7bfd Mon Sep 17 00:00:00 2001 From: Ala-Mansouri Date: Sun, 4 Sep 2022 20:10:03 +0100 Subject: [PATCH 13/24] GetFieldNameByTagValue: updated test names --- reflections_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reflections_test.go b/reflections_test.go index afb3ed3..871358d 100644 --- a/reflections_test.go +++ b/reflections_test.go @@ -443,7 +443,7 @@ func TestItems_deep(t *testing.T) { assert.Equal(t, itemsDeep["Number"], 17) } -func TestGetNameFieldByTag(t *testing.T) { +func TestGetFieldNameByTagValue(t *testing.T) { dummyStruct := TestStruct{ Dummy: "dummy", @@ -457,7 +457,7 @@ func TestGetNameFieldByTag(t *testing.T) { assert.Equal(t, field, "Dummy") } -func TestGetNameFieldByTag_on_non_existing_tag(t *testing.T) { +func TestGetFieldNameByTagValue_on_non_existing_tag(t *testing.T) { dummyStruct := TestStruct{ Dummy: "dummy", From cc76746290ab865fd0647c57e342768c6265bc6d Mon Sep 17 00:00:00 2001 From: oleiade Date: Sun, 4 Sep 2022 22:15:18 +0200 Subject: [PATCH 14/24] Improve technical writing thanks to the vale linter --- .gitignore | 2 ++ .vale.ini | 9 ++++++ README.md | 24 +++++++-------- reflections.go | 84 ++++++++++++++++++++++++-------------------------- 4 files changed, 64 insertions(+), 55 deletions(-) create mode 100644 .vale.ini diff --git a/.gitignore b/.gitignore index 0026861..20682a2 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ _cgo_export.* _testmain.go *.exe + +styles/ \ No newline at end of file diff --git a/.vale.ini b/.vale.ini new file mode 100644 index 0000000..133bc22 --- /dev/null +++ b/.vale.ini @@ -0,0 +1,9 @@ +StylesPath = styles + +MinAlertLevel = suggestion +Vocab = Lane + +Packages = Google, proselint, write-good + +[*] +BasedOnStyles = Vale, Google, proselint, write-good diff --git a/README.md b/README.md index c7ef71f..bcc40a4 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ The `reflections` library provides high-level abstractions on top of the go language standard `reflect` library. -My experience of the `reflect` library's API is that it's somewhat low-level and unintuitive. Using it can rapidly become pretty complex, daunting, and scary, especially when doing simple things like accessing a structure field value, a field tag, etc. +In practice, the `reflect` library's API proves somewhat low-level and un-intuitive. Using it can turn out pretty complex, daunting, and scary, especially when doing simple things like accessing a structure field value, a field tag, etc. -The `reflections` package aims to make developers' life easier when it comes to introspect struct values at runtime. Its API is inspired by the python language `getattr,` `setattr,` and `hasattr` set of methods and provides simplified access to structure fields and tags. +The `reflections` package aims to make developers' life easier when it comes to introspect struct values at runtime. Its API takes inspiration in the python language's `getattr,` `setattr,` and `hasattr` set of methods and provides simplified access to structure fields and tags. ## Documentation @@ -20,9 +20,9 @@ Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) t ## Accessing structure fields -### GetField +### `GetField` -`GetField` returns the content of a structure field. For example, it can be beneficial when you want to iterate over struct-specific field values. You can provide `GetField` a structure or a pointer to a struct as the first argument. +`GetField` returns the content of a structure field. For example, it proves beneficial when you want to iterate over struct-specific field values. You can provide `GetField` a structure or a pointer to a struct as the first argument. ```go s := MyStruct { @@ -39,7 +39,7 @@ for _, fieldName := range fieldsToExtract { } ``` -### GetFieldKind +### `GetFieldKind` `GetFieldKind` returns the [`reflect.Kind`](http://golang.org/src/pkg/reflect/type.go?s=6916:6930#L189) of a structure field. You can use it to operate type assertion over a structure field at runtime. You can provide `GetFieldKind` a structure or a pointer to structure as the first argument. @@ -65,7 +65,7 @@ if err != nil { } ``` -### GetFieldType +### `GetFieldType` `GetFieldType` returns the string literal of a structure field type. You can use it to operate type assertion over a structure field at runtime. You can provide `GetFieldType` a structure or a pointer to structure as the first argument. @@ -91,7 +91,7 @@ if err != nil { } ``` -### GetFieldTag +### `GetFieldTag` `GetFieldTag` extracts a specific structure field tag. You can provide `GetFieldTag` a structure or a pointer to structure as the first argument. @@ -111,7 +111,7 @@ if err != nil { fmt.Println(tag) ``` -### HasField +### `HasField` `HasField` asserts a field exists through the structure. You can provide `HasField` a struct or a pointer to a struct as the first argument. @@ -131,7 +131,7 @@ has, _ := reflections.HasField(s, "FourthField") ### Fields -`Fields` returns the list of structure field names so that you can access or modify them later. You can provide `Fields` with a struct or a pointer to a struct as the first argument. +`Fields` returns the list of structure field names so that you can access or update them later. You can provide `Fields` with a struct or a pointer to a struct as the first argument. ```go s := MyStruct { @@ -192,7 +192,7 @@ structTags, _ = reflections.Tags(s, "matched") ### Set a structure field value -`SetField` update's a structure's field value with the one provided. Note that you can't set unexported fields and that the field and value types must match. +`SetField` update's a structure's field value with the one provided. Note that you can't set un-exported fields and that the field and value types must match. ```go s := MyStruct { @@ -212,12 +212,12 @@ err := reflection.SetField(&s, "FirstField", 123) // err != nil ## Important notes -- **unexported fields** cannot be accessed nor set using the `reflections` library. The Go lang standard `reflect` library intentionally prohibits unexported fields values access or modifications. +- **un-exported fields** can't be accessed nor set using the `reflections` library. The Go lang standard `reflect` library intentionally prohibits un-exported fields values access or modifications. ## Contribute - Check for open issues or open a new issue to start a discussion around a feature idea or a bug. -- Fork `the repository`\_ on GitHub to start making your changes to the **master** branch (or branch off of it). +- Fork `the repository`\_ on GitHub to start making your changes to the **master** branch, or branch off of it. - Write tests showing that the bug was fixed or the feature works as expected. - Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS\_. diff --git a/reflections.go b/reflections.go index 0289b6b..be2bc02 100644 --- a/reflections.go +++ b/reflections.go @@ -2,22 +2,17 @@ // // See the file LICENSE for copying permission. -/* -Package reflections provides high level abstractions above the -Go standard [reflect] library. - -My experience of the `reflect` library's API is that it's somewhat low-level -and unintuitive. Using it can rapidly become pretty complex, -daunting, and scary, especially when doing simple things like -accessing a structure field value, a field tag, etc. - -The `reflections` package aims to make developers' life easier when it comes to -introspect struct values at runtime. -Its API is inspired by the python language `getattr,` `setattr,` and `hasattr` set -of methods and provides simplified access to structure fields and tags. - -[reflect]: http://golang.org/pkg/reflect/ -*/ +// Package reflections provides high level abstractions over the Go standard [reflect] library. +// +// In practice, the `reflect` library's API proves somewhat low-level and un-intuitive. +// Using it can turn out pretty complex, daunting, and scary, especially when doing simple +// things like accessing a structure field value, a field tag, etc. +// +// The `reflections` package aims to make developers' life easier when it comes to introspect +// struct values at runtime. Its API takes inspiration in the python language's `getattr,` `setattr,` and `hasattr` set +// of methods and provides simplified access to structure fields and tags. +// +// [reflect]: http://golang.org/pkg/reflect/ package reflections import ( @@ -31,11 +26,11 @@ import ( var ErrUnsupportedType = errors.New("unsupported type") // ErrUnexportedField indicates that an operation failed as a result of -// being applied on a non-exported struct field. +// applying to a non-exported struct field. var ErrUnexportedField = errors.New("unexported field") -// GetField returns the value of the provided obj field. obj can whether -// be a structure or pointer to structure. +// GetField returns the value of the provided obj field. +// The `obj` can either be a structure or pointer to structure. func GetField(obj interface{}, name string) (interface{}, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, fmt.Errorf("cannot use GetField on a non-struct object: %w", ErrUnsupportedType) @@ -50,8 +45,8 @@ func GetField(obj interface{}, name string) (interface{}, error) { return field.Interface(), nil } -// GetFieldKind returns the kind of the provided obj field. obj can whether -// be a structure or pointer to structure. +// GetFieldKind returns the kind of the provided obj field. +// The `obj` can either be a structure or pointer to structure. func GetFieldKind(obj interface{}, name string) (reflect.Kind, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return reflect.Invalid, fmt.Errorf("cannot use GetFieldKind on a non-struct interface: %w", ErrUnsupportedType) @@ -67,8 +62,8 @@ func GetFieldKind(obj interface{}, name string) (reflect.Kind, error) { return field.Type().Kind(), nil } -// GetFieldType returns the kind of the provided obj field. obj can whether -// be a structure or pointer to structure. +// GetFieldType returns the kind of the provided obj field. +// The `obj` can either be a structure or pointer to structure. func GetFieldType(obj interface{}, name string) (string, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return "", fmt.Errorf("cannot use GetFieldType on a non-struct interface: %w", ErrUnsupportedType) @@ -84,8 +79,8 @@ func GetFieldType(obj interface{}, name string) (string, error) { return field.Type().String(), nil } -// GetFieldTag returns the provided obj field tag value. obj can whether -// be a structure or pointer to structure. +// GetFieldTag returns the provided obj field tag value. +// The `obj` parameter can either be a structure or pointer to structure. func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return "", fmt.Errorf("cannot use GetFieldTag on a non-struct interface: %w", ErrUnsupportedType) @@ -106,9 +101,10 @@ func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) { return field.Tag.Get(tagKey), nil } -// SetField sets the provided obj field with provided value. obj param has -// to be a pointer to a struct, otherwise it will soundly fail. Provided -// value type should match with the struct field you're trying to set. +// SetField sets the provided obj field with provided value. +// +// The `obj` parameter must be a pointer to a struct, otherwise it soundly fails. +// Provided value type should match with the struct field you're trying to set. func SetField(obj interface{}, name string, value interface{}) error { // Fetch the field reflect.Value structValue := reflect.ValueOf(obj).Elem() @@ -118,7 +114,7 @@ func SetField(obj interface{}, name string, value interface{}) error { return fmt.Errorf("no such field: %s in obj", name) } - // If obj field value is not settable an error is thrown + // If obj field value can't be set, return an error if !structFieldValue.CanSet() { return fmt.Errorf("cannot set %s field value", name) } @@ -134,8 +130,8 @@ func SetField(obj interface{}, name string, value interface{}) error { return nil } -// HasField checks if the provided field name is part of a struct. obj can whether -// be a structure or pointer to structure. +// HasField checks if the provided field name is part of a struct. +// The `obj` can either be a structure or pointer to structure. func HasField(obj interface{}, name string) (bool, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return false, fmt.Errorf("cannot use HasField on a non-struct interface: %w", ErrUnsupportedType) @@ -151,14 +147,15 @@ func HasField(obj interface{}, name string) (bool, error) { return true, nil } -// Fields returns the struct fields names list. obj can whether -// be a structure or pointer to structure. +// Fields returns the struct fields names list. +// The `obj` parameter can either be a structure or pointer to structure. func Fields(obj interface{}) ([]string, error) { return fields(obj, false) } -// FieldsDeep returns "flattened" fields (fields from anonymous -// inner structs are treated as normal fields) +// FieldsDeep returns "flattened" fields. +// +// Note that FieldsDeept treats fields from anonymous inner structs as normal fields. func FieldsDeep(obj interface{}) ([]string, error) { return fields(obj, true) } @@ -193,14 +190,14 @@ func fields(obj interface{}, deep bool) ([]string, error) { return allFields, nil } -// Items returns the field - value struct pairs as a map. obj can whether -// be a structure or pointer to structure. +// Items returns the field:value struct pairs as a map. +// The `obj` parameter can either be a structure or pointer to structure. func Items(obj interface{}) (map[string]interface{}, error) { return items(obj, false) } -// ItemsDeep returns "flattened" items (fields from anonymous -// inner structs are treated as normal fields) +// ItemsDeep returns "flattened" items. +// Note that ItemsDeep will treat fields from anonymous inner structs as normal fields. func ItemsDeep(obj interface{}) (map[string]interface{}, error) { return items(obj, true) } @@ -240,14 +237,15 @@ func items(obj interface{}, deep bool) (map[string]interface{}, error) { return allItems, nil } -// Tags lists the struct tag fields. obj can whether -// be a structure or pointer to structure. +// Tags lists the struct tag fields. +// The `obj` can whether be a structure or pointer to structure. func Tags(obj interface{}, key string) (map[string]string, error) { return tags(obj, key, false) } -// TagsDeep returns "flattened" tags (fields from anonymous -// inner structs are treated as normal fields) +// TagsDeep returns "flattened" tags. +// Note that TagsDeep treats fields from anonymous +// inner structs as normal fields. func TagsDeep(obj interface{}, key string) (map[string]string, error) { return tags(obj, key, true) } From f74d60e125d9c9bfbc2b2c798c9c445fac6922a4 Mon Sep 17 00:00:00 2001 From: oleiade Date: Sun, 4 Sep 2022 22:16:12 +0200 Subject: [PATCH 15/24] Add table of content --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index bcc40a4..7025b2b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,22 @@ In practice, the `reflect` library's API proves somewhat low-level and un-intuit The `reflections` package aims to make developers' life easier when it comes to introspect struct values at runtime. Its API takes inspiration in the python language's `getattr,` `setattr,` and `hasattr` set of methods and provides simplified access to structure fields and tags. +- [Reflections](#reflections) + - [Documentation](#documentation) + - [Usage](#usage) + - [Accessing structure fields](#accessing-structure-fields) + - [`GetField`](#getfield) + - [`GetFieldKind`](#getfieldkind) + - [`GetFieldType`](#getfieldtype) + - [`GetFieldTag`](#getfieldtag) + - [`HasField`](#hasfield) + - [Fields](#fields) + - [Items](#items) + - [Tags](#tags) + - [Set a structure field value](#set-a-structure-field-value) + - [Important notes](#important-notes) + - [Contribute](#contribute) + ## Documentation Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) to get more details on the library's API. From 0b9d42bb9768985d07049a7a849fc8ebb16c0f89 Mon Sep 17 00:00:00 2001 From: oleiade Date: Mon, 5 Sep 2022 09:10:25 +0200 Subject: [PATCH 16/24] Fix param order, linter issues, and example --- example_test.go | 33 +++++++++++++++++++-------------- reflections.go | 7 ++++--- reflections_test.go | 19 ++++++++++--------- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/example_test.go b/example_test.go index 56d5a88..2030854 100644 --- a/example_test.go +++ b/example_test.go @@ -218,7 +218,7 @@ func ExampleSetField() { func ExampleGetFieldNameByTagValue() { type Order struct { Step string `json:"order_step"` - Id string `json:"id"` + ID string `json:"id"` Category string `json:"category"` } type Condition struct { @@ -228,32 +228,37 @@ func ExampleGetFieldNameByTagValue() { } // JSON data from external source - orderJson := `{ + orderJSON := `{ "order_step": "cooking", "id": "45457-fv54f54", "category": "Pizzas" }` - conditionJson := `{ + conditionJSON := `{ "field": "order_step", "value": "cooking", "next": "serve" }` - // Storing Json in corresponding Variables + // Storing JSON in corresponding Variables var order Order - json.Unmarshal([]byte(orderJson), &order) + err := json.Unmarshal([]byte(orderJSON), &order) + if err != nil { + log.Fatal(err) + } var condition Condition - json.Unmarshal([]byte(conditionJson), &condition) - - // example - // condition.Field = "order_step" - // we need to get this value order[condition.Field] - // but condition.Field in go needs to be "Step" not "order_step" - // this is what GetFieldNameByTagValue is about - // returns fieldName = "Step" - fieldName, _ := reflections.GetFieldNameByTagValue(condition, condition.Field, "json") + err = json.Unmarshal([]byte(conditionJSON), &condition) + if err != nil { + log.Fatal(err) + } + + fieldName, _ := reflections.GetFieldNameByTagValue(order, "json", condition.Field) + fmt.Println(fieldName) fieldValue, _ := reflections.GetField(order, fieldName) fmt.Println(fieldValue) + + // Output: + // Step + // cooking } diff --git a/reflections.go b/reflections.go index 44bf720..f39e02e 100644 --- a/reflections.go +++ b/reflections.go @@ -102,10 +102,11 @@ func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) { } // GetFieldNameByTagValue looks up a field with a matching `{tagKey}:"{tagValue}"` tag in the provided `obj` item. -// If `obj` is not a `struct`, nor a `pointer`, or it does not have a field tagged with the `tagKey`, and the matching `tagValue`, this function returns an error. -func GetFieldNameByTagValue(obj interface{}, tagValue string, tagKey string) (string, error) { +// The `obj` parameter must be a `struct`, or a `pointer` to one. If the `obj` parameter doesn't have a field tagged +// with the `tagKey`, and the matching `tagValue`, this function returns an error. +func GetFieldNameByTagValue(obj interface{}, tagKey, tagValue string) (string, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return "", errors.New("Cannot use GetFieldByTag on a non-struct interface") + return "", fmt.Errorf("cannot use GetFieldByTag on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) diff --git a/reflections_test.go b/reflections_test.go index 1b6eb0f..c601594 100644 --- a/reflections_test.go +++ b/reflections_test.go @@ -514,20 +514,22 @@ func TestItems_deep(t *testing.T) { } func TestGetFieldNameByTagValue(t *testing.T) { + t.Parallel() dummyStruct := TestStruct{ Dummy: "dummy", Yummy: 123, } - tagJson := "dummytag" - field, err := GetFieldNameByTagValue(dummyStruct, tagJson, "test") + tagJSON := "dummytag" + field, err := GetFieldNameByTagValue(dummyStruct, "test", tagJSON) assert.NoError(t, err) assert.Equal(t, field, "Dummy") } func TestGetFieldNameByTagValue_on_non_existing_tag(t *testing.T) { + t.Parallel() dummyStruct := TestStruct{ Dummy: "dummy", @@ -535,20 +537,19 @@ func TestGetFieldNameByTagValue_on_non_existing_tag(t *testing.T) { } // non existing tag value with an existing tag key - tagJson := "tag" - _, errTagValue := GetFieldNameByTagValue(dummyStruct, tagJson, "test") + tagJSON := "tag" + _, errTagValue := GetFieldNameByTagValue(dummyStruct, "test", tagJSON) assert.Error(t, errTagValue) // non existing tag key with an existing tag value - tagJson = "dummytag" - _, errTagKey := GetFieldNameByTagValue(dummyStruct, tagJson, "json") + tagJSON = "dummytag" + _, errTagKey := GetFieldNameByTagValue(dummyStruct, "json", tagJSON) assert.Error(t, errTagKey) // non existing tag key and value - tagJson = "tag" - _, errTagKeyValue := GetFieldNameByTagValue(dummyStruct, tagJson, "json") + tagJSON = "tag" + _, errTagKeyValue := GetFieldNameByTagValue(dummyStruct, "json", tagJSON) assert.Error(t, errTagKeyValue) - } //nolint:unused From c90864b2ca5e49715f17af0aa8173db0abb5d91e Mon Sep 17 00:00:00 2001 From: oleiade Date: Mon, 5 Sep 2022 09:16:09 +0200 Subject: [PATCH 17/24] Address minor syntax issues --- reflections.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/reflections.go b/reflections.go index f39e02e..6ca21c7 100644 --- a/reflections.go +++ b/reflections.go @@ -1,11 +1,11 @@ -// Copyright (c) 2013 Théo Crevon +// Copyright © 2013 Théo Crevon // // See the file LICENSE for copying permission. // Package reflections provides high level abstractions over the Go standard [reflect] library. // // In practice, the `reflect` library's API proves somewhat low-level and un-intuitive. -// Using it can turn out pretty complex, daunting, and scary, especially when doing simple +// Using it can turn out pretty complex, daunting, and scary, when doing simple // things like accessing a structure field value, a field tag, etc. // // The `reflections` package aims to make developers' life easier when it comes to introspect @@ -21,8 +21,7 @@ import ( "reflect" ) -// ErrUnsupportedType indicates that the provided type is not supported -// by the requested reflection operation. +// ErrUnsupportedType indicates that the provided type doesn't support the requested reflection operation. var ErrUnsupportedType = errors.New("unsupported type") // ErrUnexportedField indicates that an operation failed as a result of @@ -126,7 +125,7 @@ func GetFieldNameByTagValue(obj interface{}, tagKey, tagValue string) (string, e // SetField sets the provided obj field with provided value. // // The `obj` parameter must be a pointer to a struct, otherwise it soundly fails. -// Provided value type should match with the struct field you're trying to set. +// The provided `value` type should match with the struct field being set. func SetField(obj interface{}, name string, value interface{}) error { // Fetch the field reflect.Value structValue := reflect.ValueOf(obj).Elem() @@ -136,7 +135,6 @@ func SetField(obj interface{}, name string, value interface{}) error { return fmt.Errorf("no such field: %s in obj", name) } - // If obj field value can't be set, return an error if !structFieldValue.CanSet() { return fmt.Errorf("cannot set %s field value", name) } @@ -152,7 +150,7 @@ func SetField(obj interface{}, name string, value interface{}) error { return nil } -// HasField checks if the provided field name is part of a struct. +// HasField checks if the provided `obj` struct has field named `name`. // The `obj` can either be a structure or pointer to structure. func HasField(obj interface{}, name string) (bool, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { From cf2f11dd3fb251bf0b0c227dbd2ff5494771f38b Mon Sep 17 00:00:00 2001 From: Chris Barr Date: Mon, 26 Dec 2022 22:54:49 +0000 Subject: [PATCH 18/24] README updates and fixes --- README.md | 64 +++++++++++++++++++++++++------------------------------ 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 58a6e8e..b7f264c 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,15 @@ The `reflections` package aims to make developers' life easier when it comes to - [Reflections](#reflections) - [Documentation](#documentation) - [Usage](#usage) - - [Accessing structure fields](#accessing-structure-fields) - [`GetField`](#getfield) - [`GetFieldKind`](#getfieldkind) - [`GetFieldType`](#getfieldtype) - [`GetFieldTag`](#getfieldtag) - [`HasField`](#hasfield) - - [Fields](#fields) - - [Items](#items) - - [Tags](#tags) - - [Set a structure field value](#set-a-structure-field-value) + - [`Fields`](#fields) + - [`Items`](#items) + - [`Tags`](#tags) + - [`GetFieldNameByTagValue`](#getfieldnamebytagvalue) - [Important notes](#important-notes) - [Contribute](#contribute) @@ -34,8 +33,6 @@ Head to the [documentation](https://pkg.go.dev/github.com/oleiade/reflections) t ## Usage -## Accessing structure fields - ### `GetField` `GetField` returns the content of a structure field. For example, it proves beneficial when you want to iterate over struct-specific field values. You can provide `GetField` a structure or a pointer to a struct as the first argument. @@ -145,7 +142,7 @@ has, _ := reflections.HasField(s, "FirstField") has, _ := reflections.HasField(s, "FourthField") ``` -### Fields +### `Fields` `Fields` returns the list of structure field names so that you can access or update them later. You can provide `Fields` with a struct or a pointer to a struct as the first argument. @@ -164,7 +161,7 @@ var fields []string fields, _ = reflections.Fields(s) ``` -### Items +### `Items` `Items` returns the structure's field name to the values map. You can provide `Items` with a struct or a pointer to structure as the first argument. @@ -182,15 +179,15 @@ var structItems map[string]interface{} structItems, _ = reflections.Items(s) ``` -### Tags +### `Tags` `Tags` returns the structure's fields tag with the provided key. You can provide `Tags` with a struct or a pointer to a struct as the first argument. ```go s := MyStruct { - FirstField: "first value", `matched:"first tag"` - SecondField: 2, `matched:"second tag"` - ThirdField: "third value", `unmatched:"third tag"` + FirstField: "first value", `matched:"first tag"` + SecondField: 2, `matched:"second tag"` + ThirdField: "third value", `unmatched:"third tag"` } var structTags map[string]string @@ -206,9 +203,9 @@ var structTags map[string]string structTags, _ = reflections.Tags(s, "matched") ``` -### Set a structure field value +### `SetField` -`SetField` update's a structure's field value with the one provided. Note that you can't set un-exported fields and that the field and value types must match. +`SetField` updates a structure's field value with the one provided. Note that you can't set un-exported fields and that the field and value types must match. ```go s := MyStruct { @@ -226,37 +223,34 @@ _ := reflections.SetField(&s, "FirstField", "new value") err := reflection.SetField(&s, "FirstField", 123) // err != nil ``` -##### GetFieldNameByTagValue +### `GetFieldNameByTagValue` -*GetFieldNameByTagValue* looks up a field with a matching `{tagKey}:"{tagValue}"` tag in the provided `obj` item. +`GetFieldNameByTagValue` looks up a field with a matching `{tagKey}:"{tagValue}"` tag in the provided `obj` item. If `obj` is not a `struct`, nor a `pointer`, or it does not have a field tagged with the `tagKey`, and the matching `tagValue`, this function returns an error. ```go - s := MyStruct { - FirstField: "first value", `matched:"first tag"` - SecondField: 2, `matched:"second tag"` - ThirdField: "third value", `unmatched:"third tag"` - } - - // Getting field name from external source as json would be a headache to convert it manually, - // so we get it directly from struct tag - // returns fieldName = "FirstField" - fieldName, _ = reflections.GetFieldNameByTagValue(s, "first tag", "matched"); - - // later we can do GetField(s, fieldName) +s := MyStruct { + FirstField: "first value", `matched:"first tag"` + SecondField: 2, `matched:"second tag"` + ThirdField: "third value", `unmatched:"third tag"` +} + +// Getting field name from external source as json would be a headache to convert it manually, +// so we get it directly from struct tag +// returns fieldName = "FirstField" +fieldName, _ = reflections.GetFieldNameByTagValue(s, "matched", "first tag"); + +// later we can do GetField(s, fieldName) ``` ## Important notes -- **un-exported fields** can't be accessed nor set using the `reflections` library. The Go lang standard `reflect` library intentionally prohibits un-exported fields values access or modifications. +- **Un-exported fields** can't be accessed nor set using the `reflections` library. The Go lang standard `reflect` library intentionally prohibits un-exported fields values access or modifications. ## Contribute - Check for open issues or open a new issue to start a discussion around a feature idea or a bug. -- Fork `the repository`\_ on GitHub to start making your changes to the **master** branch, or branch off of it. +- Fork [the repository](http://github.com/oleiade/reflections) on GitHub to start making your changes to the **master** branch, or branch off of it. - Write tests showing that the bug was fixed or the feature works as expected. -- Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS\_. - -[the repository](http://github.com/oleiade/reflections) -[AUTHORS](https://github.com/oleiade/reflections/blob/master/AUTHORS.md) +- Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to [`AUTHORS`](https://github.com/oleiade/reflections/blob/master/AUTHORS.md). From 0977624c8b20d1a4a78a3005a151aead0a4ee7cb Mon Sep 17 00:00:00 2001 From: John Pedrie Date: Tue, 13 Aug 2024 13:06:09 -0400 Subject: [PATCH 19/24] replace strict field type check with AssignableTo --- reflections.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reflections.go b/reflections.go index 6ca21c7..d3c457d 100644 --- a/reflections.go +++ b/reflections.go @@ -141,8 +141,8 @@ func SetField(obj interface{}, name string, value interface{}) error { structFieldType := structFieldValue.Type() val := reflect.ValueOf(value) - if structFieldType != val.Type() { - invalidTypeError := fmt.Errorf("provided value type didn't match obj field type") + if !val.Type().AssignableTo(structFieldType) { + invalidTypeError := fmt.Errorf("provided value type not assignable to obj field type") return invalidTypeError } From 846ede2ad6e0e2e8d7569e58eb67ce477bb2ec47 Mon Sep 17 00:00:00 2001 From: John Pedrie Date: Tue, 13 Aug 2024 13:12:47 -0400 Subject: [PATCH 20/24] add test --- reflections_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/reflections_test.go b/reflections_test.go index c601594..badf693 100644 --- a/reflections_test.go +++ b/reflections_test.go @@ -619,3 +619,23 @@ func TestFields_deep(t *testing.T) { assert.Equal(t, fieldsDeep[1], "Street") assert.Equal(t, fieldsDeep[2], "Number") } + +type SingleString string + +type StringList []string + +type Bar struct { + A StringList +} + +func TestAssignable(t *testing.T) { + var b Bar + expected := []string{"a", "b", "c"} + assert.Nil(t, SetField(&b, "A", expected)) + assert.Equal(t, StringList(expected), b.A) + + err := SetField(&b, "A", []int{0, 1, 2}) + assert.NotNil(t, err) + assert.Equal(t, "provided value type not assignable to obj field type", + err.Error()) +} From ed6db0c6075aa41a30c6432310fc1918b2289ace Mon Sep 17 00:00:00 2001 From: oleiade Date: Sun, 11 Aug 2024 17:02:59 +0200 Subject: [PATCH 21/24] Update to Go v1.22 --- go.mod | 10 ++++++++-- go.sum | 13 ++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index b9373be..fb7b66d 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,11 @@ module github.com/oleiade/reflections -go 1.15 +go 1.22.6 -require github.com/stretchr/testify v1.6.1 +require github.com/stretchr/testify v1.9.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index afe7890..60ce688 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,10 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From da2d72345148ea9e827e228c8f2c84782caf87f0 Mon Sep 17 00:00:00 2001 From: oleiade Date: Sat, 24 Aug 2024 18:18:32 +0200 Subject: [PATCH 22/24] Update golangci-lint configuration and fix code accordingly --- .golangci.yml | 18 ++--- example_test.go | 42 +++++++++-- reflections.go | 12 ++-- reflections_test.go | 167 ++++++++++++++++++++++---------------------- 4 files changed, 133 insertions(+), 106 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index c0057a4..9e8e5fa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -29,17 +29,14 @@ issues: text: "does not use range value in test Run" linters-settings: - nolintlint: - # Disable to ensure that nolint directives don't have a leading space. Default is true. - allow-leading-space: false exhaustive: default-signifies-exhaustive: true govet: - check-shadowing: true + enable-all: true + disable: + - fieldalignment cyclop: max-complexity: 25 - maligned: - suggest-new: true dupl: threshold: 150 goconst: @@ -52,6 +49,7 @@ linters-settings: linters: enable-all: true disable: + - depguard - nlreturn - gci - gochecknoinits @@ -61,16 +59,11 @@ linters: - testpackage - wsl - gomnd - - goerr113 # most of the errors here are meant for humans + - err113 - goheader - - exhaustivestruct - thelper - gocyclo # replaced by cyclop since it also calculates the package complexity - - maligned # replaced by govet 'fieldalignment' - - interfacer # deprecated - - scopelint # deprecated, replaced by exportloopref - wrapcheck # a little bit too much for k6, maybe after https://github.com/tomarrell/wrapcheck/issues/2 is fixed - - golint # this linter is deprecated - varnamelen - ireturn - tagliatelle @@ -80,5 +73,4 @@ linters: - grouper - decorder - nonamedreturns - - nosnakecase fast: false \ No newline at end of file diff --git a/example_test.go b/example_test.go index 2030854..f16790b 100644 --- a/example_test.go +++ b/example_test.go @@ -34,7 +34,12 @@ func ExampleGetField() { if err != nil { log.Fatal(err) } + fmt.Println(value) + + // output: + // first value + // third value } } @@ -62,6 +67,10 @@ func ExampleGetFieldKind() { log.Fatal(err) } fmt.Println(secondFieldKind) + + // output: + // string + // int } func ExampleGetFieldType() { @@ -88,6 +97,10 @@ func ExampleGetFieldType() { log.Fatal(err) } fmt.Println(secondFieldType) + + // output: + // string + // int } func ExampleGetFieldTag() { @@ -104,6 +117,10 @@ func ExampleGetFieldTag() { log.Fatal(err) } fmt.Println(tag) + + // output: + // first tag + // third tag } func ExampleHasField() { @@ -120,6 +137,10 @@ func ExampleHasField() { // has == false has, _ = reflections.HasField(s, "FourthField") fmt.Println(has) + + // output: + // true + // false } func ExampleFields() { @@ -136,6 +157,9 @@ func ExampleFields() { // []string{"FirstField", "SecondField", "ThirdField"} fields, _ = reflections.Fields(s) fmt.Println(fields) + + // output: + // [MyEmbeddedStruct FirstField SecondField ThirdField] } func ExampleItems() { @@ -151,6 +175,9 @@ func ExampleItems() { // field value map structItems, _ = reflections.Items(s) fmt.Println(structItems) + + // output: + // map[FirstField:first value MyEmbeddedStruct:{} SecondField:2 ThirdField:third value] } func ExampleItemsDeep() { @@ -170,6 +197,9 @@ func ExampleItemsDeep() { // anonymous embedded structs structItems, _ = reflections.ItemsDeep(s) fmt.Println(structItems) + + // output: + // map[EmbeddedField:embedded value FirstField:first value SecondField:2 ThirdField:third value] } func ExampleTags() { @@ -191,6 +221,9 @@ func ExampleTags() { // } structTags, _ = reflections.Tags(s, "matched") fmt.Println(structTags) + + // output: + // map[FirstField:first tag MyEmbeddedStruct: SecondField:second tag ThirdField:] } func ExampleSetField() { @@ -207,12 +240,11 @@ func ExampleSetField() { log.Fatal(err) } - // If you try to set a field's value using the wrong type, + // Note that if you try to set a field's value using the wrong type, // an error will be returned - err = reflections.SetField(&s, "FirstField", 123) // err != nil - if err != nil { - log.Fatal(err) - } + _ = reflections.SetField(&s, "FirstField", 123) // err != nil + + // output: } func ExampleGetFieldNameByTagValue() { diff --git a/reflections.go b/reflections.go index d3c457d..90c0ac2 100644 --- a/reflections.go +++ b/reflections.go @@ -112,7 +112,7 @@ func GetFieldNameByTagValue(obj interface{}, tagKey, tagValue string) (string, e objType := objValue.Type() fieldsCount := objType.NumField() - for i := 0; i < fieldsCount; i++ { + for i := range fieldsCount { structField := objType.Field(i) if structField.Tag.Get(tagKey) == tagValue { return structField.Name, nil @@ -142,7 +142,7 @@ func SetField(obj interface{}, name string, value interface{}) error { structFieldType := structFieldValue.Type() val := reflect.ValueOf(value) if !val.Type().AssignableTo(structFieldType) { - invalidTypeError := fmt.Errorf("provided value type not assignable to obj field type") + invalidTypeError := errors.New("provided value type not assignable to obj field type") return invalidTypeError } @@ -175,7 +175,7 @@ func Fields(obj interface{}) ([]string, error) { // FieldsDeep returns "flattened" fields. // -// Note that FieldsDeept treats fields from anonymous inner structs as normal fields. +// Note that FieldsDeep treats fields from anonymous inner structs as normal fields. func FieldsDeep(obj interface{}) ([]string, error) { return fields(obj, true) } @@ -190,7 +190,7 @@ func fields(obj interface{}, deep bool) ([]string, error) { fieldsCount := objType.NumField() var allFields []string - for i := 0; i < fieldsCount; i++ { + for i := range fieldsCount { field := objType.Field(i) if isExportableField(field) { if !deep || !field.Anonymous { @@ -233,7 +233,7 @@ func items(obj interface{}, deep bool) (map[string]interface{}, error) { allItems := make(map[string]interface{}) - for i := 0; i < fieldsCount; i++ { + for i := range fieldsCount { field := objType.Field(i) fieldValue := objValue.Field(i) @@ -281,7 +281,7 @@ func tags(obj interface{}, key string, deep bool) (map[string]string, error) { allTags := make(map[string]string) - for i := 0; i < fieldsCount; i++ { + for i := range fieldsCount { structField := objType.Field(i) if isExportableField(structField) { if !deep || !structField.Anonymous { diff --git a/reflections_test.go b/reflections_test.go index badf693..6cf8975 100644 --- a/reflections_test.go +++ b/reflections_test.go @@ -9,12 +9,13 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type TestStruct struct { - unexported uint64 Dummy string `test:"dummytag"` - Yummy int `test:"yummytag"` + unexported uint64 + Yummy int `test:"yummytag"` } func TestGetField_on_struct(t *testing.T) { @@ -25,8 +26,8 @@ func TestGetField_on_struct(t *testing.T) { } value, err := GetField(dummyStruct, "Dummy") - assert.NoError(t, err) - assert.Equal(t, value, "test") + require.NoError(t, err) + assert.Equal(t, "test", value) } func TestGetField_on_struct_pointer(t *testing.T) { @@ -37,8 +38,8 @@ func TestGetField_on_struct_pointer(t *testing.T) { } value, err := GetField(dummyStruct, "Dummy") - assert.NoError(t, err) - assert.Equal(t, value, "test") + require.NoError(t, err) + assert.Equal(t, "test", value) } func TestGetField_on_non_struct(t *testing.T) { @@ -83,12 +84,12 @@ func TestGetFieldKind_on_struct(t *testing.T) { } kind, err := GetFieldKind(dummyStruct, "Dummy") - assert.NoError(t, err) - assert.Equal(t, kind, reflect.String) + require.NoError(t, err) + assert.Equal(t, reflect.String, kind) kind, err = GetFieldKind(dummyStruct, "Yummy") - assert.NoError(t, err) - assert.Equal(t, kind, reflect.Int) + require.NoError(t, err) + assert.Equal(t, reflect.Int, kind) } func TestGetFieldKind_on_struct_pointer(t *testing.T) { @@ -100,12 +101,12 @@ func TestGetFieldKind_on_struct_pointer(t *testing.T) { } kind, err := GetFieldKind(dummyStruct, "Dummy") - assert.NoError(t, err) - assert.Equal(t, kind, reflect.String) + require.NoError(t, err) + assert.Equal(t, reflect.String, kind) kind, err = GetFieldKind(dummyStruct, "Yummy") - assert.NoError(t, err) - assert.Equal(t, kind, reflect.Int) + require.NoError(t, err) + assert.Equal(t, reflect.Int, kind) } func TestGetFieldKind_on_non_struct(t *testing.T) { @@ -138,12 +139,12 @@ func TestGetFieldType_on_struct(t *testing.T) { } typeString, err := GetFieldType(dummyStruct, "Dummy") - assert.NoError(t, err) - assert.Equal(t, typeString, "string") + require.NoError(t, err) + assert.Equal(t, "string", typeString) typeString, err = GetFieldType(dummyStruct, "Yummy") - assert.NoError(t, err) - assert.Equal(t, typeString, "int") + require.NoError(t, err) + assert.Equal(t, "int", typeString) } func TestGetFieldType_on_struct_pointer(t *testing.T) { @@ -155,12 +156,12 @@ func TestGetFieldType_on_struct_pointer(t *testing.T) { } typeString, err := GetFieldType(dummyStruct, "Dummy") - assert.NoError(t, err) - assert.Equal(t, typeString, "string") + require.NoError(t, err) + assert.Equal(t, "string", typeString) typeString, err = GetFieldType(dummyStruct, "Yummy") - assert.NoError(t, err) - assert.Equal(t, typeString, "int") + require.NoError(t, err) + assert.Equal(t, "int", typeString) } func TestGetFieldType_on_non_struct(t *testing.T) { @@ -190,12 +191,12 @@ func TestGetFieldTag_on_struct(t *testing.T) { dummyStruct := TestStruct{} tag, err := GetFieldTag(dummyStruct, "Dummy", "test") - assert.NoError(t, err) - assert.Equal(t, tag, "dummytag") + require.NoError(t, err) + assert.Equal(t, "dummytag", tag) tag, err = GetFieldTag(dummyStruct, "Yummy", "test") - assert.NoError(t, err) - assert.Equal(t, tag, "yummytag") + require.NoError(t, err) + assert.Equal(t, "yummytag", tag) } func TestGetFieldTag_on_struct_pointer(t *testing.T) { @@ -204,12 +205,12 @@ func TestGetFieldTag_on_struct_pointer(t *testing.T) { dummyStruct := &TestStruct{} tag, err := GetFieldTag(dummyStruct, "Dummy", "test") - assert.NoError(t, err) - assert.Equal(t, tag, "dummytag") + require.NoError(t, err) + assert.Equal(t, "dummytag", tag) tag, err = GetFieldTag(dummyStruct, "Yummy", "test") - assert.NoError(t, err) - assert.Equal(t, tag, "yummytag") + require.NoError(t, err) + assert.Equal(t, "yummytag", tag) } func TestGetFieldTag_on_non_struct(t *testing.T) { @@ -250,8 +251,8 @@ func TestSetField_on_struct_with_valid_value_type(t *testing.T) { } err := SetField(&dummyStruct, "Dummy", "abc") - assert.NoError(t, err) - assert.Equal(t, dummyStruct.Dummy, "abc") + require.NoError(t, err) + assert.Equal(t, "abc", dummyStruct.Dummy) } func TestSetField_non_existing_field(t *testing.T) { @@ -295,8 +296,8 @@ func TestFields_on_struct(t *testing.T) { } fields, err := Fields(dummyStruct) - assert.NoError(t, err) - assert.Equal(t, fields, []string{"Dummy", "Yummy"}) + require.NoError(t, err) + assert.Equal(t, []string{"Dummy", "Yummy"}, fields) } func TestFields_on_struct_pointer(t *testing.T) { @@ -308,8 +309,8 @@ func TestFields_on_struct_pointer(t *testing.T) { } fields, err := Fields(dummyStruct) - assert.NoError(t, err) - assert.Equal(t, fields, []string{"Dummy", "Yummy"}) + require.NoError(t, err) + assert.Equal(t, []string{"Dummy", "Yummy"}, fields) } func TestFields_on_non_struct(t *testing.T) { @@ -331,8 +332,8 @@ func TestFields_with_non_exported_fields(t *testing.T) { } fields, err := Fields(dummyStruct) - assert.NoError(t, err) - assert.Equal(t, fields, []string{"Dummy", "Yummy"}) + require.NoError(t, err) + assert.Equal(t, []string{"Dummy", "Yummy"}, fields) } func TestHasField_on_struct_with_existing_field(t *testing.T) { @@ -344,7 +345,7 @@ func TestHasField_on_struct_with_existing_field(t *testing.T) { } has, err := HasField(dummyStruct, "Dummy") - assert.NoError(t, err) + require.NoError(t, err) assert.True(t, has) } @@ -357,7 +358,7 @@ func TestHasField_on_struct_pointer_with_existing_field(t *testing.T) { } has, err := HasField(dummyStruct, "Dummy") - assert.NoError(t, err) + require.NoError(t, err) assert.True(t, has) } @@ -370,7 +371,7 @@ func TestHasField_non_existing_field(t *testing.T) { } has, err := HasField(dummyStruct, "Test") - assert.NoError(t, err) + require.NoError(t, err) assert.False(t, has) } @@ -393,7 +394,7 @@ func TestHasField_unexported_field(t *testing.T) { } has, err := HasField(dummyStruct, "unexported") - assert.NoError(t, err) + require.NoError(t, err) assert.False(t, has) } @@ -406,11 +407,11 @@ func TestTags_on_struct(t *testing.T) { } tags, err := Tags(dummyStruct, "test") - assert.NoError(t, err) - assert.Equal(t, tags, map[string]string{ + require.NoError(t, err) + assert.Equal(t, map[string]string{ "Dummy": "dummytag", "Yummy": "yummytag", - }) + }, tags) } func TestTags_on_struct_pointer(t *testing.T) { @@ -422,11 +423,11 @@ func TestTags_on_struct_pointer(t *testing.T) { } tags, err := Tags(dummyStruct, "test") - assert.NoError(t, err) - assert.Equal(t, tags, map[string]string{ + require.NoError(t, err) + assert.Equal(t, map[string]string{ "Dummy": "dummytag", "Yummy": "yummytag", - }) + }, tags) } func TestTags_on_non_struct(t *testing.T) { @@ -447,11 +448,11 @@ func TestItems_on_struct(t *testing.T) { } tags, err := Items(dummyStruct) - assert.NoError(t, err) - assert.Equal(t, tags, map[string]interface{}{ + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{ "Dummy": "test", "Yummy": 123, - }) + }, tags) } func TestItems_on_struct_pointer(t *testing.T) { @@ -463,11 +464,11 @@ func TestItems_on_struct_pointer(t *testing.T) { } tags, err := Items(dummyStruct) - assert.NoError(t, err) - assert.Equal(t, tags, map[string]interface{}{ + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{ "Dummy": "test", "Yummy": 123, - }) + }, tags) } func TestItems_on_non_struct(t *testing.T) { @@ -502,15 +503,15 @@ func TestItems_deep(t *testing.T) { p.Number = 17 items, err := Items(p) - assert.NoError(t, err) + require.NoError(t, err) itemsDeep, err := ItemsDeep(p) - assert.NoError(t, err) + require.NoError(t, err) - assert.Equal(t, len(items), 2) - assert.Equal(t, len(itemsDeep), 3) - assert.Equal(t, itemsDeep["Name"], "John") - assert.Equal(t, itemsDeep["Street"], "Decumanus maximus") - assert.Equal(t, itemsDeep["Number"], 17) + assert.Len(t, items, 2) + assert.Len(t, itemsDeep, 3) + assert.Equal(t, "John", itemsDeep["Name"]) + assert.Equal(t, "Decumanus maximus", itemsDeep["Street"]) + assert.Equal(t, 17, itemsDeep["Number"]) } func TestGetFieldNameByTagValue(t *testing.T) { @@ -524,8 +525,8 @@ func TestGetFieldNameByTagValue(t *testing.T) { tagJSON := "dummytag" field, err := GetFieldNameByTagValue(dummyStruct, "test", tagJSON) - assert.NoError(t, err) - assert.Equal(t, field, "Dummy") + require.NoError(t, err) + assert.Equal(t, "Dummy", field) } func TestGetFieldNameByTagValue_on_non_existing_tag(t *testing.T) { @@ -539,17 +540,17 @@ func TestGetFieldNameByTagValue_on_non_existing_tag(t *testing.T) { // non existing tag value with an existing tag key tagJSON := "tag" _, errTagValue := GetFieldNameByTagValue(dummyStruct, "test", tagJSON) - assert.Error(t, errTagValue) + require.Error(t, errTagValue) // non existing tag key with an existing tag value tagJSON = "dummytag" _, errTagKey := GetFieldNameByTagValue(dummyStruct, "json", tagJSON) - assert.Error(t, errTagKey) + require.Error(t, errTagKey) // non existing tag key and value tagJSON = "tag" _, errTagKeyValue := GetFieldNameByTagValue(dummyStruct, "json", tagJSON) - assert.Error(t, errTagKeyValue) + require.Error(t, errTagKeyValue) } //nolint:unused @@ -575,15 +576,15 @@ func TestTags_deep(t *testing.T) { p.Number = 17 tags, err := Tags(p, "tag") - assert.NoError(t, err) + require.NoError(t, err) tagsDeep, err := TagsDeep(p, "tag") - assert.NoError(t, err) + require.NoError(t, err) - assert.Equal(t, len(tags), 2) - assert.Equal(t, len(tagsDeep), 3) - assert.Equal(t, tagsDeep["Name"], "bu") - assert.Equal(t, tagsDeep["Street"], "be") - assert.Equal(t, tagsDeep["Number"], "bi") + assert.Len(t, tags, 2) + assert.Len(t, tagsDeep, 3) + assert.Equal(t, "bu", tagsDeep["Name"]) + assert.Equal(t, "be", tagsDeep["Street"]) + assert.Equal(t, "bi", tagsDeep["Number"]) } //nolint:unused @@ -609,15 +610,15 @@ func TestFields_deep(t *testing.T) { p.Number = 17 fields, err := Fields(p) - assert.NoError(t, err) + require.NoError(t, err) fieldsDeep, err := FieldsDeep(p) - assert.NoError(t, err) + require.NoError(t, err) - assert.Equal(t, len(fields), 2) - assert.Equal(t, len(fieldsDeep), 3) - assert.Equal(t, fieldsDeep[0], "Name") - assert.Equal(t, fieldsDeep[1], "Street") - assert.Equal(t, fieldsDeep[2], "Number") + assert.Len(t, fields, 2) + assert.Len(t, fieldsDeep, 3) + assert.Equal(t, "Name", fieldsDeep[0]) + assert.Equal(t, "Street", fieldsDeep[1]) + assert.Equal(t, "Number", fieldsDeep[2]) } type SingleString string @@ -629,13 +630,15 @@ type Bar struct { } func TestAssignable(t *testing.T) { + t.Parallel() + var b Bar expected := []string{"a", "b", "c"} - assert.Nil(t, SetField(&b, "A", expected)) + require.NoError(t, SetField(&b, "A", expected)) assert.Equal(t, StringList(expected), b.A) err := SetField(&b, "A", []int{0, 1, 2}) - assert.NotNil(t, err) + require.Error(t, err) assert.Equal(t, "provided value type not assignable to obj field type", err.Error()) } From 502dc55f556659165007c5fc55a8e3ec46a5a382 Mon Sep 17 00:00:00 2001 From: oleiade Date: Sat, 24 Aug 2024 18:18:50 +0200 Subject: [PATCH 23/24] Update GitHub action versions --- .github/workflows/go.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e54a9df..03e7456 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -13,12 +13,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: - go-version: ^1.13 + go-version: '^1.13' - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Build run: go build -v . @@ -27,4 +27,4 @@ jobs: run: go test -v . - name: Lint - uses: golangci/golangci-lint-action@v3.2.0 + uses: golangci/golangci-lint-action@v6.1.0 From def9a377dea060538575e78dd338f69a6fd90c7d Mon Sep 17 00:00:00 2001 From: GitButler Date: Sat, 24 Aug 2024 18:28:28 +0200 Subject: [PATCH 24/24] GitButler Integration Commit This is an integration commit for the virtual branches that GitButler is tracking. Due to GitButler managing multiple virtual branches, you cannot switch back and forth between git branches and virtual branches easily. If you switch to another branch, GitButler will need to be reinitialized. If you commit on this branch, GitButler will throw it away. Here are the branches that are currently applied: - Virtual branch (refs/gitbutler/Virtual-branch) branch head: 502dc55f556659165007c5fc55a8e3ec46a5a382 - example_test.go For more information about what we're doing here, check out our docs: https://docs.gitbutler.com/features/virtual-branches/integration-branch