From 643ba46aac097a7576249d15a31c9ff4638a14d8 Mon Sep 17 00:00:00 2001 From: Alex McKinney Date: Thu, 12 Dec 2024 16:31:02 -0500 Subject: [PATCH] feat(go): Unions include runtime validation (#5403) --- generators/go/cmd/fern-go-model/main_test.go | 6 - generators/go/internal/generator/generator.go | 2 +- generators/go/internal/generator/model.go | 60 ++ .../testdata/model/alias/fixtures/imdb.go | 42 +- .../testdata/model/builtin/fixtures/imdb.go | 2 +- .../testdata/model/custom/fixtures/imdb.go | 2 +- .../testdata/model/extends/fixtures/imdb.go | 68 +++ .../testdata/model/github/fixtures/foo.go | 2 +- .../testdata/model/ir/fixtures/auth.go | 40 ++ .../testdata/model/ir/fixtures/environment.go | 37 ++ .../testdata/model/ir/fixtures/http.go | 305 +++++++++++ .../internal/testdata/model/ir/fixtures/ir.go | 37 ++ .../testdata/model/ir/fixtures/types.go | 515 +++++++++++++++++- .../testdata/model/packages/fixtures/union.go | 43 ++ .../testdata/model/primitive/fixtures/imdb.go | 2 +- .../testdata/model/sum/fixtures/foo.go | 2 +- .../testdata/model/union/fixtures/imdb.go | 296 ++++++++++ .../fixtures/organization/metrics/tag.go | 40 ++ .../fixtures/user.go | 37 ++ .../post-with-path-params/fixtures/user.go | 37 ++ generators/go/sdk/versions.yml | 29 + seed/go-fiber/audiences/folderc/common.go | 2 +- .../circular-references-advanced/ast.go | 77 +++ seed/go-fiber/circular-references/ast.go | 77 +++ .../folderc/common.go | 2 +- seed/go-fiber/examples/commons/types.go | 74 +++ seed/go-fiber/examples/types.go | 113 +++- seed/go-fiber/exhaustive/types/union.go | 37 ++ seed/go-fiber/mixed-case/service.go | 37 ++ seed/go-fiber/object/types.go | 2 +- .../pagination/.mock/definition/users.yml | 116 ++-- seed/go-fiber/unions/types.go | 370 +++++++++++++ seed/go-fiber/unions/union.go | 37 ++ seed/go-model/audiences/folderc/common.go | 2 +- .../circular-references-advanced/ast.go | 77 +++ seed/go-model/circular-references/ast.go | 77 +++ .../folderc/common.go | 2 +- seed/go-model/examples/commons/types.go | 74 +++ seed/go-model/examples/types.go | 113 +++- seed/go-model/exhaustive/types/union.go | 37 ++ .../file-upload/.mock/definition/service.yml | 3 + seed/go-model/mixed-case/service.go | 37 ++ seed/go-model/object/types.go | 2 +- .../pagination/.mock/definition/users.yml | 116 ++-- seed/go-model/unions/types.go | 370 +++++++++++++ seed/go-model/unions/union.go | 37 ++ seed/go-sdk/audiences/folderc/common.go | 2 +- .../circular-references-advanced/ast.go | 77 +++ seed/go-sdk/circular-references/ast.go | 77 +++ .../folderc/common.go | 2 +- .../commons/types.go | 74 +++ .../always-send-required-properties/types.go | 113 +++- .../exported-client-name/commons/types.go | 74 +++ .../examples/exported-client-name/types.go | 113 +++- .../no-custom-config/commons/types.go | 74 +++ .../go-sdk/examples/no-custom-config/types.go | 113 +++- seed/go-sdk/mixed-case/service.go | 37 ++ seed/go-sdk/object/types.go | 2 +- seed/go-sdk/unions/types.go | 370 +++++++++++++ seed/go-sdk/unions/union.go | 37 ++ 60 files changed, 4514 insertions(+), 146 deletions(-) diff --git a/generators/go/cmd/fern-go-model/main_test.go b/generators/go/cmd/fern-go-model/main_test.go index 3643dd7fb2a..9c7c5a8b92b 100644 --- a/generators/go/cmd/fern-go-model/main_test.go +++ b/generators/go/cmd/fern-go-model/main_test.go @@ -249,12 +249,6 @@ func TestTime(t *testing.T) { }) t.Run("union (optional)", func(t *testing.T) { - empty := union.NewUnionWithOptionalTimeFromDate(nil) - - emptyBytes, err := json.Marshal(empty) - require.NoError(t, err) - assert.Equal(t, `{"type":"date"}`, string(emptyBytes)) - value := union.NewUnionWithOptionalTimeFromDate(&date) bytes, err := json.Marshal(value) diff --git a/generators/go/internal/generator/generator.go b/generators/go/internal/generator/generator.go index bbe66c7ae70..f7be9702728 100644 --- a/generators/go/internal/generator/generator.go +++ b/generators/go/internal/generator/generator.go @@ -1703,7 +1703,7 @@ func zeroValueForPrimitive(primitive fernir.PrimitiveTypeV1) string { case fernir.PrimitiveTypeV1DateTime, fernir.PrimitiveTypeV1Date: return "time.Time{}" case fernir.PrimitiveTypeV1Uuid: - return "uuid.UUID{}" + return "uuid.Nil" case fernir.PrimitiveTypeV1Base64: return "nil" } diff --git a/generators/go/internal/generator/model.go b/generators/go/internal/generator/model.go index d13a67599f8..c8ad426c25b 100644 --- a/generators/go/internal/generator/model.go +++ b/generators/go/internal/generator/model.go @@ -474,6 +474,9 @@ func (t *typeVisitor) VisitUnion(union *ir.UnionTypeDeclaration) error { // Implement the json.Marshaler interface. t.writer.P("func (", receiver, " ", t.typeName, ") MarshalJSON() ([]byte, error) {") + t.writer.P("if err := ", receiver, ".validate(); err != nil {") + t.writer.P("return nil, err") + t.writer.P("}") if t.unionVersion != UnionVersionV1 { t.writer.P("switch ", receiver, ".", discriminantName, " {") } @@ -659,6 +662,63 @@ func (t *typeVisitor) VisitUnion(union *ir.UnionTypeDeclaration) error { t.writer.P("}") t.writer.P() + // Generate the validate method. + t.writer.P("func (", receiver, " *", t.typeName, ") validate() error {") + t.writer.P("if ", receiver, " == nil {") + t.writer.P(`return fmt.Errorf("type %T is nil", `, receiver, ")") + t.writer.P("}") + t.writer.P("var fields []string") + for _, unionType := range union.Types { + var ( + isLiteral bool + isOptional bool + date *date + ) + if unionType.Shape.SingleProperty != nil { + isLiteral = isLiteralType(unionType.Shape.SingleProperty.Type, t.writer.types) + isOptional = isOptionalType(unionType.Shape.SingleProperty.Type, t.writer.types) + date = maybeDate(unionType.Shape.SingleProperty.Type, isOptional) + } + zeroValue := "nil" + if unionType.Shape.PropertiesType == "singleProperty" { + zeroValue = zeroValueForTypeReference(unionType.Shape.SingleProperty.Type, t.writer.types) + } + unionTypeValue := receiver + "." + unionType.DiscriminantValue.Name.PascalCase.UnsafeName + if isLiteral { + unionTypeValue = receiver + "." + unionType.DiscriminantValue.Name.CamelCase.SafeName + } + if date != nil && !isOptional { + t.writer.P("if !", unionTypeValue, ".IsZero() {") + } else { + t.writer.P("if ", unionTypeValue, " != ", zeroValue, " {") + } + t.writer.P(`fields = append(fields, "`, unionType.DiscriminantValue.WireValue, `")`) + t.writer.P("}") + } + t.writer.P("if len(fields) == 0 {") + t.writer.P("if ", receiver, ".", discriminantName, ` != "" {`) + t.writer.P(`return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", `, receiver, ", ", receiver, ".", discriminantName, ")") + t.writer.P("}") + t.writer.P(`return fmt.Errorf("type %T is empty", `, receiver, ")") + t.writer.P("}") + t.writer.P("if len(fields) > 1 {") + t.writer.P(`return fmt.Errorf("type %T defines values for %s, but only one value is allowed", `, receiver, ", fields)") + t.writer.P("}") + t.writer.P("if ", receiver, ".", discriminantName, ` != "" {`) + t.writer.P("field := fields[0]") + t.writer.P("if ", receiver, ".", discriminantName, " != field {") + t.writer.P("return fmt.Errorf(") + t.writer.P(`"type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match",`) + t.writer.P(receiver, ", ") + t.writer.P(receiver, ".", discriminantName, ", ") + t.writer.P(receiver, ", ") + t.writer.P(")") + t.writer.P("}") + t.writer.P("}") + t.writer.P("return nil") + t.writer.P("}") + t.writer.P() + return nil } diff --git a/generators/go/internal/testdata/model/alias/fixtures/imdb.go b/generators/go/internal/testdata/model/alias/fixtures/imdb.go index c7e1899b9af..d2c96f6619f 100644 --- a/generators/go/internal/testdata/model/alias/fixtures/imdb.go +++ b/generators/go/internal/testdata/model/alias/fixtures/imdb.go @@ -73,7 +73,7 @@ type Foo struct { func (f *Foo) GetId() uuid.UUID { if f == nil { - return uuid.UUID{} + return uuid.Nil } return f.Id } @@ -224,6 +224,9 @@ func (u *Union) UnmarshalJSON(data []byte) error { } func (u Union) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -269,6 +272,43 @@ func (u *Union) Accept(visitor UnionVisitor) error { } } +func (u *Union) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.FooAlias != nil { + fields = append(fields, "fooAlias") + } + if u.BarAlias != nil { + fields = append(fields, "barAlias") + } + if u.DoubleAlias != 0 { + fields = append(fields, "doubleAlias") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type Unknown = interface{} type Uuid = uuid.UUID diff --git a/generators/go/internal/testdata/model/builtin/fixtures/imdb.go b/generators/go/internal/testdata/model/builtin/fixtures/imdb.go index 11deb630f6a..feb7b73e211 100644 --- a/generators/go/internal/testdata/model/builtin/fixtures/imdb.go +++ b/generators/go/internal/testdata/model/builtin/fixtures/imdb.go @@ -86,7 +86,7 @@ func (t *Type) GetSeven() time.Time { func (t *Type) GetEight() uuid.UUID { if t == nil { - return uuid.UUID{} + return uuid.Nil } return t.Eight } diff --git a/generators/go/internal/testdata/model/custom/fixtures/imdb.go b/generators/go/internal/testdata/model/custom/fixtures/imdb.go index 9520bc87b4f..59ae79a4f99 100644 --- a/generators/go/internal/testdata/model/custom/fixtures/imdb.go +++ b/generators/go/internal/testdata/model/custom/fixtures/imdb.go @@ -59,7 +59,7 @@ type Foo struct { func (f *Foo) GetId() uuid.UUID { if f == nil { - return uuid.UUID{} + return uuid.Nil } return f.Id } diff --git a/generators/go/internal/testdata/model/extends/fixtures/imdb.go b/generators/go/internal/testdata/model/extends/fixtures/imdb.go index 2b1c9de746b..586734a22ab 100644 --- a/generators/go/internal/testdata/model/extends/fixtures/imdb.go +++ b/generators/go/internal/testdata/model/extends/fixtures/imdb.go @@ -262,6 +262,9 @@ func (n *NestedUnion) UnmarshalJSON(data []byte) error { } func (n NestedUnion) MarshalJSON() ([]byte, error) { + if err := n.validate(); err != nil { + return nil, err + } switch n.Type { default: return nil, fmt.Errorf("invalid type %s in %T", n.Type, n) @@ -283,6 +286,37 @@ func (n *NestedUnion) Accept(visitor NestedUnionVisitor) error { } } +func (n *NestedUnion) validate() error { + if n == nil { + return fmt.Errorf("type %T is nil", n) + } + var fields []string + if n.One != nil { + fields = append(fields, "one") + } + if len(fields) == 0 { + if n.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", n, n.Type) + } + return fmt.Errorf("type %T is empty", n) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", n, fields) + } + if n.Type != "" { + field := fields[0] + if n.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + n, + n.Type, + n, + ) + } + } + return nil +} + type Union struct { Type string Docs string @@ -339,6 +373,9 @@ func (u *Union) UnmarshalJSON(data []byte) error { } func (u Union) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -359,3 +396,34 @@ func (u *Union) Accept(visitor UnionVisitor) error { return visitor.VisitOne(u.One) } } + +func (u *Union) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.One != nil { + fields = append(fields, "one") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} diff --git a/generators/go/internal/testdata/model/github/fixtures/foo.go b/generators/go/internal/testdata/model/github/fixtures/foo.go index 1d9f7ff4b9d..22f33defdad 100644 --- a/generators/go/internal/testdata/model/github/fixtures/foo.go +++ b/generators/go/internal/testdata/model/github/fixtures/foo.go @@ -34,7 +34,7 @@ func (f *Foo) GetBar() *bar.Bar { func (f *Foo) GetUuid() uuid.UUID { if f == nil { - return uuid.UUID{} + return uuid.Nil } return f.Uuid } diff --git a/generators/go/internal/testdata/model/ir/fixtures/auth.go b/generators/go/internal/testdata/model/ir/fixtures/auth.go index 1460944c028..0f61aa70f0c 100644 --- a/generators/go/internal/testdata/model/ir/fixtures/auth.go +++ b/generators/go/internal/testdata/model/ir/fixtures/auth.go @@ -145,6 +145,9 @@ func (a *AuthScheme) UnmarshalJSON(data []byte) error { } func (a AuthScheme) MarshalJSON() ([]byte, error) { + if err := a.validate(); err != nil { + return nil, err + } switch a.Type { default: return nil, fmt.Errorf("invalid type %s in %T", a.Type, a) @@ -176,6 +179,43 @@ func (a *AuthScheme) Accept(visitor AuthSchemeVisitor) error { } } +func (a *AuthScheme) validate() error { + if a == nil { + return fmt.Errorf("type %T is nil", a) + } + var fields []string + if a.Bearer != nil { + fields = append(fields, "bearer") + } + if a.Basic != nil { + fields = append(fields, "basic") + } + if a.Header != nil { + fields = append(fields, "header") + } + if len(fields) == 0 { + if a.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", a, a.Type) + } + return fmt.Errorf("type %T is empty", a) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", a, fields) + } + if a.Type != "" { + field := fields[0] + if a.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + a, + a.Type, + a, + ) + } + } + return nil +} + type AuthSchemesRequirement string const ( diff --git a/generators/go/internal/testdata/model/ir/fixtures/environment.go b/generators/go/internal/testdata/model/ir/fixtures/environment.go index b7b5c847051..ef0ff5187d2 100644 --- a/generators/go/internal/testdata/model/ir/fixtures/environment.go +++ b/generators/go/internal/testdata/model/ir/fixtures/environment.go @@ -125,6 +125,9 @@ func (e *Environments) UnmarshalJSON(data []byte) error { } func (e Environments) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -151,6 +154,40 @@ func (e *Environments) Accept(visitor EnvironmentsVisitor) error { } } +func (e *Environments) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.SingleBaseUrl != nil { + fields = append(fields, "singleBaseUrl") + } + if e.MultipleBaseUrls != nil { + fields = append(fields, "multipleBaseUrls") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type EnvironmentsConfig struct { DefaultEnvironment *EnvironmentId `json:"defaultEnvironment,omitempty" url:"defaultEnvironment,omitempty"` Environments *Environments `json:"environments,omitempty" url:"environments,omitempty"` diff --git a/generators/go/internal/testdata/model/ir/fixtures/http.go b/generators/go/internal/testdata/model/ir/fixtures/http.go index 8ae0647332e..ae26c962661 100644 --- a/generators/go/internal/testdata/model/ir/fixtures/http.go +++ b/generators/go/internal/testdata/model/ir/fixtures/http.go @@ -563,6 +563,9 @@ func (e *ExampleRequestBody) UnmarshalJSON(data []byte) error { } func (e ExampleRequestBody) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -589,6 +592,40 @@ func (e *ExampleRequestBody) Accept(visitor ExampleRequestBodyVisitor) error { } } +func (e *ExampleRequestBody) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.InlinedRequestBody != nil { + fields = append(fields, "inlinedRequestBody") + } + if e.Reference != nil { + fields = append(fields, "reference") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type ExampleResponse struct { Type string Ok *ExampleEndpointSuccessResponse @@ -653,6 +690,9 @@ func (e *ExampleResponse) UnmarshalJSON(data []byte) error { } func (e ExampleResponse) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -679,6 +719,40 @@ func (e *ExampleResponse) Accept(visitor ExampleResponseVisitor) error { } } +func (e *ExampleResponse) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Ok != nil { + fields = append(fields, "ok") + } + if e.Error != nil { + fields = append(fields, "error") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type FileDownloadResponse struct { Docs *string `json:"docs,omitempty" url:"docs,omitempty"` @@ -876,6 +950,9 @@ func (f *FileUploadRequestProperty) UnmarshalJSON(data []byte) error { } func (f FileUploadRequestProperty) MarshalJSON() ([]byte, error) { + if err := f.validate(); err != nil { + return nil, err + } switch f.Type { default: return nil, fmt.Errorf("invalid type %s in %T", f.Type, f) @@ -902,6 +979,40 @@ func (f *FileUploadRequestProperty) Accept(visitor FileUploadRequestPropertyVisi } } +func (f *FileUploadRequestProperty) validate() error { + if f == nil { + return fmt.Errorf("type %T is nil", f) + } + var fields []string + if f.File != nil { + fields = append(fields, "file") + } + if f.BodyProperty != nil { + fields = append(fields, "bodyProperty") + } + if len(fields) == 0 { + if f.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", f, f.Type) + } + return fmt.Errorf("type %T is empty", f) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", f, fields) + } + if f.Type != "" { + field := fields[0] + if f.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + f, + f.Type, + f, + ) + } + } + return nil +} + type HttpEndpoint struct { Docs *string `json:"docs,omitempty" url:"docs,omitempty"` Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` @@ -1363,6 +1474,9 @@ func (h *HttpRequestBody) UnmarshalJSON(data []byte) error { } func (h HttpRequestBody) MarshalJSON() ([]byte, error) { + if err := h.validate(); err != nil { + return nil, err + } switch h.Type { default: return nil, fmt.Errorf("invalid type %s in %T", h.Type, h) @@ -1394,6 +1508,43 @@ func (h *HttpRequestBody) Accept(visitor HttpRequestBodyVisitor) error { } } +func (h *HttpRequestBody) validate() error { + if h == nil { + return fmt.Errorf("type %T is nil", h) + } + var fields []string + if h.InlinedRequestBody != nil { + fields = append(fields, "inlinedRequestBody") + } + if h.Reference != nil { + fields = append(fields, "reference") + } + if h.FileUpload != nil { + fields = append(fields, "fileUpload") + } + if len(fields) == 0 { + if h.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", h, h.Type) + } + return fmt.Errorf("type %T is empty", h) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", h, fields) + } + if h.Type != "" { + field := fields[0] + if h.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + h, + h.Type, + h, + ) + } + } + return nil +} + type HttpRequestBodyReference struct { Docs *string `json:"docs,omitempty" url:"docs,omitempty"` RequestBodyType *TypeReference `json:"requestBodyType,omitempty" url:"requestBodyType,omitempty"` @@ -1505,6 +1656,9 @@ func (h *HttpResponse) UnmarshalJSON(data []byte) error { } func (h HttpResponse) MarshalJSON() ([]byte, error) { + if err := h.validate(); err != nil { + return nil, err + } switch h.Type { default: return nil, fmt.Errorf("invalid type %s in %T", h.Type, h) @@ -1531,6 +1685,40 @@ func (h *HttpResponse) Accept(visitor HttpResponseVisitor) error { } } +func (h *HttpResponse) validate() error { + if h == nil { + return fmt.Errorf("type %T is nil", h) + } + var fields []string + if h.Json != nil { + fields = append(fields, "json") + } + if h.FileDownload != nil { + fields = append(fields, "fileDownload") + } + if len(fields) == 0 { + if h.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", h, h.Type) + } + return fmt.Errorf("type %T is empty", h) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", h, fields) + } + if h.Type != "" { + field := fields[0] + if h.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + h, + h.Type, + h, + ) + } + } + return nil +} + type HttpService struct { Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` Name *DeclaredServiceName `json:"name,omitempty" url:"name,omitempty"` @@ -2157,6 +2345,9 @@ func (s *SdkRequestShape) UnmarshalJSON(data []byte) error { } func (s SdkRequestShape) MarshalJSON() ([]byte, error) { + if err := s.validate(); err != nil { + return nil, err + } switch s.Type { default: return nil, fmt.Errorf("invalid type %s in %T", s.Type, s) @@ -2183,6 +2374,40 @@ func (s *SdkRequestShape) Accept(visitor SdkRequestShapeVisitor) error { } } +func (s *SdkRequestShape) validate() error { + if s == nil { + return fmt.Errorf("type %T is nil", s) + } + var fields []string + if s.JustRequestBody != nil { + fields = append(fields, "justRequestBody") + } + if s.Wrapper != nil { + fields = append(fields, "wrapper") + } + if len(fields) == 0 { + if s.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", s, s.Type) + } + return fmt.Errorf("type %T is empty", s) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", s, fields) + } + if s.Type != "" { + field := fields[0] + if s.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + s, + s.Type, + s, + ) + } + } + return nil +} + type SdkRequestWrapper struct { WrapperName *Name `json:"wrapperName,omitempty" url:"wrapperName,omitempty"` BodyKey *Name `json:"bodyKey,omitempty" url:"bodyKey,omitempty"` @@ -2330,6 +2555,9 @@ func (s *SdkResponse) UnmarshalJSON(data []byte) error { } func (s SdkResponse) MarshalJSON() ([]byte, error) { + if err := s.validate(); err != nil { + return nil, err + } switch s.Type { default: return nil, fmt.Errorf("invalid type %s in %T", s.Type, s) @@ -2366,6 +2594,46 @@ func (s *SdkResponse) Accept(visitor SdkResponseVisitor) error { } } +func (s *SdkResponse) validate() error { + if s == nil { + return fmt.Errorf("type %T is nil", s) + } + var fields []string + if s.Json != nil { + fields = append(fields, "json") + } + if s.Streaming != nil { + fields = append(fields, "streaming") + } + if s.MaybeStreaming != nil { + fields = append(fields, "maybeStreaming") + } + if s.FileDownload != nil { + fields = append(fields, "fileDownload") + } + if len(fields) == 0 { + if s.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", s, s.Type) + } + return fmt.Errorf("type %T is empty", s) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", s, fields) + } + if s.Type != "" { + field := fields[0] + if s.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + s, + s.Type, + s, + ) + } + } + return nil +} + type StreamCondition struct { Type string // The name of a boolean query parameter. If it is true, the response @@ -2438,6 +2706,9 @@ func (s *StreamCondition) UnmarshalJSON(data []byte) error { } func (s StreamCondition) MarshalJSON() ([]byte, error) { + if err := s.validate(); err != nil { + return nil, err + } switch s.Type { default: return nil, fmt.Errorf("invalid type %s in %T", s.Type, s) @@ -2478,6 +2749,40 @@ func (s *StreamCondition) Accept(visitor StreamConditionVisitor) error { } } +func (s *StreamCondition) validate() error { + if s == nil { + return fmt.Errorf("type %T is nil", s) + } + var fields []string + if s.QueryParameterKey != "" { + fields = append(fields, "queryParameterKey") + } + if s.RequestPropertyKey != "" { + fields = append(fields, "requestPropertyKey") + } + if len(fields) == 0 { + if s.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", s, s.Type) + } + return fmt.Errorf("type %T is empty", s) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", s, fields) + } + if s.Type != "" { + field := fields[0] + if s.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + s, + s.Type, + s, + ) + } + } + return nil +} + type StreamingResponse struct { DataEventType *TypeReference `json:"dataEventType,omitempty" url:"dataEventType,omitempty"` Terminator *string `json:"terminator,omitempty" url:"terminator,omitempty"` diff --git a/generators/go/internal/testdata/model/ir/fixtures/ir.go b/generators/go/internal/testdata/model/ir/fixtures/ir.go index 339fc9f1cf1..f34440d16d4 100644 --- a/generators/go/internal/testdata/model/ir/fixtures/ir.go +++ b/generators/go/internal/testdata/model/ir/fixtures/ir.go @@ -119,6 +119,9 @@ func (e *ErrorDiscriminationStrategy) UnmarshalJSON(data []byte) error { } func (e ErrorDiscriminationStrategy) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -152,6 +155,40 @@ func (e *ErrorDiscriminationStrategy) Accept(visitor ErrorDiscriminationStrategy } } +func (e *ErrorDiscriminationStrategy) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.StatusCode != nil { + fields = append(fields, "statusCode") + } + if e.Property != nil { + fields = append(fields, "property") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + // Complete representation of the API schema type IntermediateRepresentation struct { // This is the human readable unique id for the API. diff --git a/generators/go/internal/testdata/model/ir/fixtures/types.go b/generators/go/internal/testdata/model/ir/fixtures/types.go index 189af23c818..1bfaaa55936 100644 --- a/generators/go/internal/testdata/model/ir/fixtures/types.go +++ b/generators/go/internal/testdata/model/ir/fixtures/types.go @@ -200,6 +200,9 @@ func (e *ErrorDeclarationDiscriminantValue) UnmarshalJSON(data []byte) error { } func (e ErrorDeclarationDiscriminantValue) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -233,6 +236,40 @@ func (e *ErrorDeclarationDiscriminantValue) Accept(visitor ErrorDeclarationDiscr } } +func (e *ErrorDeclarationDiscriminantValue) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Property != nil { + fields = append(fields, "property") + } + if e.StatusCode != nil { + fields = append(fields, "statusCode") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type AliasTypeDeclaration struct { AliasOf *TypeReference `json:"aliasOf,omitempty" url:"aliasOf,omitempty"` ResolvedType *ResolvedTypeReference `json:"resolvedType,omitempty" url:"resolvedType,omitempty"` @@ -406,6 +443,9 @@ func (c *ContainerType) UnmarshalJSON(data []byte) error { } func (c ContainerType) MarshalJSON() ([]byte, error) { + if err := c.validate(); err != nil { + return nil, err + } switch c.Type { default: return nil, fmt.Errorf("invalid type %s in %T", c.Type, c) @@ -475,6 +515,49 @@ func (c *ContainerType) Accept(visitor ContainerTypeVisitor) error { } } +func (c *ContainerType) validate() error { + if c == nil { + return fmt.Errorf("type %T is nil", c) + } + var fields []string + if c.List != nil { + fields = append(fields, "list") + } + if c.Map != nil { + fields = append(fields, "map") + } + if c.Optional != nil { + fields = append(fields, "optional") + } + if c.Set != nil { + fields = append(fields, "set") + } + if c.Literal != nil { + fields = append(fields, "literal") + } + if len(fields) == 0 { + if c.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", c, c.Type) + } + return fmt.Errorf("type %T is empty", c) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", c, fields) + } + if c.Type != "" { + field := fields[0] + if c.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + c, + c.Type, + c, + ) + } + } + return nil +} + type DeclaredTypeName struct { TypeId TypeId `json:"typeId" url:"typeId"` FernFilepath *FernFilepath `json:"fernFilepath,omitempty" url:"fernFilepath,omitempty"` @@ -771,6 +854,9 @@ func (e *ExampleContainer) UnmarshalJSON(data []byte) error { } func (e ExampleContainer) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -835,6 +921,46 @@ func (e *ExampleContainer) Accept(visitor ExampleContainerVisitor) error { } } +func (e *ExampleContainer) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.List != nil { + fields = append(fields, "list") + } + if e.Set != nil { + fields = append(fields, "set") + } + if e.Optional != nil { + fields = append(fields, "optional") + } + if e.Map != nil { + fields = append(fields, "map") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type ExampleEnumType struct { WireValue string `json:"wireValue" url:"wireValue"` @@ -1166,7 +1292,7 @@ func (e *ExamplePrimitive) GetDate() time.Time { func (e *ExamplePrimitive) GetUuid() uuid.UUID { if e == nil { - return uuid.UUID{} + return uuid.Nil } return e.Uuid } @@ -1252,6 +1378,9 @@ func (e *ExamplePrimitive) UnmarshalJSON(data []byte) error { } func (e ExamplePrimitive) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -1364,6 +1493,58 @@ func (e *ExamplePrimitive) Accept(visitor ExamplePrimitiveVisitor) error { } } +func (e *ExamplePrimitive) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Integer != 0 { + fields = append(fields, "integer") + } + if e.Double != 0 { + fields = append(fields, "double") + } + if e.String != "" { + fields = append(fields, "string") + } + if e.Boolean != false { + fields = append(fields, "boolean") + } + if e.Long != 0 { + fields = append(fields, "long") + } + if !e.Datetime.IsZero() { + fields = append(fields, "datetime") + } + if !e.Date.IsZero() { + fields = append(fields, "date") + } + if e.Uuid != uuid.Nil { + fields = append(fields, "uuid") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type ExampleSingleUnionType struct { WireDiscriminantValue string `json:"wireDiscriminantValue" url:"wireDiscriminantValue"` Properties *ExampleSingleUnionTypeProperties `json:"properties,omitempty" url:"properties,omitempty"` @@ -1493,6 +1674,9 @@ func (e *ExampleSingleUnionTypeProperties) UnmarshalJSON(data []byte) error { } func (e ExampleSingleUnionTypeProperties) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -1531,6 +1715,43 @@ func (e *ExampleSingleUnionTypeProperties) Accept(visitor ExampleSingleUnionType } } +func (e *ExampleSingleUnionTypeProperties) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.SamePropertiesAsObject != nil { + fields = append(fields, "samePropertiesAsObject") + } + if e.SingleProperty != nil { + fields = append(fields, "singleProperty") + } + if e.NoProperties != nil { + fields = append(fields, "noProperties") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type ExampleType struct { JsonExample interface{} `json:"jsonExample,omitempty" url:"jsonExample,omitempty"` Docs *string `json:"docs,omitempty" url:"docs,omitempty"` @@ -1747,6 +1968,9 @@ func (e *ExampleTypeReferenceShape) UnmarshalJSON(data []byte) error { } func (e ExampleTypeReferenceShape) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -1804,6 +2028,46 @@ func (e *ExampleTypeReferenceShape) Accept(visitor ExampleTypeReferenceShapeVisi } } +func (e *ExampleTypeReferenceShape) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Primitive != nil { + fields = append(fields, "primitive") + } + if e.Container != nil { + fields = append(fields, "container") + } + if e.Unknown != nil { + fields = append(fields, "unknown") + } + if e.Named != nil { + fields = append(fields, "named") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type ExampleTypeShape struct { Type string Alias *ExampleAliasType @@ -1904,6 +2168,9 @@ func (e *ExampleTypeShape) UnmarshalJSON(data []byte) error { } func (e ExampleTypeShape) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -1940,6 +2207,46 @@ func (e *ExampleTypeShape) Accept(visitor ExampleTypeShapeVisitor) error { } } +func (e *ExampleTypeShape) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Alias != nil { + fields = append(fields, "alias") + } + if e.Enum != nil { + fields = append(fields, "enum") + } + if e.Object != nil { + fields = append(fields, "object") + } + if e.Union != nil { + fields = append(fields, "union") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type Literal struct { Type string String string @@ -1988,6 +2295,9 @@ func (l *Literal) UnmarshalJSON(data []byte) error { } func (l Literal) MarshalJSON() ([]byte, error) { + if err := l.validate(); err != nil { + return nil, err + } switch l.Type { default: return nil, fmt.Errorf("invalid type %s in %T", l.Type, l) @@ -2016,6 +2326,37 @@ func (l *Literal) Accept(visitor LiteralVisitor) error { } } +func (l *Literal) validate() error { + if l == nil { + return fmt.Errorf("type %T is nil", l) + } + var fields []string + if l.String != "" { + fields = append(fields, "string") + } + if len(fields) == 0 { + if l.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", l, l.Type) + } + return fmt.Errorf("type %T is empty", l) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", l, fields) + } + if l.Type != "" { + field := fields[0] + if l.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + l, + l.Type, + l, + ) + } + } + return nil +} + type MapType struct { KeyType *TypeReference `json:"keyType,omitempty" url:"keyType,omitempty"` ValueType *TypeReference `json:"valueType,omitempty" url:"valueType,omitempty"` @@ -2369,6 +2710,9 @@ func (r *ResolvedTypeReference) UnmarshalJSON(data []byte) error { } func (r ResolvedTypeReference) MarshalJSON() ([]byte, error) { + if err := r.validate(); err != nil { + return nil, err + } switch r.Type { default: return nil, fmt.Errorf("invalid type %s in %T", r.Type, r) @@ -2426,6 +2770,46 @@ func (r *ResolvedTypeReference) Accept(visitor ResolvedTypeReferenceVisitor) err } } +func (r *ResolvedTypeReference) validate() error { + if r == nil { + return fmt.Errorf("type %T is nil", r) + } + var fields []string + if r.Container != nil { + fields = append(fields, "container") + } + if r.Named != nil { + fields = append(fields, "named") + } + if r.Primitive != "" { + fields = append(fields, "primitive") + } + if r.Unknown != nil { + fields = append(fields, "unknown") + } + if len(fields) == 0 { + if r.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", r, r.Type) + } + return fmt.Errorf("type %T is empty", r) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", r, fields) + } + if r.Type != "" { + field := fields[0] + if r.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + r, + r.Type, + r, + ) + } + } + return nil +} + type ShapeType string const ( @@ -2591,6 +2975,9 @@ func (s *SingleUnionTypeProperties) UnmarshalJSON(data []byte) error { } func (s SingleUnionTypeProperties) MarshalJSON() ([]byte, error) { + if err := s.validate(); err != nil { + return nil, err + } switch s.PropertiesType { default: return nil, fmt.Errorf("invalid type %s in %T", s.PropertiesType, s) @@ -2629,6 +3016,43 @@ func (s *SingleUnionTypeProperties) Accept(visitor SingleUnionTypePropertiesVisi } } +func (s *SingleUnionTypeProperties) validate() error { + if s == nil { + return fmt.Errorf("type %T is nil", s) + } + var fields []string + if s.SamePropertiesAsObject != nil { + fields = append(fields, "samePropertiesAsObject") + } + if s.SingleProperty != nil { + fields = append(fields, "singleProperty") + } + if s.NoProperties != nil { + fields = append(fields, "noProperties") + } + if len(fields) == 0 { + if s.PropertiesType != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", s, s.PropertiesType) + } + return fmt.Errorf("type %T is empty", s) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", s, fields) + } + if s.PropertiesType != "" { + field := fields[0] + if s.PropertiesType != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + s, + s.PropertiesType, + s, + ) + } + } + return nil +} + type SingleUnionTypeProperty struct { Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` Type *TypeReference `json:"type,omitempty" url:"type,omitempty"` @@ -2794,6 +3218,9 @@ func (t *Type) UnmarshalJSON(data []byte) error { } func (t Type) MarshalJSON() ([]byte, error) { + if err := t.validate(); err != nil { + return nil, err + } switch t.Type { default: return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) @@ -2835,6 +3262,49 @@ func (t *Type) Accept(visitor TypeVisitor) error { } } +func (t *Type) validate() error { + if t == nil { + return fmt.Errorf("type %T is nil", t) + } + var fields []string + if t.Alias != nil { + fields = append(fields, "alias") + } + if t.Enum != nil { + fields = append(fields, "enum") + } + if t.Object != nil { + fields = append(fields, "object") + } + if t.Union != nil { + fields = append(fields, "union") + } + if t.UndiscriminatedUnion != nil { + fields = append(fields, "undiscriminatedUnion") + } + if len(fields) == 0 { + if t.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", t, t.Type) + } + return fmt.Errorf("type %T is empty", t) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", t, fields) + } + if t.Type != "" { + field := fields[0] + if t.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + t, + t.Type, + t, + ) + } + } + return nil +} + // A type, which is a name and a shape type TypeDeclaration struct { Docs *string `json:"docs,omitempty" url:"docs,omitempty"` @@ -3020,6 +3490,9 @@ func (t *TypeReference) UnmarshalJSON(data []byte) error { } func (t TypeReference) MarshalJSON() ([]byte, error) { + if err := t.validate(); err != nil { + return nil, err + } switch t.Type { default: return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) @@ -3077,6 +3550,46 @@ func (t *TypeReference) Accept(visitor TypeReferenceVisitor) error { } } +func (t *TypeReference) validate() error { + if t == nil { + return fmt.Errorf("type %T is nil", t) + } + var fields []string + if t.Container != nil { + fields = append(fields, "container") + } + if t.Named != nil { + fields = append(fields, "named") + } + if t.Primitive != "" { + fields = append(fields, "primitive") + } + if t.Unknown != nil { + fields = append(fields, "unknown") + } + if len(fields) == 0 { + if t.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", t, t.Type) + } + return fmt.Errorf("type %T is empty", t) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", t, fields) + } + if t.Type != "" { + field := fields[0] + if t.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + t, + t.Type, + t, + ) + } + } + return nil +} + type UndiscriminatedUnionMember struct { Docs *string `json:"docs,omitempty" url:"docs,omitempty"` Type *TypeReference `json:"type,omitempty" url:"type,omitempty"` diff --git a/generators/go/internal/testdata/model/packages/fixtures/union.go b/generators/go/internal/testdata/model/packages/fixtures/union.go index 310c849577f..c1cfe95094a 100644 --- a/generators/go/internal/testdata/model/packages/fixtures/union.go +++ b/generators/go/internal/testdata/model/packages/fixtures/union.go @@ -113,6 +113,9 @@ func (u *Union) UnmarshalJSON(data []byte) error { } func (u Union) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -162,3 +165,43 @@ func (u *Union) Accept(visitor UnionVisitor) error { return visitor.VisitAnotherBar(u.AnotherBar) } } + +func (u *Union) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Value != nil { + fields = append(fields, "value") + } + if u.AnotherValue != nil { + fields = append(fields, "anotherValue") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if u.AnotherBar != nil { + fields = append(fields, "anotherBar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} diff --git a/generators/go/internal/testdata/model/primitive/fixtures/imdb.go b/generators/go/internal/testdata/model/primitive/fixtures/imdb.go index 477143f7d82..dd0da96a56f 100644 --- a/generators/go/internal/testdata/model/primitive/fixtures/imdb.go +++ b/generators/go/internal/testdata/model/primitive/fixtures/imdb.go @@ -75,7 +75,7 @@ func (t *Type) GetSeven() time.Time { func (t *Type) GetEight() uuid.UUID { if t == nil { - return uuid.UUID{} + return uuid.Nil } return t.Eight } diff --git a/generators/go/internal/testdata/model/sum/fixtures/foo.go b/generators/go/internal/testdata/model/sum/fixtures/foo.go index 1e33de60141..bdf27a0693e 100644 --- a/generators/go/internal/testdata/model/sum/fixtures/foo.go +++ b/generators/go/internal/testdata/model/sum/fixtures/foo.go @@ -34,7 +34,7 @@ func (f *Foo) GetBar() *bar.Bar { func (f *Foo) GetUuid() uuid.UUID { if f == nil { - return uuid.UUID{} + return uuid.Nil } return f.Uuid } diff --git a/generators/go/internal/testdata/model/union/fixtures/imdb.go b/generators/go/internal/testdata/model/union/fixtures/imdb.go index b3278e2f175..72935a39823 100644 --- a/generators/go/internal/testdata/model/union/fixtures/imdb.go +++ b/generators/go/internal/testdata/model/union/fixtures/imdb.go @@ -213,6 +213,9 @@ func (u *Union) UnmarshalJSON(data []byte) error { } func (u Union) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -253,6 +256,40 @@ func (u *Union) Accept(visitor UnionVisitor) error { } } +func (u *Union) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithDiscriminant struct { Type string // This is a Foo field. @@ -322,6 +359,9 @@ func (u *UnionWithDiscriminant) UnmarshalJSON(data []byte) error { } func (u UnionWithDiscriminant) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -362,6 +402,40 @@ func (u *UnionWithDiscriminant) Accept(visitor UnionWithDiscriminantVisitor) err } } +func (u *UnionWithDiscriminant) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithLiteral struct { Type string fern string @@ -439,6 +513,9 @@ func (u *UnionWithLiteral) UnmarshalJSON(data []byte) error { } func (u UnionWithLiteral) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -471,6 +548,37 @@ func (u *UnionWithLiteral) Accept(visitor UnionWithLiteralVisitor) error { } } +func (u *UnionWithLiteral) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.fern != "" { + fields = append(fields, "fern") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithOptionalTime struct { Type string Date *time.Time @@ -539,6 +647,9 @@ func (u *UnionWithOptionalTime) UnmarshalJSON(data []byte) error { } func (u UnionWithOptionalTime) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -579,6 +690,40 @@ func (u *UnionWithOptionalTime) Accept(visitor UnionWithOptionalTimeVisitor) err } } +func (u *UnionWithOptionalTime) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Date != nil { + fields = append(fields, "date") + } + if u.Dateimte != nil { + fields = append(fields, "dateimte") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithPrimitive struct { Type string Boolean bool @@ -647,6 +792,9 @@ func (u *UnionWithPrimitive) UnmarshalJSON(data []byte) error { } func (u UnionWithPrimitive) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -687,6 +835,40 @@ func (u *UnionWithPrimitive) Accept(visitor UnionWithPrimitiveVisitor) error { } } +func (u *UnionWithPrimitive) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Boolean != false { + fields = append(fields, "boolean") + } + if u.String != "" { + fields = append(fields, "string") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithTime struct { Type string Value int @@ -775,6 +957,9 @@ func (u *UnionWithTime) UnmarshalJSON(data []byte) error { } func (u UnionWithTime) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -827,6 +1012,43 @@ func (u *UnionWithTime) Accept(visitor UnionWithTimeVisitor) error { } } +func (u *UnionWithTime) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Value != 0 { + fields = append(fields, "value") + } + if !u.Date.IsZero() { + fields = append(fields, "date") + } + if !u.Datetime.IsZero() { + fields = append(fields, "datetime") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithUnknown struct { Type string Foo *Foo @@ -891,6 +1113,9 @@ func (u *UnionWithUnknown) UnmarshalJSON(data []byte) error { } func (u UnionWithUnknown) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -924,6 +1149,40 @@ func (u *UnionWithUnknown) Accept(visitor UnionWithUnknownVisitor) error { } } +func (u *UnionWithUnknown) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Unknown != nil { + fields = append(fields, "unknown") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithoutKey struct { Type string Foo *Foo @@ -989,6 +1248,9 @@ func (u *UnionWithoutKey) UnmarshalJSON(data []byte) error { } func (u UnionWithoutKey) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -1014,3 +1276,37 @@ func (u *UnionWithoutKey) Accept(visitor UnionWithoutKeyVisitor) error { return visitor.VisitBar(u.Bar) } } + +func (u *UnionWithoutKey) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} diff --git a/generators/go/internal/testdata/sdk/packages/fixtures/organization/metrics/tag.go b/generators/go/internal/testdata/sdk/packages/fixtures/organization/metrics/tag.go index cf7d7efa384..81a70acd72b 100644 --- a/generators/go/internal/testdata/sdk/packages/fixtures/organization/metrics/tag.go +++ b/generators/go/internal/testdata/sdk/packages/fixtures/organization/metrics/tag.go @@ -95,6 +95,9 @@ func (t *Tag) UnmarshalJSON(data []byte) error { } func (t Tag) MarshalJSON() ([]byte, error) { + if err := t.validate(); err != nil { + return nil, err + } switch t.Type { default: return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) @@ -146,3 +149,40 @@ func (t *Tag) Accept(visitor TagVisitor) error { return visitor.VisitBoolean(t.Boolean) } } + +func (t *Tag) validate() error { + if t == nil { + return fmt.Errorf("type %T is nil", t) + } + var fields []string + if t.Number != 0 { + fields = append(fields, "number") + } + if t.String != "" { + fields = append(fields, "string") + } + if t.Boolean != false { + fields = append(fields, "boolean") + } + if len(fields) == 0 { + if t.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", t, t.Type) + } + return fmt.Errorf("type %T is empty", t) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", t, fields) + } + if t.Type != "" { + field := fields[0] + if t.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + t, + t.Type, + t, + ) + } + } + return nil +} diff --git a/generators/go/internal/testdata/sdk/post-with-path-params-generics/fixtures/user.go b/generators/go/internal/testdata/sdk/post-with-path-params-generics/fixtures/user.go index 4a8639e08f9..393e6030663 100644 --- a/generators/go/internal/testdata/sdk/post-with-path-params-generics/fixtures/user.go +++ b/generators/go/internal/testdata/sdk/post-with-path-params-generics/fixtures/user.go @@ -336,6 +336,9 @@ func (u *Union) UnmarshalJSON(data []byte) error { } func (u Union) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -362,6 +365,40 @@ func (u *Union) Accept(visitor UnionVisitor) error { } } +func (u *Union) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UpdateRequest struct { Tag string `json:"-" url:"tag"` Extra *string `json:"-" url:"extra,omitempty"` diff --git a/generators/go/internal/testdata/sdk/post-with-path-params/fixtures/user.go b/generators/go/internal/testdata/sdk/post-with-path-params/fixtures/user.go index e32f7b4257b..5d5bc21e1cc 100644 --- a/generators/go/internal/testdata/sdk/post-with-path-params/fixtures/user.go +++ b/generators/go/internal/testdata/sdk/post-with-path-params/fixtures/user.go @@ -362,6 +362,9 @@ func (u *Union) UnmarshalJSON(data []byte) error { } func (u Union) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -388,6 +391,40 @@ func (u *Union) Accept(visitor UnionVisitor) error { } } +func (u *Union) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UpdateRequest struct { Tag string `json:"-" url:"tag"` Extra *string `json:"-" url:"extra,omitempty"` diff --git a/generators/go/sdk/versions.yml b/generators/go/sdk/versions.yml index 9ce98490dc0..61aa5c667e7 100644 --- a/generators/go/sdk/versions.yml +++ b/generators/go/sdk/versions.yml @@ -1,3 +1,32 @@ +- version: 0.35.0 + changelogEntry: + - type: feat + summary: >- + Add runtime validation for discriminated unions to prevent users from accidentally + sending the wrong type of value. With this, users will be expected to set exactly + one of the union's values like so: + + ```go + package example + + type Animal struct { + Type string + Cat *Cat + Dog *Dog + } + + func do() { + union := &Animal{ + Cat: &Cat{ + Name: "Fluffy", + }, + } + } + ``` + + If the user sets _both_ `Cat` and `Dog`, the user will receive an error when the + type is serialized to JSON (i.e. in the `json.Marshaler` implementation). + irVersion: 53 - version: 0.34.0 changelogEntry: - type: feat diff --git a/seed/go-fiber/audiences/folderc/common.go b/seed/go-fiber/audiences/folderc/common.go index 378d7148e96..7dacbb2c4db 100644 --- a/seed/go-fiber/audiences/folderc/common.go +++ b/seed/go-fiber/audiences/folderc/common.go @@ -17,7 +17,7 @@ type FolderCFoo struct { func (f *FolderCFoo) GetBarProperty() uuid.UUID { if f == nil { - return uuid.UUID{} + return uuid.Nil } return f.BarProperty } diff --git a/seed/go-fiber/circular-references-advanced/ast.go b/seed/go-fiber/circular-references-advanced/ast.go index 78628f27a50..4bf2c09126d 100644 --- a/seed/go-fiber/circular-references-advanced/ast.go +++ b/seed/go-fiber/circular-references-advanced/ast.go @@ -76,6 +76,9 @@ func (c *ContainerValue) UnmarshalJSON(data []byte) error { } func (c ContainerValue) MarshalJSON() ([]byte, error) { + if err := c.validate(); err != nil { + return nil, err + } switch c.Type { default: return nil, fmt.Errorf("invalid type %s in %T", c.Type, c) @@ -116,6 +119,40 @@ func (c *ContainerValue) Accept(visitor ContainerValueVisitor) error { } } +func (c *ContainerValue) validate() error { + if c == nil { + return fmt.Errorf("type %T is nil", c) + } + var fields []string + if c.List != nil { + fields = append(fields, "list") + } + if c.Optional != nil { + fields = append(fields, "optional") + } + if len(fields) == 0 { + if c.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", c, c.Type) + } + return fmt.Errorf("type %T is empty", c) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", c, fields) + } + if c.Type != "" { + field := fields[0] + if c.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + c, + c.Type, + c, + ) + } + } + return nil +} + type FieldName = string type FieldValue struct { @@ -204,6 +241,9 @@ func (f *FieldValue) UnmarshalJSON(data []byte) error { } func (f FieldValue) MarshalJSON() ([]byte, error) { + if err := f.validate(); err != nil { + return nil, err + } switch f.Type { default: return nil, fmt.Errorf("invalid type %s in %T", f.Type, f) @@ -249,6 +289,43 @@ func (f *FieldValue) Accept(visitor FieldValueVisitor) error { } } +func (f *FieldValue) validate() error { + if f == nil { + return fmt.Errorf("type %T is nil", f) + } + var fields []string + if f.PrimitiveValue != "" { + fields = append(fields, "primitive_value") + } + if f.ObjectValue != nil { + fields = append(fields, "object_value") + } + if f.ContainerValue != nil { + fields = append(fields, "container_value") + } + if len(fields) == 0 { + if f.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", f, f.Type) + } + return fmt.Errorf("type %T is empty", f) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", f, fields) + } + if f.Type != "" { + field := fields[0] + if f.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + f, + f.Type, + f, + ) + } + } + return nil +} + // This type allows us to test a circular reference with a union type (see FieldValue). type ObjectFieldValue struct { Name FieldName `json:"name" url:"name"` diff --git a/seed/go-fiber/circular-references/ast.go b/seed/go-fiber/circular-references/ast.go index 2ec37d5d03f..6d209b54f88 100644 --- a/seed/go-fiber/circular-references/ast.go +++ b/seed/go-fiber/circular-references/ast.go @@ -76,6 +76,9 @@ func (c *ContainerValue) UnmarshalJSON(data []byte) error { } func (c ContainerValue) MarshalJSON() ([]byte, error) { + if err := c.validate(); err != nil { + return nil, err + } switch c.Type { default: return nil, fmt.Errorf("invalid type %s in %T", c.Type, c) @@ -116,6 +119,40 @@ func (c *ContainerValue) Accept(visitor ContainerValueVisitor) error { } } +func (c *ContainerValue) validate() error { + if c == nil { + return fmt.Errorf("type %T is nil", c) + } + var fields []string + if c.List != nil { + fields = append(fields, "list") + } + if c.Optional != nil { + fields = append(fields, "optional") + } + if len(fields) == 0 { + if c.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", c, c.Type) + } + return fmt.Errorf("type %T is empty", c) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", c, fields) + } + if c.Type != "" { + field := fields[0] + if c.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + c, + c.Type, + c, + ) + } + } + return nil +} + type FieldValue struct { Type string PrimitiveValue PrimitiveValue @@ -202,6 +239,9 @@ func (f *FieldValue) UnmarshalJSON(data []byte) error { } func (f FieldValue) MarshalJSON() ([]byte, error) { + if err := f.validate(); err != nil { + return nil, err + } switch f.Type { default: return nil, fmt.Errorf("invalid type %s in %T", f.Type, f) @@ -247,6 +287,43 @@ func (f *FieldValue) Accept(visitor FieldValueVisitor) error { } } +func (f *FieldValue) validate() error { + if f == nil { + return fmt.Errorf("type %T is nil", f) + } + var fields []string + if f.PrimitiveValue != "" { + fields = append(fields, "primitive_value") + } + if f.ObjectValue != nil { + fields = append(fields, "object_value") + } + if f.ContainerValue != nil { + fields = append(fields, "container_value") + } + if len(fields) == 0 { + if f.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", f, f.Type) + } + return fmt.Errorf("type %T is empty", f) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", f, fields) + } + if f.Type != "" { + field := fields[0] + if f.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + f, + f.Type, + f, + ) + } + } + return nil +} + type JsonLike struct { JsonLikeList []*JsonLike StringJsonLikeMap map[string]*JsonLike diff --git a/seed/go-fiber/cross-package-type-names/folderc/common.go b/seed/go-fiber/cross-package-type-names/folderc/common.go index d6f963cbf1c..b1b4c7c0bbf 100644 --- a/seed/go-fiber/cross-package-type-names/folderc/common.go +++ b/seed/go-fiber/cross-package-type-names/folderc/common.go @@ -17,7 +17,7 @@ type Foo struct { func (f *Foo) GetBarProperty() uuid.UUID { if f == nil { - return uuid.UUID{} + return uuid.Nil } return f.BarProperty } diff --git a/seed/go-fiber/examples/commons/types.go b/seed/go-fiber/examples/commons/types.go index 9dc532153a4..e5aa4d8fdbb 100644 --- a/seed/go-fiber/examples/commons/types.go +++ b/seed/go-fiber/examples/commons/types.go @@ -76,6 +76,9 @@ func (d *Data) UnmarshalJSON(data []byte) error { } func (d Data) MarshalJSON() ([]byte, error) { + if err := d.validate(); err != nil { + return nil, err + } switch d.Type { default: return nil, fmt.Errorf("invalid type %s in %T", d.Type, d) @@ -116,6 +119,40 @@ func (d *Data) Accept(visitor DataVisitor) error { } } +func (d *Data) validate() error { + if d == nil { + return fmt.Errorf("type %T is nil", d) + } + var fields []string + if d.String != "" { + fields = append(fields, "string") + } + if d.Base64 != nil { + fields = append(fields, "base64") + } + if len(fields) == 0 { + if d.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", d, d.Type) + } + return fmt.Errorf("type %T is empty", d) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", d, fields) + } + if d.Type != "" { + field := fields[0] + if d.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + d, + d.Type, + d, + ) + } + } + return nil +} + type EventInfo struct { Type string Metadata *Metadata @@ -182,6 +219,9 @@ func (e *EventInfo) UnmarshalJSON(data []byte) error { } func (e EventInfo) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -215,6 +255,40 @@ func (e *EventInfo) Accept(visitor EventInfoVisitor) error { } } +func (e *EventInfo) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Metadata != nil { + fields = append(fields, "metadata") + } + if e.Tag != "" { + fields = append(fields, "tag") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type Metadata struct { Id string `json:"id" url:"id"` Data map[string]string `json:"data,omitempty" url:"data,omitempty"` diff --git a/seed/go-fiber/examples/types.go b/seed/go-fiber/examples/types.go index e710faed33e..45dc319f94c 100644 --- a/seed/go-fiber/examples/types.go +++ b/seed/go-fiber/examples/types.go @@ -673,6 +673,9 @@ func (e *Exception) UnmarshalJSON(data []byte) error { } func (e Exception) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -706,6 +709,40 @@ func (e *Exception) Accept(visitor ExceptionVisitor) error { } } +func (e *Exception) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Generic != nil { + fields = append(fields, "generic") + } + if e.Timeout != nil { + fields = append(fields, "timeout") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type ExceptionInfo struct { ExceptionType string `json:"exceptionType" url:"exceptionType"` ExceptionMessage string `json:"exceptionMessage" url:"exceptionMessage"` @@ -1034,6 +1071,9 @@ func (m *Metadata) UnmarshalJSON(data []byte) error { } func (m Metadata) MarshalJSON() ([]byte, error) { + if err := m.validate(); err != nil { + return nil, err + } switch m.Type { default: return nil, fmt.Errorf("invalid type %s in %T", m.Type, m) @@ -1082,6 +1122,40 @@ func (m *Metadata) Accept(visitor MetadataVisitor) error { } } +func (m *Metadata) validate() error { + if m == nil { + return fmt.Errorf("type %T is nil", m) + } + var fields []string + if m.Html != "" { + fields = append(fields, "html") + } + if m.Markdown != "" { + fields = append(fields, "markdown") + } + if len(fields) == 0 { + if m.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", m, m.Type) + } + return fmt.Errorf("type %T is empty", m) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", m, fields) + } + if m.Type != "" { + field := fields[0] + if m.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + m, + m.Type, + m, + ) + } + } + return nil +} + type Migration struct { Name string `json:"name" url:"name"` Status MigrationStatus `json:"status" url:"status"` @@ -1166,7 +1240,7 @@ type Moment struct { func (m *Moment) GetId() uuid.UUID { if m == nil { - return uuid.UUID{} + return uuid.Nil } return m.Id } @@ -1660,6 +1734,9 @@ func (t *Test) UnmarshalJSON(data []byte) error { } func (t Test) MarshalJSON() ([]byte, error) { + if err := t.validate(); err != nil { + return nil, err + } switch t.Type { default: return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) @@ -1700,6 +1777,40 @@ func (t *Test) Accept(visitor TestVisitor) error { } } +func (t *Test) validate() error { + if t == nil { + return fmt.Errorf("type %T is nil", t) + } + var fields []string + if t.And != false { + fields = append(fields, "and") + } + if t.Or != false { + fields = append(fields, "or") + } + if len(fields) == 0 { + if t.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", t, t.Type) + } + return fmt.Errorf("type %T is empty", t) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", t, fields) + } + if t.Type != "" { + field := fields[0] + if t.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + t, + t.Type, + t, + ) + } + } + return nil +} + type Tree struct { Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` diff --git a/seed/go-fiber/exhaustive/types/union.go b/seed/go-fiber/exhaustive/types/union.go index 8e64ae2d59a..705a1627d11 100644 --- a/seed/go-fiber/exhaustive/types/union.go +++ b/seed/go-fiber/exhaustive/types/union.go @@ -72,6 +72,9 @@ func (a *Animal) UnmarshalJSON(data []byte) error { } func (a Animal) MarshalJSON() ([]byte, error) { + if err := a.validate(); err != nil { + return nil, err + } switch a.Animal { default: return nil, fmt.Errorf("invalid type %s in %T", a.Animal, a) @@ -98,6 +101,40 @@ func (a *Animal) Accept(visitor AnimalVisitor) error { } } +func (a *Animal) validate() error { + if a == nil { + return fmt.Errorf("type %T is nil", a) + } + var fields []string + if a.Dog != nil { + fields = append(fields, "dog") + } + if a.Cat != nil { + fields = append(fields, "cat") + } + if len(fields) == 0 { + if a.Animal != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", a, a.Animal) + } + return fmt.Errorf("type %T is empty", a) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", a, fields) + } + if a.Animal != "" { + field := fields[0] + if a.Animal != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + a, + a.Animal, + a, + ) + } + } + return nil +} + type Cat struct { Name string `json:"name" url:"name"` LikesToMeow bool `json:"likesToMeow" url:"likesToMeow"` diff --git a/seed/go-fiber/mixed-case/service.go b/seed/go-fiber/mixed-case/service.go index 9aec9e16833..5e829516b9e 100644 --- a/seed/go-fiber/mixed-case/service.go +++ b/seed/go-fiber/mixed-case/service.go @@ -174,6 +174,9 @@ func (r *Resource) UnmarshalJSON(data []byte) error { } func (r Resource) MarshalJSON() ([]byte, error) { + if err := r.validate(); err != nil { + return nil, err + } switch r.ResourceType { default: return nil, fmt.Errorf("invalid type %s in %T", r.ResourceType, r) @@ -200,6 +203,40 @@ func (r *Resource) Accept(visitor ResourceVisitor) error { } } +func (r *Resource) validate() error { + if r == nil { + return fmt.Errorf("type %T is nil", r) + } + var fields []string + if r.User != nil { + fields = append(fields, "user") + } + if r.Organization != nil { + fields = append(fields, "Organization") + } + if len(fields) == 0 { + if r.ResourceType != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", r, r.ResourceType) + } + return fmt.Errorf("type %T is empty", r) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", r, fields) + } + if r.ResourceType != "" { + field := fields[0] + if r.ResourceType != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + r, + r.ResourceType, + r, + ) + } + } + return nil +} + type ResourceStatus string const ( diff --git a/seed/go-fiber/object/types.go b/seed/go-fiber/object/types.go index d90f171fe0e..28a5af9c296 100644 --- a/seed/go-fiber/object/types.go +++ b/seed/go-fiber/object/types.go @@ -137,7 +137,7 @@ func (t *Type) GetSeven() time.Time { func (t *Type) GetEight() uuid.UUID { if t == nil { - return uuid.UUID{} + return uuid.Nil } return t.Eight } diff --git a/seed/go-fiber/pagination/.mock/definition/users.yml b/seed/go-fiber/pagination/.mock/definition/users.yml index ecde10ef707..ddc0bc1848b 100644 --- a/seed/go-fiber/pagination/.mock/definition/users.yml +++ b/seed/go-fiber/pagination/.mock/definition/users.yml @@ -1,9 +1,9 @@ imports: root: __package__.yml -types: - Order: - enum: +types: + Order: + enum: - asc - desc @@ -15,8 +15,8 @@ types: properties: cursor: optional - UserListContainer: - properties: + UserListContainer: + properties: users: list UserPage: @@ -24,60 +24,59 @@ types: data: UserListContainer next: optional - UserOptionalListContainer: - properties: + UserOptionalListContainer: + properties: users: optional> UserOptionalListPage: properties: data: UserOptionalListContainer next: optional - UsernameContainer: properties: results: list - ListUsersExtendedResponse: + ListUsersExtendedResponse: extends: - UserPage properties: - total_count: + total_count: type: integer docs: The totall number of /users - ListUsersExtendedOptionalListResponse: + ListUsersExtendedOptionalListResponse: extends: - UserOptionalListPage properties: - total_count: + total_count: type: integer docs: The totall number of /users - - ListUsersPaginationResponse: - properties: + + ListUsersPaginationResponse: + properties: hasNextPage: optional page: optional - total_count: + total_count: type: integer docs: The totall number of /users data: list - Page: - properties: - page: + Page: + properties: + page: type: integer docs: The current page next: optional per_page: integer total_page: integer - NextPage: - properties: + NextPage: + properties: page: integer starting_after: string - User: - properties: + User: + properties: name: string id: integer @@ -86,7 +85,7 @@ service: base-path: /users endpoints: listWithCursorPagination: - pagination: + pagination: cursor: $request.starting_after next_cursor: $response.page.next.starting_after results: $response.data @@ -95,41 +94,41 @@ service: request: name: ListUsersCursorPaginationRequest query-parameters: - page: + page: type: optional docs: Defaults to first page - per_page: + per_page: type: optional docs: Defaults to per page - order: - type: optional - starting_after: + order: + type: optional + starting_after: type: optional - docs: | - The cursor used for pagination in order to fetch + docs: | + The cursor used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse listWithBodyCursorPagination: - pagination: + pagination: cursor: $request.pagination.cursor next_cursor: $response.page.next.starting_after results: $response.data method: POST path: "" - request: + request: name: ListUsersBodyCursorPaginationRequest - body: + body: properties: - pagination: + pagination: type: optional - docs: | + docs: | The object that contains the cursor used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse listWithOffsetPagination: - pagination: + pagination: offset: $request.page results: $response.data method: GET @@ -137,38 +136,37 @@ service: request: name: ListUsersOffsetPaginationRequest query-parameters: - page: + page: type: optional docs: Defaults to first page - per_page: + per_page: type: optional docs: Defaults to per page - order: - type: optional - starting_after: + order: + type: optional + starting_after: type: optional - docs: | - The cursor used for pagination in order to fetch + docs: | + The cursor used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse listWithBodyOffsetPagination: - pagination: + pagination: offset: $request.pagination.page results: $response.data method: POST path: "" - request: + request: name: ListUsersBodyOffsetPaginationRequest - body: + body: properties: - pagination: + pagination: type: optional - docs: | + docs: | The object that contains the offset used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse - listWithOffsetStepPagination: pagination: offset: $request.page @@ -191,8 +189,8 @@ service: order: type: optional response: ListUsersPaginationResponse - - listWithOffsetPaginationHasNextPage: + + listWithOffsetPaginationHasNextPage: pagination: offset: $request.page results: $response.data @@ -217,7 +215,7 @@ service: response: ListUsersPaginationResponse listWithExtendedResults: - pagination: + pagination: cursor: $request.cursor next_cursor: $response.next results: $response.data.users @@ -230,7 +228,7 @@ service: response: ListUsersExtendedResponse listWithExtendedResultsAndOptionalData: - pagination: + pagination: cursor: $request.cursor next_cursor: $response.next results: $response.data.users @@ -243,7 +241,7 @@ service: response: ListUsersExtendedOptionalListResponse listUsernames: - pagination: + pagination: cursor: $request.starting_after next_cursor: $response.cursor.after results: $response.cursor.data @@ -252,10 +250,10 @@ service: request: name: ListUsernamesRequest query-parameters: - starting_after: + starting_after: type: optional - docs: | - The cursor used for pagination in order to fetch + docs: | + The cursor used for pagination in order to fetch the next page of results. response: root.UsernameCursor @@ -266,6 +264,6 @@ service: request: name: ListWithGlobalConfigRequest query-parameters: - offset: + offset: type: optional response: UsernameContainer \ No newline at end of file diff --git a/seed/go-fiber/unions/types.go b/seed/go-fiber/unions/types.go index b1d811cdc32..e444aee0916 100644 --- a/seed/go-fiber/unions/types.go +++ b/seed/go-fiber/unions/types.go @@ -156,6 +156,9 @@ func (u *Union) UnmarshalJSON(data []byte) error { } func (u Union) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -196,6 +199,40 @@ func (u *Union) Accept(visitor UnionVisitor) error { } } +func (u *Union) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithBaseProperties struct { Type string Id string @@ -292,6 +329,9 @@ func (u *UnionWithBaseProperties) UnmarshalJSON(data []byte) error { } func (u UnionWithBaseProperties) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -341,6 +381,43 @@ func (u *UnionWithBaseProperties) Accept(visitor UnionWithBasePropertiesVisitor) } } +func (u *UnionWithBaseProperties) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Integer != 0 { + fields = append(fields, "integer") + } + if u.String != "" { + fields = append(fields, "string") + } + if u.Foo != nil { + fields = append(fields, "foo") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithDiscriminant struct { Type string // This is a Foo field. @@ -410,6 +487,9 @@ func (u *UnionWithDiscriminant) UnmarshalJSON(data []byte) error { } func (u UnionWithDiscriminant) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -450,6 +530,40 @@ func (u *UnionWithDiscriminant) Accept(visitor UnionWithDiscriminantVisitor) err } } +func (u *UnionWithDiscriminant) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithLiteral struct { Type string fern string @@ -514,6 +628,9 @@ func (u *UnionWithLiteral) UnmarshalJSON(data []byte) error { } func (u UnionWithLiteral) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -544,6 +661,37 @@ func (u *UnionWithLiteral) Accept(visitor UnionWithLiteralVisitor) error { } } +func (u *UnionWithLiteral) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.fern != "" { + fields = append(fields, "fern") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithOptionalTime struct { Type string Date *time.Time @@ -612,6 +760,9 @@ func (u *UnionWithOptionalTime) UnmarshalJSON(data []byte) error { } func (u UnionWithOptionalTime) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -652,6 +803,40 @@ func (u *UnionWithOptionalTime) Accept(visitor UnionWithOptionalTimeVisitor) err } } +func (u *UnionWithOptionalTime) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Date != nil { + fields = append(fields, "date") + } + if u.Dateimte != nil { + fields = append(fields, "dateimte") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithPrimitive struct { Type string Integer int @@ -720,6 +905,9 @@ func (u *UnionWithPrimitive) UnmarshalJSON(data []byte) error { } func (u UnionWithPrimitive) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -760,6 +948,40 @@ func (u *UnionWithPrimitive) Accept(visitor UnionWithPrimitiveVisitor) error { } } +func (u *UnionWithPrimitive) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Integer != 0 { + fields = append(fields, "integer") + } + if u.String != "" { + fields = append(fields, "string") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithSingleElement struct { Type string Foo *Foo @@ -806,6 +1028,9 @@ func (u *UnionWithSingleElement) UnmarshalJSON(data []byte) error { } func (u UnionWithSingleElement) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -827,6 +1052,37 @@ func (u *UnionWithSingleElement) Accept(visitor UnionWithSingleElementVisitor) e } } +func (u *UnionWithSingleElement) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithTime struct { Type string Value int @@ -915,6 +1171,9 @@ func (u *UnionWithTime) UnmarshalJSON(data []byte) error { } func (u UnionWithTime) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -967,6 +1226,43 @@ func (u *UnionWithTime) Accept(visitor UnionWithTimeVisitor) error { } } +func (u *UnionWithTime) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Value != 0 { + fields = append(fields, "value") + } + if !u.Date.IsZero() { + fields = append(fields, "date") + } + if !u.Datetime.IsZero() { + fields = append(fields, "datetime") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithUnknown struct { Type string Foo *Foo @@ -1031,6 +1327,9 @@ func (u *UnionWithUnknown) UnmarshalJSON(data []byte) error { } func (u UnionWithUnknown) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -1064,6 +1363,40 @@ func (u *UnionWithUnknown) Accept(visitor UnionWithUnknownVisitor) error { } } +func (u *UnionWithUnknown) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Unknown != nil { + fields = append(fields, "unknown") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithoutKey struct { Type string Foo *Foo @@ -1129,6 +1462,9 @@ func (u *UnionWithoutKey) UnmarshalJSON(data []byte) error { } func (u UnionWithoutKey) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -1154,3 +1490,37 @@ func (u *UnionWithoutKey) Accept(visitor UnionWithoutKeyVisitor) error { return visitor.VisitBar(u.Bar) } } + +func (u *UnionWithoutKey) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} diff --git a/seed/go-fiber/unions/union.go b/seed/go-fiber/unions/union.go index 857d4ba3130..1c65d65ace2 100644 --- a/seed/go-fiber/unions/union.go +++ b/seed/go-fiber/unions/union.go @@ -160,6 +160,9 @@ func (s *Shape) UnmarshalJSON(data []byte) error { } func (s Shape) MarshalJSON() ([]byte, error) { + if err := s.validate(); err != nil { + return nil, err + } switch s.Type { default: return nil, fmt.Errorf("invalid type %s in %T", s.Type, s) @@ -186,6 +189,40 @@ func (s *Shape) Accept(visitor ShapeVisitor) error { } } +func (s *Shape) validate() error { + if s == nil { + return fmt.Errorf("type %T is nil", s) + } + var fields []string + if s.Circle != nil { + fields = append(fields, "circle") + } + if s.Square != nil { + fields = append(fields, "square") + } + if len(fields) == 0 { + if s.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", s, s.Type) + } + return fmt.Errorf("type %T is empty", s) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", s, fields) + } + if s.Type != "" { + field := fields[0] + if s.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + s, + s.Type, + s, + ) + } + } + return nil +} + type Square struct { Length float64 `json:"length" url:"length"` diff --git a/seed/go-model/audiences/folderc/common.go b/seed/go-model/audiences/folderc/common.go index 378d7148e96..7dacbb2c4db 100644 --- a/seed/go-model/audiences/folderc/common.go +++ b/seed/go-model/audiences/folderc/common.go @@ -17,7 +17,7 @@ type FolderCFoo struct { func (f *FolderCFoo) GetBarProperty() uuid.UUID { if f == nil { - return uuid.UUID{} + return uuid.Nil } return f.BarProperty } diff --git a/seed/go-model/circular-references-advanced/ast.go b/seed/go-model/circular-references-advanced/ast.go index 78628f27a50..4bf2c09126d 100644 --- a/seed/go-model/circular-references-advanced/ast.go +++ b/seed/go-model/circular-references-advanced/ast.go @@ -76,6 +76,9 @@ func (c *ContainerValue) UnmarshalJSON(data []byte) error { } func (c ContainerValue) MarshalJSON() ([]byte, error) { + if err := c.validate(); err != nil { + return nil, err + } switch c.Type { default: return nil, fmt.Errorf("invalid type %s in %T", c.Type, c) @@ -116,6 +119,40 @@ func (c *ContainerValue) Accept(visitor ContainerValueVisitor) error { } } +func (c *ContainerValue) validate() error { + if c == nil { + return fmt.Errorf("type %T is nil", c) + } + var fields []string + if c.List != nil { + fields = append(fields, "list") + } + if c.Optional != nil { + fields = append(fields, "optional") + } + if len(fields) == 0 { + if c.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", c, c.Type) + } + return fmt.Errorf("type %T is empty", c) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", c, fields) + } + if c.Type != "" { + field := fields[0] + if c.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + c, + c.Type, + c, + ) + } + } + return nil +} + type FieldName = string type FieldValue struct { @@ -204,6 +241,9 @@ func (f *FieldValue) UnmarshalJSON(data []byte) error { } func (f FieldValue) MarshalJSON() ([]byte, error) { + if err := f.validate(); err != nil { + return nil, err + } switch f.Type { default: return nil, fmt.Errorf("invalid type %s in %T", f.Type, f) @@ -249,6 +289,43 @@ func (f *FieldValue) Accept(visitor FieldValueVisitor) error { } } +func (f *FieldValue) validate() error { + if f == nil { + return fmt.Errorf("type %T is nil", f) + } + var fields []string + if f.PrimitiveValue != "" { + fields = append(fields, "primitive_value") + } + if f.ObjectValue != nil { + fields = append(fields, "object_value") + } + if f.ContainerValue != nil { + fields = append(fields, "container_value") + } + if len(fields) == 0 { + if f.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", f, f.Type) + } + return fmt.Errorf("type %T is empty", f) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", f, fields) + } + if f.Type != "" { + field := fields[0] + if f.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + f, + f.Type, + f, + ) + } + } + return nil +} + // This type allows us to test a circular reference with a union type (see FieldValue). type ObjectFieldValue struct { Name FieldName `json:"name" url:"name"` diff --git a/seed/go-model/circular-references/ast.go b/seed/go-model/circular-references/ast.go index 2ec37d5d03f..6d209b54f88 100644 --- a/seed/go-model/circular-references/ast.go +++ b/seed/go-model/circular-references/ast.go @@ -76,6 +76,9 @@ func (c *ContainerValue) UnmarshalJSON(data []byte) error { } func (c ContainerValue) MarshalJSON() ([]byte, error) { + if err := c.validate(); err != nil { + return nil, err + } switch c.Type { default: return nil, fmt.Errorf("invalid type %s in %T", c.Type, c) @@ -116,6 +119,40 @@ func (c *ContainerValue) Accept(visitor ContainerValueVisitor) error { } } +func (c *ContainerValue) validate() error { + if c == nil { + return fmt.Errorf("type %T is nil", c) + } + var fields []string + if c.List != nil { + fields = append(fields, "list") + } + if c.Optional != nil { + fields = append(fields, "optional") + } + if len(fields) == 0 { + if c.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", c, c.Type) + } + return fmt.Errorf("type %T is empty", c) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", c, fields) + } + if c.Type != "" { + field := fields[0] + if c.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + c, + c.Type, + c, + ) + } + } + return nil +} + type FieldValue struct { Type string PrimitiveValue PrimitiveValue @@ -202,6 +239,9 @@ func (f *FieldValue) UnmarshalJSON(data []byte) error { } func (f FieldValue) MarshalJSON() ([]byte, error) { + if err := f.validate(); err != nil { + return nil, err + } switch f.Type { default: return nil, fmt.Errorf("invalid type %s in %T", f.Type, f) @@ -247,6 +287,43 @@ func (f *FieldValue) Accept(visitor FieldValueVisitor) error { } } +func (f *FieldValue) validate() error { + if f == nil { + return fmt.Errorf("type %T is nil", f) + } + var fields []string + if f.PrimitiveValue != "" { + fields = append(fields, "primitive_value") + } + if f.ObjectValue != nil { + fields = append(fields, "object_value") + } + if f.ContainerValue != nil { + fields = append(fields, "container_value") + } + if len(fields) == 0 { + if f.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", f, f.Type) + } + return fmt.Errorf("type %T is empty", f) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", f, fields) + } + if f.Type != "" { + field := fields[0] + if f.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + f, + f.Type, + f, + ) + } + } + return nil +} + type JsonLike struct { JsonLikeList []*JsonLike StringJsonLikeMap map[string]*JsonLike diff --git a/seed/go-model/cross-package-type-names/folderc/common.go b/seed/go-model/cross-package-type-names/folderc/common.go index d6f963cbf1c..b1b4c7c0bbf 100644 --- a/seed/go-model/cross-package-type-names/folderc/common.go +++ b/seed/go-model/cross-package-type-names/folderc/common.go @@ -17,7 +17,7 @@ type Foo struct { func (f *Foo) GetBarProperty() uuid.UUID { if f == nil { - return uuid.UUID{} + return uuid.Nil } return f.BarProperty } diff --git a/seed/go-model/examples/commons/types.go b/seed/go-model/examples/commons/types.go index 9dc532153a4..e5aa4d8fdbb 100644 --- a/seed/go-model/examples/commons/types.go +++ b/seed/go-model/examples/commons/types.go @@ -76,6 +76,9 @@ func (d *Data) UnmarshalJSON(data []byte) error { } func (d Data) MarshalJSON() ([]byte, error) { + if err := d.validate(); err != nil { + return nil, err + } switch d.Type { default: return nil, fmt.Errorf("invalid type %s in %T", d.Type, d) @@ -116,6 +119,40 @@ func (d *Data) Accept(visitor DataVisitor) error { } } +func (d *Data) validate() error { + if d == nil { + return fmt.Errorf("type %T is nil", d) + } + var fields []string + if d.String != "" { + fields = append(fields, "string") + } + if d.Base64 != nil { + fields = append(fields, "base64") + } + if len(fields) == 0 { + if d.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", d, d.Type) + } + return fmt.Errorf("type %T is empty", d) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", d, fields) + } + if d.Type != "" { + field := fields[0] + if d.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + d, + d.Type, + d, + ) + } + } + return nil +} + type EventInfo struct { Type string Metadata *Metadata @@ -182,6 +219,9 @@ func (e *EventInfo) UnmarshalJSON(data []byte) error { } func (e EventInfo) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -215,6 +255,40 @@ func (e *EventInfo) Accept(visitor EventInfoVisitor) error { } } +func (e *EventInfo) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Metadata != nil { + fields = append(fields, "metadata") + } + if e.Tag != "" { + fields = append(fields, "tag") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type Metadata struct { Id string `json:"id" url:"id"` Data map[string]string `json:"data,omitempty" url:"data,omitempty"` diff --git a/seed/go-model/examples/types.go b/seed/go-model/examples/types.go index e710faed33e..45dc319f94c 100644 --- a/seed/go-model/examples/types.go +++ b/seed/go-model/examples/types.go @@ -673,6 +673,9 @@ func (e *Exception) UnmarshalJSON(data []byte) error { } func (e Exception) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -706,6 +709,40 @@ func (e *Exception) Accept(visitor ExceptionVisitor) error { } } +func (e *Exception) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Generic != nil { + fields = append(fields, "generic") + } + if e.Timeout != nil { + fields = append(fields, "timeout") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type ExceptionInfo struct { ExceptionType string `json:"exceptionType" url:"exceptionType"` ExceptionMessage string `json:"exceptionMessage" url:"exceptionMessage"` @@ -1034,6 +1071,9 @@ func (m *Metadata) UnmarshalJSON(data []byte) error { } func (m Metadata) MarshalJSON() ([]byte, error) { + if err := m.validate(); err != nil { + return nil, err + } switch m.Type { default: return nil, fmt.Errorf("invalid type %s in %T", m.Type, m) @@ -1082,6 +1122,40 @@ func (m *Metadata) Accept(visitor MetadataVisitor) error { } } +func (m *Metadata) validate() error { + if m == nil { + return fmt.Errorf("type %T is nil", m) + } + var fields []string + if m.Html != "" { + fields = append(fields, "html") + } + if m.Markdown != "" { + fields = append(fields, "markdown") + } + if len(fields) == 0 { + if m.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", m, m.Type) + } + return fmt.Errorf("type %T is empty", m) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", m, fields) + } + if m.Type != "" { + field := fields[0] + if m.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + m, + m.Type, + m, + ) + } + } + return nil +} + type Migration struct { Name string `json:"name" url:"name"` Status MigrationStatus `json:"status" url:"status"` @@ -1166,7 +1240,7 @@ type Moment struct { func (m *Moment) GetId() uuid.UUID { if m == nil { - return uuid.UUID{} + return uuid.Nil } return m.Id } @@ -1660,6 +1734,9 @@ func (t *Test) UnmarshalJSON(data []byte) error { } func (t Test) MarshalJSON() ([]byte, error) { + if err := t.validate(); err != nil { + return nil, err + } switch t.Type { default: return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) @@ -1700,6 +1777,40 @@ func (t *Test) Accept(visitor TestVisitor) error { } } +func (t *Test) validate() error { + if t == nil { + return fmt.Errorf("type %T is nil", t) + } + var fields []string + if t.And != false { + fields = append(fields, "and") + } + if t.Or != false { + fields = append(fields, "or") + } + if len(fields) == 0 { + if t.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", t, t.Type) + } + return fmt.Errorf("type %T is empty", t) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", t, fields) + } + if t.Type != "" { + field := fields[0] + if t.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + t, + t.Type, + t, + ) + } + } + return nil +} + type Tree struct { Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` diff --git a/seed/go-model/exhaustive/types/union.go b/seed/go-model/exhaustive/types/union.go index 8e64ae2d59a..705a1627d11 100644 --- a/seed/go-model/exhaustive/types/union.go +++ b/seed/go-model/exhaustive/types/union.go @@ -72,6 +72,9 @@ func (a *Animal) UnmarshalJSON(data []byte) error { } func (a Animal) MarshalJSON() ([]byte, error) { + if err := a.validate(); err != nil { + return nil, err + } switch a.Animal { default: return nil, fmt.Errorf("invalid type %s in %T", a.Animal, a) @@ -98,6 +101,40 @@ func (a *Animal) Accept(visitor AnimalVisitor) error { } } +func (a *Animal) validate() error { + if a == nil { + return fmt.Errorf("type %T is nil", a) + } + var fields []string + if a.Dog != nil { + fields = append(fields, "dog") + } + if a.Cat != nil { + fields = append(fields, "cat") + } + if len(fields) == 0 { + if a.Animal != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", a, a.Animal) + } + return fmt.Errorf("type %T is empty", a) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", a, fields) + } + if a.Animal != "" { + field := fields[0] + if a.Animal != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + a, + a.Animal, + a, + ) + } + } + return nil +} + type Cat struct { Name string `json:"name" url:"name"` LikesToMeow bool `json:"likesToMeow" url:"likesToMeow"` diff --git a/seed/go-model/file-upload/.mock/definition/service.yml b/seed/go-model/file-upload/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/go-model/file-upload/.mock/definition/service.yml +++ b/seed/go-model/file-upload/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/go-model/mixed-case/service.go b/seed/go-model/mixed-case/service.go index a04f95c90a7..ade0e4e6b55 100644 --- a/seed/go-model/mixed-case/service.go +++ b/seed/go-model/mixed-case/service.go @@ -168,6 +168,9 @@ func (r *Resource) UnmarshalJSON(data []byte) error { } func (r Resource) MarshalJSON() ([]byte, error) { + if err := r.validate(); err != nil { + return nil, err + } switch r.ResourceType { default: return nil, fmt.Errorf("invalid type %s in %T", r.ResourceType, r) @@ -194,6 +197,40 @@ func (r *Resource) Accept(visitor ResourceVisitor) error { } } +func (r *Resource) validate() error { + if r == nil { + return fmt.Errorf("type %T is nil", r) + } + var fields []string + if r.User != nil { + fields = append(fields, "user") + } + if r.Organization != nil { + fields = append(fields, "Organization") + } + if len(fields) == 0 { + if r.ResourceType != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", r, r.ResourceType) + } + return fmt.Errorf("type %T is empty", r) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", r, fields) + } + if r.ResourceType != "" { + field := fields[0] + if r.ResourceType != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + r, + r.ResourceType, + r, + ) + } + } + return nil +} + type ResourceStatus string const ( diff --git a/seed/go-model/object/types.go b/seed/go-model/object/types.go index d90f171fe0e..28a5af9c296 100644 --- a/seed/go-model/object/types.go +++ b/seed/go-model/object/types.go @@ -137,7 +137,7 @@ func (t *Type) GetSeven() time.Time { func (t *Type) GetEight() uuid.UUID { if t == nil { - return uuid.UUID{} + return uuid.Nil } return t.Eight } diff --git a/seed/go-model/pagination/.mock/definition/users.yml b/seed/go-model/pagination/.mock/definition/users.yml index ecde10ef707..ddc0bc1848b 100644 --- a/seed/go-model/pagination/.mock/definition/users.yml +++ b/seed/go-model/pagination/.mock/definition/users.yml @@ -1,9 +1,9 @@ imports: root: __package__.yml -types: - Order: - enum: +types: + Order: + enum: - asc - desc @@ -15,8 +15,8 @@ types: properties: cursor: optional - UserListContainer: - properties: + UserListContainer: + properties: users: list UserPage: @@ -24,60 +24,59 @@ types: data: UserListContainer next: optional - UserOptionalListContainer: - properties: + UserOptionalListContainer: + properties: users: optional> UserOptionalListPage: properties: data: UserOptionalListContainer next: optional - UsernameContainer: properties: results: list - ListUsersExtendedResponse: + ListUsersExtendedResponse: extends: - UserPage properties: - total_count: + total_count: type: integer docs: The totall number of /users - ListUsersExtendedOptionalListResponse: + ListUsersExtendedOptionalListResponse: extends: - UserOptionalListPage properties: - total_count: + total_count: type: integer docs: The totall number of /users - - ListUsersPaginationResponse: - properties: + + ListUsersPaginationResponse: + properties: hasNextPage: optional page: optional - total_count: + total_count: type: integer docs: The totall number of /users data: list - Page: - properties: - page: + Page: + properties: + page: type: integer docs: The current page next: optional per_page: integer total_page: integer - NextPage: - properties: + NextPage: + properties: page: integer starting_after: string - User: - properties: + User: + properties: name: string id: integer @@ -86,7 +85,7 @@ service: base-path: /users endpoints: listWithCursorPagination: - pagination: + pagination: cursor: $request.starting_after next_cursor: $response.page.next.starting_after results: $response.data @@ -95,41 +94,41 @@ service: request: name: ListUsersCursorPaginationRequest query-parameters: - page: + page: type: optional docs: Defaults to first page - per_page: + per_page: type: optional docs: Defaults to per page - order: - type: optional - starting_after: + order: + type: optional + starting_after: type: optional - docs: | - The cursor used for pagination in order to fetch + docs: | + The cursor used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse listWithBodyCursorPagination: - pagination: + pagination: cursor: $request.pagination.cursor next_cursor: $response.page.next.starting_after results: $response.data method: POST path: "" - request: + request: name: ListUsersBodyCursorPaginationRequest - body: + body: properties: - pagination: + pagination: type: optional - docs: | + docs: | The object that contains the cursor used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse listWithOffsetPagination: - pagination: + pagination: offset: $request.page results: $response.data method: GET @@ -137,38 +136,37 @@ service: request: name: ListUsersOffsetPaginationRequest query-parameters: - page: + page: type: optional docs: Defaults to first page - per_page: + per_page: type: optional docs: Defaults to per page - order: - type: optional - starting_after: + order: + type: optional + starting_after: type: optional - docs: | - The cursor used for pagination in order to fetch + docs: | + The cursor used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse listWithBodyOffsetPagination: - pagination: + pagination: offset: $request.pagination.page results: $response.data method: POST path: "" - request: + request: name: ListUsersBodyOffsetPaginationRequest - body: + body: properties: - pagination: + pagination: type: optional - docs: | + docs: | The object that contains the offset used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse - listWithOffsetStepPagination: pagination: offset: $request.page @@ -191,8 +189,8 @@ service: order: type: optional response: ListUsersPaginationResponse - - listWithOffsetPaginationHasNextPage: + + listWithOffsetPaginationHasNextPage: pagination: offset: $request.page results: $response.data @@ -217,7 +215,7 @@ service: response: ListUsersPaginationResponse listWithExtendedResults: - pagination: + pagination: cursor: $request.cursor next_cursor: $response.next results: $response.data.users @@ -230,7 +228,7 @@ service: response: ListUsersExtendedResponse listWithExtendedResultsAndOptionalData: - pagination: + pagination: cursor: $request.cursor next_cursor: $response.next results: $response.data.users @@ -243,7 +241,7 @@ service: response: ListUsersExtendedOptionalListResponse listUsernames: - pagination: + pagination: cursor: $request.starting_after next_cursor: $response.cursor.after results: $response.cursor.data @@ -252,10 +250,10 @@ service: request: name: ListUsernamesRequest query-parameters: - starting_after: + starting_after: type: optional - docs: | - The cursor used for pagination in order to fetch + docs: | + The cursor used for pagination in order to fetch the next page of results. response: root.UsernameCursor @@ -266,6 +264,6 @@ service: request: name: ListWithGlobalConfigRequest query-parameters: - offset: + offset: type: optional response: UsernameContainer \ No newline at end of file diff --git a/seed/go-model/unions/types.go b/seed/go-model/unions/types.go index b1d811cdc32..e444aee0916 100644 --- a/seed/go-model/unions/types.go +++ b/seed/go-model/unions/types.go @@ -156,6 +156,9 @@ func (u *Union) UnmarshalJSON(data []byte) error { } func (u Union) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -196,6 +199,40 @@ func (u *Union) Accept(visitor UnionVisitor) error { } } +func (u *Union) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithBaseProperties struct { Type string Id string @@ -292,6 +329,9 @@ func (u *UnionWithBaseProperties) UnmarshalJSON(data []byte) error { } func (u UnionWithBaseProperties) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -341,6 +381,43 @@ func (u *UnionWithBaseProperties) Accept(visitor UnionWithBasePropertiesVisitor) } } +func (u *UnionWithBaseProperties) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Integer != 0 { + fields = append(fields, "integer") + } + if u.String != "" { + fields = append(fields, "string") + } + if u.Foo != nil { + fields = append(fields, "foo") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithDiscriminant struct { Type string // This is a Foo field. @@ -410,6 +487,9 @@ func (u *UnionWithDiscriminant) UnmarshalJSON(data []byte) error { } func (u UnionWithDiscriminant) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -450,6 +530,40 @@ func (u *UnionWithDiscriminant) Accept(visitor UnionWithDiscriminantVisitor) err } } +func (u *UnionWithDiscriminant) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithLiteral struct { Type string fern string @@ -514,6 +628,9 @@ func (u *UnionWithLiteral) UnmarshalJSON(data []byte) error { } func (u UnionWithLiteral) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -544,6 +661,37 @@ func (u *UnionWithLiteral) Accept(visitor UnionWithLiteralVisitor) error { } } +func (u *UnionWithLiteral) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.fern != "" { + fields = append(fields, "fern") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithOptionalTime struct { Type string Date *time.Time @@ -612,6 +760,9 @@ func (u *UnionWithOptionalTime) UnmarshalJSON(data []byte) error { } func (u UnionWithOptionalTime) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -652,6 +803,40 @@ func (u *UnionWithOptionalTime) Accept(visitor UnionWithOptionalTimeVisitor) err } } +func (u *UnionWithOptionalTime) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Date != nil { + fields = append(fields, "date") + } + if u.Dateimte != nil { + fields = append(fields, "dateimte") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithPrimitive struct { Type string Integer int @@ -720,6 +905,9 @@ func (u *UnionWithPrimitive) UnmarshalJSON(data []byte) error { } func (u UnionWithPrimitive) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -760,6 +948,40 @@ func (u *UnionWithPrimitive) Accept(visitor UnionWithPrimitiveVisitor) error { } } +func (u *UnionWithPrimitive) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Integer != 0 { + fields = append(fields, "integer") + } + if u.String != "" { + fields = append(fields, "string") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithSingleElement struct { Type string Foo *Foo @@ -806,6 +1028,9 @@ func (u *UnionWithSingleElement) UnmarshalJSON(data []byte) error { } func (u UnionWithSingleElement) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -827,6 +1052,37 @@ func (u *UnionWithSingleElement) Accept(visitor UnionWithSingleElementVisitor) e } } +func (u *UnionWithSingleElement) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithTime struct { Type string Value int @@ -915,6 +1171,9 @@ func (u *UnionWithTime) UnmarshalJSON(data []byte) error { } func (u UnionWithTime) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -967,6 +1226,43 @@ func (u *UnionWithTime) Accept(visitor UnionWithTimeVisitor) error { } } +func (u *UnionWithTime) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Value != 0 { + fields = append(fields, "value") + } + if !u.Date.IsZero() { + fields = append(fields, "date") + } + if !u.Datetime.IsZero() { + fields = append(fields, "datetime") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithUnknown struct { Type string Foo *Foo @@ -1031,6 +1327,9 @@ func (u *UnionWithUnknown) UnmarshalJSON(data []byte) error { } func (u UnionWithUnknown) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -1064,6 +1363,40 @@ func (u *UnionWithUnknown) Accept(visitor UnionWithUnknownVisitor) error { } } +func (u *UnionWithUnknown) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Unknown != nil { + fields = append(fields, "unknown") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithoutKey struct { Type string Foo *Foo @@ -1129,6 +1462,9 @@ func (u *UnionWithoutKey) UnmarshalJSON(data []byte) error { } func (u UnionWithoutKey) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } switch u.Type { default: return nil, fmt.Errorf("invalid type %s in %T", u.Type, u) @@ -1154,3 +1490,37 @@ func (u *UnionWithoutKey) Accept(visitor UnionWithoutKeyVisitor) error { return visitor.VisitBar(u.Bar) } } + +func (u *UnionWithoutKey) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} diff --git a/seed/go-model/unions/union.go b/seed/go-model/unions/union.go index 857d4ba3130..1c65d65ace2 100644 --- a/seed/go-model/unions/union.go +++ b/seed/go-model/unions/union.go @@ -160,6 +160,9 @@ func (s *Shape) UnmarshalJSON(data []byte) error { } func (s Shape) MarshalJSON() ([]byte, error) { + if err := s.validate(); err != nil { + return nil, err + } switch s.Type { default: return nil, fmt.Errorf("invalid type %s in %T", s.Type, s) @@ -186,6 +189,40 @@ func (s *Shape) Accept(visitor ShapeVisitor) error { } } +func (s *Shape) validate() error { + if s == nil { + return fmt.Errorf("type %T is nil", s) + } + var fields []string + if s.Circle != nil { + fields = append(fields, "circle") + } + if s.Square != nil { + fields = append(fields, "square") + } + if len(fields) == 0 { + if s.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", s, s.Type) + } + return fmt.Errorf("type %T is empty", s) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", s, fields) + } + if s.Type != "" { + field := fields[0] + if s.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + s, + s.Type, + s, + ) + } + } + return nil +} + type Square struct { Length float64 `json:"length" url:"length"` diff --git a/seed/go-sdk/audiences/folderc/common.go b/seed/go-sdk/audiences/folderc/common.go index e1dad36f84c..fba79a659ca 100644 --- a/seed/go-sdk/audiences/folderc/common.go +++ b/seed/go-sdk/audiences/folderc/common.go @@ -18,7 +18,7 @@ type FolderCFoo struct { func (f *FolderCFoo) GetBarProperty() uuid.UUID { if f == nil { - return uuid.UUID{} + return uuid.Nil } return f.BarProperty } diff --git a/seed/go-sdk/circular-references-advanced/ast.go b/seed/go-sdk/circular-references-advanced/ast.go index 433a65142c9..4462970e7b7 100644 --- a/seed/go-sdk/circular-references-advanced/ast.go +++ b/seed/go-sdk/circular-references-advanced/ast.go @@ -76,6 +76,9 @@ func (c *ContainerValue) UnmarshalJSON(data []byte) error { } func (c ContainerValue) MarshalJSON() ([]byte, error) { + if err := c.validate(); err != nil { + return nil, err + } switch c.Type { default: return nil, fmt.Errorf("invalid type %s in %T", c.Type, c) @@ -116,6 +119,40 @@ func (c *ContainerValue) Accept(visitor ContainerValueVisitor) error { } } +func (c *ContainerValue) validate() error { + if c == nil { + return fmt.Errorf("type %T is nil", c) + } + var fields []string + if c.List != nil { + fields = append(fields, "list") + } + if c.Optional != nil { + fields = append(fields, "optional") + } + if len(fields) == 0 { + if c.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", c, c.Type) + } + return fmt.Errorf("type %T is empty", c) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", c, fields) + } + if c.Type != "" { + field := fields[0] + if c.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + c, + c.Type, + c, + ) + } + } + return nil +} + type FieldName = string type FieldValue struct { @@ -204,6 +241,9 @@ func (f *FieldValue) UnmarshalJSON(data []byte) error { } func (f FieldValue) MarshalJSON() ([]byte, error) { + if err := f.validate(); err != nil { + return nil, err + } switch f.Type { default: return nil, fmt.Errorf("invalid type %s in %T", f.Type, f) @@ -249,6 +289,43 @@ func (f *FieldValue) Accept(visitor FieldValueVisitor) error { } } +func (f *FieldValue) validate() error { + if f == nil { + return fmt.Errorf("type %T is nil", f) + } + var fields []string + if f.PrimitiveValue != "" { + fields = append(fields, "primitive_value") + } + if f.ObjectValue != nil { + fields = append(fields, "object_value") + } + if f.ContainerValue != nil { + fields = append(fields, "container_value") + } + if len(fields) == 0 { + if f.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", f, f.Type) + } + return fmt.Errorf("type %T is empty", f) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", f, fields) + } + if f.Type != "" { + field := fields[0] + if f.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + f, + f.Type, + f, + ) + } + } + return nil +} + // This type allows us to test a circular reference with a union type (see FieldValue). type ObjectFieldValue struct { Name FieldName `json:"name" url:"name"` diff --git a/seed/go-sdk/circular-references/ast.go b/seed/go-sdk/circular-references/ast.go index b621f5abcfb..dd6a1f1b549 100644 --- a/seed/go-sdk/circular-references/ast.go +++ b/seed/go-sdk/circular-references/ast.go @@ -76,6 +76,9 @@ func (c *ContainerValue) UnmarshalJSON(data []byte) error { } func (c ContainerValue) MarshalJSON() ([]byte, error) { + if err := c.validate(); err != nil { + return nil, err + } switch c.Type { default: return nil, fmt.Errorf("invalid type %s in %T", c.Type, c) @@ -116,6 +119,40 @@ func (c *ContainerValue) Accept(visitor ContainerValueVisitor) error { } } +func (c *ContainerValue) validate() error { + if c == nil { + return fmt.Errorf("type %T is nil", c) + } + var fields []string + if c.List != nil { + fields = append(fields, "list") + } + if c.Optional != nil { + fields = append(fields, "optional") + } + if len(fields) == 0 { + if c.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", c, c.Type) + } + return fmt.Errorf("type %T is empty", c) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", c, fields) + } + if c.Type != "" { + field := fields[0] + if c.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + c, + c.Type, + c, + ) + } + } + return nil +} + type FieldValue struct { Type string PrimitiveValue PrimitiveValue @@ -202,6 +239,9 @@ func (f *FieldValue) UnmarshalJSON(data []byte) error { } func (f FieldValue) MarshalJSON() ([]byte, error) { + if err := f.validate(); err != nil { + return nil, err + } switch f.Type { default: return nil, fmt.Errorf("invalid type %s in %T", f.Type, f) @@ -247,6 +287,43 @@ func (f *FieldValue) Accept(visitor FieldValueVisitor) error { } } +func (f *FieldValue) validate() error { + if f == nil { + return fmt.Errorf("type %T is nil", f) + } + var fields []string + if f.PrimitiveValue != "" { + fields = append(fields, "primitive_value") + } + if f.ObjectValue != nil { + fields = append(fields, "object_value") + } + if f.ContainerValue != nil { + fields = append(fields, "container_value") + } + if len(fields) == 0 { + if f.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", f, f.Type) + } + return fmt.Errorf("type %T is empty", f) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", f, fields) + } + if f.Type != "" { + field := fields[0] + if f.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + f, + f.Type, + f, + ) + } + } + return nil +} + type JsonLike struct { JsonLikeList []*JsonLike StringJsonLikeMap map[string]*JsonLike diff --git a/seed/go-sdk/cross-package-type-names/folderc/common.go b/seed/go-sdk/cross-package-type-names/folderc/common.go index 7e6eff77d6a..516c5930987 100644 --- a/seed/go-sdk/cross-package-type-names/folderc/common.go +++ b/seed/go-sdk/cross-package-type-names/folderc/common.go @@ -18,7 +18,7 @@ type Foo struct { func (f *Foo) GetBarProperty() uuid.UUID { if f == nil { - return uuid.UUID{} + return uuid.Nil } return f.BarProperty } diff --git a/seed/go-sdk/examples/always-send-required-properties/commons/types.go b/seed/go-sdk/examples/always-send-required-properties/commons/types.go index b1cd753cd24..64535ab25bf 100644 --- a/seed/go-sdk/examples/always-send-required-properties/commons/types.go +++ b/seed/go-sdk/examples/always-send-required-properties/commons/types.go @@ -76,6 +76,9 @@ func (d *Data) UnmarshalJSON(data []byte) error { } func (d Data) MarshalJSON() ([]byte, error) { + if err := d.validate(); err != nil { + return nil, err + } switch d.Type { default: return nil, fmt.Errorf("invalid type %s in %T", d.Type, d) @@ -116,6 +119,40 @@ func (d *Data) Accept(visitor DataVisitor) error { } } +func (d *Data) validate() error { + if d == nil { + return fmt.Errorf("type %T is nil", d) + } + var fields []string + if d.String != "" { + fields = append(fields, "string") + } + if d.Base64 != nil { + fields = append(fields, "base64") + } + if len(fields) == 0 { + if d.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", d, d.Type) + } + return fmt.Errorf("type %T is empty", d) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", d, fields) + } + if d.Type != "" { + field := fields[0] + if d.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + d, + d.Type, + d, + ) + } + } + return nil +} + type EventInfo struct { Type string Metadata *Metadata @@ -182,6 +219,9 @@ func (e *EventInfo) UnmarshalJSON(data []byte) error { } func (e EventInfo) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -215,6 +255,40 @@ func (e *EventInfo) Accept(visitor EventInfoVisitor) error { } } +func (e *EventInfo) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Metadata != nil { + fields = append(fields, "metadata") + } + if e.Tag != "" { + fields = append(fields, "tag") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type Metadata struct { Id string `json:"id" url:"id"` Data map[string]string `json:"data,omitempty" url:"data,omitempty"` diff --git a/seed/go-sdk/examples/always-send-required-properties/types.go b/seed/go-sdk/examples/always-send-required-properties/types.go index 591fa565ab2..8e45e80dba9 100644 --- a/seed/go-sdk/examples/always-send-required-properties/types.go +++ b/seed/go-sdk/examples/always-send-required-properties/types.go @@ -715,6 +715,9 @@ func (e *Exception) UnmarshalJSON(data []byte) error { } func (e Exception) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -748,6 +751,40 @@ func (e *Exception) Accept(visitor ExceptionVisitor) error { } } +func (e *Exception) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Generic != nil { + fields = append(fields, "generic") + } + if e.Timeout != nil { + fields = append(fields, "timeout") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type ExceptionInfo struct { ExceptionType string `json:"exceptionType" url:"exceptionType"` ExceptionMessage string `json:"exceptionMessage" url:"exceptionMessage"` @@ -1097,6 +1134,9 @@ func (m *Metadata) UnmarshalJSON(data []byte) error { } func (m Metadata) MarshalJSON() ([]byte, error) { + if err := m.validate(); err != nil { + return nil, err + } switch m.Type { default: return nil, fmt.Errorf("invalid type %s in %T", m.Type, m) @@ -1145,6 +1185,40 @@ func (m *Metadata) Accept(visitor MetadataVisitor) error { } } +func (m *Metadata) validate() error { + if m == nil { + return fmt.Errorf("type %T is nil", m) + } + var fields []string + if m.Html != "" { + fields = append(fields, "html") + } + if m.Markdown != "" { + fields = append(fields, "markdown") + } + if len(fields) == 0 { + if m.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", m, m.Type) + } + return fmt.Errorf("type %T is empty", m) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", m, fields) + } + if m.Type != "" { + field := fields[0] + if m.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + m, + m.Type, + m, + ) + } + } + return nil +} + type Migration struct { Name string `json:"name" url:"name"` Status MigrationStatus `json:"status" url:"status"` @@ -1237,7 +1311,7 @@ type Moment struct { func (m *Moment) GetId() uuid.UUID { if m == nil { - return uuid.UUID{} + return uuid.Nil } return m.Id } @@ -1779,6 +1853,9 @@ func (t *Test) UnmarshalJSON(data []byte) error { } func (t Test) MarshalJSON() ([]byte, error) { + if err := t.validate(); err != nil { + return nil, err + } switch t.Type { default: return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) @@ -1819,6 +1896,40 @@ func (t *Test) Accept(visitor TestVisitor) error { } } +func (t *Test) validate() error { + if t == nil { + return fmt.Errorf("type %T is nil", t) + } + var fields []string + if t.And != false { + fields = append(fields, "and") + } + if t.Or != false { + fields = append(fields, "or") + } + if len(fields) == 0 { + if t.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", t, t.Type) + } + return fmt.Errorf("type %T is empty", t) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", t, fields) + } + if t.Type != "" { + field := fields[0] + if t.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + t, + t.Type, + t, + ) + } + } + return nil +} + type Tree struct { Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` diff --git a/seed/go-sdk/examples/exported-client-name/commons/types.go b/seed/go-sdk/examples/exported-client-name/commons/types.go index b1cd753cd24..64535ab25bf 100644 --- a/seed/go-sdk/examples/exported-client-name/commons/types.go +++ b/seed/go-sdk/examples/exported-client-name/commons/types.go @@ -76,6 +76,9 @@ func (d *Data) UnmarshalJSON(data []byte) error { } func (d Data) MarshalJSON() ([]byte, error) { + if err := d.validate(); err != nil { + return nil, err + } switch d.Type { default: return nil, fmt.Errorf("invalid type %s in %T", d.Type, d) @@ -116,6 +119,40 @@ func (d *Data) Accept(visitor DataVisitor) error { } } +func (d *Data) validate() error { + if d == nil { + return fmt.Errorf("type %T is nil", d) + } + var fields []string + if d.String != "" { + fields = append(fields, "string") + } + if d.Base64 != nil { + fields = append(fields, "base64") + } + if len(fields) == 0 { + if d.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", d, d.Type) + } + return fmt.Errorf("type %T is empty", d) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", d, fields) + } + if d.Type != "" { + field := fields[0] + if d.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + d, + d.Type, + d, + ) + } + } + return nil +} + type EventInfo struct { Type string Metadata *Metadata @@ -182,6 +219,9 @@ func (e *EventInfo) UnmarshalJSON(data []byte) error { } func (e EventInfo) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -215,6 +255,40 @@ func (e *EventInfo) Accept(visitor EventInfoVisitor) error { } } +func (e *EventInfo) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Metadata != nil { + fields = append(fields, "metadata") + } + if e.Tag != "" { + fields = append(fields, "tag") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type Metadata struct { Id string `json:"id" url:"id"` Data map[string]string `json:"data,omitempty" url:"data,omitempty"` diff --git a/seed/go-sdk/examples/exported-client-name/types.go b/seed/go-sdk/examples/exported-client-name/types.go index b5e7c7e870b..87f11b38e9c 100644 --- a/seed/go-sdk/examples/exported-client-name/types.go +++ b/seed/go-sdk/examples/exported-client-name/types.go @@ -715,6 +715,9 @@ func (e *Exception) UnmarshalJSON(data []byte) error { } func (e Exception) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -748,6 +751,40 @@ func (e *Exception) Accept(visitor ExceptionVisitor) error { } } +func (e *Exception) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Generic != nil { + fields = append(fields, "generic") + } + if e.Timeout != nil { + fields = append(fields, "timeout") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type ExceptionInfo struct { ExceptionType string `json:"exceptionType" url:"exceptionType"` ExceptionMessage string `json:"exceptionMessage" url:"exceptionMessage"` @@ -1097,6 +1134,9 @@ func (m *Metadata) UnmarshalJSON(data []byte) error { } func (m Metadata) MarshalJSON() ([]byte, error) { + if err := m.validate(); err != nil { + return nil, err + } switch m.Type { default: return nil, fmt.Errorf("invalid type %s in %T", m.Type, m) @@ -1145,6 +1185,40 @@ func (m *Metadata) Accept(visitor MetadataVisitor) error { } } +func (m *Metadata) validate() error { + if m == nil { + return fmt.Errorf("type %T is nil", m) + } + var fields []string + if m.Html != "" { + fields = append(fields, "html") + } + if m.Markdown != "" { + fields = append(fields, "markdown") + } + if len(fields) == 0 { + if m.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", m, m.Type) + } + return fmt.Errorf("type %T is empty", m) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", m, fields) + } + if m.Type != "" { + field := fields[0] + if m.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + m, + m.Type, + m, + ) + } + } + return nil +} + type Migration struct { Name string `json:"name" url:"name"` Status MigrationStatus `json:"status" url:"status"` @@ -1237,7 +1311,7 @@ type Moment struct { func (m *Moment) GetId() uuid.UUID { if m == nil { - return uuid.UUID{} + return uuid.Nil } return m.Id } @@ -1779,6 +1853,9 @@ func (t *Test) UnmarshalJSON(data []byte) error { } func (t Test) MarshalJSON() ([]byte, error) { + if err := t.validate(); err != nil { + return nil, err + } switch t.Type { default: return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) @@ -1819,6 +1896,40 @@ func (t *Test) Accept(visitor TestVisitor) error { } } +func (t *Test) validate() error { + if t == nil { + return fmt.Errorf("type %T is nil", t) + } + var fields []string + if t.And != false { + fields = append(fields, "and") + } + if t.Or != false { + fields = append(fields, "or") + } + if len(fields) == 0 { + if t.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", t, t.Type) + } + return fmt.Errorf("type %T is empty", t) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", t, fields) + } + if t.Type != "" { + field := fields[0] + if t.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + t, + t.Type, + t, + ) + } + } + return nil +} + type Tree struct { Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` diff --git a/seed/go-sdk/examples/no-custom-config/commons/types.go b/seed/go-sdk/examples/no-custom-config/commons/types.go index b1cd753cd24..64535ab25bf 100644 --- a/seed/go-sdk/examples/no-custom-config/commons/types.go +++ b/seed/go-sdk/examples/no-custom-config/commons/types.go @@ -76,6 +76,9 @@ func (d *Data) UnmarshalJSON(data []byte) error { } func (d Data) MarshalJSON() ([]byte, error) { + if err := d.validate(); err != nil { + return nil, err + } switch d.Type { default: return nil, fmt.Errorf("invalid type %s in %T", d.Type, d) @@ -116,6 +119,40 @@ func (d *Data) Accept(visitor DataVisitor) error { } } +func (d *Data) validate() error { + if d == nil { + return fmt.Errorf("type %T is nil", d) + } + var fields []string + if d.String != "" { + fields = append(fields, "string") + } + if d.Base64 != nil { + fields = append(fields, "base64") + } + if len(fields) == 0 { + if d.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", d, d.Type) + } + return fmt.Errorf("type %T is empty", d) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", d, fields) + } + if d.Type != "" { + field := fields[0] + if d.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + d, + d.Type, + d, + ) + } + } + return nil +} + type EventInfo struct { Type string Metadata *Metadata @@ -182,6 +219,9 @@ func (e *EventInfo) UnmarshalJSON(data []byte) error { } func (e EventInfo) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -215,6 +255,40 @@ func (e *EventInfo) Accept(visitor EventInfoVisitor) error { } } +func (e *EventInfo) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Metadata != nil { + fields = append(fields, "metadata") + } + if e.Tag != "" { + fields = append(fields, "tag") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type Metadata struct { Id string `json:"id" url:"id"` Data map[string]string `json:"data,omitempty" url:"data,omitempty"` diff --git a/seed/go-sdk/examples/no-custom-config/types.go b/seed/go-sdk/examples/no-custom-config/types.go index b5e7c7e870b..87f11b38e9c 100644 --- a/seed/go-sdk/examples/no-custom-config/types.go +++ b/seed/go-sdk/examples/no-custom-config/types.go @@ -715,6 +715,9 @@ func (e *Exception) UnmarshalJSON(data []byte) error { } func (e Exception) MarshalJSON() ([]byte, error) { + if err := e.validate(); err != nil { + return nil, err + } switch e.Type { default: return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) @@ -748,6 +751,40 @@ func (e *Exception) Accept(visitor ExceptionVisitor) error { } } +func (e *Exception) validate() error { + if e == nil { + return fmt.Errorf("type %T is nil", e) + } + var fields []string + if e.Generic != nil { + fields = append(fields, "generic") + } + if e.Timeout != nil { + fields = append(fields, "timeout") + } + if len(fields) == 0 { + if e.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", e, e.Type) + } + return fmt.Errorf("type %T is empty", e) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", e, fields) + } + if e.Type != "" { + field := fields[0] + if e.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + e, + e.Type, + e, + ) + } + } + return nil +} + type ExceptionInfo struct { ExceptionType string `json:"exceptionType" url:"exceptionType"` ExceptionMessage string `json:"exceptionMessage" url:"exceptionMessage"` @@ -1097,6 +1134,9 @@ func (m *Metadata) UnmarshalJSON(data []byte) error { } func (m Metadata) MarshalJSON() ([]byte, error) { + if err := m.validate(); err != nil { + return nil, err + } switch m.Type { default: return nil, fmt.Errorf("invalid type %s in %T", m.Type, m) @@ -1145,6 +1185,40 @@ func (m *Metadata) Accept(visitor MetadataVisitor) error { } } +func (m *Metadata) validate() error { + if m == nil { + return fmt.Errorf("type %T is nil", m) + } + var fields []string + if m.Html != "" { + fields = append(fields, "html") + } + if m.Markdown != "" { + fields = append(fields, "markdown") + } + if len(fields) == 0 { + if m.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", m, m.Type) + } + return fmt.Errorf("type %T is empty", m) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", m, fields) + } + if m.Type != "" { + field := fields[0] + if m.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + m, + m.Type, + m, + ) + } + } + return nil +} + type Migration struct { Name string `json:"name" url:"name"` Status MigrationStatus `json:"status" url:"status"` @@ -1237,7 +1311,7 @@ type Moment struct { func (m *Moment) GetId() uuid.UUID { if m == nil { - return uuid.UUID{} + return uuid.Nil } return m.Id } @@ -1779,6 +1853,9 @@ func (t *Test) UnmarshalJSON(data []byte) error { } func (t Test) MarshalJSON() ([]byte, error) { + if err := t.validate(); err != nil { + return nil, err + } switch t.Type { default: return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) @@ -1819,6 +1896,40 @@ func (t *Test) Accept(visitor TestVisitor) error { } } +func (t *Test) validate() error { + if t == nil { + return fmt.Errorf("type %T is nil", t) + } + var fields []string + if t.And != false { + fields = append(fields, "and") + } + if t.Or != false { + fields = append(fields, "or") + } + if len(fields) == 0 { + if t.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", t, t.Type) + } + return fmt.Errorf("type %T is empty", t) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", t, fields) + } + if t.Type != "" { + field := fields[0] + if t.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + t, + t.Type, + t, + ) + } + } + return nil +} + type Tree struct { Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` diff --git a/seed/go-sdk/mixed-case/service.go b/seed/go-sdk/mixed-case/service.go index f9c58169781..1c6ee133345 100644 --- a/seed/go-sdk/mixed-case/service.go +++ b/seed/go-sdk/mixed-case/service.go @@ -188,6 +188,9 @@ func (r *Resource) UnmarshalJSON(data []byte) error { } func (r Resource) MarshalJSON() ([]byte, error) { + if err := r.validate(); err != nil { + return nil, err + } switch r.ResourceType { default: return nil, fmt.Errorf("invalid type %s in %T", r.ResourceType, r) @@ -214,6 +217,40 @@ func (r *Resource) Accept(visitor ResourceVisitor) error { } } +func (r *Resource) validate() error { + if r == nil { + return fmt.Errorf("type %T is nil", r) + } + var fields []string + if r.User != nil { + fields = append(fields, "user") + } + if r.Organization != nil { + fields = append(fields, "Organization") + } + if len(fields) == 0 { + if r.ResourceType != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", r, r.ResourceType) + } + return fmt.Errorf("type %T is empty", r) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", r, fields) + } + if r.ResourceType != "" { + field := fields[0] + if r.ResourceType != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + r, + r.ResourceType, + r, + ) + } + } + return nil +} + type ResourceStatus string const ( diff --git a/seed/go-sdk/object/types.go b/seed/go-sdk/object/types.go index 62f0e3b5ce7..4523b8ff47b 100644 --- a/seed/go-sdk/object/types.go +++ b/seed/go-sdk/object/types.go @@ -145,7 +145,7 @@ func (t *Type) GetSeven() time.Time { func (t *Type) GetEight() uuid.UUID { if t == nil { - return uuid.UUID{} + return uuid.Nil } return t.Eight } diff --git a/seed/go-sdk/unions/types.go b/seed/go-sdk/unions/types.go index d4ed1935761..58be1314e89 100644 --- a/seed/go-sdk/unions/types.go +++ b/seed/go-sdk/unions/types.go @@ -162,6 +162,9 @@ func (u *Union) UnmarshalJSON(data []byte) error { } func (u Union) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } if u.Foo != nil { var marshaler = struct { Type string `json:"type"` @@ -200,6 +203,40 @@ func (u *Union) Accept(visitor UnionVisitor) error { return fmt.Errorf("type %T does not define a non-empty union type", u) } +func (u *Union) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithBaseProperties struct { Type string Id string @@ -284,6 +321,9 @@ func (u *UnionWithBaseProperties) UnmarshalJSON(data []byte) error { } func (u UnionWithBaseProperties) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } if u.Integer != 0 { var marshaler = struct { Type string `json:"type"` @@ -333,6 +373,43 @@ func (u *UnionWithBaseProperties) Accept(visitor UnionWithBasePropertiesVisitor) return fmt.Errorf("type %T does not define a non-empty union type", u) } +func (u *UnionWithBaseProperties) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Integer != 0 { + fields = append(fields, "integer") + } + if u.String != "" { + fields = append(fields, "string") + } + if u.Foo != nil { + fields = append(fields, "foo") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithDiscriminant struct { Type string // This is a Foo field. @@ -394,6 +471,9 @@ func (u *UnionWithDiscriminant) UnmarshalJSON(data []byte) error { } func (u UnionWithDiscriminant) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } if u.Foo != nil { var marshaler = struct { Type string `json:"_type"` @@ -432,6 +512,40 @@ func (u *UnionWithDiscriminant) Accept(visitor UnionWithDiscriminantVisitor) err return fmt.Errorf("type %T does not define a non-empty union type", u) } +func (u *UnionWithDiscriminant) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithLiteral struct { Type string fern string @@ -496,6 +610,9 @@ func (u *UnionWithLiteral) UnmarshalJSON(data []byte) error { } func (u UnionWithLiteral) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } if u.fern != "" { var marshaler = struct { Type string `json:"type"` @@ -522,6 +639,37 @@ func (u *UnionWithLiteral) Accept(visitor UnionWithLiteralVisitor) error { return fmt.Errorf("type %T does not define a non-empty union type", u) } +func (u *UnionWithLiteral) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.fern != "" { + fields = append(fields, "fern") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithOptionalTime struct { Type string Date *time.Time @@ -582,6 +730,9 @@ func (u *UnionWithOptionalTime) UnmarshalJSON(data []byte) error { } func (u UnionWithOptionalTime) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } if u.Date != nil { var marshaler = struct { Type string `json:"type"` @@ -620,6 +771,40 @@ func (u *UnionWithOptionalTime) Accept(visitor UnionWithOptionalTimeVisitor) err return fmt.Errorf("type %T does not define a non-empty union type", u) } +func (u *UnionWithOptionalTime) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Date != nil { + fields = append(fields, "date") + } + if u.Dateimte != nil { + fields = append(fields, "dateimte") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithPrimitive struct { Type string Integer int @@ -680,6 +865,9 @@ func (u *UnionWithPrimitive) UnmarshalJSON(data []byte) error { } func (u UnionWithPrimitive) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } if u.Integer != 0 { var marshaler = struct { Type string `json:"type"` @@ -718,6 +906,40 @@ func (u *UnionWithPrimitive) Accept(visitor UnionWithPrimitiveVisitor) error { return fmt.Errorf("type %T does not define a non-empty union type", u) } +func (u *UnionWithPrimitive) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Integer != 0 { + fields = append(fields, "integer") + } + if u.String != "" { + fields = append(fields, "string") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithSingleElement struct { Type string Foo *Foo @@ -760,6 +982,9 @@ func (u *UnionWithSingleElement) UnmarshalJSON(data []byte) error { } func (u UnionWithSingleElement) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } if u.Foo != nil { return internal.MarshalJSONWithExtraProperty(u.Foo, "type", "foo") } @@ -777,6 +1002,37 @@ func (u *UnionWithSingleElement) Accept(visitor UnionWithSingleElementVisitor) e return fmt.Errorf("type %T does not define a non-empty union type", u) } +func (u *UnionWithSingleElement) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithTime struct { Type string Value int @@ -853,6 +1109,9 @@ func (u *UnionWithTime) UnmarshalJSON(data []byte) error { } func (u UnionWithTime) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } if u.Value != 0 { var marshaler = struct { Type string `json:"type"` @@ -905,6 +1164,43 @@ func (u *UnionWithTime) Accept(visitor UnionWithTimeVisitor) error { return fmt.Errorf("type %T does not define a non-empty union type", u) } +func (u *UnionWithTime) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Value != 0 { + fields = append(fields, "value") + } + if !u.Date.IsZero() { + fields = append(fields, "date") + } + if !u.Datetime.IsZero() { + fields = append(fields, "datetime") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithUnknown struct { Type string Foo *Foo @@ -961,6 +1257,9 @@ func (u *UnionWithUnknown) UnmarshalJSON(data []byte) error { } func (u UnionWithUnknown) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } if u.Foo != nil { return internal.MarshalJSONWithExtraProperty(u.Foo, "type", "foo") } @@ -992,6 +1291,40 @@ func (u *UnionWithUnknown) Accept(visitor UnionWithUnknownVisitor) error { return fmt.Errorf("type %T does not define a non-empty union type", u) } +func (u *UnionWithUnknown) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Unknown != nil { + fields = append(fields, "unknown") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} + type UnionWithoutKey struct { Type string Foo *Foo @@ -1049,6 +1382,9 @@ func (u *UnionWithoutKey) UnmarshalJSON(data []byte) error { } func (u UnionWithoutKey) MarshalJSON() ([]byte, error) { + if err := u.validate(); err != nil { + return nil, err + } if u.Foo != nil { return internal.MarshalJSONWithExtraProperty(u.Foo, "type", "foo") } @@ -1072,3 +1408,37 @@ func (u *UnionWithoutKey) Accept(visitor UnionWithoutKeyVisitor) error { } return fmt.Errorf("type %T does not define a non-empty union type", u) } + +func (u *UnionWithoutKey) validate() error { + if u == nil { + return fmt.Errorf("type %T is nil", u) + } + var fields []string + if u.Foo != nil { + fields = append(fields, "foo") + } + if u.Bar != nil { + fields = append(fields, "bar") + } + if len(fields) == 0 { + if u.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", u, u.Type) + } + return fmt.Errorf("type %T is empty", u) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", u, fields) + } + if u.Type != "" { + field := fields[0] + if u.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + u, + u.Type, + u, + ) + } + } + return nil +} diff --git a/seed/go-sdk/unions/union.go b/seed/go-sdk/unions/union.go index 5392b312a32..8300fc3e1db 100644 --- a/seed/go-sdk/unions/union.go +++ b/seed/go-sdk/unions/union.go @@ -166,6 +166,9 @@ func (s *Shape) UnmarshalJSON(data []byte) error { } func (s Shape) MarshalJSON() ([]byte, error) { + if err := s.validate(); err != nil { + return nil, err + } if s.Circle != nil { return internal.MarshalJSONWithExtraProperty(s.Circle, "type", "circle") } @@ -190,6 +193,40 @@ func (s *Shape) Accept(visitor ShapeVisitor) error { return fmt.Errorf("type %T does not define a non-empty union type", s) } +func (s *Shape) validate() error { + if s == nil { + return fmt.Errorf("type %T is nil", s) + } + var fields []string + if s.Circle != nil { + fields = append(fields, "circle") + } + if s.Square != nil { + fields = append(fields, "square") + } + if len(fields) == 0 { + if s.Type != "" { + return fmt.Errorf("type %T defines a discriminant set to %q but the field is not set", s, s.Type) + } + return fmt.Errorf("type %T is empty", s) + } + if len(fields) > 1 { + return fmt.Errorf("type %T defines values for %s, but only one value is allowed", s, fields) + } + if s.Type != "" { + field := fields[0] + if s.Type != field { + return fmt.Errorf( + "type %T defines a discriminant set to %q, but it does not match the %T field; either remove or update the discriminant to match", + s, + s.Type, + s, + ) + } + } + return nil +} + type Square struct { Length float64 `json:"length" url:"length"`