Skip to content

Commit

Permalink
Properly handle required/nullable types and fields
Browse files Browse the repository at this point in the history
  • Loading branch information
K-Phoen committed Sep 21, 2023
1 parent 9c1f79a commit 581cfea
Show file tree
Hide file tree
Showing 13 changed files with 375 additions and 114 deletions.
6 changes: 3 additions & 3 deletions internal/ast/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type Assignment struct {
Constraints []TypeConstraint

// Some more context on the what
IntoOptionalField bool
IntoNullableField bool
}

type BuilderGenerator struct {
Expand Down Expand Up @@ -106,7 +106,7 @@ func (generator *BuilderGenerator) structFieldToStaticInitialization(field Struc
Path: field.Name,
Value: field.Type.AsScalar().Value,
ValueType: field.Type,
IntoOptionalField: !field.Required,
IntoNullableField: field.Type.Nullable,
}
}

Expand All @@ -131,7 +131,7 @@ func (generator *BuilderGenerator) structFieldToOption(field StructField) Option
ArgumentName: field.Name,
ValueType: field.Type,
Constraints: constraints,
IntoOptionalField: !field.Required,
IntoNullableField: field.Type.Nullable,
},
},
}
Expand Down
82 changes: 48 additions & 34 deletions internal/ast/compiler/disjunctions.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,68 +77,74 @@ func (pass *DisjunctionToType) processObject(file *ast.File, object ast.Object)

func (pass *DisjunctionToType) processType(file *ast.File, def ast.Type) (ast.Type, error) {
if def.Kind == ast.KindArray {
return pass.processArray(file, def.AsArray())
return pass.processArray(file, def)
}

if def.Kind == ast.KindStruct {
return pass.processStruct(file, def.AsStruct())
return pass.processStruct(file, def)
}

if def.Kind == ast.KindDisjunction {
return pass.processDisjunction(file, def.AsDisjunction())
return pass.processDisjunction(file, def)
}

return def, nil
}

