github.com/grpc-ecosystem/grpc-gateway/v2@v2.19.1/runtime/fieldmask_test.go (about)

     1  package runtime
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"testing"
     7  
     8  	"github.com/google/go-cmp/cmp"
     9  	"github.com/google/go-cmp/cmp/cmpopts"
    10  	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime/internal/examplepb"
    11  	"google.golang.org/protobuf/proto"
    12  	"google.golang.org/protobuf/testing/protocmp"
    13  	field_mask "google.golang.org/protobuf/types/known/fieldmaskpb"
    14  )
    15  
    16  func newFieldMask(paths ...string) *field_mask.FieldMask {
    17  	return &field_mask.FieldMask{Paths: paths}
    18  }
    19  
    20  func TestFieldMaskFromRequestBody(t *testing.T) {
    21  	for _, tc := range []struct {
    22  		name     string
    23  		input    string
    24  		msg      proto.Message
    25  		expected *field_mask.FieldMask
    26  	}{
    27  		{
    28  			name:     "empty",
    29  			expected: newFieldMask(),
    30  		},
    31  		{
    32  			name:     "EmptyMessage",
    33  			msg:      &examplepb.ABitOfEverything{},
    34  			input:    `{"oneof_empty": {}}`,
    35  			expected: newFieldMask("oneof_empty"),
    36  		},
    37  		{
    38  			name:     "simple",
    39  			msg:      &examplepb.ABitOfEverything{},
    40  			input:    `{"uuid":"1234", "floatValue":3.14}`,
    41  			expected: newFieldMask("uuid", "float_value"),
    42  		},
    43  		{
    44  			name:     "NonStandardMessage",
    45  			msg:      &examplepb.NonStandardMessage{},
    46  			input:    `{"id":"foo", "thing":{"subThing":{"sub_value":"bar"}}}`,
    47  			expected: newFieldMask("id", "thing.subThing.sub_value"),
    48  		},
    49  		{
    50  			name:     "NonStandardMessageWithJSONNames",
    51  			msg:      &examplepb.NonStandardMessageWithJSONNames{},
    52  			input:    `{"ID":"foo", "Thingy":{"SubThing":{"sub_Value":"bar"}}}`,
    53  			expected: newFieldMask("id", "thing.subThing.sub_value"),
    54  		},
    55  		{
    56  			name: "nested",
    57  
    58  			msg:      &examplepb.ABitOfEverything{},
    59  			input:    `{"single_nested": {"name":"bob", "amount": 2}, "uuid":"1234"}`,
    60  			expected: newFieldMask("single_nested.name", "single_nested.amount", "uuid"),
    61  		},
    62  		{
    63  			name:     "struct",
    64  			msg:      &examplepb.NonStandardMessage{},
    65  			input:    `{"struct_field": {"name":{"first": "bob"}, "amount": 2}}`,
    66  			expected: newFieldMask("struct_field.name.first", "struct_field.amount"),
    67  		},
    68  		{
    69  			name:     "NonStandardMessageWithJSONNamesForStruct",
    70  			msg:      &examplepb.NonStandardMessage{},
    71  			input:    `{"lineNum": 123, "structField": {"name":"bob"}}`,
    72  			expected: newFieldMask("line_num", "struct_field.name"),
    73  		},
    74  		{
    75  			name:     "value",
    76  			msg:      &examplepb.NonStandardMessage{},
    77  			input:    `{"value_field": {"name":{"first": "bob"}, "amount": 2}}`,
    78  			expected: newFieldMask("value_field.name.first", "value_field.amount"),
    79  		},
    80  		{
    81  			name: "map",
    82  
    83  			msg:      &examplepb.ABitOfEverything{},
    84  			input:    `{"mapped_string_value": {"a": "x"}}`,
    85  			expected: newFieldMask("mapped_string_value"),
    86  		},
    87  		{
    88  			name:     "deeply-nested",
    89  			msg:      &examplepb.NestedOuter{},
    90  			input:    `{"one":{"two":{"three":{"a":true, "b":false}}}}`,
    91  			expected: newFieldMask("one.two.three.a", "one.two.three.b"),
    92  		},
    93  		{
    94  			name: "complex",
    95  			input: `
    96  			{
    97  				"single_nested": {
    98  					"name": "bar",
    99  					"amount": 10,
   100  					"ok": "TRUE"
   101  				},
   102  				"uuid": "6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",
   103  				"nested": [
   104  					{
   105  						"name": "bar",
   106  						"amount": 10
   107  					},
   108  					{
   109  						"name": "baz",
   110  						"amount": 20
   111  					}
   112  				],
   113  				"float_value": 1.5,
   114  				"double_value": 2.5,
   115  				"int64_value": 4294967296,
   116  				"int64_override_type": 12345,
   117  				"int32_value": -2147483648,
   118  				"uint64_value": 9223372036854775807,
   119  				"uint32_value": 4294967295,
   120  				"fixed64_value": 9223372036854775807,
   121  				"fixed32_value": 4294967295,
   122  				"sfixed64_value": -4611686018427387904,
   123  				"sfixed32_value": 2147483647,
   124  				"sint64_value": 4611686018427387903,
   125  				"sint32_value": 2147483647,
   126  				"bool_value": true,
   127  				"string_value": "strprefix/foo",
   128  				"bytes_value": "132456",
   129  				"enum_value": "ONE",
   130  				"oneof_string": "x",
   131  				"nonConventionalNameValue": "camelCase",
   132  				"timestamp_value": "2016-05-10T10:19:13.123Z",
   133  				"enum_value_annotation": "ONE",
   134  				"nested_annotation": {
   135  					"name": "hoge",
   136  					"amount": 10
   137  				}
   138  			}
   139  `,
   140  			msg: &examplepb.ABitOfEverything{},
   141  
   142  			expected: newFieldMask(
   143  				"single_nested.name",
   144  				"single_nested.amount",
   145  				"single_nested.ok",
   146  				"uuid",
   147  				"float_value",
   148  				"double_value",
   149  				"int64_value",
   150  				"int64_override_type",
   151  				"int32_value",
   152  				"uint64_value",
   153  				"uint32_value",
   154  				"fixed64_value",
   155  				"fixed32_value",
   156  				"sfixed64_value",
   157  				"sfixed32_value",
   158  				"sint64_value",
   159  				"sint32_value",
   160  				"bool_value",
   161  				"string_value",
   162  				"bytes_value",
   163  				"enum_value",
   164  				"oneof_string",
   165  				"nonConventionalNameValue",
   166  				"timestamp_value",
   167  				"enum_value_annotation",
   168  				"nested_annotation.name",
   169  				"nested_annotation.amount",
   170  				"nested",
   171  			),
   172  		},
   173  		{
   174  			name:     "protobuf-any",
   175  			msg:      &examplepb.ABitOfEverything{},
   176  			input:    `{"anytype":{"@type": "xx.xx/examplepb.NestedOuter", "one":{"two":{"three":{"a":true, "b":false}}}}}`,
   177  			expected: newFieldMask("anytype"), //going deeper makes no sense
   178  		},
   179  		{
   180  			name:     "repeated-protobuf-any",
   181  			msg:      &examplepb.ABitOfEverything{},
   182  			input:    `{"repeated_anytype":[{"@type": "xx.xx/examplepb.NestedOuter", "one":{"two":{"three":{"a":true, "b":false}}}}]}`,
   183  			expected: newFieldMask("repeated_anytype"), //going deeper makes no sense
   184  		},
   185  	} {
   186  		t.Run(tc.name, func(t *testing.T) {
   187  			actual, err := FieldMaskFromRequestBody(bytes.NewReader([]byte(tc.input)), tc.msg)
   188  			if err != nil {
   189  				t.Fatalf("unexpected error: %v", err)
   190  			}
   191  			if diff := cmp.Diff(tc.expected, actual, protocmp.Transform(), cmpopts.SortSlices(func(x, y string) bool {
   192  				return x < y
   193  			})); diff != "" {
   194  				t.Errorf("field masks differed:\n%s", diff)
   195  			}
   196  		})
   197  	}
   198  }
   199  
   200  func TestFieldMaskRepeatedFieldsLast(t *testing.T) {
   201  	for _, tc := range []struct {
   202  		name     string
   203  		input    string
   204  		expected *field_mask.FieldMask
   205  	}{
   206  		{
   207  			name:  "map",
   208  			input: `{"mapped_string_value": {"a": "x"}, "repeated_string_value": {"b": "y"}, "uuid":"1234"}`,
   209  			expected: &field_mask.FieldMask{
   210  				Paths: []string{
   211  					"mapped_string_value",
   212  					"repeated_string_value",
   213  					"uuid",
   214  				},
   215  			},
   216  		},
   217  		{
   218  			name: "slice",
   219  			input: `
   220  			{
   221  				"nested": [
   222  					{
   223  						"name": "bar",
   224  						"amount": 10
   225  					},
   226  					{
   227  						"name": "baz",
   228  						"amount": 20
   229  					}
   230  				],
   231  				"nested_annotation": [
   232  					{
   233  						"name": "foo",
   234  						"amount": 100
   235  					},
   236  					{
   237  						"name": "widget",
   238  						"amount": 200
   239  					}
   240  				],
   241  				"uuid":"1234"
   242  			}`,
   243  			expected: &field_mask.FieldMask{
   244  				Paths: []string{
   245  					"nested",
   246  					"nested_annotation",
   247  					"uuid",
   248  				},
   249  			},
   250  		},
   251  	} {
   252  		t.Run(tc.name, func(t *testing.T) {
   253  			actual, err := FieldMaskFromRequestBody(bytes.NewReader([]byte(tc.input)), &examplepb.ABitOfEverything{})
   254  			if err != nil {
   255  				t.Fatalf("unexpected error: %v", err)
   256  			}
   257  			if diff := cmp.Diff(tc.expected, actual, protocmp.Transform()); diff != "" {
   258  				t.Errorf("field masks differed:\n%s", diff)
   259  			}
   260  		})
   261  	}
   262  }
   263  
   264  func TestFieldMaskErrors(t *testing.T) {
   265  	for _, tc := range []struct {
   266  		name        string
   267  		input       string
   268  		expectedErr error
   269  	}{
   270  		{
   271  			name:        "object under scalar",
   272  			input:       `{"uuid": {"a": "x"}}`,
   273  			expectedErr: errors.New("JSON structure did not match request type"),
   274  		},
   275  	} {
   276  		t.Run(tc.name, func(t *testing.T) {
   277  			_, err := FieldMaskFromRequestBody(bytes.NewReader([]byte(tc.input)), &examplepb.ABitOfEverything{})
   278  			if err.Error() != tc.expectedErr.Error() {
   279  				t.Fatalf("errors did not match: got %q, wanted %q", err, tc.expectedErr)
   280  			}
   281  		})
   282  	}
   283  }
   284  
   285  // avoid compiler optimising benchmark away
   286  var result *field_mask.FieldMask
   287  
   288  func BenchmarkABEFieldMaskFromRequestBody(b *testing.B) {
   289  	input := `{` +
   290  		`"single_nested":				{"name": "bar",` +
   291  		`                           	 "amount": 10,` +
   292  		`                           	 "ok": "TRUE"},` +
   293  		`"uuid":						"6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",` +
   294  		`"nested": 						[{"name": "bar",` +
   295  		`								  "amount": 10},` +
   296  		`								 {"name": "baz",` +
   297  		`								  "amount": 20}],` +
   298  		`"float_value":             	1.5,` +
   299  		`"double_value":            	2.5,` +
   300  		`"int64_value":             	4294967296,` +
   301  		`"uint64_value":            	9223372036854775807,` +
   302  		`"int32_value":             	-2147483648,` +
   303  		`"fixed64_value":           	9223372036854775807,` +
   304  		`"fixed32_value":           	4294967295,` +
   305  		`"bool_value":              	true,` +
   306  		`"string_value":            	"strprefix/foo",` +
   307  		`"bytes_value":					"132456",` +
   308  		`"uint32_value":            	4294967295,` +
   309  		`"enum_value":     		        "ONE",` +
   310  		`"path_enum_value":	    	    "DEF",` +
   311  		`"nested_path_enum_value":  	"JKL",` +
   312  		`"sfixed32_value":          	2147483647,` +
   313  		`"sfixed64_value":          	-4611686018427387904,` +
   314  		`"sint32_value":            	2147483647,` +
   315  		`"sint64_value":            	4611686018427387903,` +
   316  		`"repeated_string_value": 		["a", "b", "c"],` +
   317  		`"oneof_value":					{"oneof_string":"x"},` +
   318  		`"map_value": 					{"a": "ONE",` +
   319  		`								 "b": "ZERO"},` +
   320  		`"mapped_string_value": 		{"a": "x",` +
   321  		`								 "b": "y"},` +
   322  		`"mapped_nested_value": 		{"a": {"name": "x", "amount": 1},` +
   323  		`								 "b": {"name": "y", "amount": 2}},` +
   324  		`"nonConventionalNameValue":	"camelCase",` +
   325  		`"timestamp_value":				"2016-05-10T10:19:13.123Z",` +
   326  		`"repeated_enum_value":			["ONE", "ZERO"],` +
   327  		`"repeated_enum_annotation":	 ["ONE", "ZERO"],` +
   328  		`"enum_value_annotation": 		"ONE",` +
   329  		`"repeated_string_annotation":	["a", "b"],` +
   330  		`"repeated_nested_annotation": 	[{"name": "hoge",` +
   331  		`								  "amount": 10},` +
   332  		`								 {"name": "fuga",` +
   333  		`								  "amount": 20}],` +
   334  		`"nested_annotation": 			{"name": "hoge",` +
   335  		`								 "amount": 10},` +
   336  		`"int64_override_type":			12345` +
   337  		`}`
   338  	var r *field_mask.FieldMask
   339  	var err error
   340  	for i := 0; i < b.N; i++ {
   341  		r, err = FieldMaskFromRequestBody(bytes.NewReader([]byte(input)), nil)
   342  	}
   343  	if err != nil {
   344  		b.Error(err)
   345  	}
   346  	result = r
   347  }
   348  
   349  func BenchmarkNonStandardFieldMaskFromRequestBody(b *testing.B) {
   350  	input := `{` +
   351  		`"id":			"foo",` +
   352  		`"Num": 		2,` +
   353  		`"line_num": 	3,` +
   354  		`"langIdent":	"bar",` +
   355  		`"STATUS": 		"baz"` +
   356  		`}`
   357  	var r *field_mask.FieldMask
   358  	var err error
   359  	for i := 0; i < b.N; i++ {
   360  		r, err = FieldMaskFromRequestBody(bytes.NewReader([]byte(input)), nil)
   361  	}
   362  	if err != nil {
   363  		b.Error(err)
   364  	}
   365  	result = r
   366  }