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 }