func (pass *DisjunctionToType) processArray(file *ast.File, def ast.ArrayType) (ast.Type, error) {
processedType, err := pass.processType(file, def.ValueType)
func (pass *DisjunctionToType) processArray(file *ast.File, def ast.Type) (ast.Type, error) {
processedType, err := pass.processType(file, def.AsArray().ValueType)
if err != nil {
return ast.Type{}, err
}

return ast.NewArray(processedType), nil
}

func (pass *DisjunctionToType) processStruct(file *ast.File, def ast.StructType) (ast.Type, error) {
processedFields := make([]ast.StructField, 0, len(def.Fields))
for _, field := range def.Fields {
func (pass *DisjunctionToType) processStruct(file *ast.File, def ast.Type) (ast.Type, error) {
processedFields := make([]ast.StructField, 0, len(def.AsStruct().Fields))
for _, field := range def.AsStruct().Fields {
processedType, err := pass.processType(file, field.Type)
if err != nil {
return ast.Type{}, err
}

processedFields = append(processedFields, ast.StructField{
Name: field.Name,
Comments: field.Comments,
Type: processedType,
Required: field.Required,
Default: field.Default,
})
newField := field
newField.Type = processedType

processedFields = append(processedFields, newField)
}

return ast.NewStruct(processedFields...), nil
newStruct := def
newStruct.Struct.Fields = processedFields

return newStruct, nil
}

func (pass *DisjunctionToType) processDisjunction(file *ast.File, def ast.DisjunctionType) (ast.Type, error) {
func (pass *DisjunctionToType) processDisjunction(file *ast.File, def ast.Type) (ast.Type, error) {
disjunction := def.AsDisjunction()

// Ex: type | null
if len(def.Branches) == 2 && def.Branches.HasNullType() {
finalType := def.Branches.NonNullTypes()[0]
// FIXME: this should be propagated
// finalType.Nullable = true
if len(disjunction.Branches) == 2 && disjunction.Branches.HasNullType() {
finalType := disjunction.Branches.NonNullTypes()[0]
finalType.Nullable = true

return finalType, nil
}

// type | otherType | something (| null)?
// generate a type with a nullable field for every branch of the disjunction,
// add it to preprocessor.types, and use it instead.
newTypeName := pass.disjunctionTypeName(def)
newTypeName := pass.disjunctionTypeName(disjunction)

// if we already generated a new object for this disjunction, let's return
// a reference to it.
if _, ok := pass.newObjects[newTypeName]; ok {
return ast.NewRef(newTypeName), nil
ref := ast.NewRef(newTypeName)
if disjunction.Branches.HasNullType() {
ref.Nullable = true
}

return ref, nil
}

/*
Expand All @@ -148,27 +154,30 @@ func (pass *DisjunctionToType) processDisjunction(file *ast.File, def ast.Disjun
}
*/

fields := make([]ast.StructField, 0, len(def.Branches))
for _, branch := range def.Branches {
// FIXME: should ignore this completely.
// ie: if there was a nullable branch, the whole resulting type should be nullable.
fields := make([]ast.StructField, 0, len(disjunction.Branches))
for _, branch := range disjunction.Branches {
// Handled below, by allowing the reference to the disjunction struct
// to be null.
if branch.IsNull() {
continue
}

processedBranch := branch
processedBranch.Nullable = true

fields = append(fields, ast.StructField{
Name: "Val" + tools.UpperCamelCase(pass.typeName(branch)),
Type: branch,
Name: "Val" + tools.UpperCamelCase(pass.typeName(processedBranch)),
Type: processedBranch,
Required: false,
})
}

structType := ast.NewStruct(fields...)
if def.Branches.HasOnlyScalarOrArray() {
structType.Struct.Hint[ast.HintDisjunctionOfScalars] = def
if disjunction.Branches.HasOnlyScalarOrArray() {
structType.Struct.Hint[ast.HintDisjunctionOfScalars] = disjunction
}
if def.Branches.HasOnlyRefs() {
newDisjunctionDef, err := pass.ensureDiscriminator(file, def)
if disjunction.Branches.HasOnlyRefs() {
newDisjunctionDef, err := pass.ensureDiscriminator(file, disjunction)
if err != nil {
return ast.Type{}, err
}
Expand All @@ -181,7 +190,12 @@ func (pass *DisjunctionToType) processDisjunction(file *ast.File, def ast.Disjun
Type: structType,
}

return ast.NewRef(newTypeName), nil
ref := ast.NewRef(newTypeName)
if disjunction.Branches.HasNullType() {
ref.Nullable = true
}

return ref, nil
}

func (pass *DisjunctionToType) disjunctionTypeName(def ast.DisjunctionType) string {
Expand Down
54 changes: 27 additions & 27 deletions internal/ast/compiler/disjunctions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ func TestDisjunctionToType_WithDisjunctionOfScalars_AsAnObject(t *testing.T) {

// Prepare expected output
disjunctionStructType := ast.NewStruct(
ast.NewStructField("ValString", ast.NewScalar(ast.KindString)),
ast.NewStructField("ValBool", ast.NewScalar(ast.KindBool)),
ast.NewStructField("ValString", ast.NewScalar(ast.KindString, ast.Nullable())),
ast.NewStructField("ValBool", ast.NewScalar(ast.KindBool, ast.Nullable())),
)
// The original disjunction definition is preserved as a hint
disjunctionStructType.Struct.Hint[ast.HintDisjunctionOfScalars] = objects[0].Type.AsDisjunction()
Expand Down Expand Up @@ -76,8 +76,8 @@ func TestDisjunctionToType_WithDisjunctionOfScalars_AsAStructField(t *testing.T)

// Prepare expected output
disjunctionStructType := ast.NewStruct(
ast.NewStructField("ValString", ast.NewScalar(ast.KindString)),
ast.NewStructField("ValBool", ast.NewScalar(ast.KindBool)),
ast.NewStructField("ValString", ast.NewScalar(ast.KindString, ast.Nullable())),
ast.NewStructField("ValBool", ast.NewScalar(ast.KindBool, ast.Nullable())),
)
// The original disjunction definition is preserved as a hint
disjunctionStructType.Struct.Hint[ast.HintDisjunctionOfScalars] = disjunctionType.AsDisjunction()
Expand Down Expand Up @@ -105,8 +105,8 @@ func TestDisjunctionToType_WithDisjunctionOfScalars_AsAnArrayValueType(t *testin

// Prepare expected output
disjunctionStructType := ast.NewStruct(
ast.NewStructField("ValString", ast.NewScalar(ast.KindString)),
ast.NewStructField("ValBool", ast.NewScalar(ast.KindBool)),
ast.NewStructField("ValString", ast.NewScalar(ast.KindString, ast.Nullable())),
ast.NewStructField("ValBool", ast.NewScalar(ast.KindBool, ast.Nullable())),
)
// The original disjunction definition is preserved as a hint
disjunctionStructType.Struct.Hint[ast.HintDisjunctionOfScalars] = disjunctionType.AsDisjunction()
Expand All @@ -131,11 +131,11 @@ func TestDisjunctionToType_WithDisjunctionOfRefs_AsAnObject_NoDiscriminatorMetad
})),

ast.NewObject("SomeStruct", ast.NewStruct(
ast.NewStructField("Kind", ast.NewConcreteScalar(ast.KindString, "other-struct")), // No equivalent in OtherStruct
ast.NewStructField("Kind", ast.NewScalar(ast.KindString, ast.Value("some-struct"))), // No equivalent in OtherStruct
ast.NewStructField("FieldFoo", ast.NewScalar(ast.KindString)),
)),
ast.NewObject("OtherStruct", ast.NewStruct(
ast.NewStructField("Type", ast.NewConcreteScalar(ast.KindString, "other-struct")),
ast.NewStructField("Type", ast.NewScalar(ast.KindString, ast.Value("other-struct"))),
ast.NewStructField("FieldBar", ast.NewScalar(ast.KindBool)),
)),
}
Expand Down Expand Up @@ -199,7 +199,7 @@ func TestDisjunctionToType_WithDisjunctionOfRefs_AsAnObject_NoDiscriminatorMetad
ast.NewStructField("FieldFoo", ast.NewScalar(ast.KindString)),
)),
ast.NewObject("OtherStruct", ast.NewStruct(
ast.NewStructField("Type", ast.NewConcreteScalar(ast.KindString, "other-struct")),
ast.NewStructField("Type", ast.NewScalar(ast.KindString, ast.Value("other-struct"))),
ast.NewStructField("FieldBar", ast.NewScalar(ast.KindBool)),
)),
}
Expand Down Expand Up @@ -227,11 +227,11 @@ func TestDisjunctionToType_WithDisjunctionOfRefs_AsAnObject_NoDiscriminatorMetad
ast.NewObject("ADisjunctionOfRefs", disjunctionType),

ast.NewObject("SomeStruct", ast.NewStruct(
ast.NewStructField("Type", ast.NewConcreteScalar(ast.KindString, "some-struct")),
ast.NewStructField("Type", ast.NewScalar(ast.KindString, ast.Value("some-struct"))),
ast.NewStructField("FieldFoo", ast.NewScalar(ast.KindString)),
)),
ast.NewObject("OtherStruct", ast.NewStruct(
ast.NewStructField("Type", ast.NewConcreteScalar(ast.KindString, "other-struct")),
ast.NewStructField("Type", ast.NewScalar(ast.KindString, ast.Value("other-struct"))),
ast.NewStructField("FieldBar", ast.NewScalar(ast.KindBool)),
)),
}
Expand All @@ -254,19 +254,19 @@ func TestDisjunctionToType_WithDisjunctionOfRefs_AsAnObject_NoDiscriminatorMetad
})),

ast.NewObject("SomeStruct", ast.NewStruct(
ast.NewStructField("Type", ast.NewConcreteScalar(ast.KindString, "some-struct")),
ast.NewStructField("Type", ast.NewScalar(ast.KindString, ast.Value("some-struct"))),
ast.NewStructField("FieldFoo", ast.NewScalar(ast.KindString)),
)),
ast.NewObject("OtherStruct", ast.NewStruct(
ast.NewStructField("FieldBar", ast.NewMap(ast.NewScalar(ast.KindString), ast.NewScalar(ast.KindString))),
ast.NewStructField("Type", ast.NewConcreteScalar(ast.KindString, "other-struct")),
ast.NewStructField("Type", ast.NewScalar(ast.KindString, ast.Value("other-struct"))),
)),
}

