From 2e45b3cb67951a53d3eea8e4cad41acfb8a51655 Mon Sep 17 00:00:00 2001 From: zguy Date: Sat, 14 Mar 2026 02:07:03 +0300 Subject: [PATCH 1/2] fix(gen): allOf optional/nullable field boxing --- gen/schema_gen.go | 4 ++ gen/schema_gen_sum.go | 79 ++++++++++++++++++++++++------------ gen/schema_transform_test.go | 27 ++++++++++++ 3 files changed, 84 insertions(+), 26 deletions(-) diff --git a/gen/schema_gen.go b/gen/schema_gen.go index c6bee99b2..05b52413e 100644 --- a/gen/schema_gen.go +++ b/gen/schema_gen.go @@ -111,6 +111,10 @@ func (g *schemaGen) generate(name string, schema *jsonschema.Schema, optional bo }() schema = transformSchema(schema) + schema, err := flattenAllOfSchema(schema) + if err != nil { + return nil, errors.Wrap(err, "flatten allOf") + } t, err := g.generate2(name, schema) if err != nil { diff --git a/gen/schema_gen_sum.go b/gen/schema_gen_sum.go index 8d86203f7..afc6028f2 100644 --- a/gen/schema_gen_sum.go +++ b/gen/schema_gen_sum.go @@ -1203,35 +1203,11 @@ func (g *schemaGen) oneOf(name string, schema *jsonschema.Schema, side bool) (*i } func (g *schemaGen) allOf(name string, schema *jsonschema.Schema) (*ir.Type, error) { - if err := ensureNoInfiniteRecursion(schema); err != nil { - return nil, err - } - - // If there is only one schema in allOf, avoid merging to keep the reference. - if len(schema.AllOf) == 1 { - s := schema.AllOf[0] - if s != nil { - // Preserve the outer schema's Ref on the inner schema so that the - // type is registered under the correct reference key and can be - // looked up later (avoiding anonymous type name collisions). - if !schema.Ref.IsZero() && s.Ref.IsZero() { - cp := *s - cp.Ref = schema.Ref - s = &cp - } - return g.generate(name, s, false) - } - } - - mergedSchema, err := mergeNSchemes(schema.AllOf) + mergedSchema, err := flattenAllOfSchema(schema) if err != nil { return nil, err } - - // The reference field must not change - mergedSchema.Ref = schema.Ref - - return g.generate(name, mergedSchema, false) + return g.generate2(name, mergedSchema) } // shallowSchemaCopy returns a shallow copy of the given schema. @@ -1247,6 +1223,57 @@ func shallowSchemaCopy(s *jsonschema.Schema) *jsonschema.Schema { return &cpy } +func flattenAllOfSchema(schema *jsonschema.Schema) (*jsonschema.Schema, error) { + if schema == nil || len(schema.AllOf) == 0 { + return schema, nil + } + + if err := ensureNoInfiniteRecursion(schema); err != nil { + return nil, err + } + + parent := shallowSchemaCopy(schema) + parent.AllOf = nil + + // If there is only one schema in allOf, avoid merging and keep the inner schema + // while still applying wrapper-level metadata from the parent. + if len(schema.AllOf) == 1 { + child := shallowSchemaCopy(schema.AllOf[0]) + if child == nil { + return parent, nil + } + + child.Nullable = child.Nullable || parent.Nullable + if child.Type == jsonschema.Empty { + child.Type = parent.Type + } + if child.Ref.IsZero() { + child.Ref = parent.Ref + } + if _, ok := child.Position(); !ok { + child.Pointer = parent.Pointer + } + return child, nil + } + + mergedSchema, err := mergeNSchemes(schema.AllOf) + if err != nil { + return nil, err + } + + mergedSchema.Nullable = mergedSchema.Nullable || parent.Nullable + if mergedSchema.Type == jsonschema.Empty { + mergedSchema.Type = parent.Type + } + // The reference field must not change. + mergedSchema.Ref = parent.Ref + if _, ok := mergedSchema.Position(); !ok { + mergedSchema.Pointer = parent.Pointer + } + + return mergedSchema, nil +} + func mergeNSchemes(ss []*jsonschema.Schema) (_ *jsonschema.Schema, err error) { switch len(ss) { case 0: diff --git a/gen/schema_transform_test.go b/gen/schema_transform_test.go index 1589c455f..fa9c7c9ca 100644 --- a/gen/schema_transform_test.go +++ b/gen/schema_transform_test.go @@ -355,6 +355,33 @@ func TestNullableOneOf_Optionality(t *testing.T) { assertOptionalNullableGeneric(t, optionalField.Type, ir.KindPrimitive) }) + t.Run("optional nullable allOf field", func(t *testing.T) { + a := require.New(t) + s := createTestSchemaGen(nil) + + nullableStringAllOf := &jsonschema.Schema{ + AllOf: []*jsonschema.Schema{ + createPrimitiveSchema(jsonschema.String), + {Nullable: true}, + }, + } + + optionalSchema := createObjectSchema( + createProperty("optionalNullable", nullableStringAllOf, false), + ) + optionalResult, err := s.generate("WithOptionalAllOf", optionalSchema, false) + + a.NoError(err) + a.NotNil(optionalResult) + a.Equal(ir.KindStruct, optionalResult.Kind) + a.Len(optionalResult.Fields, 1) + + optionalField := optionalResult.Fields[0] + a.Equal("OptionalNullable", optionalField.Name) + assertOptionalNullableGeneric(t, optionalField.Type, ir.KindPrimitive) + a.Equal(ir.String, optionalField.Type.GenericOf.Primitive) + }) + t.Run("field in optional object", func(t *testing.T) { a := require.New(t) s := createTestSchemaGen(nil) From 5dd1f5c5deeaff93fe475bb57eb4510a30ab70d5 Mon Sep 17 00:00:00 2001 From: zguy Date: Sun, 17 May 2026 21:06:54 +0300 Subject: [PATCH 2/2] fix(tests): fix TestMAC test case --- json/mac_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json/mac_test.go b/json/mac_test.go index e088550d2..d1ae1ad78 100644 --- a/json/mac_test.go +++ b/json/mac_test.go @@ -59,11 +59,11 @@ func TestMAC(t *testing.T) { // Valid MAC. {`"00:11:22:33:44:55"`, mac("00:11:22:33:44:55"), false}, {`"A1-B2-C3-D4-E5-F6"`, mac("A1-B2-C3-D4-E5-F6"), false}, + // {`"A1B2C3D4E5F6"`, mac("A1B2C3D4E5F6"), false}, // This is accepted for Go 1.26+, commented for now // Invalid MAC. {"01:23:45:67:89:GH", nil, true}, // GH is not a valid hex digit. {`"00-1B-2C-3D-4E"`, nil, true}, // Too few octets. - {`"A1B2C3D4E5F6"`, nil, true}, // Missing separators. } for i, tt := range tests { tt := tt