From 7bc36c463354a9a7b7fb93d70bdd8cfefab2e85f Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Wed, 8 Nov 2023 01:22:24 +0100 Subject: [PATCH] feat: implement support for sub-schemas - fix: add guard against loading non-json files when reading references in schemas - feat: introduce basic allOf support - fix: handle struct slices prop merging for allOf types - feat: introduce basic anyOf support, fix default value issue. - fix: make anyOf properties of primitive types dump interface instead of first type - fix: reduce duplicate types - fix: furhter reduce duplicate types - chore: refactor cmp Opts utility - fix: add graceful handling of some edge cases in code generation - empty enum type name - missing unmarshal methods for map types - "Plain" type naming collisions - schema.Type new properties potential (de)serialization - chore: rename test files after rebase - chore: cleanup go deps - chore: fix go linting issues - chore: integrate new formatter changes after rebase - fix: set consistent var names in unmarshallers, introduce support for both yaml and json in validators - chore: remove dead code - fix: use '>' instead of '>=' to perform max length validation. - chore: remove 'two' constant - chore: disable gomnd linter --- .rules/.golangci.yml | 2 + go.mod | 2 + go.sum | 3 + go.work.sum | 4 +- internal/x/text/cases.go | 4 + pkg/cmputil/opts.go | 16 + pkg/codegen/model.go | 8 +- pkg/generator/formatter.go | 2 +- pkg/generator/generate.go | 17 +- pkg/generator/json_formatter.go | 34 +- pkg/generator/output.go | 35 + pkg/generator/schema_generator.go | 164 +++-- pkg/generator/utils.go | 2 +- pkg/generator/validator.go | 114 +++- pkg/generator/yaml_formatter.go | 28 +- pkg/schemas/model.go | 115 +++- pkg/schemas/types.go | 14 + tests/data/core/allOf/allOf.go | 62 ++ tests/data/core/allOf/allOf.json | 36 ++ tests/data/core/anyOf/anyOf.go | 206 ++++++ tests/data/core/anyOf/anyOf.json | 54 ++ tests/data/core/array/array.go | 34 +- tests/data/core/date/date.go | 25 +- tests/data/core/dateTime/dateTime.go | 25 +- tests/data/core/ip/ip.go | 25 +- tests/data/core/object/object.go | 25 +- .../objectAdditionalProperties.go | 25 +- .../objectPropertiesDefault.go | 399 ++++++++++++ .../objectPropertiesDefault.json | 104 +++ tests/data/core/primitives/primitives.go | 25 +- tests/data/core/refToEnum/refToEnum.go | 21 + tests/data/core/time/time.go | 25 +- .../extraImports/gopkgYAMLv3/gopkgYAMLv3.go | 6 +- .../specialCharacters/specialCharacters.go | 147 ++++- .../cyclicAndRequired1/cyclicAndRequired1.go | 25 +- tests/data/regressions/issue32/issue32.go | 28 +- tests/data/validation/enum/enum.go | 601 +++++++++++++----- tests/data/validation/maxItems/maxItems.go | 33 +- tests/data/validation/maxLength/maxLength.go | 31 +- tests/data/validation/minItems/minItems.go | 33 +- tests/data/validation/minLength/minLength.go | 31 +- .../validation/minMaxItems/minMaxItems.go | 42 +- .../requiredFields/requiredFields.go | 108 +++- .../validation/typed_default/typed_default.go | 30 +- .../typed_default_empty.go | 25 +- .../typed_default_enums.go | 45 +- tests/generation_test.go | 2 +- tests/go.mod | 2 + tests/go.sum | 2 + 49 files changed, 2469 insertions(+), 377 deletions(-) create mode 100644 pkg/cmputil/opts.go create mode 100644 tests/data/core/allOf/allOf.go create mode 100644 tests/data/core/allOf/allOf.json create mode 100644 tests/data/core/anyOf/anyOf.go create mode 100644 tests/data/core/anyOf/anyOf.json create mode 100644 tests/data/core/objectPropertiesDefault/objectPropertiesDefault.go create mode 100644 tests/data/core/objectPropertiesDefault/objectPropertiesDefault.json diff --git a/.rules/.golangci.yml b/.rules/.golangci.yml index 0c938749..f79b6424 100644 --- a/.rules/.golangci.yml +++ b/.rules/.golangci.yml @@ -47,6 +47,8 @@ linters: # unused - exhaustruct - forbidigo + - gomnd + linters-settings: decorder: disable-init-func-first-check: false diff --git a/go.mod b/go.mod index 222a6cb0..1a14088a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module github.com/atombender/go-jsonschema go 1.21 require ( + dario.cat/mergo v1.0.0 github.com/goccy/go-yaml v1.11.2 + github.com/google/go-cmp v0.5.9 github.com/mitchellh/go-wordwrap v1.0.1 github.com/pkg/errors v0.9.1 github.com/sanity-io/litter v1.5.5 diff --git a/go.sum b/go.sum index 9ae26564..26ebbc42 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b h1:XxMZvQZtTXpWMNWK82vdjCLCe7uGMFXdTsJH0v3Hkvw= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -48,4 +50,5 @@ golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.work.sum b/go.work.sum index dd173a54..be1853a2 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,6 +1,4 @@ -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +github.com/atombender/go-jsonschema/tests/data v0.0.0-20231003003002-2b73c089a581/go.mod h1:kLoRQLRVy+GT9/PG2e3u31DPvDmtFEn7pX6FItvbqlA= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= diff --git a/internal/x/text/cases.go b/internal/x/text/cases.go index 4a95d5ec..d0b93ea2 100644 --- a/internal/x/text/cases.go +++ b/internal/x/text/cases.go @@ -52,6 +52,10 @@ func (c *Caser) Identifierize(s string) string { } } + if ident == "" { + return "Undefined" + } + return ident } diff --git a/pkg/cmputil/opts.go b/pkg/cmputil/opts.go new file mode 100644 index 00000000..a9240a0c --- /dev/null +++ b/pkg/cmputil/opts.go @@ -0,0 +1,16 @@ +package cmputil + +import ( + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" +) + +func Opts(t ...any) []cmp.Option { + opts := make([]cmp.Option, 0) + + for _, v := range t { + opts = append(opts, cmpopts.IgnoreUnexported(v), cmpopts.IgnoreFields(v, "Ref"), cmpopts.IgnoreFields(v, "AnyOf")) + } + + return opts +} diff --git a/pkg/codegen/model.go b/pkg/codegen/model.go index 0229c7c1..718a7838 100644 --- a/pkg/codegen/model.go +++ b/pkg/codegen/model.go @@ -192,9 +192,10 @@ func (i *Import) Generate(out *Emitter) { // TypeDecl is a "type = ". type TypeDecl struct { - Name string - Type Type - Comment string + Name string + Type Type + Comment string + SchemaType *schemas.Type } func (td *TypeDecl) GetName() string { @@ -309,6 +310,7 @@ func (NullType) Generate(out *Emitter) { type StructType struct { Fields []StructField RequiredJSONFields []string + DefaultValue interface{} } func (StructType) IsNillable() bool { return false } diff --git a/pkg/generator/formatter.go b/pkg/generator/formatter.go index cc1b23ca..82e332db 100644 --- a/pkg/generator/formatter.go +++ b/pkg/generator/formatter.go @@ -7,7 +7,7 @@ import ( type formatter interface { addImport(out *codegen.File) - generate(declType codegen.TypeDecl, validators []validator) func(*codegen.Emitter) + generate(output *output, declType codegen.TypeDecl, validators []validator) func(*codegen.Emitter) enumMarshal(declType codegen.TypeDecl) func(*codegen.Emitter) enumUnmarshal( declType codegen.TypeDecl, diff --git a/pkg/generator/generate.go b/pkg/generator/generate.go index 93669e2d..aca0912d 100644 --- a/pkg/generator/generate.go +++ b/pkg/generator/generate.go @@ -16,6 +16,7 @@ const ( varNamePlainStruct = "plain" varNameRawMap = "raw" interfaceTypeName = "interface{}" + typePlain = "Plain" ) var ( @@ -26,6 +27,7 @@ var ( errMapURIToPackageName = errors.New("unable to map schema URI to Go package name") errExpectedNamedType = errors.New("expected named type") errUnsupportedRefFormat = errors.New("unsupported $ref format") + ErrUnsupportedRefExtension = errors.New("unsupported $ref extension") errConflictSameFile = errors.New("conflict: same file") errDefinitionDoesNotExistInSchema = errors.New("definition does not exist in schema") errCannotGenerateReferencedType = errors.New("cannot generate referenced type") @@ -176,10 +178,7 @@ func (g *Generator) findOutputFileForSchemaID(id string) (*output, error) { return g.beginOutput(id, g.config.DefaultOutputName, g.config.DefaultPackageName) } -func (g *Generator) beginOutput( - id string, - outputName, packageName string, -) (*output, error) { +func (g *Generator) beginOutput(id, outputName, packageName string) (*output, error) { if packageName == "" { return nil, fmt.Errorf("%w: %q", errMapURIToPackageName, id) } @@ -215,9 +214,15 @@ func (g *Generator) beginOutput( } func (g *Generator) makeEnumConstantName(typeName, value string) string { + idv := g.caser.Identifierize(value) + + if len(typeName) == 0 { + return "Enum" + idv + } + if strings.ContainsAny(typeName[len(typeName)-1:], "0123456789") { - return typeName + "_" + g.caser.Identifierize(value) + return typeName + "_" + idv } - return typeName + g.caser.Identifierize(value) + return typeName + idv } diff --git a/pkg/generator/json_formatter.go b/pkg/generator/json_formatter.go index 3c5e193b..1e2728d5 100644 --- a/pkg/generator/json_formatter.go +++ b/pkg/generator/json_formatter.go @@ -1,6 +1,8 @@ package generator import ( + "fmt" + "math" "strings" "github.com/atombender/go-jsonschema/pkg/codegen" @@ -12,29 +14,41 @@ const ( type jsonFormatter struct{} -func (jf *jsonFormatter) generate(declType codegen.TypeDecl, validators []validator) func(*codegen.Emitter) { +func (jf *jsonFormatter) generate( + output *output, + declType codegen.TypeDecl, + validators []validator, +) func(*codegen.Emitter) { return func(out *codegen.Emitter) { out.Commentf("Unmarshal%s implements %s.Unmarshaler.", strings.ToUpper(formatJSON), formatJSON) - out.Printlnf("func (j *%s) Unmarshal%s(b []byte) error {", declType.Name, strings.ToUpper(formatJSON)) + out.Printlnf("func (j *%s) Unmarshal%s(value []byte) error {", declType.Name, strings.ToUpper(formatJSON)) out.Indent(1) out.Printlnf("var %s map[string]interface{}", varNameRawMap) - out.Printlnf("if err := %s.Unmarshal(b, &%s); err != nil { return err }", + out.Printlnf("if err := %s.Unmarshal(value, &%s); err != nil { return err }", formatJSON, varNameRawMap) for _, v := range validators { - if v.desc().beforeJSONUnmarshal { - v.generate(out) + if v.desc().beforeUnmarshal { + v.generate(out, "json") + } + } + + tp := typePlain + + if tp == declType.Name { + for i := 0; !output.isUniqueTypeName(tp) && i < math.MaxInt; i++ { + tp = fmt.Sprintf("%s_%d", typePlain, i) } } - out.Printlnf("type Plain %s", declType.Name) - out.Printlnf("var %s Plain", varNamePlainStruct) - out.Printlnf("if err := %s.Unmarshal(b, &%s); err != nil { return err }", + out.Printlnf("type %s %s", tp, declType.Name) + out.Printlnf("var %s %s", varNamePlainStruct, tp) + out.Printlnf("if err := %s.Unmarshal(value, &%s); err != nil { return err }", formatJSON, varNamePlainStruct) for _, v := range validators { - if !v.desc().beforeJSONUnmarshal { - v.generate(out) + if !v.desc().beforeUnmarshal { + v.generate(out, "json") } } diff --git a/pkg/generator/output.go b/pkg/generator/output.go index 035105d5..24039029 100644 --- a/pkg/generator/output.go +++ b/pkg/generator/output.go @@ -3,6 +3,9 @@ package generator import ( "fmt" + "github.com/google/go-cmp/cmp" + + "github.com/atombender/go-jsonschema/pkg/cmputil" "github.com/atombender/go-jsonschema/pkg/codegen" "github.com/atombender/go-jsonschema/pkg/schemas" ) @@ -14,6 +17,38 @@ type output struct { warner func(string) } +func (o *output) getDeclByEqualSchema(name string, t *schemas.Type) *codegen.TypeDecl { + v, ok := o.declsByName[name] + if !ok { + o.warner(fmt.Sprintf("Name not found: %s", name)) + + return nil + } + + if cmp.Equal(v.SchemaType, t, cmputil.Opts(*v.SchemaType, *t)...) { + return v + } + + for count := 1; ; count++ { + suffixed := fmt.Sprintf("%s_%d", name, count) + + sv, ok := o.declsByName[suffixed] + if !ok { + return nil + } + + if cmp.Equal(sv.SchemaType, t, cmputil.Opts(*sv.SchemaType, *t)...) { + return sv + } + } +} + +func (o *output) isUniqueTypeName(name string) bool { + v, ok := o.declsByName[name] + + return !ok || (ok && v.Type == nil) +} + func (o *output) uniqueTypeName(name string) string { v, ok := o.declsByName[name] diff --git a/pkg/generator/schema_generator.go b/pkg/generator/schema_generator.go index 61337240..d10ea9d0 100644 --- a/pkg/generator/schema_generator.go +++ b/pkg/generator/schema_generator.go @@ -4,6 +4,9 @@ import ( "fmt" "strings" + "github.com/google/go-cmp/cmp" + + "github.com/atombender/go-jsonschema/pkg/cmputil" "github.com/atombender/go-jsonschema/pkg/codegen" "github.com/atombender/go-jsonschema/pkg/schemas" ) @@ -199,20 +202,35 @@ func (g *schemaGenerator) generateReferencedType(ref string) (codegen.Type, erro }, nil } -func (g *schemaGenerator) generateDeclaredType( - t *schemas.Type, scope nameScope, -) (codegen.Type, error) { +func (g *schemaGenerator) generateDeclaredType(t *schemas.Type, scope nameScope) (codegen.Type, error) { if decl, ok := g.output.declsBySchema[t]; ok { return &codegen.NamedType{Decl: decl}, nil } + if !g.output.isUniqueTypeName(scope.string()) { + odecl := g.output.declsByName[scope.string()] + + if cmp.Equal(odecl.SchemaType, t, cmputil.Opts(*odecl.SchemaType, *t)...) { + return &codegen.NamedType{Decl: odecl}, nil + } + + if odecl := g.output.declsBySchema[t]; odecl != nil { + return &codegen.NamedType{Decl: odecl}, nil + } + + if odecl := g.output.getDeclByEqualSchema(scope.string(), t); odecl != nil { + return &codegen.NamedType{Decl: odecl}, nil + } + } + if t.Enum != nil { return g.generateEnumType(t, scope) } decl := codegen.TypeDecl{ - Name: g.output.uniqueTypeName(scope.string()), - Comment: t.Description, + Name: g.output.uniqueTypeName(scope.string()), + Comment: t.Description, + SchemaType: t, } g.output.declsBySchema[t] = &decl g.output.declsByName[decl.Name] = &decl @@ -240,6 +258,15 @@ func (g *schemaGenerator) generateDeclaredType( if structType, ok := theType.(*codegen.StructType); ok { var validators []validator + + if t.GetSubSchemaType() == schemas.SubSchemaTypeAnyOf { + validators = append(validators, &anyOfValidator{decl.Name, t.GetSubSchemasCount()}) + + g.generateUnmarshaler(decl, validators) + + return &codegen.NamedType{Decl: &decl}, nil + } + for _, f := range structType.RequiredJSONFields { validators = append(validators, &requiredValidator{f, decl.Name}) } @@ -257,24 +284,16 @@ func (g *schemaGenerator) generateDeclaredType( validators = g.structFieldValidators(validators, f, f.Type, false) } - if len(validators) > 0 { - for _, v := range validators { - if v.desc().hasError { - g.output.file.Package.AddImport("fmt", "") - - break - } - } - - for _, formatter := range g.formatters { - formatter := formatter + if t.IsSubSchemaTypeElem() || len(validators) > 0 { + g.generateUnmarshaler(decl, validators) + } - formatter.addImport(g.output.file) + return &codegen.NamedType{Decl: &decl}, nil + } - g.output.file.Package.AddDecl(&codegen.Method{ - Impl: formatter.generate(decl, validators), - }) - } + if _, ok := theType.(*codegen.MapType); ok { + if t.IsSubSchemaTypeElem() { + g.generateUnmarshaler(decl, []validator{}) } } @@ -339,15 +358,39 @@ func (g *schemaGenerator) structFieldValidators( return validators } -func (g *schemaGenerator) generateType( - t *schemas.Type, scope nameScope, -) (codegen.Type, error) { +func (g *schemaGenerator) generateUnmarshaler(decl codegen.TypeDecl, validators []validator) { + if g.config.OnlyModels { + return + } + + for _, v := range validators { + if _, ok := v.(*anyOfValidator); ok { + g.output.file.Package.AddImport("errors", "") + } + + if v.desc().hasError { + g.output.file.Package.AddImport("fmt", "") + + break + } + } + + for _, formatter := range g.formatters { + formatter := formatter + + formatter.addImport(g.output.file) + + g.output.file.Package.AddDecl(&codegen.Method{ + Impl: formatter.generate(g.output, decl, validators), + }) + } +} + +func (g *schemaGenerator) generateType(t *schemas.Type, scope nameScope) (codegen.Type, error) { typeIndex := 0 var typeShouldBePointer bool - two := 2 - if ext := t.GoJSONSchemaExtension; ext != nil { for _, pkg := range ext.Imports { g.output.file.Package.AddImport(pkg, "") @@ -370,7 +413,7 @@ func (g *schemaGenerator) generateType( return codegen.EmptyInterfaceType{}, nil } - if len(t.Type) == two { + if len(t.Type) == 2 { for i, t := range t.Type { if t == "null" { typeShouldBePointer = true @@ -424,10 +467,7 @@ func (g *schemaGenerator) generateType( } } -func (g *schemaGenerator) generateStructType( - t *schemas.Type, - scope nameScope, -) (codegen.Type, error) { +func (g *schemaGenerator) generateStructType(t *schemas.Type, scope nameScope) (codegen.Type, error) { if len(t.Properties) == 0 { if len(t.Required) > 0 { g.warner("Object type with no properties has required fields; " + @@ -439,7 +479,7 @@ func (g *schemaGenerator) generateStructType( var err error if t.AdditionalProperties != nil { - if valueType, err = g.generateType(t.AdditionalProperties, nil); err != nil { + if valueType, err = g.generateType(t.AdditionalProperties, scope.add("Value")); err != nil { return nil, err } } @@ -459,7 +499,7 @@ func (g *schemaGenerator) generateStructType( var structType codegen.StructType - for _, name := range sortPropertiesByName(t.Properties) { + for _, name := range sortedKeys(t.Properties) { prop := t.Properties[name] isRequired := requiredNames[name] @@ -532,19 +572,23 @@ func (g *schemaGenerator) generateStructType( structType.AddField(structField) } + if t.Default != nil { + structType.DefaultValue = g.defaultPropertyValue(t) + } + return &structType, nil } func (g *schemaGenerator) defaultPropertyValue(prop *schemas.Type) any { if prop.AdditionalProperties != nil { if len(prop.AdditionalProperties.Type) == 0 { - return map[string]any{} + return prop.Default } if len(prop.AdditionalProperties.Type) != 1 { g.warner("Additional property has multiple types; will be represented as an empty interface with no validation") - return map[string]any{} + return prop.Default } switch prop.AdditionalProperties.Type[0] { @@ -564,19 +608,14 @@ func (g *schemaGenerator) defaultPropertyValue(prop *schemas.Type) any { return map[string]bool{} default: - return map[string]any{} + return prop.Default } } return prop.Default } -func (g *schemaGenerator) generateTypeInline( - t *schemas.Type, - scope nameScope, -) (codegen.Type, error) { - two := 2 - +func (g *schemaGenerator) generateTypeInline(t *schemas.Type, scope nameScope) (codegen.Type, error) { if t.Enum == nil && t.Ref == "" { if ext := t.GoJSONSchemaExtension; ext != nil { for _, pkg := range ext.Imports { @@ -588,11 +627,37 @@ func (g *schemaGenerator) generateTypeInline( } } + if len(t.AnyOf) > 0 { + for i, typ := range t.AnyOf { + typ.SetSubSchemaTypeElem() + + if _, err := g.generateTypeInline(typ, scope.add(fmt.Sprintf("_%d", i))); err != nil { + return nil, err + } + } + + anyOfType, err := schemas.AnyOf(t.AnyOf) + if err != nil { + return nil, fmt.Errorf("could not merge anyOf types: %w", err) + } + + return g.generateTypeInline(anyOfType, scope) + } + + if len(t.AllOf) > 0 { + allOfType, err := schemas.AllOf(t.AllOf) + if err != nil { + return nil, fmt.Errorf("could not merge allOf types: %w", err) + } + + return g.generateTypeInline(allOfType, scope) + } + typeIndex := 0 var typeShouldBePointer bool - if len(t.Type) == two { + if len(t.Type) == 2 { for i, t := range t.Type { if t == "null" { typeShouldBePointer = true @@ -613,6 +678,10 @@ func (g *schemaGenerator) generateTypeInline( } if schemas.IsPrimitiveType(t.Type[typeIndex]) { + if t.IsSubSchemaTypeElem() { + return nil, nil + } + cg, err := codegen.PrimitiveTypeFromJSONSchemaType(t.Type[typeIndex], t.Format, typeShouldBePointer) if err != nil { return nil, fmt.Errorf("invalid type %q: %w", t.Type[typeIndex], err) @@ -650,9 +719,7 @@ func (g *schemaGenerator) generateTypeInline( return g.generateDeclaredType(t, scope) } -func (g *schemaGenerator) generateEnumType( - t *schemas.Type, scope nameScope, -) (codegen.Type, error) { +func (g *schemaGenerator) generateEnumType(t *schemas.Type, scope nameScope) (codegen.Type, error) { if len(t.Enum) == 0 { return nil, errEnumArrCannotBeEmpty } @@ -732,8 +799,9 @@ func (g *schemaGenerator) generateEnumType( } enumDecl := codegen.TypeDecl{ - Name: g.output.uniqueTypeName(scope.string()), - Type: enumType, + Name: g.output.uniqueTypeName(scope.string()), + Type: enumType, + SchemaType: t, } g.output.file.Package.AddDecl(&enumDecl) diff --git a/pkg/generator/utils.go b/pkg/generator/utils.go index 77aff3e1..07180923 100644 --- a/pkg/generator/utils.go +++ b/pkg/generator/utils.go @@ -7,7 +7,7 @@ import ( "github.com/atombender/go-jsonschema/pkg/schemas" ) -func sortPropertiesByName(props map[string]*schemas.Type) []string { +func sortedKeys[T any](props map[string]T) []string { names := make([]string, 0, len(props)) for name := range props { names = append(names, name) diff --git a/pkg/generator/validator.go b/pkg/generator/validator.go index bd8d1fba..c1578193 100644 --- a/pkg/generator/validator.go +++ b/pkg/generator/validator.go @@ -12,13 +12,13 @@ import ( ) type validator interface { - generate(out *codegen.Emitter) + generate(out *codegen.Emitter, format string) desc() *validatorDesc } type validatorDesc struct { - hasError bool - beforeJSONUnmarshal bool + hasError bool + beforeUnmarshal bool } var ( @@ -27,6 +27,7 @@ var ( _ validator = new(defaultValidator) _ validator = new(arrayValidator) _ validator = new(stringValidator) + _ validator = new(anyOfValidator) ) type requiredValidator struct { @@ -34,7 +35,7 @@ type requiredValidator struct { declName string } -func (v *requiredValidator) generate(out *codegen.Emitter) { +func (v *requiredValidator) generate(out *codegen.Emitter, format string) { out.Printlnf(`if v, ok := %s["%s"]; !ok || v == nil {`, varNameRawMap, v.jsonName) out.Indent(1) out.Printlnf(`return fmt.Errorf("field %s in %s: required")`, v.jsonName, v.declName) @@ -44,8 +45,8 @@ func (v *requiredValidator) generate(out *codegen.Emitter) { func (v *requiredValidator) desc() *validatorDesc { return &validatorDesc{ - hasError: true, - beforeJSONUnmarshal: true, + hasError: true, + beforeUnmarshal: true, } } @@ -55,7 +56,7 @@ type nullTypeValidator struct { arrayDepth int } -func (v *nullTypeValidator) generate(out *codegen.Emitter) { +func (v *nullTypeValidator) generate(out *codegen.Emitter, format string) { value := fmt.Sprintf("%s.%s", varNamePlainStruct, v.fieldName) fieldName := v.jsonName @@ -90,8 +91,8 @@ func (v *nullTypeValidator) generate(out *codegen.Emitter) { func (v *nullTypeValidator) desc() *validatorDesc { return &validatorDesc{ - hasError: true, - beforeJSONUnmarshal: false, + hasError: true, + beforeUnmarshal: false, } } @@ -102,12 +103,8 @@ type defaultValidator struct { defaultValue interface{} } -func (v *defaultValidator) generate(out *codegen.Emitter) { - defaultValue, err := v.tryDumpDefaultSlice(out.MaxLineLength()) - if err != nil { - // Fallback to sdump in case we couldn't dump it properly. - defaultValue = litter.Sdump(v.defaultValue) - } +func (v *defaultValidator) generate(out *codegen.Emitter, format string) { + defaultValue := v.dumpDefaultValue(out) out.Printlnf(`if v, ok := %s["%s"]; !ok || v == nil {`, varNameRawMap, v.jsonName) out.Indent(1) @@ -116,6 +113,30 @@ func (v *defaultValidator) generate(out *codegen.Emitter) { out.Printlnf("}") } +func (v *defaultValidator) dumpDefaultValue(out *codegen.Emitter) any { + nt, ok := v.defaultValueType.(*codegen.NamedType) + if v.defaultValueType != nil && ok { + dvm, ok := v.defaultValue.(map[string]any) + if ok { + namedFields := "" + for _, k := range sortedKeys(dvm) { + namedFields += fmt.Sprintf("\n%s: %s,", upperFirst(k), litter.Sdump(dvm[k])) + } + + namedFields += "\n" + + return fmt.Sprintf(`%s{%s}`, nt.Decl.GetName(), namedFields) + } + } + + if defaultValue, err := v.tryDumpDefaultSlice(out.MaxLineLength()); err == nil { + return defaultValue + } + + // Fallback to sdump in case we couldn't dump it properly. + return litter.Sdump(v.defaultValue) +} + func (v *defaultValidator) tryDumpDefaultSlice(maxLineLen uint) (string, error) { tmpEmitter := codegen.NewEmitter(maxLineLen) v.defaultValueType.Generate(tmpEmitter) @@ -144,8 +165,8 @@ func (v *defaultValidator) tryDumpDefaultSlice(maxLineLen uint) (string, error) func (v *defaultValidator) desc() *validatorDesc { return &validatorDesc{ - hasError: false, - beforeJSONUnmarshal: false, + hasError: false, + beforeUnmarshal: false, } } @@ -157,7 +178,7 @@ type arrayValidator struct { maxItems int } -func (v *arrayValidator) generate(out *codegen.Emitter) { +func (v *arrayValidator) generate(out *codegen.Emitter, format string) { if v.minItems == 0 && v.maxItems == 0 { return } @@ -206,8 +227,8 @@ func (v *arrayValidator) generate(out *codegen.Emitter) { func (v *arrayValidator) desc() *validatorDesc { return &validatorDesc{ - hasError: true, - beforeJSONUnmarshal: false, + hasError: true, + beforeUnmarshal: false, } } @@ -219,7 +240,7 @@ type stringValidator struct { isNillable bool } -func (v *stringValidator) generate(out *codegen.Emitter) { +func (v *stringValidator) generate(out *codegen.Emitter, format string) { if v.minLength == 0 && v.maxLength == 0 { return } @@ -254,7 +275,54 @@ func (v *stringValidator) generate(out *codegen.Emitter) { func (v *stringValidator) desc() *validatorDesc { return &validatorDesc{ - hasError: true, - beforeJSONUnmarshal: false, + hasError: true, + beforeUnmarshal: false, + } +} + +type anyOfValidator struct { + fieldName string + elemCount int +} + +func (v *anyOfValidator) generate(out *codegen.Emitter, format string) { + for i := 0; i < v.elemCount; i++ { + out.Printlnf(`var %s_%d %s_%d`, lowerFirst(v.fieldName), i, upperFirst(v.fieldName), i) + } + + out.Printlnf(`var errs []error`) + + for i := 0; i < v.elemCount; i++ { + out.Printlnf( + `if err := %s_%d.Unmarshal%s(value); err != nil {`, + lowerFirst(v.fieldName), + i, + strings.ToUpper(format), + ) + out.Indent(1) + out.Printlnf(`errs = append(errs, err)`) + out.Indent(-1) + out.Printlnf(`}`) + } + + out.Printlnf("if len(errs) == %d {", v.elemCount) + out.Indent(1) + out.Printlnf(`return fmt.Errorf("all validators failed: %%s", errors.Join(errs...))`) + out.Indent(-1) + out.Printlnf("}") +} + +func (v *anyOfValidator) desc() *validatorDesc { + return &validatorDesc{ + hasError: true, + beforeUnmarshal: true, } } + +func lowerFirst(s string) string { + return strings.ToLower(s[:1]) + s[1:] +} + +func upperFirst(s string) string { + return strings.ToUpper(s[:1]) + s[1:] +} diff --git a/pkg/generator/yaml_formatter.go b/pkg/generator/yaml_formatter.go index aff67bb6..017eed74 100644 --- a/pkg/generator/yaml_formatter.go +++ b/pkg/generator/yaml_formatter.go @@ -1,6 +1,8 @@ package generator import ( + "fmt" + "math" "strings" "github.com/atombender/go-jsonschema/pkg/codegen" @@ -13,7 +15,11 @@ const ( type yamlFormatter struct{} -func (yf *yamlFormatter) generate(declType codegen.TypeDecl, validators []validator) func(*codegen.Emitter) { +func (yf *yamlFormatter) generate( + output *output, + declType codegen.TypeDecl, + validators []validator, +) func(*codegen.Emitter) { return func(out *codegen.Emitter) { out.Commentf("Unmarshal%s implements %s.Unmarshaler.", strings.ToUpper(formatYAML), formatYAML) out.Printlnf("func (j *%s) Unmarshal%s(value *yaml.Node) error {", declType.Name, @@ -23,18 +29,26 @@ func (yf *yamlFormatter) generate(declType codegen.TypeDecl, validators []valida out.Printlnf("if err := value.Decode(&%s); err != nil { return err }", varNameRawMap) for _, v := range validators { - if v.desc().beforeJSONUnmarshal { - v.generate(out) + if v.desc().beforeUnmarshal { + v.generate(out, "yaml") + } + } + + tp := typePlain + + if tp == declType.Name { + for i := 0; !output.isUniqueTypeName(tp) && i < math.MaxInt; i++ { + tp = fmt.Sprintf("%s_%d", typePlain, i) } } - out.Printlnf("type Plain %s", declType.Name) - out.Printlnf("var %s Plain", varNamePlainStruct) + out.Printlnf("type %s %s", tp, declType.Name) + out.Printlnf("var %s %s", varNamePlainStruct, tp) out.Printlnf("if err := value.Decode(&%s); err != nil { return err }", varNamePlainStruct) for _, v := range validators { - if !v.desc().beforeJSONUnmarshal { - v.generate(out) + if !v.desc().beforeUnmarshal { + v.generate(out, "yaml") } } diff --git a/pkg/schemas/model.go b/pkg/schemas/model.go index 8339234d..45d9401c 100644 --- a/pkg/schemas/model.go +++ b/pkg/schemas/model.go @@ -26,6 +26,14 @@ package schemas import ( "encoding/json" "fmt" + "reflect" + + "dario.cat/mergo" +) + +var ( + ErrCannotMergeTypes = fmt.Errorf("cannot merge types") + ErrEmptyTypesList = fmt.Errorf("types list is empty") ) // Schema is the root schema. @@ -106,6 +114,15 @@ func (t *TypeList) UnmarshalJSON(b []byte) error { // RFC draft-wright-json-schema-validation-00, section 5.26. type Definitions map[string]*Type +type SubSchemaType string + +const ( + SubSchemaTypeAllOf SubSchemaType = "allOf" + SubSchemaTypeAnyOf SubSchemaType = "anyOf" + SubSchemaTypeOneOf SubSchemaType = "oneOf" + SubSchemaTypeNot SubSchemaType = "not" +) + // Type represents a JSON Schema object type. type Type struct { // RFC draft-wright-json-schema-00. @@ -133,10 +150,11 @@ type Type struct { AdditionalProperties *Type `json:"additionalProperties,omitempty"` // Section 5.18. Enum []interface{} `json:"enum,omitempty"` // Section 5.20. Type TypeList `json:"type,omitempty"` // Section 5.21. - AllOf []*Type `json:"allOf,omitempty"` // Section 5.22. - AnyOf []*Type `json:"anyOf,omitempty"` // Section 5.23. - OneOf []*Type `json:"oneOf,omitempty"` // Section 5.24. - Not *Type `json:"not,omitempty"` // Section 5.25. + // RFC draft-bhutton-json-schema-01, section 10. + AllOf []*Type `json:"allOf,omitempty"` // Section 10.2.1.1. + AnyOf []*Type `json:"anyOf,omitempty"` // Section 10.2.1.2. + OneOf []*Type `json:"oneOf,omitempty"` // Section 10.2.1.3. + Not *Type `json:"not,omitempty"` // Section 10.2.1.4. // RFC draft-wright-json-schema-validation-00, section 6, 7. Title string `json:"title,omitempty"` // Section 6.1. Description string `json:"description,omitempty"` // Section 6.1. @@ -154,6 +172,35 @@ type Type struct { // ExtGoCustomType is the name of a (qualified or not) custom Go type // to use for the field. GoJSONSchemaExtension *GoJSONSchemaExtension `json:"goJSONSchema,omitempty"` //nolint:tagliatelle // breaking change + + // SubSchemaType marks the type as being a subschema type. + subSchemaType SubSchemaType + subSchemasCount int + subSchemaTypeElem bool +} + +func (value *Type) SetSubSchemaType(sst SubSchemaType) { + value.subSchemaType = sst +} + +func (value *Type) GetSubSchemaType() SubSchemaType { + return value.subSchemaType +} + +func (value *Type) SetSubSchemasCount(ssc int) { + value.subSchemasCount = ssc +} + +func (value *Type) GetSubSchemasCount() int { + return value.subSchemasCount +} + +func (value *Type) IsSubSchemaTypeElem() bool { + return value.subSchemaTypeElem +} + +func (value *Type) SetSubSchemaTypeElem() { + value.subSchemaTypeElem = true } // UnmarshalJSON accepts booleans as schemas where `true` is equivalent to `{}` @@ -198,6 +245,66 @@ func (value *Type) UnmarshalJSON(raw []byte) error { return nil } +func AllOf(types []*Type) (*Type, error) { + typ, err := MergeTypes(types) + if err != nil { + return nil, err + } + + typ.subSchemaType = SubSchemaTypeAllOf + + return typ, nil +} + +func AnyOf(types []*Type) (*Type, error) { + typ, err := MergeTypes(types) + if err != nil { + return nil, err + } + + typ.subSchemaType = SubSchemaTypeAnyOf + typ.subSchemasCount = len(types) + + return typ, nil +} + +func MergeTypes(types []*Type) (*Type, error) { + if len(types) == 0 { + return nil, ErrEmptyTypesList + } + + result := &Type{} + + if isPrimitiveTypeList(types) { + return result, nil + } + + opts := []func(*mergo.Config){ + mergo.WithAppendSlice, + mergo.WithTransformers(typeListTransformer{}), + } + + for _, t := range types { + if err := mergo.Merge(result, t, opts...); err != nil { + return nil, fmt.Errorf("%w: %w", ErrCannotMergeTypes, err) + } + } + + return result, nil +} + +type typeListTransformer struct{} + +func (t typeListTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { + if typ == reflect.TypeOf(TypeList{}) { + return func(dst, src reflect.Value) error { + return nil + } + } + + return nil +} + type GoJSONSchemaExtension struct { Type *string `json:"type,omitempty"` Identifier *string `json:"identifier,omitempty"` diff --git a/pkg/schemas/types.go b/pkg/schemas/types.go index 84de9bd0..0058c280 100644 --- a/pkg/schemas/types.go +++ b/pkg/schemas/types.go @@ -19,3 +19,17 @@ func IsPrimitiveType(t string) bool { return false } } + +func isPrimitiveTypeList(types []*Type) bool { + for _, typ := range types { + if len(typ.Type) == 0 { + continue + } + + if !IsPrimitiveType(typ.Type[0]) { + return false + } + } + + return true +} diff --git a/tests/data/core/allOf/allOf.go b/tests/data/core/allOf/allOf.go new file mode 100644 index 00000000..fcd447e0 --- /dev/null +++ b/tests/data/core/allOf/allOf.go @@ -0,0 +1,62 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package test + +import "encoding/json" +import "fmt" +import yaml "gopkg.in/yaml.v3" + +type AllOfConfigurationsElem struct { + // Bar corresponds to the JSON schema field "bar". + Bar float64 `json:"bar" yaml:"bar" mapstructure:"bar"` + + // Foo corresponds to the JSON schema field "foo". + Foo string `json:"foo" yaml:"foo" mapstructure:"foo"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *AllOfConfigurationsElem) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + if v, ok := raw["bar"]; !ok || v == nil { + return fmt.Errorf("field bar in AllOfConfigurationsElem: required") + } + if v, ok := raw["foo"]; !ok || v == nil { + return fmt.Errorf("field foo in AllOfConfigurationsElem: required") + } + type Plain AllOfConfigurationsElem + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = AllOfConfigurationsElem(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *AllOfConfigurationsElem) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["bar"]; !ok || v == nil { + return fmt.Errorf("field bar in AllOfConfigurationsElem: required") + } + if v, ok := raw["foo"]; !ok || v == nil { + return fmt.Errorf("field foo in AllOfConfigurationsElem: required") + } + type Plain AllOfConfigurationsElem + var plain Plain + if err := value.Decode(&plain); err != nil { + return err + } + *j = AllOfConfigurationsElem(plain) + return nil +} + +type AllOf struct { + // Configurations corresponds to the JSON schema field "configurations". + Configurations []AllOfConfigurationsElem `json:"configurations,omitempty" yaml:"configurations,omitempty" mapstructure:"configurations,omitempty"` +} diff --git a/tests/data/core/allOf/allOf.json b/tests/data/core/allOf/allOf.json new file mode 100644 index 00000000..bf6ca62b --- /dev/null +++ b/tests/data/core/allOf/allOf.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "id": "https://example.com/objectAllOf", + "type": "object", + "properties": { + "configurations": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "required": [ + "foo" + ] + }, + { + "type": "object", + "properties": { + "bar": { + "type": "number" + } + }, + "required": [ + "bar" + ] + } + ] + } + } + } +} diff --git a/tests/data/core/anyOf/anyOf.go b/tests/data/core/anyOf/anyOf.go new file mode 100644 index 00000000..358396a1 --- /dev/null +++ b/tests/data/core/anyOf/anyOf.go @@ -0,0 +1,206 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package test + +import "encoding/json" +import "errors" +import "fmt" +import yaml "gopkg.in/yaml.v3" + +type AnyOf struct { + // Configurations corresponds to the JSON schema field "configurations". + Configurations []AnyOfConfigurationsElem `json:"configurations,omitempty" yaml:"configurations,omitempty" mapstructure:"configurations,omitempty"` + + // Flags corresponds to the JSON schema field "flags". + Flags interface{} `json:"flags,omitempty" yaml:"flags,omitempty" mapstructure:"flags,omitempty"` +} + +type AnyOfConfigurationsElem struct { + // Bar corresponds to the JSON schema field "bar". + Bar float64 `json:"bar" yaml:"bar" mapstructure:"bar"` + + // Baz corresponds to the JSON schema field "baz". + Baz *bool `json:"baz,omitempty" yaml:"baz,omitempty" mapstructure:"baz,omitempty"` + + // Foo corresponds to the JSON schema field "foo". + Foo string `json:"foo" yaml:"foo" mapstructure:"foo"` +} + +type AnyOfConfigurationsElem_0 struct { + // Foo corresponds to the JSON schema field "foo". + Foo string `json:"foo" yaml:"foo" mapstructure:"foo"` +} + +type AnyOfConfigurationsElem_1 struct { + // Bar corresponds to the JSON schema field "bar". + Bar float64 `json:"bar" yaml:"bar" mapstructure:"bar"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *AnyOfConfigurationsElem_1) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + if v, ok := raw["bar"]; !ok || v == nil { + return fmt.Errorf("field bar in AnyOfConfigurationsElem_1: required") + } + type Plain AnyOfConfigurationsElem_1 + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = AnyOfConfigurationsElem_1(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *AnyOfConfigurationsElem_1) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["bar"]; !ok || v == nil { + return fmt.Errorf("field bar in AnyOfConfigurationsElem_1: required") + } + type Plain AnyOfConfigurationsElem_1 + var plain Plain + if err := value.Decode(&plain); err != nil { + return err + } + *j = AnyOfConfigurationsElem_1(plain) + return nil +} + +type AnyOfConfigurationsElem_2 struct { + // Baz corresponds to the JSON schema field "baz". + Baz *bool `json:"baz,omitempty" yaml:"baz,omitempty" mapstructure:"baz,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *AnyOfConfigurationsElem_2) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + type Plain AnyOfConfigurationsElem_2 + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = AnyOfConfigurationsElem_2(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *AnyOfConfigurationsElem_2) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain AnyOfConfigurationsElem_2 + var plain Plain + if err := value.Decode(&plain); err != nil { + return err + } + *j = AnyOfConfigurationsElem_2(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *AnyOfConfigurationsElem_0) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["foo"]; !ok || v == nil { + return fmt.Errorf("field foo in AnyOfConfigurationsElem_0: required") + } + type Plain AnyOfConfigurationsElem_0 + var plain Plain + if err := value.Decode(&plain); err != nil { + return err + } + *j = AnyOfConfigurationsElem_0(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *AnyOfConfigurationsElem) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + var anyOfConfigurationsElem_0 AnyOfConfigurationsElem_0 + var anyOfConfigurationsElem_1 AnyOfConfigurationsElem_1 + var anyOfConfigurationsElem_2 AnyOfConfigurationsElem_2 + var errs []error + if err := anyOfConfigurationsElem_0.UnmarshalJSON(value); err != nil { + errs = append(errs, err) + } + if err := anyOfConfigurationsElem_1.UnmarshalJSON(value); err != nil { + errs = append(errs, err) + } + if err := anyOfConfigurationsElem_2.UnmarshalJSON(value); err != nil { + errs = append(errs, err) + } + if len(errs) == 3 { + return fmt.Errorf("all validators failed: %s", errors.Join(errs...)) + } + type Plain AnyOfConfigurationsElem + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = AnyOfConfigurationsElem(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *AnyOfConfigurationsElem) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + var anyOfConfigurationsElem_0 AnyOfConfigurationsElem_0 + var anyOfConfigurationsElem_1 AnyOfConfigurationsElem_1 + var anyOfConfigurationsElem_2 AnyOfConfigurationsElem_2 + var errs []error + if err := anyOfConfigurationsElem_0.UnmarshalYAML(value); err != nil { + errs = append(errs, err) + } + if err := anyOfConfigurationsElem_1.UnmarshalYAML(value); err != nil { + errs = append(errs, err) + } + if err := anyOfConfigurationsElem_2.UnmarshalYAML(value); err != nil { + errs = append(errs, err) + } + if len(errs) == 3 { + return fmt.Errorf("all validators failed: %s", errors.Join(errs...)) + } + type Plain AnyOfConfigurationsElem + var plain Plain + if err := value.Decode(&plain); err != nil { + return err + } + *j = AnyOfConfigurationsElem(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *AnyOfConfigurationsElem_0) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + if v, ok := raw["foo"]; !ok || v == nil { + return fmt.Errorf("field foo in AnyOfConfigurationsElem_0: required") + } + type Plain AnyOfConfigurationsElem_0 + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = AnyOfConfigurationsElem_0(plain) + return nil +} diff --git a/tests/data/core/anyOf/anyOf.json b/tests/data/core/anyOf/anyOf.json new file mode 100644 index 00000000..d7433a7f --- /dev/null +++ b/tests/data/core/anyOf/anyOf.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "id": "https://example.com/objectAllOf", + "type": "object", + "properties": { + "flags": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, + "configurations": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "required": [ + "foo" + ] + }, + { + "type": "object", + "properties": { + "bar": { + "type": "number" + } + }, + "required": [ + "bar" + ] + }, + { + "type": "object", + "properties": { + "baz": { + "type": "boolean" + } + } + } + ] + } + } + } +} diff --git a/tests/data/core/array/array.go b/tests/data/core/array/array.go index 9b91d96c..c7a83351 100644 --- a/tests/data/core/array/array.go +++ b/tests/data/core/array/array.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" type Array struct { // MyArray corresponds to the JSON schema field "myArray". @@ -34,14 +35,41 @@ type Array struct { type ArrayMyObjectArrayElem map[string]interface{} // UnmarshalJSON implements json.Unmarshaler. -func (j *Array) UnmarshalJSON(b []byte) error { +func (j *Array) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } type Plain Array var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + for i0 := range plain.MyNestedNullArray { + for i1 := range plain.MyNestedNullArray[i0] { + if plain.MyNestedNullArray[i0][i1] != nil { + return fmt.Errorf("field %s: must be null", fmt.Sprintf("myNestedNullArray[%d][%d]", i0, i1)) + } + } + } + for i0 := range plain.MyNullArray { + if plain.MyNullArray[i0] != nil { + return fmt.Errorf("field %s: must be null", fmt.Sprintf("myNullArray[%d]", i0)) + } + } + *j = Array(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *Array) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain Array + var plain Plain + if err := value.Decode(&plain); err != nil { return err } for i0 := range plain.MyNestedNullArray { diff --git a/tests/data/core/date/date.go b/tests/data/core/date/date.go index adbf75d1..55cf5763 100644 --- a/tests/data/core/date/date.go +++ b/tests/data/core/date/date.go @@ -5,6 +5,7 @@ package test import "encoding/json" import "fmt" import "github.com/atombender/go-jsonschema/pkg/types" +import yaml "gopkg.in/yaml.v3" type DateMyObject struct { // MyDate corresponds to the JSON schema field "myDate". @@ -12,9 +13,9 @@ type DateMyObject struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *DateMyObject) UnmarshalJSON(b []byte) error { +func (j *DateMyObject) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } if v, ok := raw["myDate"]; !ok || v == nil { @@ -22,7 +23,25 @@ func (j *DateMyObject) UnmarshalJSON(b []byte) error { } type Plain DateMyObject var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = DateMyObject(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *DateMyObject) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["myDate"]; !ok || v == nil { + return fmt.Errorf("field myDate in DateMyObject: required") + } + type Plain DateMyObject + var plain Plain + if err := value.Decode(&plain); err != nil { return err } *j = DateMyObject(plain) diff --git a/tests/data/core/dateTime/dateTime.go b/tests/data/core/dateTime/dateTime.go index b2e40e0a..6b4a7b22 100644 --- a/tests/data/core/dateTime/dateTime.go +++ b/tests/data/core/dateTime/dateTime.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" import "time" type DateTimeMyObject struct { @@ -12,9 +13,9 @@ type DateTimeMyObject struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *DateTimeMyObject) UnmarshalJSON(b []byte) error { +func (j *DateTimeMyObject) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } if v, ok := raw["myDateTime"]; !ok || v == nil { @@ -22,7 +23,25 @@ func (j *DateTimeMyObject) UnmarshalJSON(b []byte) error { } type Plain DateTimeMyObject var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = DateTimeMyObject(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *DateTimeMyObject) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["myDateTime"]; !ok || v == nil { + return fmt.Errorf("field myDateTime in DateTimeMyObject: required") + } + type Plain DateTimeMyObject + var plain Plain + if err := value.Decode(&plain); err != nil { return err } *j = DateTimeMyObject(plain) diff --git a/tests/data/core/ip/ip.go b/tests/data/core/ip/ip.go index 5794f272..5f80010d 100644 --- a/tests/data/core/ip/ip.go +++ b/tests/data/core/ip/ip.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" import "net/netip" type IpMyObject struct { @@ -12,9 +13,9 @@ type IpMyObject struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *IpMyObject) UnmarshalJSON(b []byte) error { +func (j *IpMyObject) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } if v, ok := raw["myIp"]; !ok || v == nil { @@ -22,7 +23,25 @@ func (j *IpMyObject) UnmarshalJSON(b []byte) error { } type Plain IpMyObject var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = IpMyObject(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *IpMyObject) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["myIp"]; !ok || v == nil { + return fmt.Errorf("field myIp in IpMyObject: required") + } + type Plain IpMyObject + var plain Plain + if err := value.Decode(&plain); err != nil { return err } *j = IpMyObject(plain) diff --git a/tests/data/core/object/object.go b/tests/data/core/object/object.go index cb852542..e1fcbf08 100644 --- a/tests/data/core/object/object.go +++ b/tests/data/core/object/object.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" type ObjectMyObject struct { // MyString corresponds to the JSON schema field "myString". @@ -11,9 +12,9 @@ type ObjectMyObject struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *ObjectMyObject) UnmarshalJSON(b []byte) error { +func (j *ObjectMyObject) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } if v, ok := raw["myString"]; !ok || v == nil { @@ -21,7 +22,25 @@ func (j *ObjectMyObject) UnmarshalJSON(b []byte) error { } type Plain ObjectMyObject var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = ObjectMyObject(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *ObjectMyObject) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["myString"]; !ok || v == nil { + return fmt.Errorf("field myString in ObjectMyObject: required") + } + type Plain ObjectMyObject + var plain Plain + if err := value.Decode(&plain); err != nil { return err } *j = ObjectMyObject(plain) diff --git a/tests/data/core/objectAdditionalProperties/objectAdditionalProperties.go b/tests/data/core/objectAdditionalProperties/objectAdditionalProperties.go index 358312c2..d1041f1a 100644 --- a/tests/data/core/objectAdditionalProperties/objectAdditionalProperties.go +++ b/tests/data/core/objectAdditionalProperties/objectAdditionalProperties.go @@ -3,6 +3,7 @@ package test import "encoding/json" +import yaml "gopkg.in/yaml.v3" type ObjectAdditionalProperties struct { // Foo corresponds to the JSON schema field "foo". @@ -12,14 +13,32 @@ type ObjectAdditionalProperties struct { type ObjectAdditionalPropertiesFoo map[string]string // UnmarshalJSON implements json.Unmarshaler. -func (j *ObjectAdditionalProperties) UnmarshalJSON(b []byte) error { +func (j *ObjectAdditionalProperties) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } type Plain ObjectAdditionalProperties var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if v, ok := raw["foo"]; !ok || v == nil { + plain.Foo = map[string]string{} + } + *j = ObjectAdditionalProperties(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *ObjectAdditionalProperties) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain ObjectAdditionalProperties + var plain Plain + if err := value.Decode(&plain); err != nil { return err } if v, ok := raw["foo"]; !ok || v == nil { diff --git a/tests/data/core/objectPropertiesDefault/objectPropertiesDefault.go b/tests/data/core/objectPropertiesDefault/objectPropertiesDefault.go new file mode 100644 index 00000000..af6be8c3 --- /dev/null +++ b/tests/data/core/objectPropertiesDefault/objectPropertiesDefault.go @@ -0,0 +1,399 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package test + +import "encoding/json" +import "errors" +import "fmt" +import yaml "gopkg.in/yaml.v3" +import "reflect" + +type DecoratedPlanner struct { + // Decorator corresponds to the JSON schema field "decorator". + Decorator DecoratedPlannerDecorator `json:"decorator,omitempty" yaml:"decorator,omitempty" mapstructure:"decorator,omitempty"` + + // Event corresponds to the JSON schema field "event". + Event *Event `json:"event,omitempty" yaml:"event,omitempty" mapstructure:"event,omitempty"` +} + +type DecoratedPlannerDecorator struct { + // Color corresponds to the JSON schema field "color". + Color string `json:"color,omitempty" yaml:"color,omitempty" mapstructure:"color,omitempty"` + + // Theme corresponds to the JSON schema field "theme". + Theme *string `json:"theme,omitempty" yaml:"theme,omitempty" mapstructure:"theme,omitempty"` +} + +type DefaultPlanner struct { + // Event corresponds to the JSON schema field "event". + Event *Event `json:"event,omitempty" yaml:"event,omitempty" mapstructure:"event,omitempty"` +} + +type Event struct { + // Name corresponds to the JSON schema field "name". + Name *EventName `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` + + // Tags corresponds to the JSON schema field "tags". + Tags []EventTagsElem `json:"tags,omitempty" yaml:"tags,omitempty" mapstructure:"tags,omitempty"` +} + +type EventName string + +const EventNameBIRTHDAY EventName = "BIRTHDAY" +const EventNameGAME EventName = "GAME" + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *DecoratedPlannerDecorator) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain DecoratedPlannerDecorator + var plain Plain + if err := value.Decode(&plain); err != nil { + return err + } + if v, ok := raw["color"]; !ok || v == nil { + plain.Color = "#ffffff" + } + *j = DecoratedPlannerDecorator(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *EventName) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EventName { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EventName, v) + } + *j = EventName(v) + return nil +} + +const EventNameHOLIDAY EventName = "HOLIDAY" + +type EventTagsElem string + +var enumValues_EventTagsElem = []interface{}{ + "COUNTRY", + "REGION", + "CITY", + "PERSON", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *EventTagsElem) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EventTagsElem { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EventTagsElem, v) + } + *j = EventTagsElem(v) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EventTagsElem) UnmarshalYAML(value *yaml.Node) error { + var v string + if err := value.Decode(&v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EventTagsElem { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EventTagsElem, v) + } + *j = EventTagsElem(v) + return nil +} + +const EventTagsElemCOUNTRY EventTagsElem = "COUNTRY" +const EventTagsElemREGION EventTagsElem = "REGION" +const EventTagsElemCITY EventTagsElem = "CITY" +const EventTagsElemPERSON EventTagsElem = "PERSON" + +var enumValues_EventName = []interface{}{ + "BIRTHDAY", + "GAME", + "HOLIDAY", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Event) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + type Plain Event + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if v, ok := raw["tags"]; !ok || v == nil { + plain.Tags = []EventTagsElem{} + } + *j = Event(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *Event) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain Event + var plain Plain + if err := value.Decode(&plain); err != nil { + return err + } + if v, ok := raw["tags"]; !ok || v == nil { + plain.Tags = []EventTagsElem{} + } + *j = Event(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EventName) UnmarshalYAML(value *yaml.Node) error { + var v string + if err := value.Decode(&v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EventName { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EventName, v) + } + *j = EventName(v) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *DecoratedPlanner) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + type Plain DecoratedPlanner + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if v, ok := raw["decorator"]; !ok || v == nil { + plain.Decorator = DecoratedPlannerDecorator{ + Color: "#ffffff", + Theme: nil, + } + } + *j = DecoratedPlanner(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *DecoratedPlanner) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain DecoratedPlanner + var plain Plain + if err := value.Decode(&plain); err != nil { + return err + } + if v, ok := raw["decorator"]; !ok || v == nil { + plain.Decorator = DecoratedPlannerDecorator{ + Color: "#ffffff", + Theme: nil, + } + } + *j = DecoratedPlanner(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *DecoratedPlannerDecorator) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + type Plain DecoratedPlannerDecorator + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if v, ok := raw["color"]; !ok || v == nil { + plain.Color = "#ffffff" + } + *j = DecoratedPlannerDecorator(plain) + return nil +} + +type ObjectPropertiesDefaultPlannersElem_0 struct { + // Plain corresponds to the JSON schema field "plain". + Plain *DefaultPlanner `json:"plain,omitempty" yaml:"plain,omitempty" mapstructure:"plain,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ObjectPropertiesDefaultPlannersElem_0) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + type Plain ObjectPropertiesDefaultPlannersElem_0 + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = ObjectPropertiesDefaultPlannersElem_0(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *ObjectPropertiesDefaultPlannersElem_0) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain ObjectPropertiesDefaultPlannersElem_0 + var plain Plain + if err := value.Decode(&plain); err != nil { + return err + } + *j = ObjectPropertiesDefaultPlannersElem_0(plain) + return nil +} + +type ObjectPropertiesDefaultPlannersElem_1 struct { + // Decorated corresponds to the JSON schema field "decorated". + Decorated *DecoratedPlanner `json:"decorated,omitempty" yaml:"decorated,omitempty" mapstructure:"decorated,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ObjectPropertiesDefaultPlannersElem_1) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + type Plain ObjectPropertiesDefaultPlannersElem_1 + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = ObjectPropertiesDefaultPlannersElem_1(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *ObjectPropertiesDefaultPlannersElem_1) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain ObjectPropertiesDefaultPlannersElem_1 + var plain Plain + if err := value.Decode(&plain); err != nil { + return err + } + *j = ObjectPropertiesDefaultPlannersElem_1(plain) + return nil +} + +type ObjectPropertiesDefaultPlannersElem struct { + // Decorated corresponds to the JSON schema field "decorated". + Decorated *DecoratedPlanner `json:"decorated,omitempty" yaml:"decorated,omitempty" mapstructure:"decorated,omitempty"` + + // Plain corresponds to the JSON schema field "plain". + Plain *DefaultPlanner `json:"plain,omitempty" yaml:"plain,omitempty" mapstructure:"plain,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ObjectPropertiesDefaultPlannersElem) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + var objectPropertiesDefaultPlannersElem_0 ObjectPropertiesDefaultPlannersElem_0 + var objectPropertiesDefaultPlannersElem_1 ObjectPropertiesDefaultPlannersElem_1 + var errs []error + if err := objectPropertiesDefaultPlannersElem_0.UnmarshalJSON(value); err != nil { + errs = append(errs, err) + } + if err := objectPropertiesDefaultPlannersElem_1.UnmarshalJSON(value); err != nil { + errs = append(errs, err) + } + if len(errs) == 2 { + return fmt.Errorf("all validators failed: %s", errors.Join(errs...)) + } + type Plain ObjectPropertiesDefaultPlannersElem + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = ObjectPropertiesDefaultPlannersElem(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *ObjectPropertiesDefaultPlannersElem) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + var objectPropertiesDefaultPlannersElem_0 ObjectPropertiesDefaultPlannersElem_0 + var objectPropertiesDefaultPlannersElem_1 ObjectPropertiesDefaultPlannersElem_1 + var errs []error + if err := objectPropertiesDefaultPlannersElem_0.UnmarshalYAML(value); err != nil { + errs = append(errs, err) + } + if err := objectPropertiesDefaultPlannersElem_1.UnmarshalYAML(value); err != nil { + errs = append(errs, err) + } + if len(errs) == 2 { + return fmt.Errorf("all validators failed: %s", errors.Join(errs...)) + } + type Plain ObjectPropertiesDefaultPlannersElem + var plain Plain + if err := value.Decode(&plain); err != nil { + return err + } + *j = ObjectPropertiesDefaultPlannersElem(plain) + return nil +} + +type ObjectPropertiesDefault struct { + // Active corresponds to the JSON schema field "active". + Active interface{} `json:"active,omitempty" yaml:"active,omitempty" mapstructure:"active,omitempty"` + + // Planners corresponds to the JSON schema field "planners". + Planners []ObjectPropertiesDefaultPlannersElem `json:"planners,omitempty" yaml:"planners,omitempty" mapstructure:"planners,omitempty"` +} diff --git a/tests/data/core/objectPropertiesDefault/objectPropertiesDefault.json b/tests/data/core/objectPropertiesDefault/objectPropertiesDefault.json new file mode 100644 index 00000000..aa4b694b --- /dev/null +++ b/tests/data/core/objectPropertiesDefault/objectPropertiesDefault.json @@ -0,0 +1,104 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DecoratedPlanner": { + "type": "object", + "properties": { + "decorator": { + "type": "object", + "properties": { + "color": { + "type": "string", + "default": "#ffffff" + }, + "theme": { + "type": "string" + } + }, + "additionalProperties": false, + "default": { + "theme": null, + "color": "#ffffff" + } + }, + "event": { + "$ref": "#/definitions/Event" + } + }, + "additionalProperties": false + }, + "DefaultPlanner": { + "type": "object", + "properties": { + "event": { + "$ref": "#/definitions/Event" + } + }, + "additionalProperties": false + }, + "Event": { + "type": "object", + "properties": { + "name": { + "type": "string", + "enum": [ + "BIRTHDAY", + "GAME", + "HOLIDAY" + ] + }, + "tags": { + "default": [], + "type": "array", + "items": { + "type": "string", + "enum": [ + "COUNTRY", + "REGION", + "CITY", + "PERSON" + ] + } + } + }, + "additionalProperties": false + } + }, + "type": "object", + "properties": { + "active": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, + "planners": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "plain": { + "$ref": "#/definitions/DefaultPlanner" + } + } + }, + { + "type": "object", + "properties": { + "decorated": { + "$ref": "#/definitions/DecoratedPlanner" + } + } + } + ] + } + } + }, + "additionalProperties": false +} diff --git a/tests/data/core/primitives/primitives.go b/tests/data/core/primitives/primitives.go index 65671635..78662486 100644 --- a/tests/data/core/primitives/primitives.go +++ b/tests/data/core/primitives/primitives.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" type Primitives struct { // MyBoolean corresponds to the JSON schema field "myBoolean". @@ -23,14 +24,32 @@ type Primitives struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *Primitives) UnmarshalJSON(b []byte) error { +func (j *Primitives) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } type Plain Primitives var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if plain.MyNull != nil { + return fmt.Errorf("field %s: must be null", "myNull") + } + *j = Primitives(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *Primitives) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain Primitives + var plain Plain + if err := value.Decode(&plain); err != nil { return err } if plain.MyNull != nil { diff --git a/tests/data/core/refToEnum/refToEnum.go b/tests/data/core/refToEnum/refToEnum.go index aac189ad..e1a8eb7a 100644 --- a/tests/data/core/refToEnum/refToEnum.go +++ b/tests/data/core/refToEnum/refToEnum.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" import "reflect" type Thing string @@ -33,6 +34,26 @@ func (j *Thing) UnmarshalJSON(b []byte) error { return nil } +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *Thing) UnmarshalYAML(value *yaml.Node) error { + var v string + if err := value.Decode(&v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_Thing { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_Thing, v) + } + *j = Thing(v) + return nil +} + type RefToEnum struct { // MyThing corresponds to the JSON schema field "myThing". MyThing *Thing `json:"myThing,omitempty" yaml:"myThing,omitempty" mapstructure:"myThing,omitempty"` diff --git a/tests/data/core/time/time.go b/tests/data/core/time/time.go index f79004f7..944b80a1 100644 --- a/tests/data/core/time/time.go +++ b/tests/data/core/time/time.go @@ -5,6 +5,7 @@ package test import "encoding/json" import "fmt" import "github.com/atombender/go-jsonschema/pkg/types" +import yaml "gopkg.in/yaml.v3" type TimeMyObject struct { // MyTime corresponds to the JSON schema field "myTime". @@ -12,9 +13,9 @@ type TimeMyObject struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *TimeMyObject) UnmarshalJSON(b []byte) error { +func (j *TimeMyObject) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } if v, ok := raw["myTime"]; !ok || v == nil { @@ -22,7 +23,25 @@ func (j *TimeMyObject) UnmarshalJSON(b []byte) error { } type Plain TimeMyObject var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = TimeMyObject(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *TimeMyObject) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["myTime"]; !ok || v == nil { + return fmt.Errorf("field myTime in TimeMyObject: required") + } + type Plain TimeMyObject + var plain Plain + if err := value.Decode(&plain); err != nil { return err } *j = TimeMyObject(plain) diff --git a/tests/data/extraImports/gopkgYAMLv3/gopkgYAMLv3.go b/tests/data/extraImports/gopkgYAMLv3/gopkgYAMLv3.go index 3f366e24..9191a910 100644 --- a/tests/data/extraImports/gopkgYAMLv3/gopkgYAMLv3.go +++ b/tests/data/extraImports/gopkgYAMLv3/gopkgYAMLv3.go @@ -78,14 +78,14 @@ const GopkgYAMLv3MyEnumX GopkgYAMLv3MyEnum = "x" const GopkgYAMLv3MyEnumY GopkgYAMLv3MyEnum = "y" // UnmarshalJSON implements json.Unmarshaler. -func (j *GopkgYAMLv3) UnmarshalJSON(b []byte) error { +func (j *GopkgYAMLv3) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } type Plain GopkgYAMLv3 var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { return err } if plain.MyNull != nil { diff --git a/tests/data/misc/specialCharacters/specialCharacters.go b/tests/data/misc/specialCharacters/specialCharacters.go index eb3eb26d..f9300854 100644 --- a/tests/data/misc/specialCharacters/specialCharacters.go +++ b/tests/data/misc/specialCharacters/specialCharacters.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" import "reflect" type License string @@ -33,54 +34,125 @@ type SpecialCharacters struct { type SpecialCharactersPlainLicenses string const SpecialCharactersPlainLicensesGPL30 SpecialCharactersPlainLicenses = "GPL-3.0" -const SpecialCharactersPlainLicensesMIT SpecialCharactersPlainLicenses = "MIT" - -type SpecialCharactersPlusLicenses string -const SpecialCharactersPlusLicensesGPL30 SpecialCharactersPlusLicenses = "GPL-3.0+" -const SpecialCharactersPlusLicensesMIT SpecialCharactersPlusLicenses = "MIT+" +// UnmarshalJSON implements json.Unmarshaler. +func (j *License) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_License { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_License, v) + } + *j = License(v) + return nil +} -var enumValues_License = []interface{}{ - "GPL-3.0", - "MIT", +// UnmarshalJSON implements json.Unmarshaler. +func (j *License_1) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_License_1 { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_License_1, v) + } + *j = License_1(v) + return nil } -var enumValues_License_1 = []interface{}{ - "GPL-3.0+", - "MIT+", + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *License) UnmarshalYAML(value *yaml.Node) error { + var v string + if err := value.Decode(&v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_License { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_License, v) + } + *j = License(v) + return nil } + +const SpecialCharactersPlainLicensesMIT SpecialCharactersPlainLicenses = "MIT" + var enumValues_SpecialCharactersPlainLicenses = []interface{}{ "GPL-3.0", "MIT", } -var enumValues_SpecialCharactersPlusLicenses = []interface{}{ + +// UnmarshalJSON implements json.Unmarshaler. +func (j *SpecialCharactersPlainLicenses) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_SpecialCharactersPlainLicenses { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_SpecialCharactersPlainLicenses, v) + } + *j = SpecialCharactersPlainLicenses(v) + return nil +} + +type SpecialCharactersPlusLicenses string + +var enumValues_License_1 = []interface{}{ "GPL-3.0+", "MIT+", } -// UnmarshalJSON implements json.Unmarshaler. -func (j *SpecialCharactersPlusLicenses) UnmarshalJSON(b []byte) error { +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *License_1) UnmarshalYAML(value *yaml.Node) error { var v string - if err := json.Unmarshal(b, &v); err != nil { + if err := value.Decode(&v); err != nil { return err } var ok bool - for _, expected := range enumValues_SpecialCharactersPlusLicenses { + for _, expected := range enumValues_License_1 { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_SpecialCharactersPlusLicenses, v) + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_License_1, v) } - *j = SpecialCharactersPlusLicenses(v) + *j = License_1(v) return nil } -// UnmarshalJSON implements json.Unmarshaler. -func (j *SpecialCharactersPlainLicenses) UnmarshalJSON(b []byte) error { +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *SpecialCharactersPlainLicenses) UnmarshalYAML(value *yaml.Node) error { var v string - if err := json.Unmarshal(b, &v); err != nil { + if err := value.Decode(&v); err != nil { return err } var ok bool @@ -97,42 +169,55 @@ func (j *SpecialCharactersPlainLicenses) UnmarshalJSON(b []byte) error { return nil } +var enumValues_SpecialCharactersPlusLicenses = []interface{}{ + "GPL-3.0+", + "MIT+", +} + // UnmarshalJSON implements json.Unmarshaler. -func (j *License_1) UnmarshalJSON(b []byte) error { +func (j *SpecialCharactersPlusLicenses) UnmarshalJSON(b []byte) error { var v string if err := json.Unmarshal(b, &v); err != nil { return err } var ok bool - for _, expected := range enumValues_License_1 { + for _, expected := range enumValues_SpecialCharactersPlusLicenses { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_License_1, v) + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_SpecialCharactersPlusLicenses, v) } - *j = License_1(v) + *j = SpecialCharactersPlusLicenses(v) return nil } -// UnmarshalJSON implements json.Unmarshaler. -func (j *License) UnmarshalJSON(b []byte) error { +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *SpecialCharactersPlusLicenses) UnmarshalYAML(value *yaml.Node) error { var v string - if err := json.Unmarshal(b, &v); err != nil { + if err := value.Decode(&v); err != nil { return err } var ok bool - for _, expected := range enumValues_License { + for _, expected := range enumValues_SpecialCharactersPlusLicenses { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_License, v) + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_SpecialCharactersPlusLicenses, v) } - *j = License(v) + *j = SpecialCharactersPlusLicenses(v) return nil } + +const SpecialCharactersPlusLicensesGPL30 SpecialCharactersPlusLicenses = "GPL-3.0+" +const SpecialCharactersPlusLicensesMIT SpecialCharactersPlusLicenses = "MIT+" + +var enumValues_License = []interface{}{ + "GPL-3.0", + "MIT", +} diff --git a/tests/data/miscWithDefaults/cyclicAndRequired1/cyclicAndRequired1.go b/tests/data/miscWithDefaults/cyclicAndRequired1/cyclicAndRequired1.go index 7534c336..897c4f9c 100644 --- a/tests/data/miscWithDefaults/cyclicAndRequired1/cyclicAndRequired1.go +++ b/tests/data/miscWithDefaults/cyclicAndRequired1/cyclicAndRequired1.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" type Foo struct { // RefToBar corresponds to the JSON schema field "refToBar". @@ -11,9 +12,9 @@ type Foo struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *Foo) UnmarshalJSON(b []byte) error { +func (j *Foo) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } if v, ok := raw["refToBar"]; !ok || v == nil { @@ -21,7 +22,25 @@ func (j *Foo) UnmarshalJSON(b []byte) error { } type Plain Foo var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = Foo(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *Foo) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["refToBar"]; !ok || v == nil { + return fmt.Errorf("field refToBar in Foo: required") + } + type Plain Foo + var plain Plain + if err := value.Decode(&plain); err != nil { return err } *j = Foo(plain) diff --git a/tests/data/regressions/issue32/issue32.go b/tests/data/regressions/issue32/issue32.go index 6dc056be..1fda8c7e 100644 --- a/tests/data/regressions/issue32/issue32.go +++ b/tests/data/regressions/issue32/issue32.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" type TestObject struct { // Config corresponds to the JSON schema field "config". @@ -19,9 +20,9 @@ type TestObject struct { type TestObjectConfig map[string]interface{} // UnmarshalJSON implements json.Unmarshaler. -func (j *TestObject) UnmarshalJSON(b []byte) error { +func (j *TestObject) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } if v, ok := raw["name"]; !ok || v == nil { @@ -32,7 +33,28 @@ func (j *TestObject) UnmarshalJSON(b []byte) error { } type Plain TestObject var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = TestObject(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *TestObject) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["name"]; !ok || v == nil { + return fmt.Errorf("field name in TestObject: required") + } + if v, ok := raw["owner"]; !ok || v == nil { + return fmt.Errorf("field owner in TestObject: required") + } + type Plain TestObject + var plain Plain + if err := value.Decode(&plain); err != nil { return err } *j = TestObject(plain) diff --git a/tests/data/validation/enum/enum.go b/tests/data/validation/enum/enum.go index 616c7954..cc38f18d 100644 --- a/tests/data/validation/enum/enum.go +++ b/tests/data/validation/enum/enum.go @@ -4,112 +4,354 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" import "reflect" -type Enum struct { - // MyBooleanTypedEnum corresponds to the JSON schema field "myBooleanTypedEnum". - MyBooleanTypedEnum *EnumMyBooleanTypedEnum `json:"myBooleanTypedEnum,omitempty" yaml:"myBooleanTypedEnum,omitempty" mapstructure:"myBooleanTypedEnum,omitempty"` - - // MyBooleanUntypedEnum corresponds to the JSON schema field - // "myBooleanUntypedEnum". - MyBooleanUntypedEnum *EnumMyBooleanUntypedEnum `json:"myBooleanUntypedEnum,omitempty" yaml:"myBooleanUntypedEnum,omitempty" mapstructure:"myBooleanUntypedEnum,omitempty"` +type EnumMyBooleanTypedEnum bool - // MyIntegerTypedEnum corresponds to the JSON schema field "myIntegerTypedEnum". - MyIntegerTypedEnum *EnumMyIntegerTypedEnum `json:"myIntegerTypedEnum,omitempty" yaml:"myIntegerTypedEnum,omitempty" mapstructure:"myIntegerTypedEnum,omitempty"` +var enumValues_EnumMyBooleanTypedEnum = []interface{}{ + true, + false, +} - // MyMixedTypeEnum corresponds to the JSON schema field "myMixedTypeEnum". - MyMixedTypeEnum *EnumMyMixedTypeEnum `json:"myMixedTypeEnum,omitempty" yaml:"myMixedTypeEnum,omitempty" mapstructure:"myMixedTypeEnum,omitempty"` +// UnmarshalJSON implements json.Unmarshaler. +func (j *EnumMyBooleanTypedEnum) UnmarshalJSON(b []byte) error { + var v bool + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyBooleanTypedEnum { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyBooleanTypedEnum, v) + } + *j = EnumMyBooleanTypedEnum(v) + return nil +} - // MyMixedUntypedEnum corresponds to the JSON schema field "myMixedUntypedEnum". - MyMixedUntypedEnum *EnumMyMixedUntypedEnum `json:"myMixedUntypedEnum,omitempty" yaml:"myMixedUntypedEnum,omitempty" mapstructure:"myMixedUntypedEnum,omitempty"` +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnumMyBooleanTypedEnum) UnmarshalYAML(value *yaml.Node) error { + var v bool + if err := value.Decode(&v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyBooleanTypedEnum { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyBooleanTypedEnum, v) + } + *j = EnumMyBooleanTypedEnum(v) + return nil +} - // MyNullTypedEnum corresponds to the JSON schema field "myNullTypedEnum". - MyNullTypedEnum *EnumMyNullTypedEnum `json:"myNullTypedEnum,omitempty" yaml:"myNullTypedEnum,omitempty" mapstructure:"myNullTypedEnum,omitempty"` +type EnumMyBooleanUntypedEnum bool - // MyNullUntypedEnum corresponds to the JSON schema field "myNullUntypedEnum". - MyNullUntypedEnum *EnumMyNullUntypedEnum `json:"myNullUntypedEnum,omitempty" yaml:"myNullUntypedEnum,omitempty" mapstructure:"myNullUntypedEnum,omitempty"` +var enumValues_EnumMyBooleanUntypedEnum = []interface{}{ + true, + false, +} - // MyNumberTypedEnum corresponds to the JSON schema field "myNumberTypedEnum". - MyNumberTypedEnum *EnumMyNumberTypedEnum `json:"myNumberTypedEnum,omitempty" yaml:"myNumberTypedEnum,omitempty" mapstructure:"myNumberTypedEnum,omitempty"` +// UnmarshalJSON implements json.Unmarshaler. +func (j *EnumMyBooleanUntypedEnum) UnmarshalJSON(b []byte) error { + var v bool + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyBooleanUntypedEnum { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyBooleanUntypedEnum, v) + } + *j = EnumMyBooleanUntypedEnum(v) + return nil +} - // MyNumberUntypedEnum corresponds to the JSON schema field "myNumberUntypedEnum". - MyNumberUntypedEnum *EnumMyNumberUntypedEnum `json:"myNumberUntypedEnum,omitempty" yaml:"myNumberUntypedEnum,omitempty" mapstructure:"myNumberUntypedEnum,omitempty"` +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnumMyBooleanUntypedEnum) UnmarshalYAML(value *yaml.Node) error { + var v bool + if err := value.Decode(&v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyBooleanUntypedEnum { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyBooleanUntypedEnum, v) + } + *j = EnumMyBooleanUntypedEnum(v) + return nil +} - // MyStringTypedEnum corresponds to the JSON schema field "myStringTypedEnum". - MyStringTypedEnum *EnumMyStringTypedEnum `json:"myStringTypedEnum,omitempty" yaml:"myStringTypedEnum,omitempty" mapstructure:"myStringTypedEnum,omitempty"` +type EnumMyIntegerTypedEnum int - // MyStringUntypedEnum corresponds to the JSON schema field "myStringUntypedEnum". - MyStringUntypedEnum *EnumMyStringUntypedEnum `json:"myStringUntypedEnum,omitempty" yaml:"myStringUntypedEnum,omitempty" mapstructure:"myStringUntypedEnum,omitempty"` +var enumValues_EnumMyIntegerTypedEnum = []interface{}{ + 1, + 2, + 3, } -type EnumMyBooleanTypedEnum bool - -type EnumMyBooleanUntypedEnum bool +// UnmarshalJSON implements json.Unmarshaler. +func (j *EnumMyIntegerTypedEnum) UnmarshalJSON(b []byte) error { + var v int + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyIntegerTypedEnum { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyIntegerTypedEnum, v) + } + *j = EnumMyIntegerTypedEnum(v) + return nil +} -type EnumMyIntegerTypedEnum int +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnumMyIntegerTypedEnum) UnmarshalYAML(value *yaml.Node) error { + var v int + if err := value.Decode(&v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyIntegerTypedEnum { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyIntegerTypedEnum, v) + } + *j = EnumMyIntegerTypedEnum(v) + return nil +} type EnumMyMixedTypeEnum struct { Value interface{} } +var enumValues_EnumMyMixedTypeEnum = []interface{}{ + 42.0, + "smurf", +} + +// MarshalJSON implements json.Marshaler. +func (j *EnumMyMixedTypeEnum) MarshalJSON() ([]byte, error) { + return json.Marshal(j.Value) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *EnumMyMixedTypeEnum) UnmarshalJSON(b []byte) error { + var v struct { + Value interface{} + } + if err := json.Unmarshal(b, &v.Value); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyMixedTypeEnum { + if reflect.DeepEqual(v.Value, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyMixedTypeEnum, v.Value) + } + *j = EnumMyMixedTypeEnum(v) + return nil +} + +// MarshalYAML implements yaml.Marshal. +func (j *EnumMyMixedTypeEnum) MarshalYAML() (interface{}, error) { + return yaml.Marshal(j.Value) +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnumMyMixedTypeEnum) UnmarshalYAML(value *yaml.Node) error { + var v struct { + Value interface{} + } + if err := value.Decode(&v.Value); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyMixedTypeEnum { + if reflect.DeepEqual(v.Value, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyMixedTypeEnum, v.Value) + } + *j = EnumMyMixedTypeEnum(v) + return nil +} + type EnumMyMixedUntypedEnum struct { Value interface{} } +var enumValues_EnumMyMixedUntypedEnum = []interface{}{ + "red", + 1.0, + true, + nil, +} + +// MarshalJSON implements json.Marshaler. +func (j *EnumMyMixedUntypedEnum) MarshalJSON() ([]byte, error) { + return json.Marshal(j.Value) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *EnumMyMixedUntypedEnum) UnmarshalJSON(b []byte) error { + var v struct { + Value interface{} + } + if err := json.Unmarshal(b, &v.Value); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyMixedUntypedEnum { + if reflect.DeepEqual(v.Value, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyMixedUntypedEnum, v.Value) + } + *j = EnumMyMixedUntypedEnum(v) + return nil +} + +// MarshalYAML implements yaml.Marshal. +func (j *EnumMyMixedUntypedEnum) MarshalYAML() (interface{}, error) { + return yaml.Marshal(j.Value) +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnumMyMixedUntypedEnum) UnmarshalYAML(value *yaml.Node) error { + var v struct { + Value interface{} + } + if err := value.Decode(&v.Value); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyMixedUntypedEnum { + if reflect.DeepEqual(v.Value, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyMixedUntypedEnum, v.Value) + } + *j = EnumMyMixedUntypedEnum(v) + return nil +} + type EnumMyNullTypedEnum struct { Value interface{} } +var enumValues_EnumMyNullTypedEnum = []interface{}{ + nil, +} + +// MarshalJSON implements json.Marshaler. +func (j *EnumMyNullTypedEnum) MarshalJSON() ([]byte, error) { + return json.Marshal(j.Value) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *EnumMyNullTypedEnum) UnmarshalJSON(b []byte) error { + var v struct { + Value interface{} + } + if err := json.Unmarshal(b, &v.Value); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyNullTypedEnum { + if reflect.DeepEqual(v.Value, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyNullTypedEnum, v.Value) + } + *j = EnumMyNullTypedEnum(v) + return nil +} + +// MarshalYAML implements yaml.Marshal. +func (j *EnumMyNullTypedEnum) MarshalYAML() (interface{}, error) { + return yaml.Marshal(j.Value) +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnumMyNullTypedEnum) UnmarshalYAML(value *yaml.Node) error { + var v struct { + Value interface{} + } + if err := value.Decode(&v.Value); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyNullTypedEnum { + if reflect.DeepEqual(v.Value, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyNullTypedEnum, v.Value) + } + *j = EnumMyNullTypedEnum(v) + return nil +} + type EnumMyNullUntypedEnum struct { Value interface{} } -type EnumMyNumberTypedEnum float64 - -type EnumMyNumberUntypedEnum float64 - -type EnumMyStringTypedEnum string - -const EnumMyStringTypedEnumBlue EnumMyStringTypedEnum = "blue" -const EnumMyStringTypedEnumGreen EnumMyStringTypedEnum = "green" -const EnumMyStringTypedEnumRed EnumMyStringTypedEnum = "red" - -type EnumMyStringUntypedEnum string - -const EnumMyStringUntypedEnumBlue EnumMyStringUntypedEnum = "blue" -const EnumMyStringUntypedEnumGreen EnumMyStringUntypedEnum = "green" -const EnumMyStringUntypedEnumRed EnumMyStringUntypedEnum = "red" - -var enumValues_EnumMyBooleanTypedEnum = []interface{}{ - true, - false, -} -var enumValues_EnumMyBooleanUntypedEnum = []interface{}{ - true, - false, -} -var enumValues_EnumMyIntegerTypedEnum = []interface{}{ - 1, - 2, - 3, -} -var enumValues_EnumMyMixedTypeEnum = []interface{}{ - 42.0, - "smurf", -} -var enumValues_EnumMyMixedUntypedEnum = []interface{}{ - "red", - 1.0, - true, - nil, -} -var enumValues_EnumMyNullTypedEnum = []interface{}{ - nil, -} var enumValues_EnumMyNullUntypedEnum = []interface{}{ nil, } +// MarshalJSON implements json.Marshaler. +func (j *EnumMyNullUntypedEnum) MarshalJSON() ([]byte, error) { + return json.Marshal(j.Value) +} + // UnmarshalJSON implements json.Unmarshaler. func (j *EnumMyNullUntypedEnum) UnmarshalJSON(b []byte) error { var v struct { @@ -132,6 +374,35 @@ func (j *EnumMyNullUntypedEnum) UnmarshalJSON(b []byte) error { return nil } +// MarshalYAML implements yaml.Marshal. +func (j *EnumMyNullUntypedEnum) MarshalYAML() (interface{}, error) { + return yaml.Marshal(j.Value) +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnumMyNullUntypedEnum) UnmarshalYAML(value *yaml.Node) error { + var v struct { + Value interface{} + } + if err := value.Decode(&v.Value); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyNullUntypedEnum { + if reflect.DeepEqual(v.Value, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyNullUntypedEnum, v.Value) + } + *j = EnumMyNullUntypedEnum(v) + return nil +} + +type EnumMyNumberTypedEnum float64 + var enumValues_EnumMyNumberTypedEnum = []interface{}{ 1.0, 2.0, @@ -158,11 +429,28 @@ func (j *EnumMyNumberTypedEnum) UnmarshalJSON(b []byte) error { return nil } -// MarshalJSON implements json.Marshaler. -func (j *EnumMyNullUntypedEnum) MarshalJSON() ([]byte, error) { - return json.Marshal(j.Value) +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnumMyNumberTypedEnum) UnmarshalYAML(value *yaml.Node) error { + var v float64 + if err := value.Decode(&v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_EnumMyNumberTypedEnum { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyNumberTypedEnum, v) + } + *j = EnumMyNumberTypedEnum(v) + return nil } +type EnumMyNumberUntypedEnum float64 + var enumValues_EnumMyNumberUntypedEnum = []interface{}{ 1.0, 2.0, @@ -189,28 +477,28 @@ func (j *EnumMyNumberUntypedEnum) UnmarshalJSON(b []byte) error { return nil } -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnumMyNullTypedEnum) UnmarshalJSON(b []byte) error { - var v struct { - Value interface{} - } - if err := json.Unmarshal(b, &v.Value); err != nil { +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnumMyNumberUntypedEnum) UnmarshalYAML(value *yaml.Node) error { + var v float64 + if err := value.Decode(&v); err != nil { return err } var ok bool - for _, expected := range enumValues_EnumMyNullTypedEnum { - if reflect.DeepEqual(v.Value, expected) { + for _, expected := range enumValues_EnumMyNumberUntypedEnum { + if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyNullTypedEnum, v.Value) + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyNumberUntypedEnum, v) } - *j = EnumMyNullTypedEnum(v) + *j = EnumMyNumberUntypedEnum(v) return nil } +type EnumMyStringTypedEnum string + var enumValues_EnumMyStringTypedEnum = []interface{}{ "red", "blue", @@ -237,59 +525,31 @@ func (j *EnumMyStringTypedEnum) UnmarshalJSON(b []byte) error { return nil } -// MarshalJSON implements json.Marshaler. -func (j *EnumMyNullTypedEnum) MarshalJSON() ([]byte, error) { - return json.Marshal(j.Value) -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnumMyMixedUntypedEnum) UnmarshalJSON(b []byte) error { - var v struct { - Value interface{} - } - if err := json.Unmarshal(b, &v.Value); err != nil { +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnumMyStringTypedEnum) UnmarshalYAML(value *yaml.Node) error { + var v string + if err := value.Decode(&v); err != nil { return err } var ok bool - for _, expected := range enumValues_EnumMyMixedUntypedEnum { - if reflect.DeepEqual(v.Value, expected) { + for _, expected := range enumValues_EnumMyStringTypedEnum { + if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyMixedUntypedEnum, v.Value) + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyStringTypedEnum, v) } - *j = EnumMyMixedUntypedEnum(v) + *j = EnumMyStringTypedEnum(v) return nil } -// MarshalJSON implements json.Marshaler. -func (j *EnumMyMixedUntypedEnum) MarshalJSON() ([]byte, error) { - return json.Marshal(j.Value) -} +const EnumMyStringTypedEnumBlue EnumMyStringTypedEnum = "blue" +const EnumMyStringTypedEnumGreen EnumMyStringTypedEnum = "green" +const EnumMyStringTypedEnumRed EnumMyStringTypedEnum = "red" -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnumMyMixedTypeEnum) UnmarshalJSON(b []byte) error { - var v struct { - Value interface{} - } - if err := json.Unmarshal(b, &v.Value); err != nil { - return err - } - var ok bool - for _, expected := range enumValues_EnumMyMixedTypeEnum { - if reflect.DeepEqual(v.Value, expected) { - ok = true - break - } - } - if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyMixedTypeEnum, v.Value) - } - *j = EnumMyMixedTypeEnum(v) - return nil -} +type EnumMyStringUntypedEnum string var enumValues_EnumMyStringUntypedEnum = []interface{}{ "red", @@ -317,67 +577,62 @@ func (j *EnumMyStringUntypedEnum) UnmarshalJSON(b []byte) error { return nil } -// MarshalJSON implements json.Marshaler. -func (j *EnumMyMixedTypeEnum) MarshalJSON() ([]byte, error) { - return json.Marshal(j.Value) -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnumMyIntegerTypedEnum) UnmarshalJSON(b []byte) error { - var v int - if err := json.Unmarshal(b, &v); err != nil { +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnumMyStringUntypedEnum) UnmarshalYAML(value *yaml.Node) error { + var v string + if err := value.Decode(&v); err != nil { return err } var ok bool - for _, expected := range enumValues_EnumMyIntegerTypedEnum { + for _, expected := range enumValues_EnumMyStringUntypedEnum { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyIntegerTypedEnum, v) + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyStringUntypedEnum, v) } - *j = EnumMyIntegerTypedEnum(v) + *j = EnumMyStringUntypedEnum(v) return nil } -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnumMyBooleanUntypedEnum) UnmarshalJSON(b []byte) error { - var v bool - if err := json.Unmarshal(b, &v); err != nil { - return err - } - var ok bool - for _, expected := range enumValues_EnumMyBooleanUntypedEnum { - if reflect.DeepEqual(v, expected) { - ok = true - break - } - } - if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyBooleanUntypedEnum, v) - } - *j = EnumMyBooleanUntypedEnum(v) - return nil -} +type Enum struct { + // MyBooleanTypedEnum corresponds to the JSON schema field "myBooleanTypedEnum". + MyBooleanTypedEnum *EnumMyBooleanTypedEnum `json:"myBooleanTypedEnum,omitempty" yaml:"myBooleanTypedEnum,omitempty" mapstructure:"myBooleanTypedEnum,omitempty"` -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnumMyBooleanTypedEnum) UnmarshalJSON(b []byte) error { - var v bool - if err := json.Unmarshal(b, &v); err != nil { - return err - } - var ok bool - for _, expected := range enumValues_EnumMyBooleanTypedEnum { - if reflect.DeepEqual(v, expected) { - ok = true - break - } - } - if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_EnumMyBooleanTypedEnum, v) - } - *j = EnumMyBooleanTypedEnum(v) - return nil + // MyBooleanUntypedEnum corresponds to the JSON schema field + // "myBooleanUntypedEnum". + MyBooleanUntypedEnum *EnumMyBooleanUntypedEnum `json:"myBooleanUntypedEnum,omitempty" yaml:"myBooleanUntypedEnum,omitempty" mapstructure:"myBooleanUntypedEnum,omitempty"` + + // MyIntegerTypedEnum corresponds to the JSON schema field "myIntegerTypedEnum". + MyIntegerTypedEnum *EnumMyIntegerTypedEnum `json:"myIntegerTypedEnum,omitempty" yaml:"myIntegerTypedEnum,omitempty" mapstructure:"myIntegerTypedEnum,omitempty"` + + // MyMixedTypeEnum corresponds to the JSON schema field "myMixedTypeEnum". + MyMixedTypeEnum *EnumMyMixedTypeEnum `json:"myMixedTypeEnum,omitempty" yaml:"myMixedTypeEnum,omitempty" mapstructure:"myMixedTypeEnum,omitempty"` + + // MyMixedUntypedEnum corresponds to the JSON schema field "myMixedUntypedEnum". + MyMixedUntypedEnum *EnumMyMixedUntypedEnum `json:"myMixedUntypedEnum,omitempty" yaml:"myMixedUntypedEnum,omitempty" mapstructure:"myMixedUntypedEnum,omitempty"` + + // MyNullTypedEnum corresponds to the JSON schema field "myNullTypedEnum". + MyNullTypedEnum *EnumMyNullTypedEnum `json:"myNullTypedEnum,omitempty" yaml:"myNullTypedEnum,omitempty" mapstructure:"myNullTypedEnum,omitempty"` + + // MyNullUntypedEnum corresponds to the JSON schema field "myNullUntypedEnum". + MyNullUntypedEnum *EnumMyNullUntypedEnum `json:"myNullUntypedEnum,omitempty" yaml:"myNullUntypedEnum,omitempty" mapstructure:"myNullUntypedEnum,omitempty"` + + // MyNumberTypedEnum corresponds to the JSON schema field "myNumberTypedEnum". + MyNumberTypedEnum *EnumMyNumberTypedEnum `json:"myNumberTypedEnum,omitempty" yaml:"myNumberTypedEnum,omitempty" mapstructure:"myNumberTypedEnum,omitempty"` + + // MyNumberUntypedEnum corresponds to the JSON schema field "myNumberUntypedEnum". + MyNumberUntypedEnum *EnumMyNumberUntypedEnum `json:"myNumberUntypedEnum,omitempty" yaml:"myNumberUntypedEnum,omitempty" mapstructure:"myNumberUntypedEnum,omitempty"` + + // MyStringTypedEnum corresponds to the JSON schema field "myStringTypedEnum". + MyStringTypedEnum *EnumMyStringTypedEnum `json:"myStringTypedEnum,omitempty" yaml:"myStringTypedEnum,omitempty" mapstructure:"myStringTypedEnum,omitempty"` + + // MyStringUntypedEnum corresponds to the JSON schema field "myStringUntypedEnum". + MyStringUntypedEnum *EnumMyStringUntypedEnum `json:"myStringUntypedEnum,omitempty" yaml:"myStringUntypedEnum,omitempty" mapstructure:"myStringUntypedEnum,omitempty"` } + +const EnumMyStringUntypedEnumBlue EnumMyStringUntypedEnum = "blue" +const EnumMyStringUntypedEnumGreen EnumMyStringUntypedEnum = "green" +const EnumMyStringUntypedEnumRed EnumMyStringUntypedEnum = "red" diff --git a/tests/data/validation/maxItems/maxItems.go b/tests/data/validation/maxItems/maxItems.go index 562642dc..fbe7a99e 100644 --- a/tests/data/validation/maxItems/maxItems.go +++ b/tests/data/validation/maxItems/maxItems.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" type MaxItems struct { // MyNestedArray corresponds to the JSON schema field "myNestedArray". @@ -14,14 +15,40 @@ type MaxItems struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *MaxItems) UnmarshalJSON(b []byte) error { +func (j *MaxItems) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } type Plain MaxItems var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if len(plain.MyNestedArray) > 5 { + return fmt.Errorf("field %s length: must be <= %d", "myNestedArray", 5) + } + for i1 := range plain.MyNestedArray { + if len(plain.MyNestedArray[i1]) > 5 { + return fmt.Errorf("field %s length: must be <= %d", fmt.Sprintf("myNestedArray[%d]", i1), 5) + } + } + if len(plain.MyStringArray) > 5 { + return fmt.Errorf("field %s length: must be <= %d", "myStringArray", 5) + } + *j = MaxItems(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *MaxItems) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain MaxItems + var plain Plain + if err := value.Decode(&plain); err != nil { return err } if len(plain.MyNestedArray) > 5 { diff --git a/tests/data/validation/maxLength/maxLength.go b/tests/data/validation/maxLength/maxLength.go index 92cf1241..dce05c97 100644 --- a/tests/data/validation/maxLength/maxLength.go +++ b/tests/data/validation/maxLength/maxLength.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" type MaxLength struct { // MyNullableString corresponds to the JSON schema field "myNullableString". @@ -14,9 +15,9 @@ type MaxLength struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *MaxLength) UnmarshalJSON(b []byte) error { +func (j *MaxLength) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } if v, ok := raw["myString"]; !ok || v == nil { @@ -24,7 +25,31 @@ func (j *MaxLength) UnmarshalJSON(b []byte) error { } type Plain MaxLength var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if plain.MyNullableString != nil && len(*plain.MyNullableString) > 10 { + return fmt.Errorf("field %s length: must be <= %d", "myNullableString", 10) + } + if len(plain.MyString) > 5 { + return fmt.Errorf("field %s length: must be <= %d", "myString", 5) + } + *j = MaxLength(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *MaxLength) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["myString"]; !ok || v == nil { + return fmt.Errorf("field myString in MaxLength: required") + } + type Plain MaxLength + var plain Plain + if err := value.Decode(&plain); err != nil { return err } if plain.MyNullableString != nil && len(*plain.MyNullableString) > 10 { diff --git a/tests/data/validation/minItems/minItems.go b/tests/data/validation/minItems/minItems.go index 17df697c..050b2594 100644 --- a/tests/data/validation/minItems/minItems.go +++ b/tests/data/validation/minItems/minItems.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" type MinItems struct { // MyNestedArray corresponds to the JSON schema field "myNestedArray". @@ -14,14 +15,40 @@ type MinItems struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *MinItems) UnmarshalJSON(b []byte) error { +func (j *MinItems) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } type Plain MinItems var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if plain.MyNestedArray != nil && len(plain.MyNestedArray) < 5 { + return fmt.Errorf("field %s length: must be >= %d", "myNestedArray", 5) + } + for i1 := range plain.MyNestedArray { + if plain.MyNestedArray[i1] != nil && len(plain.MyNestedArray[i1]) < 5 { + return fmt.Errorf("field %s length: must be >= %d", fmt.Sprintf("myNestedArray[%d]", i1), 5) + } + } + if plain.MyStringArray != nil && len(plain.MyStringArray) < 5 { + return fmt.Errorf("field %s length: must be >= %d", "myStringArray", 5) + } + *j = MinItems(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *MinItems) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain MinItems + var plain Plain + if err := value.Decode(&plain); err != nil { return err } if plain.MyNestedArray != nil && len(plain.MyNestedArray) < 5 { diff --git a/tests/data/validation/minLength/minLength.go b/tests/data/validation/minLength/minLength.go index 25020340..ba0ce09c 100644 --- a/tests/data/validation/minLength/minLength.go +++ b/tests/data/validation/minLength/minLength.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" type MinLength struct { // MyNullableString corresponds to the JSON schema field "myNullableString". @@ -14,9 +15,9 @@ type MinLength struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *MinLength) UnmarshalJSON(b []byte) error { +func (j *MinLength) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } if v, ok := raw["myString"]; !ok || v == nil { @@ -24,7 +25,31 @@ func (j *MinLength) UnmarshalJSON(b []byte) error { } type Plain MinLength var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if plain.MyNullableString != nil && len(*plain.MyNullableString) < 10 { + return fmt.Errorf("field %s length: must be >= %d", "myNullableString", 10) + } + if len(plain.MyString) < 5 { + return fmt.Errorf("field %s length: must be >= %d", "myString", 5) + } + *j = MinLength(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *MinLength) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["myString"]; !ok || v == nil { + return fmt.Errorf("field myString in MinLength: required") + } + type Plain MinLength + var plain Plain + if err := value.Decode(&plain); err != nil { return err } if plain.MyNullableString != nil && len(*plain.MyNullableString) < 10 { diff --git a/tests/data/validation/minMaxItems/minMaxItems.go b/tests/data/validation/minMaxItems/minMaxItems.go index 5f60d3eb..1d338c8f 100644 --- a/tests/data/validation/minMaxItems/minMaxItems.go +++ b/tests/data/validation/minMaxItems/minMaxItems.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" type MinMaxItems struct { // MyNestedArray corresponds to the JSON schema field "myNestedArray". @@ -14,14 +15,49 @@ type MinMaxItems struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *MinMaxItems) UnmarshalJSON(b []byte) error { +func (j *MinMaxItems) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } type Plain MinMaxItems var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if plain.MyNestedArray != nil && len(plain.MyNestedArray) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "myNestedArray", 1) + } + if len(plain.MyNestedArray) > 5 { + return fmt.Errorf("field %s length: must be <= %d", "myNestedArray", 5) + } + for i1 := range plain.MyNestedArray { + if plain.MyNestedArray[i1] != nil && len(plain.MyNestedArray[i1]) < 1 { + return fmt.Errorf("field %s length: must be >= %d", fmt.Sprintf("myNestedArray[%d]", i1), 1) + } + if len(plain.MyNestedArray[i1]) > 5 { + return fmt.Errorf("field %s length: must be <= %d", fmt.Sprintf("myNestedArray[%d]", i1), 5) + } + } + if plain.MyStringArray != nil && len(plain.MyStringArray) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "myStringArray", 1) + } + if len(plain.MyStringArray) > 3 { + return fmt.Errorf("field %s length: must be <= %d", "myStringArray", 3) + } + *j = MinMaxItems(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *MinMaxItems) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain MinMaxItems + var plain Plain + if err := value.Decode(&plain); err != nil { return err } if plain.MyNestedArray != nil && len(plain.MyNestedArray) < 1 { diff --git a/tests/data/validation/requiredFields/requiredFields.go b/tests/data/validation/requiredFields/requiredFields.go index b28390ca..2b5b119b 100644 --- a/tests/data/validation/requiredFields/requiredFields.go +++ b/tests/data/validation/requiredFields/requiredFields.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" type RequiredFieldsMyObject struct { // MyNestedObjectString corresponds to the JSON schema field @@ -12,9 +13,9 @@ type RequiredFieldsMyObject struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *RequiredFieldsMyObject) UnmarshalJSON(b []byte) error { +func (j *RequiredFieldsMyObject) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } if v, ok := raw["myNestedObjectString"]; !ok || v == nil { @@ -22,7 +23,25 @@ func (j *RequiredFieldsMyObject) UnmarshalJSON(b []byte) error { } type Plain RequiredFieldsMyObject var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = RequiredFieldsMyObject(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *RequiredFieldsMyObject) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["myNestedObjectString"]; !ok || v == nil { + return fmt.Errorf("field myNestedObjectString in RequiredFieldsMyObject: required") + } + type Plain RequiredFieldsMyObject + var plain Plain + if err := value.Decode(&plain); err != nil { return err } *j = RequiredFieldsMyObject(plain) @@ -36,9 +55,9 @@ type RequiredFieldsMyObjectArrayElem struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *RequiredFieldsMyObjectArrayElem) UnmarshalJSON(b []byte) error { +func (j *RequiredFieldsMyObjectArrayElem) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } if v, ok := raw["myNestedObjectString"]; !ok || v == nil { @@ -46,7 +65,25 @@ func (j *RequiredFieldsMyObjectArrayElem) UnmarshalJSON(b []byte) error { } type Plain RequiredFieldsMyObjectArrayElem var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + *j = RequiredFieldsMyObjectArrayElem(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *RequiredFieldsMyObjectArrayElem) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + if v, ok := raw["myNestedObjectString"]; !ok || v == nil { + return fmt.Errorf("field myNestedObjectString in RequiredFieldsMyObjectArrayElem: required") + } + type Plain RequiredFieldsMyObjectArrayElem + var plain Plain + if err := value.Decode(&plain); err != nil { return err } *j = RequiredFieldsMyObjectArrayElem(plain) @@ -92,9 +129,62 @@ type RequiredFields struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *RequiredFields) UnmarshalJSON(b []byte) error { +func (j *RequiredFields) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + if v, ok := raw["myBoolean"]; !ok || v == nil { + return fmt.Errorf("field myBoolean in RequiredFields: required") + } + if v, ok := raw["myBooleanArray"]; !ok || v == nil { + return fmt.Errorf("field myBooleanArray in RequiredFields: required") + } + if v, ok := raw["myNull"]; !ok || v == nil { + return fmt.Errorf("field myNull in RequiredFields: required") + } + if v, ok := raw["myNullArray"]; !ok || v == nil { + return fmt.Errorf("field myNullArray in RequiredFields: required") + } + if v, ok := raw["myNumber"]; !ok || v == nil { + return fmt.Errorf("field myNumber in RequiredFields: required") + } + if v, ok := raw["myNumberArray"]; !ok || v == nil { + return fmt.Errorf("field myNumberArray in RequiredFields: required") + } + if v, ok := raw["myObject"]; !ok || v == nil { + return fmt.Errorf("field myObject in RequiredFields: required") + } + if v, ok := raw["myObjectArray"]; !ok || v == nil { + return fmt.Errorf("field myObjectArray in RequiredFields: required") + } + if v, ok := raw["myString"]; !ok || v == nil { + return fmt.Errorf("field myString in RequiredFields: required") + } + if v, ok := raw["myStringArray"]; !ok || v == nil { + return fmt.Errorf("field myStringArray in RequiredFields: required") + } + type Plain RequiredFields + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if plain.MyNull != nil { + return fmt.Errorf("field %s: must be null", "myNull") + } + for i0 := range plain.MyNullArray { + if plain.MyNullArray[i0] != nil { + return fmt.Errorf("field %s: must be null", fmt.Sprintf("myNullArray[%d]", i0)) + } + } + *j = RequiredFields(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *RequiredFields) UnmarshalYAML(value *yaml.Node) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := value.Decode(&raw); err != nil { return err } if v, ok := raw["myBoolean"]; !ok || v == nil { @@ -129,7 +219,7 @@ func (j *RequiredFields) UnmarshalJSON(b []byte) error { } type Plain RequiredFields var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := value.Decode(&plain); err != nil { return err } if plain.MyNull != nil { diff --git a/tests/data/validation/typed_default/typed_default.go b/tests/data/validation/typed_default/typed_default.go index b30c1bd0..dfd38b8d 100644 --- a/tests/data/validation/typed_default/typed_default.go +++ b/tests/data/validation/typed_default/typed_default.go @@ -3,6 +3,7 @@ package test import "encoding/json" +import yaml "gopkg.in/yaml.v3" type TypedDefault struct { // TopLevelDomains corresponds to the JSON schema field "topLevelDomains". @@ -10,14 +11,37 @@ type TypedDefault struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *TypedDefault) UnmarshalJSON(b []byte) error { +func (j *TypedDefault) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } type Plain TypedDefault var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if v, ok := raw["topLevelDomains"]; !ok || v == nil { + plain.TopLevelDomains = []string{ + ".com", + ".org", + ".info", + ".gov", + } + } + *j = TypedDefault(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *TypedDefault) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain TypedDefault + var plain Plain + if err := value.Decode(&plain); err != nil { return err } if v, ok := raw["topLevelDomains"]; !ok || v == nil { diff --git a/tests/data/validation/typed_default_empty/typed_default_empty.go b/tests/data/validation/typed_default_empty/typed_default_empty.go index 322d2695..9b584f52 100644 --- a/tests/data/validation/typed_default_empty/typed_default_empty.go +++ b/tests/data/validation/typed_default_empty/typed_default_empty.go @@ -3,6 +3,7 @@ package test import "encoding/json" +import yaml "gopkg.in/yaml.v3" type TypedDefaultEmpty struct { // TopLevelDomains corresponds to the JSON schema field "topLevelDomains". @@ -10,14 +11,32 @@ type TypedDefaultEmpty struct { } // UnmarshalJSON implements json.Unmarshaler. -func (j *TypedDefaultEmpty) UnmarshalJSON(b []byte) error { +func (j *TypedDefaultEmpty) UnmarshalJSON(value []byte) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(value, &raw); err != nil { return err } type Plain TypedDefaultEmpty var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if v, ok := raw["topLevelDomains"]; !ok || v == nil { + plain.TopLevelDomains = []string{} + } + *j = TypedDefaultEmpty(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *TypedDefaultEmpty) UnmarshalYAML(value *yaml.Node) error { + var raw map[string]interface{} + if err := value.Decode(&raw); err != nil { + return err + } + type Plain TypedDefaultEmpty + var plain Plain + if err := value.Decode(&plain); err != nil { return err } if v, ok := raw["topLevelDomains"]; !ok || v == nil { diff --git a/tests/data/validation/typed_default_enums/typed_default_enums.go b/tests/data/validation/typed_default_enums/typed_default_enums.go index 922d9324..f68c80d5 100644 --- a/tests/data/validation/typed_default_enums/typed_default_enums.go +++ b/tests/data/validation/typed_default_enums/typed_default_enums.go @@ -4,6 +4,7 @@ package test import "encoding/json" import "fmt" +import yaml "gopkg.in/yaml.v3" import "reflect" type TypedDefaultEnumsSome string @@ -33,6 +34,26 @@ func (j *TypedDefaultEnumsSome) UnmarshalJSON(b []byte) error { return nil } +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *TypedDefaultEnumsSome) UnmarshalYAML(value *yaml.Node) error { + var v string + if err := value.Decode(&v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_TypedDefaultEnumsSome { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_TypedDefaultEnumsSome, v) + } + *j = TypedDefaultEnumsSome(v) + return nil +} + type TypedDefaultEnums struct { // Some corresponds to the JSON schema field "some". Some TypedDefaultEnumsSome `json:"some,omitempty" yaml:"some,omitempty" mapstructure:"some,omitempty"` @@ -42,14 +63,32 @@ const TypedDefaultEnumsSomeOther TypedDefaultEnumsSome = "other" const TypedDefaultEnumsSomeRandom TypedDefaultEnumsSome = "random" // UnmarshalJSON implements json.Unmarshaler. -func (j *TypedDefaultEnums) UnmarshalJSON(b []byte) error { +func (j *TypedDefaultEnums) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + type Plain TypedDefaultEnums + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if v, ok := raw["some"]; !ok || v == nil { + plain.Some = "random" + } + *j = TypedDefaultEnums(plain) + return nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *TypedDefaultEnums) UnmarshalYAML(value *yaml.Node) error { var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { + if err := value.Decode(&raw); err != nil { return err } type Plain TypedDefaultEnums var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { + if err := value.Decode(&plain); err != nil { return err } if v, ok := raw["some"]; !ok || v == nil { diff --git a/tests/generation_test.go b/tests/generation_test.go index ca987fc9..cea26605 100644 --- a/tests/generation_test.go +++ b/tests/generation_test.go @@ -18,7 +18,7 @@ var ( basicConfig = generator.Config{ SchemaMappings: []generator.SchemaMapping{}, - ExtraImports: false, + ExtraImports: true, DefaultPackageName: "github.com/example/test", DefaultOutputName: "-", ResolveExtensions: []string{".json", ".yaml"}, diff --git a/tests/go.mod b/tests/go.mod index 8e05ebbf..d4c25c62 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -14,8 +14,10 @@ require ( ) require ( + dario.cat/mergo v1.0.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/goccy/go-yaml v1.11.2 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect diff --git a/tests/go.sum b/tests/go.sum index 56904f36..b366c886 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -1,3 +1,5 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b h1:XxMZvQZtTXpWMNWK82vdjCLCe7uGMFXdTsJH0v3Hkvw= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=