// Prepare expected output
disjunctionStructType := ast.NewStruct(
ast.NewStructField("ValSomeStruct", ast.NewRef("SomeStruct")),
ast.NewStructField("ValOtherStruct", ast.NewRef("OtherStruct")),
ast.NewStructField("ValSomeStruct", ast.NewRef("SomeStruct", ast.Nullable())),
ast.NewStructField("ValOtherStruct", ast.NewRef("OtherStruct", ast.Nullable())),
)
// The original disjunction definition is preserved as a hint
disjunctionTypeWithDiscriminatorMeta := objects[0].Type.AsDisjunction()
Expand Down Expand Up @@ -305,21 +305,21 @@ func TestDisjunctionToType_WithDisjunctionOfRefs_AsAnObject_WithDiscriminatorFie
ast.NewObject("ADisjunctionOfRefs", disjunctionType),

ast.NewObject("SomeStruct", ast.NewStruct(
ast.NewStructField("Type", ast.NewConcreteScalar(ast.KindString, "some-struct")),
ast.NewStructField("Kind", ast.NewConcreteScalar(ast.KindString, "some-kind")),
ast.NewStructField("Type", ast.NewScalar(ast.KindString, ast.Value("some-struct"))),
ast.NewStructField("Kind", ast.NewScalar(ast.KindString, ast.Value("some-kind"))),
ast.NewStructField("FieldFoo", ast.NewScalar(ast.KindString)),
)),
ast.NewObject("OtherStruct", ast.NewStruct(
ast.NewStructField("Type", ast.NewConcreteScalar(ast.KindString, "other-struct")),
ast.NewStructField("Kind", ast.NewConcreteScalar(ast.KindString, "other-kind")),
ast.NewStructField("Type", ast.NewScalar(ast.KindString, ast.Value("other-struct"))),
ast.NewStructField("Kind", ast.NewScalar(ast.KindString, ast.Value("other-kind"))),
ast.NewStructField("FieldBar", ast.NewScalar(ast.KindBool)),
)),
}

