Skip to content

Commit 2e45b3c

Browse files
committed
fix(gen): allOf optional/nullable field boxing
1 parent db129d8 commit 2e45b3c

3 files changed

Lines changed: 84 additions & 26 deletions

File tree

gen/schema_gen.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ func (g *schemaGen) generate(name string, schema *jsonschema.Schema, optional bo
111111
}()
112112

113113
schema = transformSchema(schema)
114+
schema, err := flattenAllOfSchema(schema)
115+
if err != nil {
116+
return nil, errors.Wrap(err, "flatten allOf")
117+
}
114118

115119
t, err := g.generate2(name, schema)
116120
if err != nil {

gen/schema_gen_sum.go

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,35 +1203,11 @@ func (g *schemaGen) oneOf(name string, schema *jsonschema.Schema, side bool) (*i
12031203
}
12041204

12051205
func (g *schemaGen) allOf(name string, schema *jsonschema.Schema) (*ir.Type, error) {
1206-
if err := ensureNoInfiniteRecursion(schema); err != nil {
1207-
return nil, err
1208-
}
1209-
1210-
// If there is only one schema in allOf, avoid merging to keep the reference.
1211-
if len(schema.AllOf) == 1 {
1212-
s := schema.AllOf[0]
1213-
if s != nil {
1214-
// Preserve the outer schema's Ref on the inner schema so that the
1215-
// type is registered under the correct reference key and can be
1216-
// looked up later (avoiding anonymous type name collisions).
1217-
if !schema.Ref.IsZero() && s.Ref.IsZero() {
1218-
cp := *s
1219-
cp.Ref = schema.Ref
1220-
s = &cp
1221-
}
1222-
return g.generate(name, s, false)
1223-
}
1224-
}
1225-
1226-
mergedSchema, err := mergeNSchemes(schema.AllOf)
1206+
mergedSchema, err := flattenAllOfSchema(schema)
12271207
if err != nil {
12281208
return nil, err
12291209
}
1230-
1231-
// The reference field must not change
1232-
mergedSchema.Ref = schema.Ref
1233-
1234-
return g.generate(name, mergedSchema, false)
1210+
return g.generate2(name, mergedSchema)
12351211
}
12361212

12371213
// shallowSchemaCopy returns a shallow copy of the given schema.
@@ -1247,6 +1223,57 @@ func shallowSchemaCopy(s *jsonschema.Schema) *jsonschema.Schema {
12471223
return &cpy
12481224
}
12491225

1226+
func flattenAllOfSchema(schema *jsonschema.Schema) (*jsonschema.Schema, error) {
1227+
if schema == nil || len(schema.AllOf) == 0 {
1228+
return schema, nil
1229+
}
1230+
1231+
if err := ensureNoInfiniteRecursion(schema); err != nil {
1232+
return nil, err
1233+
}
1234+
1235+
parent := shallowSchemaCopy(schema)
1236+
parent.AllOf = nil
1237+
1238+
// If there is only one schema in allOf, avoid merging and keep the inner schema
1239+
// while still applying wrapper-level metadata from the parent.
1240+
if len(schema.AllOf) == 1 {
1241+
child := shallowSchemaCopy(schema.AllOf[0])
1242+
if child == nil {
1243+
return parent, nil
1244+
}
1245+
1246+
child.Nullable = child.Nullable || parent.Nullable
1247+
if child.Type == jsonschema.Empty {
1248+
child.Type = parent.Type
1249+
}
1250+
if child.Ref.IsZero() {
1251+
child.Ref = parent.Ref
1252+
}
1253+
if _, ok := child.Position(); !ok {
1254+
child.Pointer = parent.Pointer
1255+
}
1256+
return child, nil
1257+
}
1258+
1259+
mergedSchema, err := mergeNSchemes(schema.AllOf)
1260+
if err != nil {
1261+
return nil, err
1262+
}
1263+
1264+
mergedSchema.Nullable = mergedSchema.Nullable || parent.Nullable
1265+
if mergedSchema.Type == jsonschema.Empty {
1266+
mergedSchema.Type = parent.Type
1267+
}
1268+
// The reference field must not change.
1269+
mergedSchema.Ref = parent.Ref
1270+
if _, ok := mergedSchema.Position(); !ok {
1271+
mergedSchema.Pointer = parent.Pointer
1272+
}
1273+
1274+
return mergedSchema, nil
1275+
}
1276+
12501277
func mergeNSchemes(ss []*jsonschema.Schema) (_ *jsonschema.Schema, err error) {
12511278
switch len(ss) {
12521279
case 0:

gen/schema_transform_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,33 @@ func TestNullableOneOf_Optionality(t *testing.T) {
355355
assertOptionalNullableGeneric(t, optionalField.Type, ir.KindPrimitive)
356356
})
357357

358+
t.Run("optional nullable allOf field", func(t *testing.T) {
359+
a := require.New(t)
360+
s := createTestSchemaGen(nil)
361+
362+
nullableStringAllOf := &jsonschema.Schema{
363+
AllOf: []*jsonschema.Schema{
364+
createPrimitiveSchema(jsonschema.String),
365+
{Nullable: true},
366+
},
367+
}
368+
369+
optionalSchema := createObjectSchema(
370+
createProperty("optionalNullable", nullableStringAllOf, false),
371+
)
372+
optionalResult, err := s.generate("WithOptionalAllOf", optionalSchema, false)
373+
374+
a.NoError(err)
375+
a.NotNil(optionalResult)
376+
a.Equal(ir.KindStruct, optionalResult.Kind)
377+
a.Len(optionalResult.Fields, 1)
378+
379+
optionalField := optionalResult.Fields[0]
380+
a.Equal("OptionalNullable", optionalField.Name)
381+
assertOptionalNullableGeneric(t, optionalField.Type, ir.KindPrimitive)
382+
a.Equal(ir.String, optionalField.Type.GenericOf.Primitive)
383+
})
384+
358385
t.Run("field in optional object", func(t *testing.T) {
359386
a := require.New(t)
360387
s := createTestSchemaGen(nil)

0 commit comments

Comments
 (0)