1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package adapt
16
17import (
18	"encoding/json"
19	"reflect"
20	"testing"
21
22	"cloud.google.com/go/bigquery/storage/managedwriter/testdata"
23	"github.com/google/go-cmp/cmp"
24	storagepb "google.golang.org/genproto/googleapis/cloud/bigquery/storage/v1"
25	"google.golang.org/protobuf/encoding/protojson"
26	"google.golang.org/protobuf/proto"
27	"google.golang.org/protobuf/reflect/protodesc"
28	"google.golang.org/protobuf/reflect/protoreflect"
29	"google.golang.org/protobuf/testing/protocmp"
30	"google.golang.org/protobuf/types/descriptorpb"
31	"google.golang.org/protobuf/types/dynamicpb"
32)
33
34func TestSchemaToProtoConversion(t *testing.T) {
35	testCases := []struct {
36		description string
37		bq          *storagepb.TableSchema
38		wantProto2  *descriptorpb.DescriptorProto
39		wantProto3  *descriptorpb.DescriptorProto
40	}{
41		{
42			description: "basic",
43			bq: &storagepb.TableSchema{
44				Fields: []*storagepb.TableFieldSchema{
45					{Name: "foo", Type: storagepb.TableFieldSchema_STRING, Mode: storagepb.TableFieldSchema_NULLABLE},
46					{Name: "bar", Type: storagepb.TableFieldSchema_INT64, Mode: storagepb.TableFieldSchema_REQUIRED},
47					{Name: "baz", Type: storagepb.TableFieldSchema_BYTES, Mode: storagepb.TableFieldSchema_REPEATED},
48				}},
49			wantProto2: &descriptorpb.DescriptorProto{
50				Name: proto.String("root"),
51				Field: []*descriptorpb.FieldDescriptorProto{
52					{
53						Name:   proto.String("foo"),
54						Number: proto.Int32(1),
55						Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
56						Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum()},
57					{Name: proto.String("bar"), Number: proto.Int32(2), Type: descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(), Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum()},
58					{Name: proto.String("baz"), Number: proto.Int32(3), Type: descriptorpb.FieldDescriptorProto_TYPE_BYTES.Enum(), Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum()},
59				},
60			},
61			wantProto3: &descriptorpb.DescriptorProto{
62				Name: proto.String("root"),
63				Field: []*descriptorpb.FieldDescriptorProto{
64					{
65						Name:     proto.String("foo"),
66						Number:   proto.Int32(1),
67						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
68						TypeName: proto.String(".google.protobuf.StringValue"),
69						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum()},
70					{Name: proto.String("bar"), Number: proto.Int32(2), Type: descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(), Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum()},
71					{Name: proto.String("baz"), Number: proto.Int32(3), Type: descriptorpb.FieldDescriptorProto_TYPE_BYTES.Enum(), Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum()},
72				},
73			},
74		},
75		{
76			// exercise construct of a submessage
77			description: "nested",
78			bq: &storagepb.TableSchema{
79				Fields: []*storagepb.TableFieldSchema{
80					{Name: "curdate", Type: storagepb.TableFieldSchema_DATE, Mode: storagepb.TableFieldSchema_NULLABLE},
81					{
82						Name: "rec",
83						Type: storagepb.TableFieldSchema_STRUCT,
84						Mode: storagepb.TableFieldSchema_NULLABLE,
85						Fields: []*storagepb.TableFieldSchema{
86							{Name: "userid", Type: storagepb.TableFieldSchema_STRING, Mode: storagepb.TableFieldSchema_REQUIRED},
87							{Name: "location", Type: storagepb.TableFieldSchema_GEOGRAPHY, Mode: storagepb.TableFieldSchema_NULLABLE},
88						},
89					},
90				},
91			},
92			wantProto2: &descriptorpb.DescriptorProto{
93				Name: proto.String("root"),
94				Field: []*descriptorpb.FieldDescriptorProto{
95					{
96						Name:   proto.String("curdate"),
97						Number: proto.Int32(1),
98						Type:   descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
99						Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
100					},
101					{
102						Name:     proto.String("rec"),
103						Number:   proto.Int32(2),
104						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
105						TypeName: proto.String(".root__rec"),
106						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
107					},
108				},
109			},
110			wantProto3: &descriptorpb.DescriptorProto{
111				Name: proto.String("root"),
112				Field: []*descriptorpb.FieldDescriptorProto{
113					{
114						Name:     proto.String("curdate"),
115						Number:   proto.Int32(1),
116						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
117						TypeName: proto.String(".google.protobuf.Int32Value"),
118						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
119					},
120					{
121						Name:     proto.String("rec"),
122						Number:   proto.Int32(2),
123						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
124						TypeName: proto.String(".root__rec"),
125						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
126					},
127				},
128			},
129		},
130		{
131			// We expect to re-use the submessage twice, as the schema contains two identical structs.
132			description: "nested w/duplicate submessage",
133			bq: &storagepb.TableSchema{
134				Fields: []*storagepb.TableFieldSchema{
135					{Name: "curdate", Type: storagepb.TableFieldSchema_DATE, Mode: storagepb.TableFieldSchema_NULLABLE},
136					{
137						Name: "rec1",
138						Type: storagepb.TableFieldSchema_STRUCT,
139						Mode: storagepb.TableFieldSchema_NULLABLE,
140						Fields: []*storagepb.TableFieldSchema{
141							{Name: "userid", Type: storagepb.TableFieldSchema_STRING, Mode: storagepb.TableFieldSchema_REQUIRED},
142							{Name: "location", Type: storagepb.TableFieldSchema_GEOGRAPHY, Mode: storagepb.TableFieldSchema_NULLABLE},
143						},
144					},
145					{
146						Name: "rec2",
147						Type: storagepb.TableFieldSchema_STRUCT,
148						Mode: storagepb.TableFieldSchema_NULLABLE,
149						Fields: []*storagepb.TableFieldSchema{
150							{Name: "userid", Type: storagepb.TableFieldSchema_STRING, Mode: storagepb.TableFieldSchema_REQUIRED},
151							{Name: "location", Type: storagepb.TableFieldSchema_GEOGRAPHY, Mode: storagepb.TableFieldSchema_NULLABLE},
152						},
153					},
154				},
155			},
156			wantProto2: &descriptorpb.DescriptorProto{
157				Name: proto.String("root"),
158				Field: []*descriptorpb.FieldDescriptorProto{
159					{
160						Name:   proto.String("curdate"),
161						Number: proto.Int32(1),
162						Type:   descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
163						Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
164					},
165					{
166						Name:     proto.String("rec1"),
167						Number:   proto.Int32(2),
168						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
169						TypeName: proto.String(".root__rec1"),
170						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
171					},
172					{
173						Name:     proto.String("rec2"),
174						Number:   proto.Int32(3),
175						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
176						TypeName: proto.String(".root__rec1"),
177						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
178					},
179				},
180			},
181			wantProto3: &descriptorpb.DescriptorProto{
182				Name: proto.String("root"),
183				Field: []*descriptorpb.FieldDescriptorProto{
184					{
185						Name:     proto.String("curdate"),
186						Number:   proto.Int32(1),
187						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
188						TypeName: proto.String(".google.protobuf.Int32Value"),
189						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
190					},
191					{
192						Name:     proto.String("rec1"),
193						Number:   proto.Int32(2),
194						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
195						TypeName: proto.String(".root__rec1"),
196						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
197					},
198					{
199						Name:     proto.String("rec2"),
200						Number:   proto.Int32(3),
201						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
202						TypeName: proto.String(".root__rec1"),
203						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
204					},
205				},
206			},
207		},
208	}
209	for _, tc := range testCases {
210		p2d, err := StorageSchemaToProto2Descriptor(tc.bq, "root")
211		if err != nil {
212			t.Errorf("case (%s) failed proto2 conversion: %v", tc.description, err)
213		}
214
215		// convert it to DP form
216		mDesc, ok := p2d.(protoreflect.MessageDescriptor)
217		if !ok {
218			t.Errorf("%s: couldn't convert proto2 to messagedescriptor", tc.description)
219		}
220		gotDP := protodesc.ToDescriptorProto(mDesc)
221		if diff := cmp.Diff(gotDP, tc.wantProto2, protocmp.Transform()); diff != "" {
222			t.Errorf("%s proto2: -got, +want:\n%s", tc.description, diff)
223		}
224
225		p3d, err := StorageSchemaToProto3Descriptor(tc.bq, "root")
226		if err != nil {
227			t.Fatalf("case (%s) failed proto3 conversion: %v", tc.description, err)
228		}
229
230		mDesc, ok = p3d.(protoreflect.MessageDescriptor)
231		if !ok {
232			t.Errorf("%s: couldn't convert proto3 to messagedescriptor", tc.description)
233		}
234		gotDP = protodesc.ToDescriptorProto(mDesc)
235		if diff := cmp.Diff(gotDP, tc.wantProto3, protocmp.Transform()); diff != "" {
236			t.Errorf("%s proto3: -got, +want:\n%s", tc.description, diff)
237		}
238
239	}
240}
241
242func TestProtoJSONSerialization(t *testing.T) {
243
244	sourceSchema := &storagepb.TableSchema{
245		Fields: []*storagepb.TableFieldSchema{
246			{Name: "record_id", Type: storagepb.TableFieldSchema_INT64, Mode: storagepb.TableFieldSchema_NULLABLE},
247			{
248				Name: "details",
249				Type: storagepb.TableFieldSchema_STRUCT,
250				Mode: storagepb.TableFieldSchema_REPEATED,
251				Fields: []*storagepb.TableFieldSchema{
252					{Name: "key", Type: storagepb.TableFieldSchema_STRING, Mode: storagepb.TableFieldSchema_REQUIRED},
253					{Name: "value", Type: storagepb.TableFieldSchema_STRING, Mode: storagepb.TableFieldSchema_NULLABLE},
254				},
255			},
256		},
257	}
258
259	descriptor, err := StorageSchemaToProto2Descriptor(sourceSchema, "root")
260	if err != nil {
261		t.Fatalf("failed to construct descriptor")
262	}
263
264	sampleRecord := []byte(`{"record_id":"12345","details":[{"key":"name","value":"jimmy"},{"key":"title","value":"clown"}]}`)
265
266	messageDescriptor, ok := descriptor.(protoreflect.MessageDescriptor)
267	if !ok {
268		t.Fatalf("StorageSchemaToDescriptor didn't yield a valid message descriptor, got %T", descriptor)
269	}
270
271	// First, ensure we got the expected descriptors.  Check both outer and inner messages.
272	gotOuterDP := protodesc.ToDescriptorProto(messageDescriptor)
273
274	innerField := messageDescriptor.Fields().ByName("details")
275	if innerField == nil {
276		t.Fatalf("couldn't get inner descriptor for details")
277	}
278	gotInnerDP := protodesc.ToDescriptorProto(innerField.Message())
279
280	wantOuterDP := &descriptorpb.DescriptorProto{
281		Name: proto.String("root"),
282		Field: []*descriptorpb.FieldDescriptorProto{
283			{
284				Name:   proto.String("record_id"),
285				Number: proto.Int32(1),
286				Type:   descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(),
287				Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
288			},
289			{
290				Name:     proto.String("details"),
291				Number:   proto.Int32(2),
292				Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
293				TypeName: proto.String(".root__details"),
294				Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
295			},
296		},
297	}
298
299	wantInnerDP := &descriptorpb.DescriptorProto{
300		Name: proto.String("root__details"),
301		Field: []*descriptorpb.FieldDescriptorProto{
302			{
303				Name:   proto.String("key"),
304				Number: proto.Int32(1),
305				Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
306				Label:  descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
307			},
308			{
309				Name:   proto.String("value"),
310				Number: proto.Int32(2),
311				Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
312				Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
313			},
314		},
315	}
316
317	if outerDiff := cmp.Diff(gotOuterDP, wantOuterDP, protocmp.Transform()); outerDiff != "" {
318		t.Fatalf("DescriptorProto for outer message differs.\n-got, +want:\n%s", outerDiff)
319	}
320	if innerDiff := cmp.Diff(gotInnerDP, wantInnerDP, protocmp.Transform()); innerDiff != "" {
321		t.Fatalf("DescriptorProto for inner message differs.\n-got, +want:\n%s", innerDiff)
322	}
323
324	message := dynamicpb.NewMessage(messageDescriptor)
325
326	// Attempt to serialize json record into proto message.
327	err = protojson.Unmarshal(sampleRecord, message)
328	if err != nil {
329		t.Fatalf("failed to Unmarshal json message: %v", err)
330	}
331
332	// Serialize message back to json bytes.  We must use options for idempotency, otherwise
333	// we'll serialize using the Go name rather than the proto name (recordId vs record_id).
334	options := protojson.MarshalOptions{
335		UseProtoNames: true,
336	}
337	gotBytes, err := options.Marshal(message)
338	if err != nil {
339		t.Fatalf("failed to marshal message: %v", err)
340	}
341
342	var got, want interface{}
343	if err := json.Unmarshal(gotBytes, &got); err != nil {
344		t.Fatalf("couldn't marshal gotBytes: %v", err)
345	}
346	if err := json.Unmarshal(sampleRecord, &want); err != nil {
347		t.Fatalf("couldn't marshal sampleRecord: %v", err)
348	}
349	if !reflect.DeepEqual(got, want) {
350		t.Fatalf("mismatched json: got\n%q\nwant\n%q", gotBytes, sampleRecord)
351	}
352
353}
354
355func TestNormalizeDescriptor(t *testing.T) {
356	testCases := []struct {
357		description string
358		in          protoreflect.MessageDescriptor
359		wantErr     bool
360		want        *descriptorpb.DescriptorProto
361	}{
362		{
363			description: "nil",
364			in:          nil,
365			wantErr:     true,
366		},
367		{
368			description: "AllSupportedTypes",
369			in:          (&testdata.AllSupportedTypes{}).ProtoReflect().Descriptor(),
370			want: &descriptorpb.DescriptorProto{
371				Name: proto.String("testdata_AllSupportedTypes"),
372				Field: []*descriptorpb.FieldDescriptorProto{
373					{
374						Name:     proto.String("int32_value"),
375						JsonName: proto.String("int32Value"),
376						Number:   proto.Int32(1),
377						Type:     descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
378						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
379					},
380					{
381						Name:     proto.String("int64_value"),
382						JsonName: proto.String("int64Value"),
383						Number:   proto.Int32(2),
384						Type:     descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(),
385						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
386					},
387					{
388						Name:     proto.String("uint32_value"),
389						JsonName: proto.String("uint32Value"),
390						Number:   proto.Int32(3),
391						Type:     descriptorpb.FieldDescriptorProto_TYPE_UINT32.Enum(),
392						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
393					},
394					{
395						Name:     proto.String("uint64_value"),
396						JsonName: proto.String("uint64Value"),
397						Number:   proto.Int32(4),
398						Type:     descriptorpb.FieldDescriptorProto_TYPE_UINT64.Enum(),
399						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
400					},
401					{
402						Name:     proto.String("float_value"),
403						JsonName: proto.String("floatValue"),
404						Number:   proto.Int32(5),
405						Type:     descriptorpb.FieldDescriptorProto_TYPE_FLOAT.Enum(),
406						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
407					},
408					{
409						Name:     proto.String("double_value"),
410						JsonName: proto.String("doubleValue"),
411						Number:   proto.Int32(6),
412						Type:     descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(),
413						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
414					},
415					{
416						Name:     proto.String("bool_value"),
417						JsonName: proto.String("boolValue"),
418						Number:   proto.Int32(7),
419						Type:     descriptorpb.FieldDescriptorProto_TYPE_BOOL.Enum(),
420						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
421					},
422					{
423						Name:     proto.String("enum_value"),
424						JsonName: proto.String("enumValue"),
425						TypeName: proto.String("testdata_TestEnum_E.TestEnum"),
426						Number:   proto.Int32(8),
427						Type:     descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
428						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
429					},
430					{
431						Name:     proto.String("string_value"),
432						JsonName: proto.String("stringValue"),
433						Number:   proto.Int32(9),
434						Type:     descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
435						Label:    descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
436					},
437					{
438						Name:     proto.String("fixed64_value"),
439						JsonName: proto.String("fixed64Value"),
440						Number:   proto.Int32(10),
441						Type:     descriptorpb.FieldDescriptorProto_TYPE_FIXED64.Enum(),
442						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
443					},
444				},
445				NestedType: []*descriptorpb.DescriptorProto{
446					{
447						Name: proto.String("testdata_TestEnum_E"),
448						EnumType: []*descriptorpb.EnumDescriptorProto{
449							{
450								Name: proto.String("TestEnum"),
451								Value: []*descriptorpb.EnumValueDescriptorProto{
452									{
453										Name:   proto.String("TestEnum0"),
454										Number: proto.Int32(0),
455									},
456									{
457										Name:   proto.String("TestEnum1"),
458										Number: proto.Int32(1),
459									},
460								},
461							},
462						},
463					},
464				},
465			},
466		},
467		{
468			description: "ContainsRecursive",
469			in:          (&testdata.ContainsRecursive{}).ProtoReflect().Descriptor(),
470			wantErr:     true,
471		},
472		{
473			description: "RecursiveTypeTopMessage",
474			in:          (&testdata.RecursiveTypeTopMessage{}).ProtoReflect().Descriptor(),
475			wantErr:     true,
476		},
477		{
478			description: "ComplexType",
479			in:          (&testdata.ComplexType{}).ProtoReflect().Descriptor(),
480			want: &descriptorpb.DescriptorProto{
481				Name: proto.String("testdata_ComplexType"),
482				Field: []*descriptorpb.FieldDescriptorProto{
483					{
484						Name:     proto.String("nested_repeated_type"),
485						JsonName: proto.String("nestedRepeatedType"),
486						Number:   proto.Int32(1),
487						TypeName: proto.String("testdata_NestedType"),
488						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
489						Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
490					},
491					{
492						Name:     proto.String("inner_type"),
493						JsonName: proto.String("innerType"),
494						Number:   proto.Int32(2),
495						TypeName: proto.String("testdata_InnerType"),
496						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
497						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
498					},
499				},
500				NestedType: []*descriptorpb.DescriptorProto{
501					{
502						Name: proto.String("testdata_InnerType"),
503						Field: []*descriptorpb.FieldDescriptorProto{
504							{
505								Name:     proto.String("value"),
506								JsonName: proto.String("value"),
507								Number:   proto.Int32(1),
508								Type:     descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
509								Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
510							},
511						},
512					},
513					{
514						Name: proto.String("testdata_NestedType"),
515						Field: []*descriptorpb.FieldDescriptorProto{
516							{
517								Name:     proto.String("inner_type"),
518								JsonName: proto.String("innerType"),
519								Number:   proto.Int32(1),
520								TypeName: proto.String("testdata_InnerType"),
521								Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
522								Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
523							},
524						},
525					},
526				},
527			},
528		},
529		{
530			description: "WithWellKnownTypes",
531			in:          (&testdata.WithWellKnownTypes{}).ProtoReflect().Descriptor(),
532			want: &descriptorpb.DescriptorProto{
533				Name: proto.String("testdata_WithWellKnownTypes"),
534				Field: []*descriptorpb.FieldDescriptorProto{
535					{
536						Name:     proto.String("int64_value"),
537						JsonName: proto.String("int64Value"),
538						Number:   proto.Int32(1),
539						Type:     descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(),
540						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
541					},
542					{
543						Name:     proto.String("wrapped_int64"),
544						JsonName: proto.String("wrappedInt64"),
545						Number:   proto.Int32(2),
546						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
547						TypeName: proto.String("google_protobuf_Int64Value"),
548						Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
549					},
550					{
551						Name:     proto.String("string_value"),
552						JsonName: proto.String("stringValue"),
553						Number:   proto.Int32(3),
554						Type:     descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
555						Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
556					},
557					{
558						Name:     proto.String("wrapped_string"),
559						JsonName: proto.String("wrappedString"),
560						Number:   proto.Int32(4),
561						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
562						TypeName: proto.String("google_protobuf_StringValue"),
563						Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
564					},
565				},
566				NestedType: []*descriptorpb.DescriptorProto{
567					{
568						Name: proto.String("google_protobuf_Int64Value"),
569						Field: []*descriptorpb.FieldDescriptorProto{
570							{
571								Name:     proto.String("value"),
572								JsonName: proto.String("value"),
573								Number:   proto.Int32(1),
574								Type:     descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(),
575								Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
576							},
577						},
578					},
579					{
580						Name: proto.String("google_protobuf_StringValue"),
581						Field: []*descriptorpb.FieldDescriptorProto{
582							{
583								Name:     proto.String("value"),
584								JsonName: proto.String("value"),
585								Number:   proto.Int32(1),
586								Type:     descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
587								Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
588							},
589						},
590					},
591				},
592			},
593		},
594	}
595
596	for _, tc := range testCases {
597		gotDP, err := NormalizeDescriptor(tc.in)
598
599		if tc.wantErr && err == nil {
600			t.Errorf("%s: wanted err but got success", tc.description)
601			continue
602		}
603		if !tc.wantErr && err != nil {
604			t.Errorf("%s: wanted success, got err: %v", tc.description, err)
605			continue
606		}
607		if diff := cmp.Diff(gotDP, tc.want, protocmp.Transform()); diff != "" {
608			t.Errorf("%s: -got, +want:\n%s", tc.description, diff)
609		}
610	}
611}
612