// Prepare expected output
disjunctionStructType := ast.NewStruct(
ast.NewStructField("ValSomeStruct", ast.NewRef("SomeStruct")),
ast.NewStructField("ValOtherStruct", ast.NewRef("OtherStruct")),
ast.NewStructField("ValSomeStruct", ast.NewRef("SomeStruct", ast.Nullable())),
ast.NewStructField("ValOtherStruct", ast.NewRef("OtherStruct", ast.Nullable())),
)
// The original disjunction definition is preserved as a hint
disjunctionTypeWithDiscriminatorMeta := objects[0].Type.AsDisjunction()
Expand Down Expand Up @@ -360,21 +360,21 @@ func TestDisjunctionToType_WithDisjunctionOfRefs_AsAnObject_WithDiscriminatorFie
ast.NewObject("ADisjunctionOfRefs", disjunctionType),

ast.NewObject("SomeStruct", ast.NewStruct(
ast.NewStructField("Type", ast.NewConcreteScalar(ast.KindString, "some-struct")),
ast.NewStructField("Kind", ast.NewConcreteScalar(ast.KindString, "some-kind")),
ast.NewStructField("Type", ast.NewScalar(ast.KindString, ast.Value("some-struct"))),
ast.NewStructField("Kind", ast.NewScalar(ast.KindString, ast.Value("some-kind"))),
ast.NewStructField("FieldFoo", ast.NewScalar(ast.KindString)),
)),
ast.NewObject("OtherStruct", ast.NewStruct(
ast.NewStructField("Type", ast.NewConcreteScalar(ast.KindString, "other-struct")),
ast.NewStructField("Kind", ast.NewConcreteScalar(ast.KindString, "other-kind")),
ast.NewStructField("Type", ast.NewScalar(ast.KindString, ast.Value("other-struct"))),
ast.NewStructField("Kind", ast.NewScalar(ast.KindString, ast.Value("other-kind"))),
ast.NewStructField("FieldBar", ast.NewScalar(ast.KindBool)),
)),
}

// Prepare expected output
disjunctionStructType := ast.NewStruct(
ast.NewStructField("ValSomeStruct", ast.NewRef("SomeStruct")),
ast.NewStructField("ValOtherStruct", ast.NewRef("OtherStruct")),
ast.NewStructField("ValSomeStruct", ast.NewRef("SomeStruct", ast.Nullable())),
ast.NewStructField("ValOtherStruct", ast.NewRef("OtherStruct", ast.Nullable())),
)
// The original disjunction definition is preserved as a hint
disjunctionTypeWithDiscriminatorMeta := objects[0].Type.AsDisjunction()
Expand Down
Loading

0 comments on commit 581cfea

Please sign in to comment.