Skip to content

Commit

Permalink
improve union types in oas2
Browse files Browse the repository at this point in the history
  • Loading branch information
hgiasac committed Dec 23, 2024
1 parent a90fdc2 commit 3f8f362
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 149 deletions.
37 changes: 30 additions & 7 deletions ndc-http-schema/openapi/internal/oas2_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,20 @@ func (oc *oas2SchemaBuilder) evalObjectType(baseSchema *base.Schema, forceProper
if xmlSchema.Name == "" {
xmlSchema.Name = fieldPaths[0]
}
object := rest.ObjectType{

var result schema.TypeEncoder
readObject := rest.ObjectType{
Fields: make(map[string]rest.ObjectField),
XML: xmlSchema,
}
writeObject := rest.ObjectType{
Fields: make(map[string]rest.ObjectField),
XML: xmlSchema,
}

if typeResult.Description != "" {
object.Description = &typeResult.Description
readObject.Description = &typeResult.Description
writeObject.Description = &typeResult.Description
}

for prop := baseSchema.Properties.First(); prop != nil; prop = prop.Next() {
Expand Down Expand Up @@ -222,14 +230,29 @@ func (oc *oas2SchemaBuilder) evalObjectType(baseSchema *base.Schema, forceProper
objField.Description = &propApiSchema.Description
}

object.Fields[propName] = objField
readObject.Fields[propName] = objField
if !propApiSchema.ReadOnly {
writeObject.Fields[propName] = objField
}
}

writeRefName := formatWriteObjectName(refName)
if isXMLLeafObject(readObject) {
readObject.Fields[xmlValueFieldName] = xmlValueField
}

if isXMLLeafObject(object) {
object.Fields[xmlValueFieldName] = xmlValueField
if isXMLLeafObject(writeObject) {
writeObject.Fields[xmlValueFieldName] = xmlValueField
}
oc.builder.schema.ObjectTypes[refName] = object
var result schema.TypeEncoder = schema.NewNamedType(refName)

oc.builder.schema.ObjectTypes[refName] = readObject
oc.builder.schema.ObjectTypes[writeRefName] = writeObject
if oc.writeMode {
result = schema.NewNamedType(writeRefName)
} else {
result = schema.NewNamedType(refName)
}

if baseSchema.Nullable != nil && *baseSchema.Nullable {
result = schema.NewNullableType(result)
}
Expand Down
111 changes: 0 additions & 111 deletions ndc-http-schema/openapi/internal/oas3_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,114 +424,3 @@ func (oc *oas3SchemaBuilder) buildUnionSchemaType(baseSchema *base.Schema, schem

return schema.NewNamedType(refName), typeSchema, nil
}

// Find common fields in all objects to merge the type.
// If they have the same type, we don't need to wrap it with the nullable type.
func mergeUnionObjects(httpSchema *rest.NDCHttpSchema, dest *rest.ObjectType, srcObjects []rest.ObjectType, unionType oasUnionType, fieldPaths []string) error {
mergedObjectFields := make(map[string][]rest.ObjectField)
for _, object := range srcObjects {
for key, field := range object.Fields {
mergedObjectFields[key] = append(mergedObjectFields[key], field)
}
}

for key, fields := range mergedObjectFields {
if len(fields) == 1 {
newField := rest.ObjectField{
ObjectField: schema.ObjectField{
Description: fields[0].Description,
Arguments: fields[0].Arguments,
Type: fields[0].Type,
},
HTTP: fields[0].HTTP,
}

if unionType != oasAllOf && !isNullableType(newField.Type.Interface()) {
newField.Type = (schema.NullableType{
Type: schema.TypeNullable,
UnderlyingType: newField.Type,
}).Encode()
}

dest.Fields[key] = newField

continue
}

var unionField rest.ObjectField
for i, field := range fields {
if i == 0 {
unionField = field

continue
}

var ok bool
unionField, ok = mergeUnionTypes(httpSchema, field.Type, unionField.Type, append(fieldPaths, key))
if !ok {
break
}

if unionField.Description == nil && field.Description != nil {
unionField.Description = field.Description
}

if unionField.HTTP == nil && field.HTTP != nil {
unionField.HTTP = field.HTTP
}
}

if len(fields) < len(srcObjects) && unionType != oasAllOf && !isNullableType(unionField.Type.Interface()) {
unionField.Type = (schema.NullableType{
Type: schema.TypeNullable,
UnderlyingType: unionField.Type,
}).Encode()
}

dest.Fields[key] = unionField
}

// for key, field := range siblingFields {
// fieldType := field.Type
// if len(field.EnumOneOf) > 0 {
// newScalar := schema.NewScalarType()
// newScalar.Representation = schema.NewTypeRepresentationEnum(utils.SliceUnique(field.EnumOneOf)).Encode()

// newName := utils.StringSliceToPascalCase(append(fieldPaths, key, "Enum"))
// httpSchema.ScalarTypes[newName] = *newScalar

// var err error
// fieldType, err = replaceNamedType(field.Type.Encode(), newName)
// if err != nil {
// return fmt.Errorf("%s: failed to replace named type, %w", strings.Join(append(fieldPaths, key), "."), err)
// }
// }

// dest.Fields[key] = rest.ObjectField{
// ObjectField: schema.ObjectField{
// Description: field.Description,
// Type: fieldType.Encode(),
// },
// HTTP: field.HTTP,
// }
// }

// for _, objectItem := range srcObjects {
// for key, field := range objectItem.Fields {
// if _, ok := siblingFields[key]; ok {
// continue
// }

// // In anyOf and oneOf union objects, the API only requires one of union objects, other types are optional.
// // Because the NDC spec hasn't supported union types yet we make all properties optional to enable autocompletion.
// iType := field.Type.Interface()
// if unionType != oasAllOf && !isNullableType(iType) {
// field.ObjectField.Type = schema.NewNullableType(iType).Encode()
// }

// dest.Fields[key] = field
// }
// }

return nil
}
92 changes: 69 additions & 23 deletions ndc-http-schema/openapi/internal/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,75 @@ func isNullableType(input schema.TypeEncoder) bool {
return ok
}

// Find common fields in all objects to merge the type.
// If they have the same type, we don't need to wrap it with the nullable type.
func mergeUnionObjects(httpSchema *rest.NDCHttpSchema, dest *rest.ObjectType, srcObjects []rest.ObjectType, unionType oasUnionType, fieldPaths []string) error {
mergedObjectFields := make(map[string][]rest.ObjectField)
for _, object := range srcObjects {
for key, field := range object.Fields {
mergedObjectFields[key] = append(mergedObjectFields[key], field)
}
}

for key, fields := range mergedObjectFields {
if len(fields) == 1 {
newField := rest.ObjectField{
ObjectField: schema.ObjectField{
Description: fields[0].Description,
Arguments: fields[0].Arguments,
Type: fields[0].Type,
},
HTTP: fields[0].HTTP,
}

if unionType != oasAllOf && !isNullableType(newField.Type.Interface()) {
newField.Type = (schema.NullableType{
Type: schema.TypeNullable,
UnderlyingType: newField.Type,
}).Encode()
}

dest.Fields[key] = newField

continue
}

var unionField rest.ObjectField
for i, field := range fields {
if i == 0 {
unionField = field

continue
}

var ok bool
unionField, ok = mergeUnionTypes(httpSchema, field.Type, unionField.Type, append(fieldPaths, key))
if !ok {
break
}

if unionField.Description == nil && field.Description != nil {
unionField.Description = field.Description
}

if unionField.HTTP == nil && field.HTTP != nil {
unionField.HTTP = field.HTTP
}
}

if len(fields) < len(srcObjects) && unionType != oasAllOf && !isNullableType(unionField.Type.Interface()) {
unionField.Type = (schema.NullableType{
Type: schema.TypeNullable,
UnderlyingType: unionField.Type,
}).Encode()
}

dest.Fields[key] = unionField
}

return nil
}

func mergeUnionTypes(httpSchema *rest.NDCHttpSchema, a schema.Type, b schema.Type, fieldPaths []string) (rest.ObjectField, bool) {
bn, bNullErr := b.AsNullable()
bType := b
Expand Down Expand Up @@ -525,29 +594,6 @@ func evalSchemaProxiesSlice(schemaProxies []*base.SchemaProxy, location rest.Par
return results, nil, nullable
}

func getMaybeTypeRepresentationEnum(httpSchema *rest.NDCHttpSchema, input schema.Type) (*schema.TypeRepresentationEnum, bool) {
switch t := input.Interface().(type) {
case *schema.NullableType:
return getMaybeTypeRepresentationEnum(httpSchema, t.UnderlyingType)
case *schema.ArrayType:
return nil, false
case *schema.NamedType:
scl, ok := httpSchema.ScalarTypes[t.Name]
if !ok {
return nil, false
}

typeRep, err := scl.Representation.AsEnum()
if err != nil {
return nil, false
}

return typeRep, true
default:
return nil, false
}
}

func formatWriteObjectName(name string) string {
return name + "Input"
}
Expand Down
14 changes: 10 additions & 4 deletions ndc-http-schema/openapi/testdata/union2/expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,11 @@
},
"id": {
"type": {
"name": "String",
"type": "named"
"type": "nullable",
"underlying_type": {
"name": "String",
"type": "named"
}
},
"http": {
"type": [
Expand Down Expand Up @@ -202,8 +205,11 @@
},
"type": {
"type": {
"name": "PetBodyTypeEnum",
"type": "named"
"type": "nullable",
"underlying_type": {
"name": "PetBodyTypeEnum",
"type": "named"
}
},
"http": {
"type": [
Expand Down
14 changes: 10 additions & 4 deletions ndc-http-schema/openapi/testdata/union2/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,11 @@
},
"id": {
"type": {
"name": "String",
"type": "named"
"type": "nullable",
"underlying_type": {
"name": "String",
"type": "named"
}
}
},
"metadata": {
Expand All @@ -116,8 +119,11 @@
},
"type": {
"type": {
"name": "PetBodyTypeEnum",
"type": "named"
"type": "nullable",
"underlying_type": {
"name": "PetBodyTypeEnum",
"type": "named"
}
}
}
}
Expand Down

0 comments on commit 3f8f362

Please sign in to comment.