@@ -11784,3 +11784,226 @@ func Test_updateSwaggerObjectFromFieldBehavior(t *testing.T) {
1178411784 })
1178511785 }
1178611786}
11787+
11788+ // TestNestedRequiredFieldsNotHoisted tests the bug where nested required fields
11789+ // are incorrectly hoisted to the parent body schema's required array when path
11790+ // parameters reference nested fields
11791+ func TestNestedRequiredFieldsNotHoisted (t * testing.T ) {
11792+ fieldBehaviorRequired := []annotations.FieldBehavior {annotations .FieldBehavior_REQUIRED }
11793+ requiredFieldOptions := new (descriptorpb.FieldOptions )
11794+ proto .SetExtension (requiredFieldOptions , annotations .E_FieldBehavior , fieldBehaviorRequired )
11795+
11796+ // Define the nested message (Foo) with REQUIRED fields
11797+ fooDesc := & descriptorpb.DescriptorProto {
11798+ Name : proto .String ("Foo" ),
11799+ Field : []* descriptorpb.FieldDescriptorProto {
11800+ {
11801+ Name : proto .String ("name" ),
11802+ Type : descriptorpb .FieldDescriptorProto_TYPE_STRING .Enum (),
11803+ Number : proto .Int32 (1 ),
11804+ Options : requiredFieldOptions , // name is REQUIRED
11805+ },
11806+ {
11807+ Name : proto .String ("value" ),
11808+ Type : descriptorpb .FieldDescriptorProto_TYPE_STRING .Enum (),
11809+ Number : proto .Int32 (2 ),
11810+ Options : requiredFieldOptions , // value is REQUIRED
11811+ },
11812+ },
11813+ }
11814+
11815+ // Define the request message (UpdateFooRequest)
11816+ updateFooReqDesc := & descriptorpb.DescriptorProto {
11817+ Name : proto .String ("UpdateFooRequest" ),
11818+ Field : []* descriptorpb.FieldDescriptorProto {
11819+ {
11820+ Name : proto .String ("thing" ),
11821+ Type : descriptorpb .FieldDescriptorProto_TYPE_MESSAGE .Enum (),
11822+ TypeName : proto .String (".test.Foo" ),
11823+ Number : proto .Int32 (1 ),
11824+ Options : requiredFieldOptions , // thing is REQUIRED
11825+ },
11826+ {
11827+ Name : proto .String ("update_mask" ),
11828+ Type : descriptorpb .FieldDescriptorProto_TYPE_STRING .Enum (), // Simplified - normally FieldMask
11829+ Number : proto .Int32 (2 ),
11830+ },
11831+ },
11832+ }
11833+
11834+ fooMsg := & descriptor.Message {
11835+ DescriptorProto : fooDesc ,
11836+ }
11837+
11838+ updateFooReqMsg := & descriptor.Message {
11839+ DescriptorProto : updateFooReqDesc ,
11840+ }
11841+
11842+ nameField := & descriptor.Field {
11843+ Message : fooMsg ,
11844+ FieldDescriptorProto : fooMsg .GetField ()[0 ],
11845+ }
11846+ valueField := & descriptor.Field {
11847+ Message : fooMsg ,
11848+ FieldDescriptorProto : fooMsg .GetField ()[1 ],
11849+ }
11850+ fooMsg .Fields = []* descriptor.Field {nameField , valueField }
11851+
11852+ thingField := & descriptor.Field {
11853+ Message : updateFooReqMsg ,
11854+ FieldMessage : fooMsg ,
11855+ FieldDescriptorProto : updateFooReqMsg .GetField ()[0 ],
11856+ }
11857+ updateMaskField := & descriptor.Field {
11858+ Message : updateFooReqMsg ,
11859+ FieldDescriptorProto : updateFooReqMsg .GetField ()[1 ],
11860+ }
11861+ updateFooReqMsg .Fields = []* descriptor.Field {thingField , updateMaskField }
11862+
11863+ meth := & descriptorpb.MethodDescriptorProto {
11864+ Name : proto .String ("UpdateFoo" ),
11865+ InputType : proto .String ("UpdateFooRequest" ),
11866+ OutputType : proto .String ("Foo" ),
11867+ }
11868+
11869+ svc := & descriptorpb.ServiceDescriptorProto {
11870+ Name : proto .String ("FooService" ),
11871+ Method : []* descriptorpb.MethodDescriptorProto {meth },
11872+ }
11873+
11874+ file := descriptor.File {
11875+ FileDescriptorProto : & descriptorpb.FileDescriptorProto {
11876+ SourceCodeInfo : & descriptorpb.SourceCodeInfo {},
11877+ Name : proto .String ("foo.proto" ),
11878+ Package : proto .String ("test" ),
11879+ MessageType : []* descriptorpb.DescriptorProto {fooDesc , updateFooReqDesc },
11880+ Service : []* descriptorpb.ServiceDescriptorProto {svc },
11881+ Options : & descriptorpb.FileOptions {
11882+ GoPackage : proto .String ("github.com/example/test;test" ),
11883+ },
11884+ },
11885+ GoPkg : descriptor.GoPackage {
11886+ Path : "example.com/path/to/test/test.pb" ,
11887+ Name : "test_pb" ,
11888+ },
11889+ Messages : []* descriptor.Message {fooMsg , updateFooReqMsg },
11890+ Services : []* descriptor.Service {
11891+ {
11892+ ServiceDescriptorProto : svc ,
11893+ Methods : []* descriptor.Method {
11894+ {
11895+ MethodDescriptorProto : meth ,
11896+ RequestType : updateFooReqMsg ,
11897+ ResponseType : fooMsg ,
11898+ Bindings : []* descriptor.Binding {
11899+ {
11900+ HTTPMethod : "PATCH" ,
11901+ PathTmpl : httprule.Template {
11902+ Version : 1 ,
11903+ OpCodes : []int {0 , 0 },
11904+ Template : "/api/v1/{thing.name}" ,
11905+ },
11906+ PathParams : []descriptor.Parameter {
11907+ {
11908+ FieldPath : descriptor .FieldPath ([]descriptor.FieldPathComponent {
11909+ {
11910+ Name : "thing" ,
11911+ },
11912+ {
11913+ Name : "name" ,
11914+ },
11915+ }),
11916+ Target : nameField ,
11917+ },
11918+ },
11919+ Body : & descriptor.Body {
11920+ FieldPath : []descriptor.FieldPathComponent {}, // body: "*"
11921+ },
11922+ },
11923+ },
11924+ },
11925+ },
11926+ },
11927+ },
11928+ }
11929+
11930+ reg := descriptor .NewRegistry ()
11931+ fileCL := crossLinkFixture (& file )
11932+ err := reg .Load (reqFromFile (fileCL ))
11933+ if err != nil {
11934+ t .Errorf ("reg.Load(%#v) failed with %v; want success" , file , err )
11935+ return
11936+ }
11937+
11938+ result , err := applyTemplate (param {File : fileCL , reg : reg })
11939+ if err != nil {
11940+ t .Fatalf ("applyTemplate(%#v) failed with %v; want success" , file , err )
11941+ }
11942+
11943+ paths := GetPaths (result )
11944+ if got , want := len (paths ), 1 ; got != want {
11945+ t .Fatalf ("Results path length differed, got %d want %d" , got , want )
11946+ }
11947+ if got , want := paths [0 ], "/api/v1/{thing.name}" ; got != want {
11948+ t .Fatalf ("Wrong results path, got %s want %s" , got , want )
11949+ }
11950+
11951+ operation := * result .getPathItemObject ("/api/v1/{thing.name}" ).Patch
11952+ if len (operation .Parameters ) < 2 {
11953+ t .Fatalf ("Expected at least 2 parameters, got %d" , len (operation .Parameters ))
11954+ }
11955+
11956+ if got , want := operation .Parameters [0 ].Name , "thing.name" ; got != want {
11957+ t .Fatalf ("Wrong parameter name 0, got %s want %s" , got , want )
11958+ }
11959+ if got , want := operation .Parameters [0 ].In , "path" ; got != want {
11960+ t .Fatalf ("Wrong parameter location 0, got %s want %s" , got , want )
11961+ }
11962+
11963+ if got , want := operation .Parameters [1 ].Name , "body" ; got != want {
11964+ t .Fatalf ("Wrong parameter name 1, got %s want %s" , got , want )
11965+ }
11966+ if got , want := operation .Parameters [1 ].In , "body" ; got != want {
11967+ t .Fatalf ("Wrong parameter location 1, got %s want %s" , got , want )
11968+ }
11969+
11970+ bodySchemaRef := operation .Parameters [1 ].Schema .schemaCore .Ref
11971+ if bodySchemaRef == "" {
11972+ t .Fatal ("Body schema reference is empty" )
11973+ }
11974+
11975+ defName := strings .TrimPrefix (bodySchemaRef , "#/definitions/" )
11976+ definition , found := result .Definitions [defName ]
11977+ if ! found {
11978+ t .Fatalf ("expecting definition to contain %s" , defName )
11979+ }
11980+
11981+ // Verify that nested required fields are NOT hoisted to parent level
11982+ correctRequiredFields := []string {"thing" }
11983+ if got , want := definition .Required , correctRequiredFields ; ! reflect .DeepEqual (got , want ) {
11984+ t .Errorf ("Nested required fields were incorrectly hoisted to parent level.\n " +
11985+ "Body definition required fields:\n " +
11986+ " got = %v\n " +
11987+ " want = %v (only top-level field names)\n " +
11988+ "Nested field 'value' should be in 'thing' property's required array, not parent's." ,
11989+ got , want )
11990+ }
11991+
11992+ var thingKV * keyVal
11993+ if definition .Properties != nil {
11994+ for i := range * definition .Properties {
11995+ if (* definition .Properties )[i ].Key == "thing" {
11996+ thingKV = & (* definition .Properties )[i ]
11997+ break
11998+ }
11999+ }
12000+ }
12001+
12002+ if thingKV == nil {
12003+ t .Fatal ("'thing' property not found in body definition" )
12004+ }
12005+
12006+ if _ , ok := thingKV .Value .(openapiSchemaObject ); ! ok {
12007+ t .Fatal ("'thing' property value is not an openapiSchemaObject" )
12008+ }
12009+ }
0 commit comments