@@ -713,11 +713,13 @@ func mergeOpenapiSchemas(s1, s2 *base.Schema) (*base.Schema, error) {
713713 // Required. We merge these.
714714 result .Required = append (s1 .Required , s2 .Required ... )
715715
716- // We merge all properties. When both schemas declare a property with
717- // the same name, the typed declaration wins over an untyped one
718- // (which usually only carries annotations like example or
719- // description). Without this, the later schema silently overwrote
720- // the earlier one and any type/enum/constraint it carried was lost.
716+ // We merge all properties. When both schemas declare a property
717+ // with the same name, an annotation-only override (carrying only
718+ // fields like `example` or `description`) must not overwrite a
719+ // sibling that actually shapes the Go type. Otherwise we keep the
720+ // previous "s2 wins" behavior, since both sides genuinely shape
721+ // the type and the second declaration is the more specific one in
722+ // an allOf chain.
721723 //
722724 // We deliberately pick one of the original SchemaProxies rather
723725 // than fabricating a merged proxy via CreateSchemaProxy: downstream
@@ -735,13 +737,9 @@ func mergeOpenapiSchemas(s1, s2 *base.Schema) (*base.Schema, error) {
735737 }
736738
737739 if existing , exists := result .Properties .Get (k ); exists {
738- if len ( getSchemaType ( existing .Schema ())) == 0 && len ( getSchemaType ( v .Schema ())) > 0 {
739- result . Properties . Set ( k , v )
740+ if isAnnotationOnlySchema ( v .Schema ()) && ! isAnnotationOnlySchema ( existing .Schema ()) {
741+ continue
740742 }
741- // Otherwise the existing (s1's) property keeps its place;
742- // either it already has a type and we don't want to drop it,
743- // or neither side has a type and the choice is moot.
744- continue
745743 }
746744 result .Properties .Set (k , v )
747745 }
@@ -809,6 +807,45 @@ func getSchemaType(schema *base.Schema) []string {
809807 return nil
810808}
811809
810+ // isAnnotationOnlySchema reports whether a schema has no fields that
811+ // influence the generated Go type. Schemas like `{ example: cancelled }`
812+ // or `{ description: "..." }` are annotation-only and must not be
813+ // allowed to overwrite a sibling declaration that actually carries a
814+ // type, enum, or sub-schema in an allOf merge.
815+ func isAnnotationOnlySchema (s * base.Schema ) bool {
816+ if s == nil {
817+ return true
818+ }
819+ if len (s .Type ) > 0 {
820+ return false
821+ }
822+ if len (s .AllOf ) > 0 || len (s .OneOf ) > 0 || len (s .AnyOf ) > 0 {
823+ return false
824+ }
825+ if s .If != nil || s .Then != nil || s .Else != nil || s .Not != nil {
826+ return false
827+ }
828+ if s .Properties != nil && s .Properties .Len () > 0 {
829+ return false
830+ }
831+ if s .PatternProperties != nil && s .PatternProperties .Len () > 0 {
832+ return false
833+ }
834+ if s .Items != nil {
835+ return false
836+ }
837+ if len (s .Enum ) > 0 {
838+ return false
839+ }
840+ if s .AdditionalProperties != nil {
841+ return false
842+ }
843+ if s .Const != nil {
844+ return false
845+ }
846+ return true
847+ }
848+
812849// mergeNullable merges two nullable pointers using a union (more permissive) approach.
813850// If either is true, the result is true. If both are false or nil, the result is nil.
814851// This allows merging schemas where one specifies nullable and another doesn't.
0 commit comments