Repository: mennanov/fieldmask-utils Branch: master Commit: 975f889ba7b9 Files: 19 Total size: 131.3 KB Directory structure: gitextract__r76onfn/ ├── .github/ │ ├── dependabot.yaml │ └── workflows/ │ ├── blockwatch.yml │ ├── coverage.yml │ ├── linter.yml │ └── tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── copy.go ├── copy_proto_test.go ├── copy_test.go ├── go.mod ├── go.sum ├── mask.go ├── mask_test.go ├── revive.toml └── testproto/ ├── test.pb.go └── test.proto ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yaml ================================================ version: 2 updates: - package-ecosystem: gomod directory: / schedule: interval: monthly groups: all-dependencies: patterns: - "*" ================================================ FILE: .github/workflows/blockwatch.yml ================================================ name: blockwatch on: push: branches: [ "master" ] pull_request: branches: [ "master" ] permissions: contents: read jobs: blockwatch: runs-on: ubuntu-latest steps: - uses: mennanov/blockwatch-action@v1 ================================================ FILE: .github/workflows/coverage.yml ================================================ name: Tests coverage on: [ "push", "pull_request" ] jobs: build: name: Coverage runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Go 1.22 uses: actions/setup-go@v5 with: go-version: '1.22' - name: Run tests with coverage run: go test -v -coverprofile=coverage.txt -covermode=atomic ./... - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true ================================================ FILE: .github/workflows/linter.yml ================================================ name: Linter on: [ "push", "pull_request" ] jobs: build: name: Linter runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Go 1.22 uses: actions/setup-go@v5 with: go-version: '1.22' - name: Go vet run: go vet ./... ================================================ FILE: .github/workflows/tests.yml ================================================ name: Go tests on: [ push, pull_request ] jobs: go-test: runs-on: ubuntu-latest strategy: matrix: go-version: # - '1.17' - '1.18' - '1.19' - '1.20' - '1.21' - '1.22' # steps: - uses: actions/checkout@v4 - name: Setup Go ${{ matrix.go-version }} uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} cache: true - name: Run tests run: go test -v ./... ================================================ FILE: .gitignore ================================================ .idea/ vendor/ coverage.out coverage.txt ================================================ FILE: .pre-commit-config.yaml ================================================ repos: - repo: local hooks: - id: blockwatch name: blockwatch entry: bash -c 'git diff --patch --cached --unified=0 | blockwatch' language: system stages: [ pre-commit ] pass_filenames: false ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Renat Mennanov 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. ================================================ FILE: README.md ================================================ ## Protobuf Field Mask utils for Go [![Tests](https://github.com/mennanov/fieldmask-utils/actions/workflows/tests.yml/badge.svg)](https://github.com/mennanov/fieldmask-utils/actions/workflows/tests.yml) [![Coverage](https://codecov.io/gh/mennanov/fieldmask-utils/branch/master/graph/badge.svg?token=O7HtNMO6Ra)](https://codecov.io/gh/mennanov/fieldmask-utils) Features: * Copy from any Go struct to any compatible Go struct with a field mask applied * Copy from any Go struct to a `map[string]interface{}` with a field mask applied * Extensible masks (e.g. inverse mask: copy all except those mentioned, etc.) * Supports [Protobuf Any](https://developers.google.com/protocol-buffers/docs/proto3#any) message types. If you're looking for a simple FieldMask library to work with protobuf messages only (not arbitrary structs) consider this tiny repo: [https://github.com/mennanov/fmutils](https://github.com/mennanov/fmutils) ### Examples Copy from a protobuf message to a protobuf message: ```proto // testproto/test.proto message UpdateUserRequest { User user = 1; google.protobuf.FieldMask field_mask = 2; } ``` ```go package main import fieldmask_utils "github.com/mennanov/fieldmask-utils" // A function that maps field mask field names to the names used in Go structs. // It has to be implemented according to your needs. // Scroll down for a reference on how to apply field masks to your gRPC services. func naming(s string) string { if s == "foo" { return "Foo" } return s } func main () { var request UpdateUserRequest userDst := &testproto.User{} // a struct to copy to mask, _ := fieldmask_utils.MaskFromPaths(request.FieldMask.Paths, naming) fieldmask_utils.StructToStruct(mask, request.User, userDst) // Only the fields mentioned in the field mask will be copied to userDst, other fields are left intact } ``` Copy from a protobuf message to a `map[string]interface{}`: ```go package main import fieldmask_utils "github.com/mennanov/fieldmask-utils" func main() { var request UpdateUserRequest userDst := make(map[string]interface{}) // a map to copy to mask, _ := fieldmask_utils.MaskFromProtoFieldMask(request.FieldMask, naming) err := fieldmask_utils.StructToMap(mask, request.User, userDst) // Only the fields mentioned in the field mask will be copied to userDst, other fields are left intact } ``` Copy with an inverse mask: ```go package main import fieldmask_utils "github.com/mennanov/fieldmask-utils" func main() { var request UpdateUserRequest userDst := &testproto.User{} // a struct to copy to mask := fieldmask_utils.MaskInverse{"Id": nil, "Friends": fieldmask_utils.MaskInverse{"Username": nil}} fieldmask_utils.StructToStruct(mask, request.User, userDst) // Only the fields that are not mentioned in the field mask will be copied to userDst, other fields are left intact. } ``` #### Naming function For developers that are looking for a mechanism to apply a mask field in their update endpoints using gRPC services, there are multiple options for the naming function described above: - Using the `CamelCase` function provided in the [original protobuf repository](https://github.com/golang/protobuf/blob/master/protoc-gen-go/generator/generator.go#L2648). This repository has been deprecated and it will potentially trigger lint errors. - You can copy-paste the `CamelCase` function to your own project or, - You can use an [Open Source alternative](https://github.com/gojaguar/jaguar) that provides the same functionality, already took care of [copying the code](https://github.com/gojaguar/jaguar/blob/main/strings/pascal_case.go), and also added tests. ```go func main() { mask := &fieldmaskpb.FieldMask{Paths: []string{"username"}} mask.Normalize() req := &UpdateUserRequest{ User: &User{ Id: 1234, Username: "Test", }, } if !mask.IsValid(req) { return } protoMask, err := fieldmask_utils.MaskFromProtoFieldMask(mask, strings.PascalCase) if err != nil { return } m := make(map[string]any) err = fieldmask_utils.StructToMap(protoMask, req, m) if err != nil { return } fmt.Println("Resulting map:", m) } ``` This will result in a map that contains the fields that need to be updated with their respective values. #### Converter hooks When trying to assign a source field to a destination using different types, one can use the Option `WithConverterHook`. All provided converter functions will be tried in order until src is assignable to dst or an error occured. Below you will find an example of how to convert from string to int64 when applying a mask by specifying such converter: ```go type A struct { Field1 string } type B struct { Field1 int64 } func main() { src := &A{ Field1: " 42 ", } dst := &B{} mask := fieldmask_utils.MaskFromString("Field1") err = fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithConverterHook(func(src, dst *reflect.Value) (interface{}, error) { data := src.Interface() // only care for this conversion if src.Kind() != reflect.String || dst.Kind() != reflect.Int64 { return data, nil } // cast it raw, ok := data.(string) if !ok { return data, nil } // parse it return strconv.ParseInt(strings.TrimSpace(raw), 10, 64) })) fmt.Println("src:", src) fmt.Println("dst:", dst) } ``` ### Limitations 1. Larger scope field masks have no effect and are not considered invalid: field mask strings `"a", "a.b", "a.b.c"` will result in a mask `a{b{c}}`, which is the same as `"a.b.c"`. 2. Masks inside a protobuf `Map` are not supported. 3. When copying from a struct to struct the destination struct must have the same fields (or a subset) as the source struct. Either of source or destination fields can be a pointer as long as it is a pointer to the type of the corresponding field. 4. `oneof` fields are represented differently in `fieldmaskpb.FieldMask` compared to `fieldmask_util.Mask`. In [FieldMask](https://pkg.go.dev/google.golang.org/protobuf/types/known/fieldmaskpb#:~:text=%23%20Field%20Masks%20and%20Oneof%20Fields) the fields are represented using their property name, in this library they are prefixed with the `oneof` name matching how Go generated code is laid out. This can lead to issues when converting between the two, for example when using `MaskFromPaths` or `MaskFromProtoFieldMask`. ================================================ FILE: copy.go ================================================ // Package fieldmask_utils provides utility functions for copying data from structs using a field mask. package fieldmask_utils import ( "reflect" "strings" "github.com/pkg/errors" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" ) // StructToStruct copies `src` struct to `dst` struct using the given FieldFilter. // Only the fields where FieldFilter returns true will be copied to `dst`. // `src` and `dst` must be coherent in terms of the field names, but it is not required for them to be of the same type. // Unexported fields are copied only if the corresponding struct filter is empty and `dst` is assignable to `src`. func StructToStruct(filter FieldFilter, src, dst interface{}, userOpts ...Option) error { opts := newDefaultOptions() for _, o := range userOpts { o(opts) } dstVal := reflect.ValueOf(dst) if dstVal.Kind() != reflect.Ptr { return errors.Errorf("dst must be a pointer, %s given", dstVal.Kind()) } srcVal := indirect(reflect.ValueOf(src)) if srcVal.Kind() != reflect.Struct { return errors.Errorf("src kind must be a struct, %s given", srcVal.Kind()) } dstVal = indirect(dstVal) if dstVal.Kind() != reflect.Struct { return errors.Errorf("dst kind must be a struct, %s given", dstVal.Kind()) } return structToStruct(filter, &srcVal, &dstVal, opts) } func ensureCompatible(src, dst *reflect.Value) error { srcKind := src.Kind() if srcKind == reflect.Ptr { srcKind = src.Type().Elem().Kind() } dstKind := dst.Kind() if dstKind == reflect.Ptr { dstKind = dst.Type().Elem().Kind() } if srcKind != dstKind { return errors.Errorf("src kind %s differs from dst kind %s", srcKind, dstKind) } return nil } func structToStruct(filter FieldFilter, src, dst *reflect.Value, userOptions *options) error { if err := ensureCompatible(src, dst); err != nil { // incompatible, try using converters: converted := false for _, fn := range userOptions.ConverterHooks { data, err := fn(src, dst) if err != nil { // error during conversion, pass upwards return err } rdata := reflect.ValueOf(data) if err := ensureCompatible(&rdata, dst); err != nil { // no change using conversion, try next continue } // it is convertable, replace src src = &rdata converted = true break } if !converted { return err } } switch src.Kind() { case reflect.Struct: if dst.CanSet() && dst.Type().AssignableTo(src.Type()) && filter.IsEmpty() { dst.Set(*src) return nil } if dst.Kind() == reflect.Ptr { if dst.IsNil() { dst.Set(reflect.New(dst.Type().Elem())) } v := dst.Elem() dst = &v } for i := 0; i < src.NumField(); i++ { srcType := src.Type() srcName := fieldName(userOptions.SrcTag, srcType.Field(i)) dstName := fieldName(userOptions.DstTag, srcType.Field(i)) subFilter, ok := filter.Filter(srcName) if !ok { // Skip this field. continue } srcField := src.Field(i) if !srcField.CanInterface() { continue } dstField := dst.FieldByName(dstName) if !dstField.CanSet() { return errors.Errorf("Can't set a value on a destination field %s", dstName) } if err := structToStruct(subFilter, &srcField, &dstField, userOptions); err != nil { return err } } case reflect.Ptr: if src.IsNil() { // If src is nil set dst to nil too. dst.Set(reflect.Zero(dst.Type())) break } if dst.Kind() == reflect.Ptr && dst.IsNil() { // If dst is nil create a new instance of the underlying type and set dst to the pointer of that instance. dst.Set(reflect.New(dst.Type().Elem())) } if srcAny, ok := src.Interface().(*anypb.Any); ok { dstAny, ok := dst.Interface().(*anypb.Any) if !ok { return errors.Errorf("dst type is %s, expected: %s ", dst.Type(), "*any.Any") } // If subfilter is empty then copy the entire any without any unmarshalling. if filter.IsEmpty() && !userOptions.UnmarshalAllAny { dst.Set(*src) break } srcProto, err := srcAny.UnmarshalNew() if err != nil { return errors.WithStack(err) } srcProtoValue := reflect.ValueOf(srcProto) if dstAny.GetTypeUrl() == "" { dstAny.TypeUrl = srcAny.GetTypeUrl() } dstProto, err := dstAny.UnmarshalNew() if err != nil { return errors.WithStack(err) } dstProtoValue := reflect.ValueOf(dstProto) if err := structToStruct(filter, &srcProtoValue, &dstProtoValue, userOptions); err != nil { return err } newDstAny := new(anypb.Any) if err := newDstAny.MarshalFrom(dstProtoValue.Interface().(proto.Message)); err != nil { return errors.WithStack(err) } dst.Set(reflect.ValueOf(newDstAny)) break } srcElem, dstElem := src.Elem(), *dst if dst.Kind() == reflect.Ptr { dstElem = dst.Elem() } if err := structToStruct(filter, &srcElem, &dstElem, userOptions); err != nil { return err } case reflect.Interface: if src.IsNil() { // If src is nil set dst to nil too. dst.Set(reflect.Zero(dst.Type())) break } if dst.IsNil() { if src.Elem().Kind() != reflect.Ptr { // Non-pointer interface implementations are not addressable. return errors.Errorf("expected a pointer for an interface value, got %s instead", src.Elem().Kind()) } dst.Set(reflect.New(src.Elem().Elem().Type())) } srcElem, dstElem := src.Elem(), dst.Elem() if err := structToStruct(filter, &srcElem, &dstElem, userOptions); err != nil { return err } case reflect.Slice: if src.IsNil() { // If the source slice is nil the dst slice is set to nil too. dst.Set(reflect.Zero(dst.Type())) break } dstLen := dst.Len() srcLen := userOptions.CopyListSize(src) for i := 0; i < srcLen; i++ { srcItem := src.Index(i) var dstItem reflect.Value if i < dstLen { // Use an existing item. dstItem = dst.Index(i) } else { // Create a new item if needed. dstItem = reflect.New(dst.Type().Elem()).Elem() } if err := structToStruct(filter, &srcItem, &dstItem, userOptions); err != nil { return err } if i >= dstLen { // Append newly created items to the slice. dst.Set(reflect.Append(*dst, dstItem)) } } if dstLen > srcLen { dst.SetLen(srcLen) } case reflect.Array: dstLen := dst.Len() srcLen := userOptions.CopyListSize(src) if dstLen < srcLen { return errors.Errorf("dst array size %d is less than src size %d", dstLen, srcLen) } for i := 0; i < srcLen; i++ { srcItem := src.Index(i) dstItem := dst.Index(i) if err := structToStruct(filter, &srcItem, &dstItem, userOptions); err != nil { return errors.WithStack(err) } } default: if !dst.CanSet() { return errors.Errorf("dst %s, %s is not settable", dst, dst.Type()) } if dst.Kind() == reflect.Ptr { if !src.CanAddr() { return errors.Errorf("src %s, %s is not addressable", src, src.Type()) } dst.Set(src.Addr()) } else { dst.Set(*src) } } return nil } // options are used in StructToStruct and StructToMap functions to modify the copying behavior. type options struct { // DstTag can be used to customize the dst field name according to the field's tag, i.g. json. DstTag string // SrcTag can be used to customize the src field name according to the field's tag, i.g. json. SrcTag string // CopyListSize can control the number of elements copied from src depending on src's Value CopyListSize func(src *reflect.Value) int // MapVisitor is called for every filtered field in structToMap. // // It is called before copying the data from source to destination allowing custom processing. // If the visitor function returns true the visited field is skipped. MapVisitor mapVisitor // UnmarshalAllAny is used to indicate unmarshal all any fields. Default to true to keep backward compatibility. // // If an any field is encountered and this flag is not set, it will only Unmarshal it if there is a subfilter for that field. // If set it will always Unmarshal all any fields UnmarshalAllAny bool // ConverterHooks stores converter functions to be used when calling [StructToStruct]. // // All converters will be tried in order to convert a src to dst in cases // where src cannot be directly assigned to dst. // Any value returned by converters will be used for src if it is assignable // after conversion. // If a converter returns an error that error is propagated to the // initial call of StructToStruct. ConverterHooks []func(src, dst *reflect.Value) (interface{}, error) } // mapVisitor is called for every filtered field in structToMap. type mapVisitor func( filter FieldFilter, src, dst reflect.Value, srcFieldName, dstFieldName string, srcFieldValue reflect.Value) MapVisitorResult type MapVisitorResult struct { SkipToNext bool UpdatedDst *reflect.Value } // Option function modifies the given options. type Option func(*options) // WithTag sets the destination field name func WithTag(s string) Option { return func(o *options) { o.DstTag = s } } // WithSrcTag sets an option that gets the source field name from the field's tag. func WithSrcTag(s string) Option { return func(o *options) { o.SrcTag = s } } // WithCopyListSize sets CopyListSize func you can set copy size according to src. func WithCopyListSize(f func(src *reflect.Value) int) Option { return func(o *options) { o.CopyListSize = f } } // WithMapVisitor sets the fields visitor function for StructToMap. func WithMapVisitor(visitor mapVisitor) Option { return func(o *options) { o.MapVisitor = visitor } } func WithUnmarshalAllAny(unmarshal bool) Option { return func(o *options) { o.UnmarshalAllAny = unmarshal } } // WithConverterHook adds a converter hook to convert from src to dst. func WithConverterHook(converter func(src, dst *reflect.Value) (interface{}, error)) Option { return func(o *options) { o.ConverterHooks = append(o.ConverterHooks, converter) } } func newDefaultOptions() *options { // set default CopyListSize is func which return src.Len() return &options{ CopyListSize: func(src *reflect.Value) int { return src.Len() }, UnmarshalAllAny: true, } } // fieldName gets the field name according to the field's tag, or gets StructField.Name default when the field's tag is empty. func fieldName(tag string, f reflect.StructField) string { if tag == "" { return f.Name } lookupResult, ok := f.Tag.Lookup(tag) if !ok { return f.Name } firstComma := strings.Index(lookupResult, ",") if firstComma == -1 { return lookupResult } return lookupResult[:firstComma] } // StructToMap copies `src` struct to the `dst` map. // Behavior is similar to `StructToStruct`. // Arrays in the non-empty dst are converted to slices. func StructToMap(filter FieldFilter, src interface{}, dst map[string]interface{}, userOpts ...Option) error { opts := newDefaultOptions() for _, o := range userOpts { o(opts) } _, err := structToMap(filter, reflect.ValueOf(src), reflect.ValueOf(dst), opts) return err } func structToMap(filter FieldFilter, src, dst reflect.Value, userOptions *options) (reflect.Value, error) { switch src.Kind() { case reflect.Struct: if dst.Kind() != reflect.Map { return dst, errors.Errorf("incompatible destination kind: %s, expected map", dst.Kind()) } srcType := src.Type() for i := 0; i < src.NumField(); i++ { srcName := fieldName(userOptions.SrcTag, srcType.Field(i)) if !isExported(srcType.Field(i)) { // Unexported fields can not be copied. continue } subFilter, ok := filter.Filter(srcName) if !ok { // Skip this field. continue } srcField := indirect(src.Field(i)) dstName := fieldName(userOptions.DstTag, srcType.Field(i)) mapValue := indirect(dst.MapIndex(reflect.ValueOf(dstName))) if !mapValue.IsValid() { if srcField.IsValid() { mapValue = newValue(srcField.Type()) } else { dstMap := dst.Interface().(map[string]interface{}) dstMap[dstName] = nil continue } } if userOptions.MapVisitor != nil { result := userOptions.MapVisitor(filter, src, mapValue, srcName, dstName, srcField) if result.UpdatedDst != nil { mapValue = *result.UpdatedDst } if result.SkipToNext { if result.UpdatedDst != nil { dst.SetMapIndex(reflect.ValueOf(dstName), mapValue) } continue } } if isPrimitive(mapValue.Kind()) { dst.SetMapIndex(reflect.ValueOf(dstName), srcField) continue } var err error if mapValue, err = structToMap(subFilter, srcField, mapValue, userOptions); err != nil { return dst, err } dst.SetMapIndex(reflect.ValueOf(dstName), mapValue) } case reflect.Ptr: if src.IsNil() { reflect.ValueOf(dst).Set(reflect.ValueOf(nil)) break } var err error if dst, err = structToMap(filter, indirect(src), dst, userOptions); err != nil { return dst, err } case reflect.Interface: if src.IsNil() { reflect.ValueOf(dst).Set(reflect.ValueOf(nil)) break } var err error if dst, err = structToMap(filter, indirect(src), dst, userOptions); err != nil { return dst, err } case reflect.Array, reflect.Slice: if dstKind := dst.Kind(); dstKind != reflect.Slice && dstKind != reflect.Array { return dst, errors.Errorf("incompatible destination kind: %s, expected slice", dst.Kind()) } itemType := src.Type().Elem() desiredDstLen := userOptions.CopyListSize(&src) itemKind := itemType.Kind() if isPrimitive(itemKind) { // Handle this array/slice as a regular non-nested data structure: copy it entirely to dst. if desiredDstLen < src.Len() { dst = src.Slice(0, desiredDstLen) } else { dst = src } } else { if dst.Kind() == reflect.Array { // Convert the array to a slice. sliceDst := newValue(src.Type()) for i := 0; i < dst.Len(); i++ { sliceDst = reflect.Append(sliceDst, dst.Index(i)) } dst = sliceDst } var err error for i := 0; i < desiredDstLen; i++ { itemExists := false var subDst reflect.Value if i < dst.Len() { subDst = dst.Index(i) itemExists = true } else { subDst = newValue(itemType) } if subDst, err = structToMap(filter, src.Index(i), subDst, userOptions); err != nil { return subDst, err } if !itemExists { dst = reflect.Append(dst, subDst) } } if desiredDstLen < dst.Len() { dst = dst.Slice(0, desiredDstLen) } } case reflect.Invalid: dst.Set(reflect.ValueOf(nil)) default: dst.Set(src) } return dst, nil } func indirect(v reflect.Value) reflect.Value { for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { v = v.Elem() } return v } // isPrimitive checks whether the given kind is simple enough so that it can be copied directly without recursion. func isPrimitive(kind reflect.Kind) bool { return kind != reflect.Ptr && kind != reflect.Struct && kind != reflect.Interface && kind != reflect.Slice && kind != reflect.Array && kind != reflect.Map } // newValue creates a new value given its type. func newValue(t reflect.Type) reflect.Value { switch t.Kind() { case reflect.Struct: return reflect.MakeMap(reflect.TypeOf(map[string]interface{}{})) case reflect.Array, reflect.Slice: return reflect.MakeSlice(reflect.SliceOf(newValue(t.Elem()).Type()), 0, 0) case reflect.Ptr: return newValue(t.Elem()) default: return reflect.New(t).Elem() } } // isExported is a backport of reflect.StructField.IsExported() for the older versions of golang (<1.17). func isExported(f reflect.StructField) bool { return f.PkgPath == "" } ================================================ FILE: copy_proto_test.go ================================================ package fieldmask_utils_test import ( "testing" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/timestamppb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" fieldmask_utils "github.com/mennanov/fieldmask-utils" "github.com/mennanov/fieldmask-utils/testproto" ) var testUserFull *testproto.User var testUserPartial *testproto.User func init() { ts := ×tamppb.Timestamp{ Seconds: 5, // easy to verify Nanos: 6, // easy to verify } serializedTs, _ := proto.Marshal(ts) friend1 := &testproto.User{ Id: 2, Username: "friend", Role: testproto.Role_REGULAR, Meta: map[string]string{"foo": "bar"}, Deactivated: true, Permissions: []testproto.Permission{testproto.Permission_READ, testproto.Permission_WRITE}, Avatar: &testproto.Image{ OriginalUrl: "original.jpg", ResizedUrl: "resized.jpg", }, Images: []*testproto.Image{ { OriginalUrl: "FRIEND original_image1.jpg", ResizedUrl: "FRIEND resized_image1.jpg", }, { OriginalUrl: "FRIEND original_image2.jpg", ResizedUrl: "FRIEND resized_image2.jpg", }, }, Tags: []string{"FRIEND tag1", "FRIEND tag2", "FRIEND tag3"}, Name: &testproto.User_FemaleName{FemaleName: "Maggy"}, } testUserFull = &testproto.User{ Id: 1, Username: "username", Role: testproto.Role_ADMIN, Meta: map[string]string{"foo": "bar"}, Deactivated: true, Permissions: []testproto.Permission{testproto.Permission_READ, testproto.Permission_WRITE}, Avatar: &testproto.Image{ OriginalUrl: "original.jpg", ResizedUrl: "resized.jpg", }, Images: []*testproto.Image{ { OriginalUrl: "original_image1.jpg", ResizedUrl: "resized_image1.jpg", }, { OriginalUrl: "original_image2.jpg", ResizedUrl: "resized_image2.jpg", }, }, Tags: []string{"tag1", "tag2", "tag3"}, Friends: []*testproto.User{friend1}, Name: &testproto.User_MaleName{MaleName: "John"}, Details: []*anypb.Any{ { TypeUrl: string("example.com/example/" + proto.MessageName(ts)), Value: serializedTs, }, }, } extraUser, err := anypb.New(testUserFull) if err != nil { panic(err) } testUserFull.ExtraUser = extraUser testUserPartial = &testproto.User{ Id: 1, Username: "username", } } func TestStructToStruct_Proto(t *testing.T) { userDst := &testproto.User{} mask := fieldmask_utils.MaskFromString( "Id,Avatar{OriginalUrl},Tags,Images,Permissions,Friends{Images{ResizedUrl}},Name{MaleName},ExtraUser{Id,Avatar{OriginalUrl}}") err := fieldmask_utils.StructToStruct(mask, testUserFull, userDst) require.NoError(t, err) assert.Equal(t, testUserFull.Id, userDst.Id) assert.Equal(t, testUserFull.Avatar.OriginalUrl, userDst.Avatar.OriginalUrl) assert.Equal(t, "", userDst.Avatar.ResizedUrl) assert.Equal(t, testUserFull.Tags, userDst.Tags) require.Equal(t, len(testUserFull.Images), len(userDst.Images)) for i, srcImg := range testUserFull.Images { assert.Equal(t, srcImg.OriginalUrl, userDst.Images[i].OriginalUrl) assert.Equal(t, srcImg.ResizedUrl, userDst.Images[i].ResizedUrl) } assert.Equal(t, testUserFull.Name, userDst.Name) assert.Equal(t, testUserFull.Permissions, userDst.Permissions) assert.Equal(t, len(testUserFull.Friends), len(userDst.Friends)) assert.Equal(t, len(testUserFull.Friends[0].Images), len(userDst.Friends[0].Images)) assert.Equal(t, testUserFull.Friends[0].Images[0].ResizedUrl, userDst.Friends[0].Images[0].ResizedUrl) assert.Equal(t, "", userDst.Friends[0].Images[0].OriginalUrl) // Zero (default) values below. assert.Equal(t, testproto.Role_UNKNOWN, userDst.Role) assert.Equal(t, false, userDst.Deactivated) assert.Equal(t, map[string]string(nil), userDst.Meta) extraUser := &testproto.User{} err = userDst.ExtraUser.UnmarshalTo(extraUser) require.NoError(t, err) assert.Equal(t, testUserFull.Id, extraUser.Id) assert.Equal(t, testUserFull.Avatar.OriginalUrl, extraUser.Avatar.OriginalUrl) } func TestStructToStruct_ExistingAnyPreserved(t *testing.T) { existingExtraUser := &testproto.User{ Id: 42, Username: "emily", Role: testproto.Role_REGULAR, } existingExtraUserAny, err := anypb.New(existingExtraUser) require.NoError(t, err) userDst := &testproto.User{ ExtraUser: existingExtraUserAny, } mask := fieldmask_utils.MaskFromString("ExtraUser{Id,Avatar{OriginalUrl}}") err = fieldmask_utils.StructToStruct(mask, testUserFull, userDst) require.NoError(t, err) extraUser := &testproto.User{} err = userDst.ExtraUser.UnmarshalTo(extraUser) require.NoError(t, err) assert.Equal(t, testUserFull.Id, extraUser.Id) assert.Equal(t, testUserFull.Avatar.OriginalUrl, extraUser.Avatar.OriginalUrl) assert.Equal(t, existingExtraUser.Username, extraUser.Username) assert.Equal(t, existingExtraUser.Role, extraUser.Role) } func TestStructToStruct_PartialProtoSuccess(t *testing.T) { userDst := &testproto.User{} mask := fieldmask_utils.MaskFromString( "Id,Avatar{OriginalUrl},Images,Username,Permissions,Name{MaleName}") err := fieldmask_utils.StructToStruct(mask, testUserPartial, userDst) assert.Nil(t, err) assert.Equal(t, testUserPartial.Id, userDst.Id) assert.Equal(t, testUserPartial.Username, userDst.Username) assert.Equal(t, testUserPartial.Name, userDst.Name) } func TestStructToStruct_MaskInverse(t *testing.T) { userSrc := &testproto.User{ Id: 1, Username: "username", Role: testproto.Role_ADMIN, Meta: map[string]string{"foo": "bar"}, Deactivated: false, Permissions: []testproto.Permission{testproto.Permission_EXECUTE}, Name: &testproto.User_FemaleName{FemaleName: "Dana"}, Friends: []*testproto.User{ {Id: 2, Username: "friend1"}, {Id: 3, Username: "friend2"}, }, } userDst := &testproto.User{} mask := fieldmask_utils.MaskInverse{"Id": nil, "Friends": fieldmask_utils.MaskInverse{"Username": nil}} err := fieldmask_utils.StructToStruct(mask, userSrc, userDst) require.NoError(t, err) // Verify that Id is not copied. assert.Equal(t, uint32(0), userDst.Id) // Verify that Friend Usernames are not copied. assert.Equal(t, "", userDst.Friends[0].Username) assert.Equal(t, "", userDst.Friends[1].Username) // Copy missed fields manually and then compare these structs. userDst.Id = userSrc.Id userDst.Friends[0].Username = userSrc.Friends[0].Username userDst.Friends[1].Username = userSrc.Friends[1].Username assert.Equal(t, userSrc, userDst) } func TestStructToStruct_NonProtoSuccess(t *testing.T) { type Image struct { OriginalUrl string ResizedUrl string } type User struct { Id uint32 Username string Deactivated bool Images []*Image } src := &User{ Id: 1, Username: "johnny", Deactivated: true, Images: []*Image{ {"original_url.jpg", "resized_url.jpg"}, {"original_url.jpg", "resized_url.jpg"}, }, } dst := &testproto.User{} mask := fieldmask_utils.MaskFromString("") err := fieldmask_utils.StructToStruct(mask, src, dst) assert.NoError(t, err) assert.Equal(t, src.Id, dst.Id) assert.Equal(t, src.Username, dst.Username) assert.Equal(t, len(src.Images), len(dst.Images)) assert.Equal(t, src.Images[0].OriginalUrl, dst.Images[0].OriginalUrl) assert.Equal(t, src.Images[0].ResizedUrl, dst.Images[0].ResizedUrl) assert.Equal(t, src.Images[1].OriginalUrl, dst.Images[1].OriginalUrl) assert.Equal(t, src.Images[1].ResizedUrl, dst.Images[1].ResizedUrl) assert.Equal(t, src.Deactivated, dst.Deactivated) } func TestStructToStruct_MaskInverseFromMask(t *testing.T) { userSrc := &testproto.User{ Id: 1, Username: "username", Role: testproto.Role_ADMIN, Meta: map[string]string{"foo": "bar"}, Deactivated: false, Permissions: []testproto.Permission{testproto.Permission_EXECUTE}, Name: &testproto.User_FemaleName{FemaleName: "Dana"}, Friends: []*testproto.User{ {Id: 2, Username: "friend1"}, {Id: 3, Username: "friend2"}, }, } userDst := &testproto.User{} // Mask to MaskInverse mask := fieldmask_utils.MaskInverse{"Id": fieldmask_utils.Mask{}, "Friends": fieldmask_utils.Mask{"Username": fieldmask_utils.Mask{}}} err := fieldmask_utils.StructToStruct(mask, userSrc, userDst) require.NoError(t, err) assert.Equal(t, &testproto.User{ Username: userSrc.Username, Role: userSrc.Role, Meta: userSrc.Meta, Deactivated: userSrc.Deactivated, Permissions: userSrc.Permissions, Name: userSrc.Name, Friends: []*testproto.User{ {Username: "friend1"}, {Username: "friend2"}, }, }, userDst) } func TestStructToStruct_NonProtoFail(t *testing.T) { type User struct { Id uint32 UnknownField string Deactivated bool } userSrc := &User{ Id: 1, UnknownField: "johnny", Deactivated: true, } userDst := &testproto.User{} mask := fieldmask_utils.MaskFromString("") err := fieldmask_utils.StructToStruct(mask, userSrc, userDst) assert.NotNil(t, err) } func TestStructToStruct_UnknownAnyInSrcNoSubfieldMask(t *testing.T) { userWithUnknown := &testproto.User{ Details: []*anypb.Any{ { TypeUrl: "example.com/example/UnknownType", Value: []byte("unknown"), }, }, } emptyUser := &testproto.User{} mask := fieldmask_utils.MaskFromString("Details") err := fieldmask_utils.StructToStruct(mask, userWithUnknown, emptyUser, fieldmask_utils.WithUnmarshalAllAny(false)) assert.NoError(t, err) assert.Equal(t, userWithUnknown.Details, emptyUser.Details) } func TestStructToStruct_UnknownAnyInDstNoSubfieldMask(t *testing.T) { userWithUnknown := &testproto.User{ Details: []*anypb.Any{ { TypeUrl: "example.com/example/UnknownType", Value: []byte("unknown"), }, }, } emptyUser := &testproto.User{} mask := fieldmask_utils.MaskFromString("Details") err := fieldmask_utils.StructToStruct(mask, emptyUser, userWithUnknown, fieldmask_utils.WithUnmarshalAllAny(false)) assert.NoError(t, err) assert.Equal(t, userWithUnknown.Details, emptyUser.Details) } func TestStructToStruct_UnknownAnyDefault(t *testing.T) { userWithUnknown := &testproto.User{ Details: []*anypb.Any{ { TypeUrl: "example.com/example/UnknownType", Value: []byte("unknown"), }, }, } emptyUser := &testproto.User{} mask := fieldmask_utils.MaskFromString("Details") err := fieldmask_utils.StructToStruct(mask, userWithUnknown, emptyUser) assert.Contains(t, err.Error(), "not found") } func TestStructToStruct_UnknownAnySubfieldMask(t *testing.T) { userWithUnknown := &testproto.User{ Details: []*anypb.Any{ { TypeUrl: "example.com/example/UnknownType", Value: []byte("unknown"), }, }, } emptyUser := &testproto.User{} mask := fieldmask_utils.MaskFromString("Details{Id}") err := fieldmask_utils.StructToStruct(mask, userWithUnknown, emptyUser, fieldmask_utils.WithUnmarshalAllAny(false)) assert.Contains(t, err.Error(), "not found") } func TestStructToMap_Success(t *testing.T) { userDst := make(map[string]interface{}) mask := fieldmask_utils.MaskFromString( "Id,Avatar{OriginalUrl},Tags,Images,Permissions,Friends{Images{ResizedUrl}}") err := fieldmask_utils.StructToMap(mask, testUserFull, userDst) require.Nil(t, err) expected := map[string]interface{}{ "Id": testUserFull.Id, "Avatar": map[string]interface{}{ "OriginalUrl": testUserFull.Avatar.OriginalUrl, }, "Tags": testUserFull.Tags, "Images": []map[string]interface{}{ {"OriginalUrl": testUserFull.Images[0].OriginalUrl, "ResizedUrl": testUserFull.Images[0].ResizedUrl}, {"OriginalUrl": testUserFull.Images[1].OriginalUrl, "ResizedUrl": testUserFull.Images[1].ResizedUrl}, }, "Permissions": testUserFull.Permissions, "Friends": []map[string]interface{}{ { "Images": []map[string]interface{}{ {"ResizedUrl": testUserFull.Friends[0].Images[0].ResizedUrl}, {"ResizedUrl": testUserFull.Friends[0].Images[1].ResizedUrl}, }, }, }, } assert.Equal(t, expected, userDst) } func TestStructToMap_PartialProtoSuccess(t *testing.T) { userDst := make(map[string]interface{}) mask := fieldmask_utils.MaskFromString( "Id,Avatar{OriginalUrl},Images,Username,Permissions,Name{MaleName}") err := fieldmask_utils.StructToMap(mask, testUserPartial, userDst) require.Nil(t, err) expected := map[string]interface{}{ "Id": testUserPartial.Id, "Avatar": nil, "Images": []map[string]interface{}{}, "Username": testUserPartial.Username, "Permissions": []testproto.Permission(nil), "Name": nil, } assert.Equal(t, expected, userDst) } ================================================ FILE: copy_test.go ================================================ package fieldmask_utils_test import ( "encoding/json" "errors" "fmt" "reflect" "strconv" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" fieldmask_utils "github.com/mennanov/fieldmask-utils" ) func TestStructToStruct_SimpleStruct(t *testing.T) { type A struct { Field1 string Field2 int } src := &A{ Field1: "src field1", Field2: 1, } dst := new(A) mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &A{ Field1: "src field1", Field2: 0, }, dst) } func TestStructToStruct_PtrToInt(t *testing.T) { type A struct { Field2 *int } n := 42 src := &A{ Field2: &n, } dst := new(A) mask := fieldmask_utils.MaskFromString("Field2") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &A{ Field2: src.Field2, }, dst) } func TestStructToStruct_StructToPointer(t *testing.T) { v15 := 15 v42 := 42 type N struct { Field1 int } type S struct { Field1 N Field2 int } src := &S{ Field1: N{ Field1: v15, }, Field2: v42, } type SN struct { Field1 *int } type D struct { Field1 *SN Field2 *int } dst := new(D) mask := fieldmask_utils.MaskFromString("Field1,Field2") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &D{ Field1: &SN{ Field1: &v15, }, Field2: &v42, }, dst) } func TestStructToStruct_IntToPointer(t *testing.T) { v := 42 type S struct { Field2 int } src := &S{ Field2: v, } type D struct { Field2 *int } dst := new(D) mask := fieldmask_utils.MaskFromString("Field2") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &D{ Field2: &v, }, dst) } func TestStructToStruct_PointerToInt(t *testing.T) { v := 42 type S struct { Field2 *int } src := &S{ Field2: &v, } type D struct { Field2 int } dst := new(D) mask := fieldmask_utils.MaskFromString("Field2") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &D{ Field2: 42, }, dst) } func TestStructToStruct_Incompatible(t *testing.T) { type S struct { Field2 int } src := &S{ Field2: 42, } type D struct { Field2 string } dst := new(D) mask := fieldmask_utils.MaskFromString("Field2") err := fieldmask_utils.StructToStruct(mask, src, dst) require.EqualError(t, err, "src kind int differs from dst kind string") } func TestStructToStruct_PtrToStruct_EmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string Field2 int A *A } type C struct { Field1 string B *B } src := &C{ Field1: "C field1", B: &B{ Field1: "StringerB field1", Field2: 1, A: &A{ Field1: "StringerA field1", Field2: 5, }, }, } dst := new(C) mask := fieldmask_utils.MaskFromString("B{Field1,A{Field2}}") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &C{ Field1: "", B: &B{ Field1: src.B.Field1, Field2: 0, A: &A{ Field1: "", Field2: src.B.A.Field2, }, }, }, dst) } func TestStructToStruct_PtrToStruct_NonEmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string Field2 int A *A } type C struct { Field1 string B *B } src := &C{ Field1: "src C field1", B: &B{ Field1: "StringerB field1", Field2: 1, A: &A{ Field1: "StringerA field1", Field2: 5, }, }, } dst := &C{ Field1: "dst C field1", B: &B{ Field1: "dst StringerB field1", Field2: 2, A: &A{ Field1: "dst StringerA field1", Field2: 10, }, }, } mask := fieldmask_utils.MaskFromString("B{Field1,A{Field2}}") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &C{ Field1: "dst C field1", B: &B{ Field1: src.B.Field1, Field2: 2, A: &A{ Field1: "dst StringerA field1", Field2: src.B.A.Field2, }, }, }, dst) } func TestStructToStruct_NestedStruct_EmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string Field2 int A A } type C struct { Field1 string B B } src := &C{ Field1: "C field1", B: B{ Field1: "StringerB field1", Field2: 1, A: A{ Field1: "StringerA field1", Field2: 5, }, }, } dst := new(C) mask := fieldmask_utils.MaskFromString("B{Field1,A{Field2}}") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &C{ Field1: "", B: B{ Field1: src.B.Field1, Field2: 0, A: A{ Field1: "", Field2: src.B.A.Field2, }, }, }, dst) } func TestStructToStruct_NestedStruct_EmptyDst_OptionDst(t *testing.T) { opts := fieldmask_utils.WithTag("db") type ASrc struct { Field1 string Field2 int `db:"SomeField"` } type BSrc struct { Field1 string `struct:"a_name"` A ASrc `db:"AnotherName"` } src := &BSrc{ Field1: "B Field1", A: ASrc{ Field1: "A Field 1", Field2: 1, }, } type ADst struct { Field1 string SomeField int } type BDst struct { Field1 string AnotherName ADst } dst := &BDst{} mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToStruct(mask, src, dst, opts) require.NoError(t, err) assert.Equal(t, &BDst{ Field1: src.Field1, AnotherName: ADst{ SomeField: src.A.Field2, }, }, dst) } func TestStructToStruct_NestedStruct_NonEmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string Field2 int A A } type C struct { Field1 string B B } src := &C{ Field1: "src C field1", B: B{ Field1: "src StringerB field1", Field2: 1, A: A{ Field1: "src StringerA field1", Field2: 5, }, }, } dst := &C{ Field1: "dst C field1", B: B{ Field1: "dst StringerB field1", Field2: 2, A: A{ Field1: "dst StringerA field1", Field2: 10, }, }, } mask := fieldmask_utils.MaskFromString("B{Field1,A{Field2}}") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &C{ Field1: "dst C field1", B: B{ Field1: src.B.Field1, Field2: 2, A: A{ Field1: "dst StringerA field1", Field2: src.B.A.Field2, }, }, }, dst) } func TestStructToStruct_SliceOfStructs_EmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string A []A } src := &B{ Field1: "src StringerB field1", A: []A{ { Field1: "StringerA field1 0", Field2: 1, }, { Field1: "StringerA field1 1", Field2: 2, }, }, } dst := new(B) mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &B{ Field1: src.Field1, A: []A{ { Field1: "", Field2: src.A[0].Field2, }, { Field1: "", Field2: src.A[1].Field2, }, }, }, dst) } func TestStructToStruct_SliceOfStructs_NonEmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string A []A } src := &B{ Field1: "src StringerB field1", A: []A{ { Field1: "StringerA field1 0", Field2: 1, }, { Field1: "StringerA field1 1", Field2: 2, }, { Field1: "StringerA field1 2", Field2: 3, }, }, } dst := &B{ Field1: "dst StringerB field1", A: []A{ { Field1: "dst StringerA field1 0", Field2: 10, }, { Field1: "dst StringerA field1 1", Field2: 20, }, }, } mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &B{ Field1: src.Field1, A: []A{ { Field1: "dst StringerA field1 0", Field2: src.A[0].Field2, }, { Field1: "dst StringerA field1 1", Field2: src.A[1].Field2, }, { Field1: "", Field2: src.A[2].Field2, }, }, }, dst) } func TestStructToStruct_EntireSlice_NonEmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string A []A } src := &B{ Field1: "src StringerB field1", A: []A{ { Field1: "StringerA field1 0", Field2: 1, }, { Field1: "StringerA field1 1", Field2: 2, }, }, } dst := &B{ Field1: "dst StringerB field1", A: []A{ { Field1: "dst StringerA field1 0", Field2: 10, }, { Field1: "dst StringerA field1 1", Field2: 20, }, { Field1: "dst StringerA field1 2", Field2: 30, }, }, } mask := fieldmask_utils.MaskFromString("Field1,A") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &B{ Field1: src.Field1, A: src.A, }, dst) } func TestStructToStruct_NilSrcSlice_NonEmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string A []A } src := &B{ Field1: "src StringerB field1", A: nil, } dst := &B{ Field1: "dst StringerB field1", A: []A{ { Field1: "dst StringerA field1 0", Field2: 10, }, { Field1: "dst StringerA field1 1", Field2: 20, }, { Field1: "dst StringerA field1 2", Field2: 30, }, }, } mask := fieldmask_utils.MaskFromString("Field1,A") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &B{ Field1: src.Field1, A: src.A, }, dst) } func TestStructToStruct_SliceOfPtrsToStruct_EmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string A []*A } src := &B{ Field1: "src StringerB field1", A: []*A{ { Field1: "StringerA field1 0", Field2: 1, }, { Field1: "StringerA field1 1", Field2: 2, }, }, } dst := new(B) mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &B{ Field1: src.Field1, A: []*A{ { Field1: "", Field2: src.A[0].Field2, }, { Field1: "", Field2: src.A[1].Field2, }, }, }, dst) } func TestStructToStruct_ArrayOfStructs_EmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string A [2]A } type C struct { Field1 string A [3]A } src := &B{ Field1: "src StringerB field1", A: [2]A{ { Field1: "StringerA field1 0", Field2: 1, }, { Field1: "StringerA field1 1", Field2: 2, }, }, } dst := new(C) mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &C{ Field1: src.Field1, A: [3]A{ { Field1: "", Field2: src.A[0].Field2, }, { Field1: "", Field2: src.A[1].Field2, }, { Field1: "", Field2: 0, }, }, }, dst) } func TestStructToStruct_Array_DstLenLessThanSrc(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string A [2]A } type C struct { Field1 string A [1]A } src := &B{ Field1: "src StringerB field1", A: [2]A{ { Field1: "StringerA field1 0", Field2: 1, }, { Field1: "StringerA field1 1", Field2: 2, }, }, } dst := new(C) mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToStruct(mask, src, dst) assert.Error(t, err) } func TestStructToStruct_DifferentStructTypes(t *testing.T) { type A struct { Field string } type B struct { Field string } src := &A{"value"} dst := new(B) mask := fieldmask_utils.MaskFromString("Field") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &B{src.Field}, dst) } func TestStructToStruct_DifferentStructTypesNested(t *testing.T) { type A struct { Field string } type AA struct { Field string } type B struct { A A } type C struct { A AA } src := &B{ A: A{ Field: "value", }, } dst := new(C) mask := fieldmask_utils.MaskFromString("A") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &C{ A: AA{ Field: src.A.Field, }, }, dst) } func TestStructToStruct_DifferentStructTypesPtrNested(t *testing.T) { type A struct { Field string } type AA struct { Field string } type B struct { A *A } type C struct { A *AA } src := &B{ A: &A{ Field: "value", }, } dst := new(C) mask := fieldmask_utils.MaskFromString("A") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &C{ A: &AA{ Field: src.A.Field, }, }, dst) } type StringerA struct { Field string } func (a *StringerA) String() string { return a.Field } type StringerB struct { Field string } func (b *StringerB) String() string { return b.Field } func TestStructToStruct_Interface_EmptyDst(t *testing.T) { type C struct { S fmt.Stringer } src := &C{ S: &StringerA{ Field: "StringerA", }, } dst := new(C) mask := fieldmask_utils.MaskFromString("S") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, &C{ S: &StringerA{ Field: "StringerA", }, }, dst) } func TestStructToStruct_SameInterfaces_NonEmptyDst(t *testing.T) { type C struct { S fmt.Stringer } src := &C{ S: &StringerA{ Field: "StringerA", }, } dst := &C{ S: &StringerA{ Field: "StringerB", }, } mask := fieldmask_utils.MaskFromString("S") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, src.S.String(), dst.S.String()) assert.Equal(t, &C{ S: &StringerA{ Field: "StringerA", }, }, dst) } func TestStructToStruct_DifferentCompatibleInterfaces_NonEmptyDst(t *testing.T) { type C struct { S fmt.Stringer } src := &C{ S: &StringerA{ Field: "StringerA", }, } dst := &C{ S: &StringerB{ Field: "StringerB", }, } mask := fieldmask_utils.MaskFromString("S") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, src.S.String(), dst.S.String()) } type Logger interface { Log() string } type LoggerImpl struct { Field string } func (d *LoggerImpl) Log() string { return d.Field } func TestStructToStruct_DifferentIncompatibleInterfaces(t *testing.T) { type C struct { S fmt.Stringer } type E struct { S Logger } src := &C{ S: &StringerA{ Field: "StringerA", }, } dst := &E{ S: &LoggerImpl{ Field: "Logger", }, } mask := fieldmask_utils.MaskFromString("S") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, src.S.String(), dst.S.Log()) } func TestStructToStruct_EmptyMask(t *testing.T) { type A struct { Field1 string Field2 int } src := &A{ Field1: "A Field1", Field2: 1, } dst := new(A) mask := fieldmask_utils.MaskFromString("") err := fieldmask_utils.StructToStruct(mask, src, dst) require.NoError(t, err) assert.Equal(t, src, dst) } type StringerImpl struct { Name string } func (*StringerImpl) someMethod() {} func (f *StringerImpl) String() string { return f.Name } type StringerNonPtrImpl struct { Name string } func (s StringerNonPtrImpl) String() string { return s.Name } func TestStructToStruct_SameInterfacesPtr_EmptyDst(t *testing.T) { type A struct { Stringer fmt.Stringer } type B struct { Stringer fmt.Stringer } src := &A{ Stringer: &StringerImpl{Name: "Jessica"}, } dst := new(B) mask := fieldmask_utils.MaskFromString("Stringer") err := fieldmask_utils.StructToStruct(mask, src, dst) assert.NoError(t, err) assert.Equal(t, src.Stringer.String(), dst.Stringer.String()) } func TestStructToStruct_SameInterfacesPtr_NonEmptyDst(t *testing.T) { type A struct { Stringer fmt.Stringer } type B struct { Stringer fmt.Stringer } src := &A{ Stringer: &StringerImpl{ Name: "Jessica", }, } dst := &B{ Stringer: &StringerImpl{ Name: "Dana", }, } mask := fieldmask_utils.MaskFromString("Stringer") err := fieldmask_utils.StructToStruct(mask, src, dst) assert.NoError(t, err) assert.Equal(t, dst.Stringer.String(), src.Stringer.String()) } func TestStructToStruct_SameInterfacesNonPtr_EmptyDst(t *testing.T) { type A struct { Stringer fmt.Stringer } src := &A{ Stringer: StringerNonPtrImpl{Name: "Jessica"}, } dst := new(A) mask := fieldmask_utils.MaskFromString("Stringer") err := fieldmask_utils.StructToStruct(mask, src, dst) assert.Error(t, err) } func TestStructToStruct_SameInterfacesNonPtr_NonEmptyDst(t *testing.T) { type A struct { Stringer fmt.Stringer } src := &A{ Stringer: StringerNonPtrImpl{Name: "Jessica"}, } dst := &A{ Stringer: StringerNonPtrImpl{Name: "Adam"}, } mask := fieldmask_utils.MaskFromString("Stringer") err := fieldmask_utils.StructToStruct(mask, src, dst) assert.Error(t, err) } func TestStructToStruct_NonPtrDst(t *testing.T) { type A struct { Field int } src := &A{Field: 1} dst := A{} mask := fieldmask_utils.MaskFromString("") err := fieldmask_utils.StructToStruct(mask, src, dst) assert.Error(t, err) } func TestStructToStruct_DifferentDstKind(t *testing.T) { type A struct { Field int } src := &A{Field: 1} dst := &map[string]interface{}{} mask := fieldmask_utils.MaskFromString("") err := fieldmask_utils.StructToStruct(mask, src, dst) assert.Error(t, err) } func TestStructToStruct_UnexportedFieldsPtr(t *testing.T) { type A struct { foo string Bar string } type B struct { A *A B string } src := &B{ A: &A{ foo: "foo", Bar: "Bar", }, B: "B", } dst := &B{} mask := fieldmask_utils.MaskFromString("A,B") err := fieldmask_utils.StructToStruct(mask, src, dst) assert.NoError(t, err) assert.Equal(t, src, dst) } func TestStructToStruct_UnexportedFields(t *testing.T) { type A struct { foo string Bar string } type B struct { A A B string } src := &B{ A: A{ foo: "foo", Bar: "Bar", }, B: "B", } dst := &B{} mask := fieldmask_utils.MaskFromString("A,B") err := fieldmask_utils.StructToStruct(mask, src, dst) assert.NoError(t, err) assert.Equal(t, src, dst) } func TestStructToStruct_MaskWithInverseMask(t *testing.T) { type A struct { Foo string Bar string } type B struct { A A B string C string } src := &B{ A: A{ Foo: "foo", Bar: "Bar", }, B: "B", C: "C", } for _, mask := range []fieldmask_utils.FieldFilter{ fieldmask_utils.Mask{"B": nil, "A": &fieldmask_utils.MaskInverse{"Bar": nil}}, fieldmask_utils.Mask{"B": fieldmask_utils.Mask{}, "A": &fieldmask_utils.MaskInverse{"Bar": fieldmask_utils.Mask{}}}, } { dst := &B{} err := fieldmask_utils.StructToStruct(mask, src, dst) assert.NoError(t, err) assert.Equal(t, &B{ A: A{ Foo: src.A.Foo, }, B: "B", }, dst) } } func TestStructToStruct_InverseMaskWithMask(t *testing.T) { type A struct { Foo string Bar string } type B struct { A A B string C string } src := &B{ A: A{ Foo: "foo", Bar: "Bar", }, B: "B", C: "C", } for _, mask := range []fieldmask_utils.FieldFilter{ fieldmask_utils.MaskInverse{"B": fieldmask_utils.Mask{}, "A": &fieldmask_utils.Mask{"Bar": fieldmask_utils.Mask{}}}, fieldmask_utils.MaskInverse{"B": nil, "A": &fieldmask_utils.Mask{"Bar": nil}}, } { dst := &B{} err := fieldmask_utils.StructToStruct(mask, src, dst) assert.NoError(t, err) assert.Equal(t, &B{ A: A{ Bar: src.A.Bar, }, C: "C", }, dst) } } func TestStructToMap_NestedStruct_EmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string A A } src := &B{ Field1: "B Field1", A: A{ Field1: "A Field 1", Field2: 1, }, } dst := make(map[string]interface{}) mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": src.Field1, "A": map[string]interface{}{ "Field2": src.A.Field2, }, }, dst) } func TestStructToMap_NestedStruct_EmptyDst_OptionDst(t *testing.T) { opts := fieldmask_utils.WithTag("db") type A struct { Field1 string Field2 int `db:"some_field"` } type B struct { Field1 string `struct:"a_name"` A A `db:"another_name"` } src := &B{ Field1: "B Field1", A: A{ Field1: "A Field 1", Field2: 1, }, } dst := make(map[string]interface{}) mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToMap(mask, src, dst, opts) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": src.Field1, "another_name": map[string]interface{}{ "some_field": src.A.Field2, }, }, dst) } func TestStructToMap_NestedStruct_NonEmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string A A } src := &B{ Field1: "B Field1", A: A{ Field1: "A Field 1", Field2: 1, }, } dst := map[string]interface{}{ "A": map[string]interface{}{ "Field1": "existing value", "Field2": 10, }, } mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": src.Field1, "A": map[string]interface{}{ "Field1": "existing value", "Field2": src.A.Field2, }, }, dst) } func TestStructToMap_PtrToStruct_EmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string A *A } src := &B{ Field1: "B Field1", A: &A{ Field1: "A Field 1", Field2: 1, }, } dst := make(map[string]interface{}) mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": src.Field1, "A": map[string]interface{}{ "Field2": src.A.Field2, }, }, dst) } func TestStructToMap_PtrToStruct_NonEmptyDst(t *testing.T) { type A struct { Field1 string Field2 int } type B struct { Field1 string A *A } src := &B{ Field1: "B Field1", A: &A{ Field1: "A Field 1", Field2: 1, }, } dst := map[string]interface{}{ "A": map[string]interface{}{ "Field1": "existing value", "Field2": 10, }, } mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": src.Field1, "A": map[string]interface{}{ "Field1": "existing value", "Field2": src.A.Field2, }, }, dst) } func TestStructToMap_ArrayOfStructs_EmptyDst(t *testing.T) { type A struct { Field1 string Field2 string } type B struct { A [1]A } src := &B{ A: [1]A{ { Field1: "src field1", Field2: "src field2", }, }, } dst := make(map[string]interface{}) mask := fieldmask_utils.MaskFromString("A{Field2}") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "A": []map[string]interface{}{ { "Field2": src.A[0].Field2, }, }, }, dst) } func TestStructToMap_SliceOfStructs_NonEmptyDst(t *testing.T) { type A struct { Field1 string Field2 string } type B struct { A []A } src := &B{ A: []A{ { Field1: "src field1", Field2: "src field2", }, }, } dst := map[string]interface{}{ "A": []map[string]interface{}{ { "Field1": "dst field1", }, }, } mask := fieldmask_utils.MaskFromString("A{Field2}") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "A": []map[string]interface{}{ { "Field1": "dst field1", "Field2": src.A[0].Field2, }, }, }, dst) } func TestStructToMap_EntireSlicePrimitive_NonEmptyDst(t *testing.T) { type A struct { Field1 []int } src := &A{ Field1: []int{1, 2, 4, 8}, } dst := map[string]interface{}{ "Field1": []int{16, 32, 64}, } mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": src.Field1, }, dst) } func TestStructToMap_EntireSlice_NonEmptyDst(t *testing.T) { type A struct { Field1 string Field2 string } type B struct { A []A } src := &B{ A: []A{ { Field1: "src ele1 field1", Field2: "src ele1 field2", }, { Field1: "src ele2 field1", Field2: "src ele2 field2", }, }, } dst := map[string]interface{}{ "A": []map[string]interface{}{ { "Field1": "dst ele1 field1", }, { "Field2": "dst ele2 field 2", }, { "Field1": "dst ele3 field 3", }, }, } mask := fieldmask_utils.MaskFromString("A") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "A": []map[string]interface{}{ { "Field1": src.A[0].Field1, "Field2": src.A[0].Field2, }, { "Field1": src.A[1].Field1, "Field2": src.A[1].Field2, }, }, }, dst) } func TestStructToMap_NilSrcSlice_NonEmptyDst(t *testing.T) { type A struct { Field1 string Field2 string } type B struct { FieldA []A } src := &B{FieldA: nil} dst := map[string]interface{}{ "FieldA": []map[string]interface{}{ { "Field1": "dst ele1 field1", }, { "Field2": "dst ele2 field 2", }, { "Field1": "dst ele3 field 3", }, }, } mask := fieldmask_utils.MaskFromString("FieldA") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "FieldA": []map[string]interface{}{}, }, dst) } func TestStructToMap_EntireSlice_DstSliceLenIsLessThanSource(t *testing.T) { type A struct { Field1 string Field2 string } type B struct { A []A } src := &B{ A: []A{ { Field1: "src ele1 field1", Field2: "src ele1 field2", }, { Field1: "src ele2 field1", Field2: "src ele2 field2", }, }, } dst := map[string]interface{}{ "A": []map[string]interface{}{ { "Field1": "dst ele1 field1", }, }, } mask := fieldmask_utils.MaskFromString("A") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "A": []map[string]interface{}{ { "Field1": "src ele1 field1", "Field2": "src ele1 field2", }, { "Field1": "src ele2 field1", "Field2": "src ele2 field2", }, }, }, dst) } func TestStructToMap_Array_NonEmptyDst(t *testing.T) { type A struct { Field1 string Field2 string } type B struct { A [3]A } src := &B{ A: [3]A{ { Field1: "src ele1 field1", Field2: "src ele1 field2", }, { Field1: "src ele2 field1", Field2: "src ele2 field2", }, { Field1: "src ele3 field1", Field2: "src ele3 field2", }, }, } dst := map[string]interface{}{ "A": [2]map[string]interface{}{ { "Field1": "dst ele1 field1", }, { "Field1": "dst ele2 field1", }, }, } mask := fieldmask_utils.MaskFromString("A{Field2}") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "A": []map[string]interface{}{ { "Field1": "dst ele1 field1", "Field2": "src ele1 field2", }, { "Field1": "dst ele2 field1", "Field2": "src ele2 field2", }, { "Field2": "src ele3 field2", }, }, }, dst) } func TestStructToMap_ArrayPrimitive_NonEmptyDst(t *testing.T) { type A struct { Field1 [5]int } src := &A{ Field1: [5]int{1, 2, 4, 8, 10}, } dst := map[string]interface{}{ "Field1": [4]int{16, 32, 64, 0}, } mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": [5]int{1, 2, 4, 8, 10}, }, dst) } func TestStructToMap_EmptySliceSrc_NonEmptyArrayDst(t *testing.T) { type A struct { Field1 []int } src := &A{ Field1: []int{}, } dst := map[string]interface{}{ "Field1": [4]int{16, 32, 64, 0}, } mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": src.Field1, }, dst) } func TestStructToStruct_CopyStructSlice_WithMaxCopyListSize(t *testing.T) { type AA struct { Field int } type A struct { Field1 []AA } src := &A{ Field1: []AA{{1}, {2}, {3}}, } dst := &A{} const copySize int = 2 mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { return copySize })) require.NoError(t, err) assert.Equal(t, &A{ Field1: src.Field1[:copySize], }, dst) } func TestStructToStruct_CopyIntSlice_WithMaxCopyListSize(t *testing.T) { type A struct { Field1 []int } src := &A{ Field1: []int{1, 2, 3}, } const copySize int = 2 dst := &A{} mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { return copySize })) require.NoError(t, err) assert.Equal(t, &A{ Field1: src.Field1[:copySize], }, dst) } func TestStructToStruct_CopyIntArray_WithMaxCopyListSize(t *testing.T) { const arraySize int = 3 type A struct { Field1 [arraySize]int } src := &A{ Field1: [arraySize]int{1, 2, 3}, } const copySize int = arraySize - 1 dst := &A{} mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { return copySize })) require.NoError(t, err) assert.Equal(t, &A{ Field1: [3]int{1, 2}, }, dst) } func TestStructToStruct_CopyStructArray_WithMaxCopyListSize(t *testing.T) { const arraySize int = 3 type AA struct { Field int } type A struct { Field1 [3]AA } src := &A{ Field1: [3]AA{{1}, {2}, {3}}, } dst := &A{} const copySize int = arraySize - 1 mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { return copySize })) require.NoError(t, err) assert.Equal(t, &A{ Field1: [3]AA{{1}, {2}}, }, dst) } func TestStructToMap_CopyStructSlice_WithMaxCopyListSize(t *testing.T) { type AA struct { Field int } type A struct { Field1 []AA } src := &A{ Field1: []AA{{1}, {2}, {3}}, } dst := map[string]interface{}{} const copySize int = 2 mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { return copySize })) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": []map[string]interface{}{{"Field": 1}, {"Field": 2}}, }, dst) } func TestStructToMap_CopyIntSlice_WithMaxCopyListSize(t *testing.T) { type A struct { Field1 []int } src := &A{ Field1: []int{1, 2, 3}, } dst := map[string]interface{}{} const copySize int = 2 mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { return copySize })) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": []int{1, 2}, }, dst) } func TestStructToMap_CopyStructArray_WithMaxCopyListSize(t *testing.T) { const arraySize int = 3 type AA struct { Field int } type A struct { Field1 [3]AA } src := &A{ Field1: [3]AA{{1}, {2}, {3}}, } dst := map[string]interface{}{} const copySize int = arraySize - 1 mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { return copySize })) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": []map[string]interface{}{{"Field": 1}, {"Field": 2}}, }, dst) } func TestStructToMap_CopyIntArray_WithMaxCopyListSize(t *testing.T) { const arraySize int = 3 type A struct { Field1 [arraySize]int } src := &A{ Field1: [arraySize]int{1, 2, 3}, } const copySize int = arraySize - 1 dst := map[string]interface{}{} mask := fieldmask_utils.MaskFromString("Field1") err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { return copySize })) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": src.Field1[:copySize], }, dst) } func TestStructToMap_CopyStructWithPrivateFields_WithMapVisitor(t *testing.T) { type A struct { Time time.Time Other int } unixTime := time.Unix(10, 10) src := &A{Time: unixTime} dst := map[string]interface{}{} mask := fieldmask_utils.MaskFromString("Time") err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithMapVisitor( func(_ fieldmask_utils.FieldFilter, _, dst reflect.Value, srcFieldName, dstFieldName string, srcFieldValue reflect.Value) fieldmask_utils.MapVisitorResult { if srcFieldName == "Time" { return fieldmask_utils.MapVisitorResult{ SkipToNext: true, UpdatedDst: &srcFieldValue, } } return fieldmask_utils.MapVisitorResult{} })) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Time": unixTime, }, dst) } func TestStructToMap_MapVisitorVisitsOnlyFilteredFields(t *testing.T) { type A struct { Field1 int Field2 string Field3 int } src := &A{Field1: 42, Field2: "hello", Field3: 44} dst := map[string]interface{}{} mask := fieldmask_utils.MaskFromString("Field1, Field2") var visitedFields []string err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithMapVisitor( func(_ fieldmask_utils.FieldFilter, _, _ reflect.Value, srcFieldName, _ string, _ reflect.Value) fieldmask_utils.MapVisitorResult { visitedFields = append(visitedFields, srcFieldName) return fieldmask_utils.MapVisitorResult{} })) require.NoError(t, err) assert.Equal(t, visitedFields, []string{"Field1", "Field2"}) } func TestStructToMap_WithMapVisitor_SkipsToNextField(t *testing.T) { type A struct { Field1 int Field2 string Field3 int } src := &A{Field1: 42, Field2: "hello", Field3: 44} dst := map[string]interface{}{} mask := fieldmask_utils.MaskFromString("Field1, Field2") err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithMapVisitor( func(_ fieldmask_utils.FieldFilter, _, _ reflect.Value, srcFieldName, dstFieldName string, _ reflect.Value) fieldmask_utils.MapVisitorResult { if srcFieldName == "Field1" { updatedDst := reflect.ValueOf(33) return fieldmask_utils.MapVisitorResult{ SkipToNext: true, UpdatedDst: &updatedDst, } } return fieldmask_utils.MapVisitorResult{} })) require.NoError(t, err) assert.Equal(t, map[string]interface{}{"Field1": 33, "Field2": "hello"}, dst) } func TestStructToStruct_CopySlice_WithDiffentItemKind(t *testing.T) { type A struct { Field1 []int Field2 []string } src := &A{ Field1: []int{1, 2, 3}, Field2: []string{"1", "2", "3"}, } dst := &A{} const copySize int = 1 mask := fieldmask_utils.MaskFromString("Field1,Field2") err := fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { if itemKind := src.Type().Elem().Kind(); itemKind == reflect.Int { return copySize } else { return src.Len() } })) require.NoError(t, err) assert.Equal(t, &A{ Field1: []int{1}, Field2: []string{"1", "2", "3"}, }, dst) } func TestStructToMap_CopySlice_WithDiffentItemKind(t *testing.T) { type A struct { Field1 []int Field2 []string } src := &A{ Field1: []int{1, 2, 3}, Field2: []string{"1", "2", "3"}, } dst := map[string]interface{}{} const copySize int = 1 mask := fieldmask_utils.MaskFromString("Field1,Field2") err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { if itemKind := src.Type().Elem().Kind(); itemKind == reflect.Int { return copySize } else { return src.Len() } })) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": []int{1}, "Field2": []string{"1", "2", "3"}, }, dst) } func TestStructToStruct_CopySlice_WithDiffentItemType(t *testing.T) { type AA struct { Int int } type A struct { Field1 []int Field2 []AA } src := &A{ Field1: []int{1, 2, 3}, Field2: []AA{{1}, {2}, {3}}, } dst := &A{} const copySize int = 1 mask := fieldmask_utils.MaskFromString("Field1,Field2") err := fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { if itemType := src.Type().Elem().Name(); itemType == "AA" { return copySize } else { return src.Len() } })) require.NoError(t, err) assert.Equal(t, &A{ Field1: []int{1, 2, 3}, Field2: []AA{{1}}, }, dst) } func TestStructToMap_CopySlice_WithDiffentItemType(t *testing.T) { type AA struct { Int int } type A struct { Field1 []int Field2 []AA } src := &A{ Field1: []int{1, 2, 3}, Field2: []AA{{1}, {2}, {3}}, } dst := map[string]interface{}{} const copySize int = 1 mask := fieldmask_utils.MaskFromString("Field1,Field2") err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithCopyListSize(func(src *reflect.Value) int { if itemType := src.Type().Elem().Name(); itemType == "AA" { return copySize } else { return src.Len() } })) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": []int{1, 2, 3}, "Field2": []map[string]interface{}{{"Int": 1}}, }, dst) } func TestStructToStruct_WithNonStructSrcError(t *testing.T) { type A struct{ Field int } var src = 1 var dst = &A{} mask := fieldmask_utils.MaskFromString("Field") err := fieldmask_utils.StructToStruct(mask, src, dst) require.Error(t, err) } func TestStructToStruct_WithMultiTagComma(t *testing.T) { type A struct { Field int `json:"field,omitempty"` } var src = A{Field: 1} var dst = map[string]interface{}{} mask := fieldmask_utils.MaskFromString("Field") err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithTag("json")) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "field": 1, }, dst) } func TestStructToMap_WithInterface(t *testing.T) { type user struct { A string B interface{} C interface{} } type c struct { A int B interface{} } mask := fieldmask_utils.MaskFromString("A,B,C") src := &user{ A: "nick", B: []int{1, 2, 3, 4}, C: c{A: 42, B: map[string]interface{}{"hi": 34}}, } dst := make(map[string]interface{}) err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithTag(`json`)) assert.Nil(t, err) expected := map[string]interface{}{ "A": "nick", "B": []int{1, 2, 3, 4}, "C": map[string]interface{}{"A": 42, "B": map[string]interface{}{"hi": 34}}, } assert.Equal(t, expected, dst) } func TestStructToMap_PtrToInt(t *testing.T) { type example struct { MyInt *int64 WhatEver string } mask := fieldmask_utils.MaskFromString("MyInt,WhatEver") myInt := int64(42) src := &example{ MyInt: &myInt, WhatEver: "hello", } dst := make(map[string]interface{}) err := fieldmask_utils.StructToMap(mask, src, dst) assert.Nil(t, err) expected := map[string]interface{}{ "MyInt": int64(42), "WhatEver": "hello", } assert.Equal(t, expected, dst) } func TestStructToMap_DifferentTypeWithSameDstKey(t *testing.T) { t.Skip("this is a programming error which is expected to panic instead of returning an error") type BB struct { Field int } type A1 struct { FieldA []int FieldB []BB `json:"FieldA"` } var src1 = A1{FieldA: []int{1, 2}, FieldB: []BB{{1}, {2}}} var dst1 = map[string]interface{}{} mask := fieldmask_utils.MaskFromString("FieldA,FieldB") err := fieldmask_utils.StructToMap(mask, src1, dst1, fieldmask_utils.WithTag("json")) require.Error(t, err) type A2 struct { FieldA [2]int FieldB [2]BB `json:"FieldA"` } var src2 = A2{FieldA: [2]int{1, 2}, FieldB: [2]BB{{1}, {2}}} var dst2 = map[string]interface{}{} mask = fieldmask_utils.MaskFromString("FieldA,FieldB") err = fieldmask_utils.StructToMap(mask, src2, dst2, fieldmask_utils.WithTag("json")) require.Error(t, err) } func TestStructToMap_EmptySrcSlice_JsonEncode(t *testing.T) { type A struct{} type B struct { As []*A } src := &B{[]*A{}} dst := make(map[string]interface{}) mask := fieldmask_utils.MaskFromString("As") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) jsonStr, _ := json.Marshal(dst) assert.Equal(t, "{\"As\":[]}", string(jsonStr)) } func TestStructToMap_NilSrcSlice_JsonEncode(t *testing.T) { t.Skip("the behavior that this test verifies has changed") type A struct{} type B struct { As []*A } src := &B{} dst := make(map[string]interface{}) mask := fieldmask_utils.MaskFromString("As") err := fieldmask_utils.StructToMap(mask, src, dst) require.NoError(t, err) jsonStr, _ := json.Marshal(dst) assert.Equal(t, "{\"As\":null}", string(jsonStr)) } func TestStructToStruct_CopySlice_WithDiffentAddr_WithDifferentFieldName(t *testing.T) { type A struct { Field1 []int Field2 []int } var src = &A{ Field1: []int{1, 2, 3}, Field2: []int{1, 2, 3}, } var field1 = reflect.ValueOf(src).Elem().FieldByName("Field1") var dst = &A{} var mask = fieldmask_utils.MaskFromString("Field1,Field2") var err = fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithCopyListSize( func(src *reflect.Value) int { if src.Pointer() == (&field1).Pointer() { return 2 } else { return src.Len() } }, )) require.NoError(t, err) assert.Equal(t, &A{ Field1: []int{1, 2}, Field2: []int{1, 2, 3}, }, dst) } func TestStructToStruct_CopySlice_WithSameAddr_WithDifferentFieldName(t *testing.T) { t.Skip("Not Address this problem") type A struct { Field1 []int Field2 []int } var arr = []int{1, 2, 3} var src = &A{ Field1: arr, Field2: arr, } var field1 = reflect.ValueOf(src).Elem().FieldByName("Field1") var dst = &A{} var mask = fieldmask_utils.MaskFromString("Field1,Field2") var err = fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithCopyListSize( func(src *reflect.Value) int { if src.Pointer() == (&field1).Pointer() { return 2 } else { return src.Len() } }, )) require.NoError(t, err) assert.Equal(t, &A{ Field1: []int{1, 2}, Field2: []int{1, 2, 3}, }, dst) } func TestStructToStruct_CopyArraySizeAccordingFieldName(t *testing.T) { type A struct { Field1 [3]int Field2 [3]int } var src = &A{ Field1: [3]int{1, 2, 3}, Field2: [3]int{1, 2, 3}, } var field1 = reflect.ValueOf(src).Elem().FieldByName("Field1") var dst = &A{} var mask = fieldmask_utils.MaskFromString("Field1,Field2") var err = fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithCopyListSize( func(src *reflect.Value) int { if src.Addr() == (&field1).Addr() { return 2 } else { return src.Len() } }, )) require.NoError(t, err) assert.Equal(t, &A{ Field1: [3]int{1, 2}, Field2: [3]int{1, 2, 3}, }, dst) } func TestStructToStruct_WithSrcTag(t *testing.T) { type A struct { Field1 string Field2 int `db:"some_field"` } type B struct { Field1 string `struct:"a_name"` A A `db:"another_name,omitempty"` } src := &B{ Field1: "B Field1", A: A{ Field1: "A Field 1", Field2: 1, }, } dst := &B{} mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") err := fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithSrcTag("db")) require.NoError(t, err) assert.Equal(t, &B{Field1: src.Field1}, dst) mask, _ = fieldmask_utils.MaskFromPaths([]string{"Field1", "another_name.some_field"}, func(s string) string { return s }) dst = &B{} err = fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithSrcTag("db")) require.NoError(t, err) assert.Equal(t, &B{Field1: src.Field1, A: A{Field2: src.A.Field2}}, dst) } func TestStructToMap_WithSrcTag(t *testing.T) { type A struct { Field1 string Field2 int `db:"some_field1" json:"some_field1_json"` Field3 bool `db:"some_field2" json:"some_field2_json"` } type B struct { Field1 string `struct:"a_name"` A A `db:"another_name,omitempty" json:"another_name_json"` } src := &B{ Field1: "B Field1", A: A{ Field1: "A Field 1", Field2: 1, }, } mask := fieldmask_utils.MaskFromString("Field1,A{Field2}") dst := make(map[string]interface{}) err := fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithTag("json"), fieldmask_utils.WithSrcTag("db")) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": src.Field1, }, dst) mask, _ = fieldmask_utils.MaskFromPaths([]string{"Field1", "another_name.some_field1", "another_name.some_field2"}, func(s string) string { return s }) dst = make(map[string]interface{}) err = fieldmask_utils.StructToMap(mask, src, dst, fieldmask_utils.WithTag("json"), fieldmask_utils.WithSrcTag("db")) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ "Field1": src.Field1, "another_name_json": map[string]interface{}{ "some_field1_json": src.A.Field2, "some_field2_json": false, }, }, dst) } func TestStructToStruct_WithConverterHook(t *testing.T) { type E struct { Field1 string } type F struct { Field1 string } type A struct { Field1 string Field2 []*E Field3 []*E Field4 []string } src := &A{ Field1: " 42 ", Field2: nil, Field3: []*E{ {Field1: "foo"}, }, Field4: []string{" 3.141 ", "-273.15"}, } type B struct { Field1 int64 Field2 []*F Field3 []*F Field4 []float64 } dst := &B{} mask := fieldmask_utils.MaskFromString("Field1,Field2,Field3,Field4") // test original error due to no conversion err := fieldmask_utils.StructToStruct(mask, src, dst) assert.Error(t, err) // test conversion errors are critical err = fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithConverterHook(func(src, dst *reflect.Value) (interface{}, error) { return nil, errors.New("dummy error") })) require.Error(t, err) assert.ErrorContains(t, err, "dummy error") // test incompatible conversion still returns original error err = fieldmask_utils.StructToStruct(mask, src, dst, fieldmask_utils.WithConverterHook(func(src, dst *reflect.Value) (interface{}, error) { return 3.14, nil })) require.Error(t, err) assert.ErrorContains(t, err, "differs from dst kind") // test successful conversion err = fieldmask_utils.StructToStruct(mask, src, dst, // convert string to int64 fieldmask_utils.WithConverterHook(func(src, dst *reflect.Value) (interface{}, error) { data := src.Interface() if src.Kind() != reflect.String || dst.Kind() != reflect.Int64 { return data, nil } raw, ok := data.(string) if !ok { return data, nil } return strconv.ParseInt(strings.TrimSpace(raw), 10, 64) }), // convert string to float64 fieldmask_utils.WithConverterHook(func(src, dst *reflect.Value) (interface{}, error) { data := src.Interface() if src.Kind() != reflect.String || dst.Kind() != reflect.Float64 { return data, nil } raw, ok := data.(string) if !ok { return data, nil } return strconv.ParseFloat(strings.TrimSpace(raw), 64) })) require.NoError(t, err) assert.Equal(t, &B{ Field1: 42, Field2: nil, Field3: []*F{ {Field1: "foo"}, }, Field4: []float64{3.141, -273.15}, }, dst) } ================================================ FILE: go.mod ================================================ module github.com/mennanov/fieldmask-utils go 1.16 require ( github.com/kr/text v0.2.0 // indirect github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.0 google.golang.org/genproto v0.0.0-20220531173845-685668d2de03 google.golang.org/protobuf v1.33.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20220531173845-685668d2de03 h1:FG2YhwyltdDPC/0XuwzU0dijPcTzvfTtst0QdlDxoMU= google.golang.org/genproto v0.0.0-20220531173845-685668d2de03/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: mask.go ================================================ package fieldmask_utils import ( "fmt" "strings" "github.com/pkg/errors" "google.golang.org/genproto/protobuf/field_mask" ) // FieldFilter is an interface used by the copying function to filter fields that are needed to be copied. type FieldFilter interface { // Filter should return a corresponding FieldFilter for the given fieldName and a boolean result. If result is true // then the field is copied, skipped otherwise. Filter(fieldName string) (FieldFilter, bool) // Returns true if the FieldFilter is empty. In this case all fields are copied. IsEmpty() bool } // FieldFilterContainer is a FieldFilter with additional methods Get and Set. type FieldFilterContainer interface { FieldFilter // Get gets the FieldFilter for the given field name. Result is false if the filter is not found. Get(fieldName string) (filter FieldFilterContainer, result bool) // Set sets the FieldFilter for the given field name. Set(fieldName string, filter FieldFilterContainer) } // Mask is a tree-based implementation of a FieldFilter. type Mask map[string]FieldFilterContainer // Get gets the FieldFilter for the given field name. Result is false if the filter is not found. func (m Mask) Get(fieldName string) (FieldFilterContainer, bool) { f, ok := m[fieldName] return f, ok } // Set sets the FieldFilter for the given field name. func (m Mask) Set(fieldName string, filter FieldFilterContainer) { m[fieldName] = filter } // Compile time interface check. var _ FieldFilter = Mask{} // Filter returns true for those fieldNames that exist in the underlying map. // Field names that start with "XXX_" are ignored as unexported. func (m Mask) Filter(fieldName string) (FieldFilter, bool) { if len(m) == 0 { // If the mask is empty choose all the exported fields. return Mask{}, !strings.HasPrefix(fieldName, "XXX_") } subFilter, ok := m[fieldName] if !ok { subFilter = Mask{} } return subFilter, ok } // IsEmpty returns true of the mask is empty. func (m Mask) IsEmpty() bool { return len(m) == 0 } func mapToString(m map[string]FieldFilterContainer) string { if len(m) == 0 { return "" } var result []string for fieldName, maskNode := range m { r := fieldName var sub string if stringer, ok := maskNode.(fmt.Stringer); ok { sub = stringer.String() } else { sub = fmt.Sprint(maskNode) } if sub != "" { r += "{" + sub + "}" } result = append(result, r) } return strings.Join(result, ",") } func (m Mask) String() string { return mapToString(m) } // MaskInverse is an inversed version of a Mask (will copy all the fields except those mentioned in the mask). type MaskInverse map[string]FieldFilterContainer // Get gets the FieldFilter for the given field name. Result is false if the filter is not found. func (m MaskInverse) Get(fieldName string) (FieldFilterContainer, bool) { f, ok := m[fieldName] return f, ok } // Set sets the FieldFilter for the given field name. func (m MaskInverse) Set(fieldName string, filter FieldFilterContainer) { m[fieldName] = filter } // Filter returns true for those fieldNames that do NOT exist in the underlying map. // Field names that start with "XXX_" are ignored as unexported. func (m MaskInverse) Filter(fieldName string) (FieldFilter, bool) { subFilter, ok := m[fieldName] if !ok { return MaskInverse{}, !strings.HasPrefix(fieldName, "XXX_") } if subFilter == nil { return nil, false } return subFilter, !subFilter.IsEmpty() } // IsEmpty returns true if the mask is empty. func (m MaskInverse) IsEmpty() bool { return len(m) == 0 } func (m MaskInverse) String() string { return mapToString(m) } // MaskFromProtoFieldMask creates a Mask from the given FieldMask. func MaskFromProtoFieldMask(fm *field_mask.FieldMask, naming func(string) string) (Mask, error) { return MaskFromPaths(fm.GetPaths(), naming) } // MaskInverseFromProtoFieldMask creates a MaskInverse from the given FieldMask. func MaskInverseFromProtoFieldMask(fm *field_mask.FieldMask, naming func(string) string) (MaskInverse, error) { return MaskInverseFromPaths(fm.GetPaths(), naming) } // MaskFromPaths creates a new Mask from the given paths. func MaskFromPaths(paths []string, naming func(string) string) (Mask, error) { mask, err := FieldFilterFromPaths(paths, naming, func() FieldFilterContainer { return make(Mask) }) if mask != nil { return mask.(Mask), err } return nil, err } // MaskInverseFromPaths creates a new MaskInverse from the given paths. func MaskInverseFromPaths(paths []string, naming func(string) string) (MaskInverse, error) { mask, err := FieldFilterFromPaths(paths, naming, func() FieldFilterContainer { return make(MaskInverse) }) if mask != nil { return mask.(MaskInverse), err } return nil, err } // FieldFilterFromPaths creates a new FieldFilter from the given paths. func FieldFilterFromPaths(paths []string, naming func(string) string, filter func() FieldFilterContainer) (FieldFilterContainer, error) { root := filter() for _, path := range paths { mask := root for _, fieldName := range strings.Split(path, ".") { if fieldName == "" { return nil, errors.Errorf("invalid fieldName FieldFilter format: \"%s\"", path) } newFieldName := naming(fieldName) subNode, ok := mask.Get(newFieldName) if !ok { mask.Set(newFieldName, filter()) subNode, _ = mask.Get(newFieldName) } mask = subNode } } return root, nil } // MaskFromString creates a new Mask instance from a given string. // Use in tests only. See FieldFilterFromString for details. func MaskFromString(s string) Mask { return FieldFilterFromString(s, func() FieldFilterContainer { return make(Mask) }).(Mask) } // MaskInverseFromString creates a new MaskInverse instance from a given string. // Use in tests only. See FieldFilterFromString for details. func MaskInverseFromString(s string) MaskInverse { return FieldFilterFromString(s, func() FieldFilterContainer { return make(MaskInverse) }).(MaskInverse) } // FieldFilterFromString creates a new FieldFilterContainer from string. // Input string is supposed to be a valid string representation of a FieldFilter like "a,b,c{d,e{f,g}},d". // Use it in tests only as the input string is not validated and the underlying function panics in case of a // parse error. func FieldFilterFromString(input string, filter func() FieldFilterContainer) FieldFilterContainer { var fieldName []string mask := filter() masks := []FieldFilterContainer{mask} for pos, r := range input { char := string(r) switch char { case " ", "\n", "\t": // Skip white spaces. case ",": if len(fieldName) != 0 { mask.Set(strings.Join(fieldName, ""), filter()) fieldName = nil } case "{": if len(fieldName) == 0 { panic(fmt.Sprintf("invalid mask format at position %d: got '{', expected a character", pos)) } subMask := filter() mask.Set(strings.Join(fieldName, ""), subMask) fieldName = nil masks = append(masks, mask) mask = subMask case "}": if len(fieldName) != 0 { mask.Set(strings.Join(fieldName, ""), filter()) fieldName = nil } mask = masks[len(masks)-1] masks = masks[:len(masks)-1] default: fieldName = append(fieldName, char) } } if len(fieldName) != 0 { mask.Set(strings.Join(fieldName, ""), filter()) } return mask } ================================================ FILE: mask_test.go ================================================ package fieldmask_utils_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/genproto/protobuf/field_mask" fieldmask_utils "github.com/mennanov/fieldmask-utils" ) func TestMask_String(t *testing.T) { mask := fieldmask_utils.MaskFromString("a{b{c}}") assert.Equal(t, "a{b{c}}", mask.String()) } func TestMaskInverse_String(t *testing.T) { mask := fieldmask_utils.MaskInverseFromString("a{b{c}}") assert.Equal(t, "a{b{c}}", mask.String()) } func TestFieldFilterFromPaths_Success(t *testing.T) { eye := func(s string) string { return s } testCases := []struct { mask *field_mask.FieldMask expectedTree string }{ { &field_mask.FieldMask{Paths: []string{ "a", // overwritten by the paths below (a.*) "a.b.c", "a.b.d", "a.c.d", "b.c.d", "a", // has no effect, since more strict rules are applied above "c", }}, "a{b{c,d},c{d}},b{c{d}},c", }, { &field_mask.FieldMask{Paths: []string{"a", "b", "b", "a"}}, "a,b", }, { &field_mask.FieldMask{Paths: []string{}}, "", }, } for _, testCase := range testCases { maskFromProto, err := fieldmask_utils.MaskFromProtoFieldMask(testCase.mask, eye) require.NoError(t, err) maskFromPaths, err := fieldmask_utils.MaskFromPaths(testCase.mask.Paths, eye) require.NoError(t, err) assert.Equal(t, fieldmask_utils.MaskFromString(testCase.expectedTree), maskFromProto) assert.Equal(t, maskFromProto, maskFromPaths) // MaskInverse maskInverseFromProto, err := fieldmask_utils.MaskInverseFromProtoFieldMask(testCase.mask, eye) require.NoError(t, err) maskInverseFromPaths, err := fieldmask_utils.MaskInverseFromPaths(testCase.mask.Paths, eye) require.NoError(t, err) fs := fieldmask_utils.MaskInverseFromString(testCase.expectedTree) assert.Equal(t, fs, maskInverseFromProto) assert.Equal(t, maskInverseFromProto, maskInverseFromPaths) } } func TestMaskFromProtoFieldMask_Failure(t *testing.T) { testCases := []*field_mask.FieldMask{ {Paths: []string{"a", ".a"}}, {Paths: []string{"."}}, {Paths: []string{"a.b.c.d.e", "a.b."}}, } for _, fieldMask := range testCases { _, err := fieldmask_utils.MaskFromProtoFieldMask(fieldMask, func(s string) string { return s }) assert.NotNil(t, err) } } func TestMaskFromString(t *testing.T) { testCases := []struct { input string expectedMask fieldmask_utils.Mask length int }{ { "foo,bar{c{d,e{f,g,h}}}", fieldmask_utils.Mask{ "foo": fieldmask_utils.Mask{}, "bar": fieldmask_utils.Mask{ "c": fieldmask_utils.Mask{ "d": fieldmask_utils.Mask{}, "e": fieldmask_utils.Mask{ "f": fieldmask_utils.Mask{}, "g": fieldmask_utils.Mask{}, "h": fieldmask_utils.Mask{}, }, }, }, }, 2, }, {"foo, bar{c {d,e{f,\ng,h}}},\tt", fieldmask_utils.Mask{ "foo": fieldmask_utils.Mask{}, "bar": fieldmask_utils.Mask{ "c": fieldmask_utils.Mask{ "d": fieldmask_utils.Mask{}, "e": fieldmask_utils.Mask{ "f": fieldmask_utils.Mask{}, "g": fieldmask_utils.Mask{}, "h": fieldmask_utils.Mask{}, }, }, }, "t": fieldmask_utils.Mask{}, }, 3}, {"foo", fieldmask_utils.Mask{"foo": fieldmask_utils.Mask{}}, 1}, {"foo,bar", fieldmask_utils.Mask{ "foo": fieldmask_utils.Mask{}, "bar": fieldmask_utils.Mask{}, }, 2}, {"foo,bar{c},d,e", fieldmask_utils.Mask{ "foo": fieldmask_utils.Mask{}, "bar": fieldmask_utils.Mask{ "c": fieldmask_utils.Mask{}, }, "d": fieldmask_utils.Mask{}, "e": fieldmask_utils.Mask{}, }, 4}, {"", fieldmask_utils.Mask{}, 0}, } for _, testCase := range testCases { mask := fieldmask_utils.MaskFromString(testCase.input) assert.Equal(t, testCase.expectedMask, mask) assert.Equal(t, testCase.length, len(mask)) } } ================================================ FILE: revive.toml ================================================ ignoreGeneratedHeader = false severity = "warning" confidence = 0.8 errorCode = 1 warningCode = 1 # [rule.blank-imports] [rule.context-as-argument] [rule.context-keys-type] [rule.dot-imports] [rule.empty-block] [rule.error-naming] [rule.error-return] [rule.error-strings] [rule.errorf] [rule.exported] [rule.if-return] [rule.increment-decrement] [rule.indent-error-flow] [rule.package-comments] [rule.range] [rule.receiver-naming] [rule.redefines-builtin-id] [rule.superfluous-else] [rule.time-naming] [rule.unexported-return] [rule.unreachable-code] [rule.unused-parameter] [rule.var-declaration] # ================================================ FILE: testproto/test.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 // protoc v3.17.1 // source: test.proto package testproto import ( reflect "reflect" sync "sync" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" anypb "google.golang.org/protobuf/types/known/anypb" fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type Role int32 const ( Role_UNKNOWN Role = 0 Role_REGULAR Role = 1 Role_ADMIN Role = 2 ) // Enum value maps for Role. var ( Role_name = map[int32]string{ 0: "UNKNOWN", 1: "REGULAR", 2: "ADMIN", } Role_value = map[string]int32{ "UNKNOWN": 0, "REGULAR": 1, "ADMIN": 2, } ) func (x Role) Enum() *Role { p := new(Role) *p = x return p } func (x Role) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Role) Descriptor() protoreflect.EnumDescriptor { return file_test_proto_enumTypes[0].Descriptor() } func (Role) Type() protoreflect.EnumType { return &file_test_proto_enumTypes[0] } func (x Role) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Role.Descriptor instead. func (Role) EnumDescriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{0} } type Permission int32 const ( Permission_READ Permission = 0 Permission_WRITE Permission = 1 Permission_EXECUTE Permission = 2 ) // Enum value maps for Permission. var ( Permission_name = map[int32]string{ 0: "READ", 1: "WRITE", 2: "EXECUTE", } Permission_value = map[string]int32{ "READ": 0, "WRITE": 1, "EXECUTE": 2, } ) func (x Permission) Enum() *Permission { p := new(Permission) *p = x return p } func (x Permission) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Permission) Descriptor() protoreflect.EnumDescriptor { return file_test_proto_enumTypes[1].Descriptor() } func (Permission) Type() protoreflect.EnumType { return &file_test_proto_enumTypes[1] } func (x Permission) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Permission.Descriptor instead. func (Permission) EnumDescriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{1} } type Image struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields OriginalUrl string `protobuf:"bytes,1,opt,name=original_url,json=originalUrl,proto3" json:"original_url,omitempty"` ResizedUrl string `protobuf:"bytes,2,opt,name=resized_url,json=resizedUrl,proto3" json:"resized_url,omitempty"` } func (x *Image) Reset() { *x = Image{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Image) String() string { return protoimpl.X.MessageStringOf(x) } func (*Image) ProtoMessage() {} func (x *Image) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Image.ProtoReflect.Descriptor instead. func (*Image) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{0} } func (x *Image) GetOriginalUrl() string { if x != nil { return x.OriginalUrl } return "" } func (x *Image) GetResizedUrl() string { if x != nil { return x.ResizedUrl } return "" } type Metrics struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Height uint32 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` Weight uint32 `protobuf:"varint,2,opt,name=weight,proto3" json:"weight,omitempty"` } func (x *Metrics) Reset() { *x = Metrics{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Metrics) String() string { return protoimpl.X.MessageStringOf(x) } func (*Metrics) ProtoMessage() {} func (x *Metrics) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Metrics.ProtoReflect.Descriptor instead. func (*Metrics) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{1} } func (x *Metrics) GetHeight() uint32 { if x != nil { return x.Height } return 0 } func (x *Metrics) GetWeight() uint32 { if x != nil { return x.Weight } return 0 } type User struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` Role Role `protobuf:"varint,3,opt,name=role,proto3,enum=Role" json:"role,omitempty"` Meta map[string]string `protobuf:"bytes,4,rep,name=meta,proto3" json:"meta,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Deactivated bool `protobuf:"varint,5,opt,name=deactivated,proto3" json:"deactivated,omitempty"` Permissions []Permission `protobuf:"varint,6,rep,packed,name=permissions,proto3,enum=Permission" json:"permissions,omitempty"` // Types that are assignable to Name: // *User_MaleName // *User_FemaleName Name isUser_Name `protobuf_oneof:"name"` Details []*anypb.Any `protobuf:"bytes,9,rep,name=details,proto3" json:"details,omitempty"` Images []*Image `protobuf:"bytes,10,rep,name=images,proto3" json:"images,omitempty"` Avatar *Image `protobuf:"bytes,11,opt,name=avatar,proto3" json:"avatar,omitempty"` Tags []string `protobuf:"bytes,12,rep,name=tags,proto3" json:"tags,omitempty"` Friends []*User `protobuf:"bytes,13,rep,name=friends,proto3" json:"friends,omitempty"` ExtraUser *anypb.Any `protobuf:"bytes,14,opt,name=extra_user,json=extraUser,proto3" json:"extra_user,omitempty"` } func (x *User) Reset() { *x = User{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *User) String() string { return protoimpl.X.MessageStringOf(x) } func (*User) ProtoMessage() {} func (x *User) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use User.ProtoReflect.Descriptor instead. func (*User) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{2} } func (x *User) GetId() uint32 { if x != nil { return x.Id } return 0 } func (x *User) GetUsername() string { if x != nil { return x.Username } return "" } func (x *User) GetRole() Role { if x != nil { return x.Role } return Role_UNKNOWN } func (x *User) GetMeta() map[string]string { if x != nil { return x.Meta } return nil } func (x *User) GetDeactivated() bool { if x != nil { return x.Deactivated } return false } func (x *User) GetPermissions() []Permission { if x != nil { return x.Permissions } return nil } func (m *User) GetName() isUser_Name { if m != nil { return m.Name } return nil } func (x *User) GetMaleName() string { if x, ok := x.GetName().(*User_MaleName); ok { return x.MaleName } return "" } func (x *User) GetFemaleName() string { if x, ok := x.GetName().(*User_FemaleName); ok { return x.FemaleName } return "" } func (x *User) GetDetails() []*anypb.Any { if x != nil { return x.Details } return nil } func (x *User) GetImages() []*Image { if x != nil { return x.Images } return nil } func (x *User) GetAvatar() *Image { if x != nil { return x.Avatar } return nil } func (x *User) GetTags() []string { if x != nil { return x.Tags } return nil } func (x *User) GetFriends() []*User { if x != nil { return x.Friends } return nil } func (x *User) GetExtraUser() *anypb.Any { if x != nil { return x.ExtraUser } return nil } type isUser_Name interface { isUser_Name() } type User_MaleName struct { MaleName string `protobuf:"bytes,7,opt,name=male_name,json=maleName,proto3,oneof"` } type User_FemaleName struct { FemaleName string `protobuf:"bytes,8,opt,name=female_name,json=femaleName,proto3,oneof"` } func (*User_MaleName) isUser_Name() {} func (*User_FemaleName) isUser_Name() {} type UpdateUserRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` FieldMask *fieldmaskpb.FieldMask `protobuf:"bytes,2,opt,name=field_mask,json=fieldMask,proto3" json:"field_mask,omitempty"` } func (x *UpdateUserRequest) Reset() { *x = UpdateUserRequest{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *UpdateUserRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateUserRequest) ProtoMessage() {} func (x *UpdateUserRequest) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateUserRequest.ProtoReflect.Descriptor instead. func (*UpdateUserRequest) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{3} } func (x *UpdateUserRequest) GetUser() *User { if x != nil { return x.User } return nil } func (x *UpdateUserRequest) GetFieldMask() *fieldmaskpb.FieldMask { if x != nil { return x.FieldMask } return nil } var File_test_proto protoreflect.FileDescriptor var file_test_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4b, 0x0a, 0x05, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x64, 0x55, 0x72, 0x6c, 0x22, 0x39, 0x0a, 0x07, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xa0, 0x04, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x05, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x23, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x64, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2d, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x09, 0x6d, 0x61, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x61, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0b, 0x66, 0x65, 0x6d, 0x61, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x65, 0x6d, 0x61, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1e, 0x0a, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x1f, 0x0a, 0x07, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x33, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x09, 0x65, 0x78, 0x74, 0x72, 0x61, 0x55, 0x73, 0x65, 0x72, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x69, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x2a, 0x2b, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x47, 0x55, 0x4c, 0x41, 0x52, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x02, 0x2a, 0x2e, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x45, 0x41, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x57, 0x52, 0x49, 0x54, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x10, 0x02, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x65, 0x6e, 0x6e, 0x61, 0x6e, 0x6f, 0x76, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x6d, 0x61, 0x73, 0x6b, 0x2d, 0x75, 0x74, 0x69, 0x6c, 0x73, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_test_proto_rawDescOnce sync.Once file_test_proto_rawDescData = file_test_proto_rawDesc ) func file_test_proto_rawDescGZIP() []byte { file_test_proto_rawDescOnce.Do(func() { file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData) }) return file_test_proto_rawDescData } var file_test_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_test_proto_goTypes = []interface{}{ (Role)(0), // 0: Role (Permission)(0), // 1: Permission (*Image)(nil), // 2: Image (*Metrics)(nil), // 3: Metrics (*User)(nil), // 4: User (*UpdateUserRequest)(nil), // 5: UpdateUserRequest nil, // 6: User.MetaEntry (*anypb.Any)(nil), // 7: google.protobuf.Any (*fieldmaskpb.FieldMask)(nil), // 8: google.protobuf.FieldMask } var file_test_proto_depIdxs = []int32{ 0, // 0: User.role:type_name -> Role 6, // 1: User.meta:type_name -> User.MetaEntry 1, // 2: User.permissions:type_name -> Permission 7, // 3: User.details:type_name -> google.protobuf.Any 2, // 4: User.images:type_name -> Image 2, // 5: User.avatar:type_name -> Image 4, // 6: User.friends:type_name -> User 7, // 7: User.extra_user:type_name -> google.protobuf.Any 4, // 8: UpdateUserRequest.user:type_name -> User 8, // 9: UpdateUserRequest.field_mask:type_name -> google.protobuf.FieldMask 10, // [10:10] is the sub-list for method output_type 10, // [10:10] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name } func init() { file_test_proto_init() } func file_test_proto_init() { if File_test_proto != nil { return } if !protoimpl.UnsafeEnabled { file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Image); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Metrics); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*User); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdateUserRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_test_proto_msgTypes[2].OneofWrappers = []interface{}{ (*User_MaleName)(nil), (*User_FemaleName)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_test_proto_rawDesc, NumEnums: 2, NumMessages: 5, NumExtensions: 0, NumServices: 0, }, GoTypes: file_test_proto_goTypes, DependencyIndexes: file_test_proto_depIdxs, EnumInfos: file_test_proto_enumTypes, MessageInfos: file_test_proto_msgTypes, }.Build() File_test_proto = out.File file_test_proto_rawDesc = nil file_test_proto_goTypes = nil file_test_proto_depIdxs = nil } ================================================ FILE: testproto/test.proto ================================================ syntax = "proto3"; option go_package = "github.com/mennanov/fieldmask-utils/testproto"; import "google/protobuf/any.proto"; import "google/protobuf/field_mask.proto"; message Image { string original_url = 1; string resized_url = 2; } message Metrics { uint32 height = 1; uint32 weight = 2; } enum Role { UNKNOWN = 0; REGULAR = 1; ADMIN = 2; } enum Permission { READ = 0; WRITE = 1; EXECUTE = 2; } message User { uint32 id = 1; string username = 2; Role role = 3; map meta = 4; bool deactivated = 5; repeated Permission permissions = 6; oneof name { string male_name = 7; string female_name = 8; } repeated google.protobuf.Any details = 9; repeated Image images = 10; Image avatar = 11; repeated string tags = 12; repeated User friends = 13; google.protobuf.Any extra_user = 14; } message UpdateUserRequest { User user = 1; google.protobuf.FieldMask field_mask = 2; }