Skip to content

Commit 7b8deef

Browse files
authored
Fix OneOf type templates when property types differ (#234)
## Overview A few of our OpenAPI OneOf types contain properties that are of different types. These were not handled properly, as the generator would take the first property type only and set that as the expected type. Sadly this impacted only a single type as the others had been set as `string` which the JSON decoder happily used. This commit contains a fix for said situation. ## Fix All struct field types that have different property types in the OpenAPI spec have now been set to `any`. ## Manual testing I have tested manually against a rack by running the following code ```go func main() { client, err := oxide.NewClient(nil) if err != nil { panic(err) } startTime, err := time.Parse(time.RFC3339, "2024-09-17T06:25:23.696Z") if err != nil { panic(err) } endTime := time.Now() ctx := context.Background() params := oxide.DiskMetricsListParams{ Disk: oxide.NameOrId("5cea3d86-cb2a-43b1-bd3a-2a487b048f44"), Metric: oxide.DiskMetricNameReadBytes, Limit: 3, StartTime: &startTime, EndTime: &endTime, } resp, err := client.DiskMetricsList(ctx, params) if err != nil { panic(err) } fmt.Printf("%+v\n", resp) } ``` The results were as expected ```console $ go run main.go &{Items:[{Datum:{Datum:map[start_time:2024-09-18T06:24:22.956977316Z value:1.41800448e+08] Type:cumulative_i64} Timestamp:2024-09-18 06:24:53.186026344 +0000 UTC} {Datum:{Datum:map[start_time:2024-09-18T06:24:22.956977316Z value:1.4213632e+08] Type:cumulative_i64} Timestamp:2024-09-18 06:25:23.187327342 +0000 UTC} {Datum:{Datum:map[start_time:2024-09-18T06:24:22.956977316Z value:1.4213632e+08] Type:cumulative_i64} Timestamp:2024-09-18 06:25:53.188840965 +0000 UTC}] NextPage:eyJ2IjoidjEiLCJwYWdlX3N0YXJ0Ijp7InN0YXJ0X3RpbWUiOiIyMDI0LTA5LTE4VDA2OjI1OjUzLjE4ODg0MDk2NVoiLCJlbmRfdGltZSI6IjIwMjQtMDktMThUMDY6NTM6MDJaIiwib3JkZXIiOm51bGx9fQ==} ``` Closes: #233
1 parent 2633306 commit 7b8deef

File tree

4 files changed

+77
-12
lines changed

4 files changed

+77
-12
lines changed

.changelog/v0.1.0-beta9.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[[breaking]]
2-
title = ""
3-
description = ""
2+
title = "OneOf generic types"
3+
description = "All struct field types that have different property types in the OpenAPI spec have now been set to `any`. [#234](https://github.com/oxidecomputer/oxide.go/pull/234)"
44

55
[[features]]
66
title = ""
@@ -11,5 +11,5 @@ title = ""
1111
description = ""
1212

1313
[[bugs]]
14-
title = ""
15-
description = ""
14+
title = "Fix for fields of type `time.Time`"
15+
description = "Change encoding of time parameters to RFC3339. [232](https://github.com/oxidecomputer/oxide.go/pull/232)"

internal/generate/types.go

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,10 +632,60 @@ func createAllOf(s *openapi3.Schema, stringEnums map[string][]string, name, type
632632
}
633633

634634
func createOneOf(s *openapi3.Schema, name, typeName string) ([]TypeTemplate, []EnumTemplate) {
635+
var parsedProperties []string
635636
var properties []string
637+
var genericTypes []string
636638
enumTpls := make([]EnumTemplate, 0)
637639
typeTpls := make([]TypeTemplate, 0)
638640
fields := make([]TypeFields, 0)
641+
for _, v := range s.OneOf {
642+
// Iterate over all the schema components in the spec and write the types.
643+
// We want to ensure we keep the order.
644+
keys := make([]string, 0)
645+
for k := range v.Value.Properties {
646+
keys = append(keys, k)
647+
}
648+
sort.Strings(keys)
649+
650+
for _, prop := range keys {
651+
p := v.Value.Properties[prop]
652+
// We want to collect all the unique properties to create our global oneOf type.
653+
propertyType := convertToValidGoType(prop, p)
654+
properties = append(properties, prop+"="+propertyType)
655+
}
656+
}
657+
658+
// When dealing with oneOf sometimes property types will not be the same, we want to
659+
// catch these to set them as "any" when we generate the type.
660+
typeKeys := []string{}
661+
// First we gather all unique properties
662+
for _, v := range properties {
663+
parts := strings.Split(v, "=")
664+
key := parts[0]
665+
if !sliceContains(typeKeys, key) {
666+
typeKeys = append(typeKeys, key)
667+
}
668+
}
669+
670+
// For each of the properties above we gather all possible types
671+
// and gather all of those that are not. We will be setting those
672+
// as a generic type
673+
for _, k := range typeKeys {
674+
values := []string{}
675+
for _, v := range properties {
676+
parts := strings.Split(v, "=")
677+
key := parts[0]
678+
value := parts[1]
679+
if key == k {
680+
values = append(values, value)
681+
}
682+
}
683+
684+
if !allItemsAreSame(values) {
685+
genericTypes = append(genericTypes, k)
686+
}
687+
}
688+
639689
for _, v := range s.OneOf {
640690
// We want to iterate over the properties of the embedded object
641691
// and find the type that is a string.
@@ -661,16 +711,22 @@ func createOneOf(s *openapi3.Schema, name, typeName string) ([]TypeTemplate, []E
661711

662712
propertyName := strcase.ToCamel(prop)
663713
// Avoids duplication for every enum
664-
if !containsMatchFirstWord(properties, propertyName) {
714+
if !containsMatchFirstWord(parsedProperties, propertyName) {
665715
field := TypeFields{
666716
Description: formatTypeDescription(propertyName, p.Value),
667717
Name: propertyName,
668718
Type: propertyType,
669719
SerializationInfo: fmt.Sprintf("`json:\"%s,omitempty\" yaml:\"%s,omitempty\"`", prop, prop),
670720
}
721+
722+
// We set the type of a field as "any" if every element of the oneOf property isn't the same
723+
if sliceContains(genericTypes, prop) {
724+
field.Type = "any"
725+
}
726+
671727
fields = append(fields, field)
672728

673-
properties = append(properties, propertyName)
729+
parsedProperties = append(parsedProperties, propertyName)
674730
}
675731

676732
if p.Value.Enum != nil {

internal/generate/utils.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,12 @@ func sliceContains[T comparable](s []T, str T) bool {
214214
}
215215
return false
216216
}
217+
218+
func allItemsAreSame[T comparable](a []T) bool {
219+
for _, v := range a {
220+
if v != a[0] {
221+
return false
222+
}
223+
}
224+
return true
225+
}

oxide/types.go

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)