pax_global_header00006660000000000000000000000064146624050540014520gustar00rootroot0000000000000052 comment=def9a377dea060538575e78dd338f69a6fd90c7d oleiade-reflections-2bf1146/000077500000000000000000000000001466240505400157645ustar00rootroot00000000000000oleiade-reflections-2bf1146/.github/000077500000000000000000000000001466240505400173245ustar00rootroot00000000000000oleiade-reflections-2bf1146/.github/workflows/000077500000000000000000000000001466240505400213615ustar00rootroot00000000000000oleiade-reflections-2bf1146/.github/workflows/go.yml000066400000000000000000000007631466240505400225170ustar00rootroot00000000000000name: Go on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v5 with: go-version: '^1.13' - name: Check out code into the Go module directory uses: actions/checkout@v4 - name: Build run: go build -v . - name: Test run: go test -v . - name: Lint uses: golangci/golangci-lint-action@v6.1.0 oleiade-reflections-2bf1146/.gitignore000066400000000000000000000004041466240505400177520ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe styles/oleiade-reflections-2bf1146/.golangci.yml000066400000000000000000000035301466240505400203510ustar00rootroot00000000000000# 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: exhaustive: default-signifies-exhaustive: true govet: enable-all: true disable: - fieldalignment cyclop: max-complexity: 25 dupl: threshold: 150 goconst: min-len: 10 min-occurrences: 4 funlen: lines: 80 statements: 60 linters: enable-all: true disable: - depguard - nlreturn - gci - gochecknoinits - godot - godox - gomodguard - testpackage - wsl - gomnd - err113 - goheader - thelper - gocyclo # replaced by cyclop since it also calculates the package complexity - wrapcheck # a little bit too much for k6, maybe after https://github.com/tomarrell/wrapcheck/issues/2 is fixed - varnamelen - ireturn - tagliatelle - exhaustruct - execinquery - maintidx - grouper - decorder - nonamedreturns fast: falseoleiade-reflections-2bf1146/.vale.ini000066400000000000000000000002401466240505400174660ustar00rootroot00000000000000StylesPath = styles MinAlertLevel = suggestion Vocab = Lane Packages = Google, proselint, write-good [*] BasedOnStyles = Vale, Google, proselint, write-good oleiade-reflections-2bf1146/AUTHORS.md000066400000000000000000000002751466240505400174370ustar00rootroot00000000000000## Creator * Oleiade ## Contributors * Cengle * Tomo Krajina * Seth Shelnutt oleiade-reflections-2bf1146/LICENSE000066400000000000000000000020671466240505400167760ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2013 Théo Crevon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. oleiade-reflections-2bf1146/README.md000066400000000000000000000202031466240505400172400ustar00rootroot00000000000000# Reflections [![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) The `reflections` library provides high-level abstractions on top of the go language 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. - [Reflections](#reflections) - [Documentation](#documentation) - [Usage](#usage) - [`GetField`](#getfield) - [`GetFieldKind`](#getfieldkind) - [`GetFieldType`](#getfieldtype) - [`GetFieldTag`](#getfieldtag) - [`HasField`](#hasfield) - [`Fields`](#fields) - [`Items`](#items) - [`Tags`](#tags) - [`GetFieldNameByTagValue`](#getfieldnamebytagvalue) - [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. ## Usage ### `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. ```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) } ``` ### `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. ```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) } ``` ### `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. ```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) } ``` ### `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) ``` ### `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", } // has == true has, _ := reflections.HasField(s, "FirstField") // has == false has, _ := reflections.HasField(s, "FourthField") ``` ### `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. ```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) ``` ### `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", } var structItems map[string]interface{} // 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 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") ``` ### `SetField` `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 { 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 ``` ### `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 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. ## Contribute - Check for open issues or open a new issue to start a discussion around a feature idea or a bug. - 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`](https://github.com/oleiade/reflections/blob/master/AUTHORS.md). oleiade-reflections-2bf1146/example_test.go000066400000000000000000000136041466240505400210110ustar00rootroot00000000000000package reflections_test import ( "encoding/json" "fmt" "log" "reflect" "github.com/oleiade/reflections" ) type MyStruct struct { MyEmbeddedStruct FirstField string `matched:"first tag"` SecondField int `matched:"second tag"` ThirdField string `unmatched:"third tag"` } type MyEmbeddedStruct struct { EmbeddedField string } func ExampleGetField() { s := MyStruct{ FirstField: "first value", SecondField: 2, ThirdField: "third value", } fieldsToExtract := []string{"FirstField", "ThirdField"} for _, fieldName := range fieldsToExtract { value, err := reflections.GetField(s, fieldName) if err != nil { log.Fatal(err) } fmt.Println(value) // output: // first value // third value } } func ExampleGetFieldKind() { s := MyStruct{ FirstField: "first value", SecondField: 2, ThirdField: "third value", } var firstFieldKind reflect.Kind var secondFieldKind reflect.Kind var err error // GetFieldKind will return reflect.String firstFieldKind, err = reflections.GetFieldKind(s, "FirstField") if err != nil { log.Fatal(err) } fmt.Println(firstFieldKind) // GetFieldKind will return reflect.Int secondFieldKind, err = reflections.GetFieldKind(s, "SecondField") if err != nil { log.Fatal(err) } fmt.Println(secondFieldKind) // output: // string // int } func ExampleGetFieldType() { s := MyStruct{ FirstField: "first value", SecondField: 2, ThirdField: "third value", } var firstFieldType string var secondFieldType string var err error // GetFieldType will return reflect.String firstFieldType, err = reflections.GetFieldType(s, "FirstField") if err != nil { log.Fatal(err) } fmt.Println(firstFieldType) // GetFieldType will return reflect.Int secondFieldType, err = reflections.GetFieldType(s, "SecondField") if err != nil { log.Fatal(err) } fmt.Println(secondFieldType) // output: // string // int } func ExampleGetFieldTag() { 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) // output: // first tag // third tag } func ExampleHasField() { s := MyStruct{ FirstField: "first value", SecondField: 2, ThirdField: "third value", } // has == true has, _ := reflections.HasField(s, "FirstField") fmt.Println(has) // has == false has, _ = reflections.HasField(s, "FourthField") fmt.Println(has) // output: // true // false } func ExampleFields() { 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) fmt.Println(fields) // output: // [MyEmbeddedStruct FirstField SecondField ThirdField] } func ExampleItems() { s := MyStruct{ FirstField: "first value", SecondField: 2, ThirdField: "third value", } var structItems map[string]interface{} // Items will return a field name to // field value map structItems, _ = reflections.Items(s) fmt.Println(structItems) // output: // map[FirstField:first value MyEmbeddedStruct:{} SecondField:2 ThirdField:third value] } func ExampleItemsDeep() { s := MyStruct{ FirstField: "first value", SecondField: 2, ThirdField: "third value", MyEmbeddedStruct: MyEmbeddedStruct{ EmbeddedField: "embedded value", }, } var structItems map[string]interface{} // ItemsDeep will return a field name to // field value map, including fields from // 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() { s := MyStruct{ FirstField: "first value", SecondField: 2, ThirdField: "third value", } 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") fmt.Println(structTags) // output: // map[FirstField:first tag MyEmbeddedStruct: SecondField:second tag ThirdField:] } func ExampleSetField() { 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. err := reflections.SetField(&s, "FirstField", "new value") if err != nil { log.Fatal(err) } // Note that if you try to set a field's value using the wrong type, // an error will be returned _ = reflections.SetField(&s, "FirstField", 123) // err != nil // output: } 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 err := json.Unmarshal([]byte(orderJSON), &order) if err != nil { log.Fatal(err) } var condition Condition 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 } oleiade-reflections-2bf1146/go.mod000066400000000000000000000003601466240505400170710ustar00rootroot00000000000000module github.com/oleiade/reflections go 1.22.6 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 ) oleiade-reflections-2bf1146/go.sum000066400000000000000000000015611466240505400171220ustar00rootroot00000000000000github.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/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= oleiade-reflections-2bf1146/reflections.go000066400000000000000000000240451466240505400206350ustar00rootroot00000000000000// 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, 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 ( "errors" "fmt" "reflect" ) // 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 // applying to a non-exported struct field. var ErrUnexportedField = errors.New("unexported field") // 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) } objValue := reflectValue(obj) field := objValue.FieldByName(name) if !field.IsValid() { return nil, fmt.Errorf("no such field: %s in obj", name) } return field.Interface(), nil } // 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) } objValue := reflectValue(obj) field := objValue.FieldByName(name) if !field.IsValid() { return reflect.Invalid, fmt.Errorf("no such field: %s in obj", name) } return field.Type().Kind(), nil } // 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) } objValue := reflectValue(obj) field := objValue.FieldByName(name) if !field.IsValid() { return "", fmt.Errorf("no such field: %s in obj", name) } return field.Type().String(), nil } // 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) } objValue := reflectValue(obj) objType := objValue.Type() field, ok := objType.FieldByName(fieldName) if !ok { return "", fmt.Errorf("no such field: %s in obj", fieldName) } if !isExportableField(field) { return "", fmt.Errorf("cannot GetFieldTag on a non-exported struct field: %w", ErrUnexportedField) } return field.Tag.Get(tagKey), nil } // GetFieldNameByTagValue looks up a field with a matching `{tagKey}:"{tagValue}"` tag in the provided `obj` item. // 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 "", fmt.Errorf("cannot use GetFieldByTag on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) objType := objValue.Type() fieldsCount := objType.NumField() for i := range fieldsCount { structField := objType.Field(i) if structField.Tag.Get(tagKey) == tagValue { return structField.Name, nil } } return "", errors.New("tag doesn't exist in the given struct") } // SetField sets the provided obj field with provided value. // // The `obj` parameter must be a pointer to a struct, otherwise it soundly fails. // 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() structFieldValue := structValue.FieldByName(name) if !structFieldValue.IsValid() { return fmt.Errorf("no such field: %s in obj", name) } if !structFieldValue.CanSet() { return fmt.Errorf("cannot set %s field value", name) } structFieldType := structFieldValue.Type() val := reflect.ValueOf(value) if !val.Type().AssignableTo(structFieldType) { invalidTypeError := errors.New("provided value type not assignable to obj field type") return invalidTypeError } structFieldValue.Set(val) return nil } // 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}) { return false, fmt.Errorf("cannot use HasField on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) objType := objValue.Type() field, ok := objType.FieldByName(name) if !ok || !isExportableField(field) { return false, nil } return true, nil } // 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. // // Note that FieldsDeep treats fields from anonymous inner structs as normal fields. func FieldsDeep(obj interface{}) ([]string, error) { return fields(obj, true) } func fields(obj interface{}, deep bool) ([]string, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, fmt.Errorf("cannot use fields on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) objType := objValue.Type() fieldsCount := objType.NumField() var allFields []string for i := range fieldsCount { field := objType.Field(i) if isExportableField(field) { 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...) } } return allFields, nil } // 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. // 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) } func items(obj interface{}, deep bool) (map[string]interface{}, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, fmt.Errorf("cannot use items on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) objType := objValue.Type() fieldsCount := objType.NumField() allItems := make(map[string]interface{}) for i := range fieldsCount { field := objType.Field(i) fieldValue := objValue.Field(i) if isExportableField(field) { 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 } } } return allItems, nil } // 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. // 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) } func tags(obj interface{}, key string, deep bool) (map[string]string, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, fmt.Errorf("cannot use tags on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) objType := objValue.Type() fieldsCount := objType.NumField() allTags := make(map[string]string) for i := range fieldsCount { structField := objType.Field(i) if isExportableField(structField) { 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 } } } return allTags, nil } func reflectValue(obj interface{}) reflect.Value { var val reflect.Value if reflect.TypeOf(obj).Kind() == reflect.Ptr { val = reflect.ValueOf(obj).Elem() } else { val = reflect.ValueOf(obj) } return val } func isExportableField(field reflect.StructField) bool { // PkgPath is empty for exported fields. return field.PkgPath == "" } func isSupportedType(obj interface{}, types []reflect.Kind) bool { for _, t := range types { if reflect.TypeOf(obj).Kind() == t { return true } } return false } oleiade-reflections-2bf1146/reflections_test.go000066400000000000000000000277341466240505400217040ustar00rootroot00000000000000// Copyright (c) 2013 Théo Crevon // // See the file LICENSE for copying permission. package reflections import ( "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type TestStruct struct { Dummy string `test:"dummytag"` unexported uint64 Yummy int `test:"yummytag"` } func TestGetField_on_struct(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", } value, err := GetField(dummyStruct, "Dummy") require.NoError(t, err) assert.Equal(t, "test", value) } func TestGetField_on_struct_pointer(t *testing.T) { t.Parallel() dummyStruct := &TestStruct{ Dummy: "test", } value, err := GetField(dummyStruct, "Dummy") require.NoError(t, err) assert.Equal(t, "test", value) } func TestGetField_on_non_struct(t *testing.T) { t.Parallel() dummy := "abc 123" _, err := GetField(dummy, "Dummy") assert.Error(t, err) } func TestGetField_non_existing_field(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", } _, err := GetField(dummyStruct, "obladioblada") assert.Error(t, err) } func TestGetField_unexported_field(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ unexported: 12345, Dummy: "test", } assert.Panics(t, func() { GetField(dummyStruct, "unexported") //nolint:errcheck,gosec }) } func TestGetFieldKind_on_struct(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, } kind, err := GetFieldKind(dummyStruct, "Dummy") require.NoError(t, err) assert.Equal(t, reflect.String, kind) kind, err = GetFieldKind(dummyStruct, "Yummy") require.NoError(t, err) assert.Equal(t, reflect.Int, kind) } func TestGetFieldKind_on_struct_pointer(t *testing.T) { t.Parallel() dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, } kind, err := GetFieldKind(dummyStruct, "Dummy") require.NoError(t, err) assert.Equal(t, reflect.String, kind) kind, err = GetFieldKind(dummyStruct, "Yummy") require.NoError(t, err) assert.Equal(t, reflect.Int, kind) } func TestGetFieldKind_on_non_struct(t *testing.T) { t.Parallel() dummy := "abc 123" _, err := GetFieldKind(dummy, "Dummy") assert.Error(t, err) } func TestGetFieldKind_non_existing_field(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, } _, err := GetFieldKind(dummyStruct, "obladioblada") assert.Error(t, err) } func TestGetFieldType_on_struct(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, } typeString, err := GetFieldType(dummyStruct, "Dummy") require.NoError(t, err) assert.Equal(t, "string", typeString) typeString, err = GetFieldType(dummyStruct, "Yummy") require.NoError(t, err) assert.Equal(t, "int", typeString) } func TestGetFieldType_on_struct_pointer(t *testing.T) { t.Parallel() dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, } typeString, err := GetFieldType(dummyStruct, "Dummy") require.NoError(t, err) assert.Equal(t, "string", typeString) typeString, err = GetFieldType(dummyStruct, "Yummy") require.NoError(t, err) assert.Equal(t, "int", typeString) } func TestGetFieldType_on_non_struct(t *testing.T) { t.Parallel() dummy := "abc 123" _, err := GetFieldType(dummy, "Dummy") assert.Error(t, err) } func TestGetFieldType_non_existing_field(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, } _, err := GetFieldType(dummyStruct, "obladioblada") assert.Error(t, err) } func TestGetFieldTag_on_struct(t *testing.T) { t.Parallel() dummyStruct := TestStruct{} tag, err := GetFieldTag(dummyStruct, "Dummy", "test") require.NoError(t, err) assert.Equal(t, "dummytag", tag) tag, err = GetFieldTag(dummyStruct, "Yummy", "test") require.NoError(t, err) assert.Equal(t, "yummytag", tag) } func TestGetFieldTag_on_struct_pointer(t *testing.T) { t.Parallel() dummyStruct := &TestStruct{} tag, err := GetFieldTag(dummyStruct, "Dummy", "test") require.NoError(t, err) assert.Equal(t, "dummytag", tag) tag, err = GetFieldTag(dummyStruct, "Yummy", "test") require.NoError(t, err) assert.Equal(t, "yummytag", tag) } func TestGetFieldTag_on_non_struct(t *testing.T) { t.Parallel() dummy := "abc 123" _, err := GetFieldTag(dummy, "Dummy", "test") assert.Error(t, err) } func TestGetFieldTag_non_existing_field(t *testing.T) { t.Parallel() dummyStruct := TestStruct{} _, err := GetFieldTag(dummyStruct, "obladioblada", "test") assert.Error(t, err) } func TestGetFieldTag_unexported_field(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ unexported: 12345, Dummy: "test", } _, err := GetFieldTag(dummyStruct, "unexported", "test") assert.Error(t, err) } func TestSetField_on_struct_with_valid_value_type(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", } err := SetField(&dummyStruct, "Dummy", "abc") require.NoError(t, err) assert.Equal(t, "abc", dummyStruct.Dummy) } func TestSetField_non_existing_field(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", } err := SetField(&dummyStruct, "obladioblada", "life goes on") assert.Error(t, err) } func TestSetField_invalid_value_type(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", } err := SetField(&dummyStruct, "Yummy", "123") assert.Error(t, err) } func TestSetField_non_exported_field(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", } assert.Error(t, SetField(&dummyStruct, "unexported", "fail, bitch")) } func TestFields_on_struct(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, } fields, err := Fields(dummyStruct) require.NoError(t, err) assert.Equal(t, []string{"Dummy", "Yummy"}, fields) } func TestFields_on_struct_pointer(t *testing.T) { t.Parallel() dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, } fields, err := Fields(dummyStruct) require.NoError(t, err) assert.Equal(t, []string{"Dummy", "Yummy"}, fields) } func TestFields_on_non_struct(t *testing.T) { t.Parallel() dummy := "abc 123" _, err := Fields(dummy) assert.Error(t, err) } func TestFields_with_non_exported_fields(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ unexported: 6789, Dummy: "test", Yummy: 123, } fields, err := Fields(dummyStruct) require.NoError(t, err) assert.Equal(t, []string{"Dummy", "Yummy"}, fields) } func TestHasField_on_struct_with_existing_field(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, } has, err := HasField(dummyStruct, "Dummy") require.NoError(t, err) assert.True(t, has) } func TestHasField_on_struct_pointer_with_existing_field(t *testing.T) { t.Parallel() dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, } has, err := HasField(dummyStruct, "Dummy") require.NoError(t, err) assert.True(t, has) } func TestHasField_non_existing_field(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, } has, err := HasField(dummyStruct, "Test") require.NoError(t, err) assert.False(t, has) } func TestHasField_on_non_struct(t *testing.T) { t.Parallel() dummy := "abc 123" _, err := HasField(dummy, "Test") assert.Error(t, err) } func TestHasField_unexported_field(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ unexported: 7890, Dummy: "test", Yummy: 123, } has, err := HasField(dummyStruct, "unexported") require.NoError(t, err) assert.False(t, has) } func TestTags_on_struct(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, } tags, err := Tags(dummyStruct, "test") require.NoError(t, err) assert.Equal(t, map[string]string{ "Dummy": "dummytag", "Yummy": "yummytag", }, tags) } func TestTags_on_struct_pointer(t *testing.T) { t.Parallel() dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, } tags, err := Tags(dummyStruct, "test") require.NoError(t, err) assert.Equal(t, map[string]string{ "Dummy": "dummytag", "Yummy": "yummytag", }, tags) } func TestTags_on_non_struct(t *testing.T) { t.Parallel() dummy := "abc 123" _, err := Tags(dummy, "test") assert.Error(t, err) } func TestItems_on_struct(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "test", Yummy: 123, } tags, err := Items(dummyStruct) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Dummy": "test", "Yummy": 123, }, tags) } func TestItems_on_struct_pointer(t *testing.T) { t.Parallel() dummyStruct := &TestStruct{ Dummy: "test", Yummy: 123, } tags, err := Items(dummyStruct) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Dummy": "test", "Yummy": 123, }, tags) } 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"` } type unexportedStruct struct{} type Person struct { Name string `tag:"bu"` Address unexportedStruct } p := Person{} p.Name = "John" p.Street = "Decumanus maximus" p.Number = 17 items, err := Items(p) require.NoError(t, err) itemsDeep, err := ItemsDeep(p) require.NoError(t, err) 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) { t.Parallel() dummyStruct := TestStruct{ Dummy: "dummy", Yummy: 123, } tagJSON := "dummytag" field, err := GetFieldNameByTagValue(dummyStruct, "test", tagJSON) require.NoError(t, err) assert.Equal(t, "Dummy", field) } func TestGetFieldNameByTagValue_on_non_existing_tag(t *testing.T) { t.Parallel() dummyStruct := TestStruct{ Dummy: "dummy", Yummy: 123, } // non existing tag value with an existing tag key tagJSON := "tag" _, errTagValue := GetFieldNameByTagValue(dummyStruct, "test", tagJSON) require.Error(t, errTagValue) // non existing tag key with an existing tag value tagJSON = "dummytag" _, errTagKey := GetFieldNameByTagValue(dummyStruct, "json", tagJSON) require.Error(t, errTagKey) // non existing tag key and value tagJSON = "tag" _, errTagKeyValue := GetFieldNameByTagValue(dummyStruct, "json", tagJSON) require.Error(t, errTagKeyValue) } //nolint:unused func TestTags_deep(t *testing.T) { t.Parallel() type Address struct { Street string `tag:"be"` Number int `tag:"bi"` } type unexportedStruct struct{} type Person struct { Name string `tag:"bu"` Address unexportedStruct } p := Person{} p.Name = "John" p.Street = "Decumanus maximus" p.Number = 17 tags, err := Tags(p, "tag") require.NoError(t, err) tagsDeep, err := TagsDeep(p, "tag") require.NoError(t, err) 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 func TestFields_deep(t *testing.T) { t.Parallel() type Address struct { Street string `tag:"be"` Number int `tag:"bi"` } type unexportedStruct struct{} type Person struct { Name string `tag:"bu"` Address unexportedStruct } p := Person{} p.Name = "John" p.Street = "street?" p.Number = 17 fields, err := Fields(p) require.NoError(t, err) fieldsDeep, err := FieldsDeep(p) require.NoError(t, err) 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 type StringList []string type Bar struct { A StringList } func TestAssignable(t *testing.T) { t.Parallel() var b Bar expected := []string{"a", "b", "c"} require.NoError(t, SetField(&b, "A", expected)) assert.Equal(t, StringList(expected), b.A) err := SetField(&b, "A", []int{0, 1, 2}) require.Error(t, err) assert.Equal(t, "provided value type not assignable to obj field type", err.Error()) }