diff --git a/go.mod b/go.mod index 42207689..edb3b92d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.22.0 require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.18.0 // indirect github.com/goccy/go-yaml v1.14.3 github.com/mitchellh/go-wordwrap v1.0.1 github.com/pkg/errors v0.9.1 @@ -16,11 +15,7 @@ require ( require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c4fc249c..fd0dd010 100644 --- a/go.sum +++ b/go.sum @@ -2,57 +2,10 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= -github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= -github.com/goccy/go-yaml v1.13.0 h1:0Wtp0FZLd7Sm8gERmR9S6Iczzb3vItJj7NaHmFg8pTs= -github.com/goccy/go-yaml v1.13.0/go.mod h1:IjYwxUiJDoqpx2RmbdjMUceGHZwYLon3sfOGl5Hi9lc= -github.com/goccy/go-yaml v1.13.1 h1:xZqDO9euwefeRx5am/ca9DPSCbV3fMNPkrl+Tivmz8A= -github.com/goccy/go-yaml v1.13.1/go.mod h1:IjYwxUiJDoqpx2RmbdjMUceGHZwYLon3sfOGl5Hi9lc= -github.com/goccy/go-yaml v1.13.2 h1:jApcuETDAB6R10spnUfQDTVu090oUwGo+p3GakYTJKw= -github.com/goccy/go-yaml v1.13.2/go.mod h1:IjYwxUiJDoqpx2RmbdjMUceGHZwYLon3sfOGl5Hi9lc= -github.com/goccy/go-yaml v1.13.3 h1:IXRULR8mAa0MXQobzzp0VOfMUJ8EnaQ4x3jhf7S0/nI= -github.com/goccy/go-yaml v1.13.3/go.mod h1:IjYwxUiJDoqpx2RmbdjMUceGHZwYLon3sfOGl5Hi9lc= -github.com/goccy/go-yaml v1.13.4 h1:XOnLX9GqT+kH/gB7YzCMUiDBFU9B7pm3HZz6kyeDPkk= -github.com/goccy/go-yaml v1.13.4/go.mod h1:IjYwxUiJDoqpx2RmbdjMUceGHZwYLon3sfOGl5Hi9lc= -github.com/goccy/go-yaml v1.13.5 h1:/Hh9Q3d1Q2T7E8ECUUS7Uh53FBGKLfetvQoSV7kLViU= -github.com/goccy/go-yaml v1.13.5/go.mod h1:IjYwxUiJDoqpx2RmbdjMUceGHZwYLon3sfOGl5Hi9lc= -github.com/goccy/go-yaml v1.13.6 h1:pa3JkBPBseTtfqpG9DiSFhyxNPSpJ0BFa39BlMZE16E= -github.com/goccy/go-yaml v1.13.6/go.mod h1:IjYwxUiJDoqpx2RmbdjMUceGHZwYLon3sfOGl5Hi9lc= -github.com/goccy/go-yaml v1.13.7 h1:5k2i973KptPV1mur30XMXwGepDmskip4gA2zHWzWmOY= -github.com/goccy/go-yaml v1.13.7/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/goccy/go-yaml v1.13.8 h1:ftugzaplJyFaFwfyVNeq1XQOBxmlp8zazmuiobaCXbk= -github.com/goccy/go-yaml v1.13.8/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/goccy/go-yaml v1.13.9 h1:D/LhDa7E5HS/iYxSZzikUSHt1U9q/TeymVBJwodaglc= -github.com/goccy/go-yaml v1.13.9/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/goccy/go-yaml v1.14.0 h1:G/NDXJvf1CX0FshjxKn2AOL0MnrxsSJNpY9FpvMRblw= -github.com/goccy/go-yaml v1.14.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/goccy/go-yaml v1.14.1 h1:NJ9Ch49K/WichY7pAtSvIJbvGjmBDjHVQxuWBbPSOPg= -github.com/goccy/go-yaml v1.14.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/goccy/go-yaml v1.14.2 h1:MzONUP3PM6jnePSNWb2A9fI/xEx1OduPaK/hMC9L9fQ= -github.com/goccy/go-yaml v1.14.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.14.3 h1:8tVD+aqqPLWisSEhM+6wWoiURWXCx6BwaTKS6ZeITgM= github.com/goccy/go-yaml v1.14.3/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -70,24 +23,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= -golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 h1:1wqE9dj9NpSm04INVsJhhEUzhuDVjbcyKH91sVyPATw= -golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/main.go b/main.go index a8cde64f..dbda534e 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,7 @@ var ( tags []string structNameFromTitle bool minSizedInts bool + minimalNames bool errFlagFormat = errors.New("flag must be in the format URI=PACKAGE") @@ -77,6 +78,7 @@ var ( Tags: tags, OnlyModels: onlyModels, MinSizedInts: minSizedInts, + MinimalNames: minimalNames, } for _, id := range allKeys(schemaPackageMap, schemaOutputMap, schemaRootTypeMap) { mapping := generator.SchemaMapping{SchemaID: id} @@ -173,6 +175,8 @@ also look for foo.json if --resolve-extension json is provided.`) "min-sized-ints", false, "Uses sized int and uint values based on the min and max values for the field") + rootCmd.PersistentFlags().BoolVar(&minimalNames, "minimal-names", false, + "Uses the shortest possible names") abortWithErr(rootCmd.Execute()) } diff --git a/pkg/generator/config.go b/pkg/generator/config.go index b97009ee..17022c1f 100644 --- a/pkg/generator/config.go +++ b/pkg/generator/config.go @@ -3,19 +3,33 @@ package generator import "github.com/atombender/go-jsonschema/pkg/schemas" type Config struct { - SchemaMappings []SchemaMapping - ExtraImports bool - Capitalizations []string - ResolveExtensions []string - YAMLExtensions []string - DefaultPackageName string - DefaultOutputName string + SchemaMappings []SchemaMapping + // ExtraImports allows the generator to pull imports from outside the standard library. + ExtraImports bool + // Capitalizations configures capitalized forms for identifiers which take precedence over the default algorithm. + Capitalizations []string + // ResolveExtensions configures file extensions to use when resolving schema names. + ResolveExtensions []string + // YAMLExtensions configures the file extensions that are recognized as YAML files. + YAMLExtensions []string + // DefaultPackageName configures the package to declare files under. + DefaultPackageName string + // DefaultOutputName configures the file to write. + DefaultOutputName string + // StructNameFromTitle configures the generator to use the schema title as the generated struct name. StructNameFromTitle bool - Warner func(string) - Tags []string - OnlyModels bool - MinSizedInts bool - Loader schemas.Loader + // Warner provides a handler for warning messages. + Warner func(string) + // Tags specifies which struct tags should be generated. + Tags []string + // OnlyModels configures the generator to omit unmarshal methods, validations, anything but models. + OnlyModels bool + // MinSizedInts configures the generator to use the smallest int and uint types based on schema maximum values. + MinSizedInts bool + // MinimalNames configures the generator to use the shortest identifier names possible. + MinimalNames bool + // Loader provides a schema loader for the generator. + Loader schemas.Loader } type SchemaMapping struct { diff --git a/pkg/generator/generate.go b/pkg/generator/generate.go index 4e071496..c45f27eb 100644 --- a/pkg/generator/generate.go +++ b/pkg/generator/generate.go @@ -31,13 +31,14 @@ var ( ) type Generator struct { - caser *text.Caser - config Config - inScope map[qualifiedDefinition]struct{} - outputs map[string]*output - warner func(string) - formatters []formatter - loader schemas.Loader + caser *text.Caser + config Config + inScope map[qualifiedDefinition]struct{} + outputs map[string]*output + warner func(string) + formatters []formatter + loader schemas.Loader + minimalNames bool } type qualifiedDefinition struct { @@ -54,13 +55,14 @@ func New(config Config) (*Generator, error) { } generator := &Generator{ - caser: text.NewCaser(config.Capitalizations, config.ResolveExtensions), - config: config, - inScope: map[qualifiedDefinition]struct{}{}, - outputs: map[string]*output{}, - warner: config.Warner, - formatters: formatters, - loader: config.Loader, + caser: text.NewCaser(config.Capitalizations, config.ResolveExtensions), + config: config, + inScope: map[qualifiedDefinition]struct{}{}, + outputs: map[string]*output{}, + warner: config.Warner, + formatters: formatters, + loader: config.Loader, + minimalNames: config.MinimalNames, } if config.Loader == nil { @@ -199,7 +201,8 @@ func (g *Generator) beginOutput( } output := &output{ - warner: g.warner, + minimalNames: g.minimalNames, + warner: g.warner, file: &codegen.File{ FileName: outputName, Package: pkg, diff --git a/pkg/generator/output.go b/pkg/generator/output.go index fe0b8de1..ad84b729 100644 --- a/pkg/generator/output.go +++ b/pkg/generator/output.go @@ -8,21 +8,39 @@ import ( ) type output struct { + minimalNames bool file *codegen.File declsByName map[string]*codegen.TypeDecl declsBySchema map[*schemas.Type]*codegen.TypeDecl warner func(string) } -func (o *output) uniqueTypeName(name string) string { - v, ok := o.declsByName[name] +// uniqueTypeName finds the shortest identifier in a name scope that yields a unique type name. +// If a given suffix on the name scope is not unique, more context from the scope is added. If the +// entire context does not yield a unique name, a numeric suffix is used. +// TODO: we should check for schema equality on name collisions here to deduplicate identifiers. +func (o *output) uniqueTypeName(scope nameScope) string { + if o.minimalNames { + for i := len(scope) - 1; i >= 0; i-- { + name := scope[i:].string() + + v, ok := o.declsByName[name] + if !ok || (ok && v.Type == nil) { + // An identifier using the current amount of name context is unique, use it. + return name + } + } + } + + // If we can't make a unique name with the entire context, attempt a numeric suffix. + count := 1 + name := scope.string() + v, ok := o.declsByName[name] if !ok || (ok && v.Type == nil) { return name } - count := 1 - for { suffixed := fmt.Sprintf("%s_%d", name, count) if _, ok := o.declsByName[suffixed]; !ok { diff --git a/pkg/generator/schema_generator.go b/pkg/generator/schema_generator.go index d7c6072c..25bea185 100644 --- a/pkg/generator/schema_generator.go +++ b/pkg/generator/schema_generator.go @@ -205,7 +205,7 @@ func (g *schemaGenerator) generateDeclaredType( } decl := codegen.TypeDecl{ - Name: g.output.uniqueTypeName(scope.string()), + Name: g.output.uniqueTypeName(scope), Comment: t.Description, } g.output.declsBySchema[t] = &decl @@ -432,7 +432,7 @@ func (g *schemaGenerator) generateType( return nil, errArrayPropertyItems } - elemType, err := g.generateType(t.Items, scope.add("Elem")) + elemType, err := g.generateType(t.Items, g.singularScope(scope)) if err != nil { return nil, err } @@ -764,7 +764,7 @@ func (g *schemaGenerator) generateTypeInline( } else { var err error - theType, err = g.generateTypeInline(t.Items, scope.add("Elem")) + theType, err = g.generateTypeInline(t.Items, g.singularScope(scope)) if err != nil { return nil, err } @@ -777,6 +777,17 @@ func (g *schemaGenerator) generateTypeInline( return g.generateDeclaredType(t, scope) } +// singularScope attempts to create a name scope for an element of a collection. If the parent collection +// has a plural name like "Actions", then the singular name for the element will be "Action". If the collection +// is not plural, like "WhateverElse", then the element's name will be "WhateverElseElem". +func (g *Generator) singularScope(scope nameScope) nameScope { + if g.minimalNames && len(scope) > 0 && strings.HasSuffix(scope[len(scope)-1], "s") { + return scope[:len(scope)-1].add(strings.TrimSuffix(scope[len(scope)-1], "s")) + } + + return scope.add("Elem") +} + func (g *schemaGenerator) generateEnumType( t *schemas.Type, scope nameScope, ) (codegen.Type, error) { @@ -876,7 +887,7 @@ func (g *schemaGenerator) generateEnumType( } enumDecl := codegen.TypeDecl{ - Name: g.output.uniqueTypeName(scope.string()), + Name: g.output.uniqueTypeName(scope), Type: enumType, } g.output.file.Package.AddDecl(&enumDecl) diff --git a/tests/data/deeplyNested/Makefile b/tests/data/deeplyNested/Makefile new file mode 100644 index 00000000..8e4de96c --- /dev/null +++ b/tests/data/deeplyNested/Makefile @@ -0,0 +1,2 @@ +standalone/RolloutSpecification.json: + wget --output-document=$@ --quiet https://ev2schema.azure.net/schemas/2020-01-01/$(notdir $@) \ No newline at end of file diff --git a/tests/data/deeplyNested/standalone/RolloutSpecification.go b/tests/data/deeplyNested/standalone/RolloutSpecification.go new file mode 100644 index 00000000..9fe8cdbc --- /dev/null +++ b/tests/data/deeplyNested/standalone/RolloutSpecification.go @@ -0,0 +1,483 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package test + +import "encoding/json" +import "fmt" +import "regexp" + +// The details of applications to be deployed. +type Applications struct { + // The list of actions to be performed. + Actions []string `json:"actions" yaml:"actions" mapstructure:"actions"` + + // The details of the service resources across which the application has to be + // deployed. + ApplyAcrossServiceResources ApplyAcrossServiceResources `json:"applyAcrossServiceResources" yaml:"applyAcrossServiceResources" mapstructure:"applyAcrossServiceResources"` + + // The list of the application instance names.. + Names []string `json:"names" yaml:"names" mapstructure:"names"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Applications) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["actions"]; raw != nil && !ok { + return fmt.Errorf("field actions in Applications: required") + } + if _, ok := raw["applyAcrossServiceResources"]; raw != nil && !ok { + return fmt.Errorf("field applyAcrossServiceResources in Applications: required") + } + if _, ok := raw["names"]; raw != nil && !ok { + return fmt.Errorf("field names in Applications: required") + } + type Plain Applications + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Applications(plain) + return nil +} + +// The details of the service resources across which the application has to be +// deployed. +type ApplyAcrossServiceResources struct { + // The service resource definition name. + DefinitionName string `json:"definitionName" yaml:"definitionName" mapstructure:"definitionName"` + + // Indicates if the cluster has to be deployed before application deployment. + DeployArmResources *bool `json:"deployArmResources,omitempty" yaml:"deployArmResources,omitempty" mapstructure:"deployArmResources,omitempty"` + + // The list of service resource instance names. + Names []string `json:"names" yaml:"names" mapstructure:"names"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ApplyAcrossServiceResources) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["definitionName"]; raw != nil && !ok { + return fmt.Errorf("field definitionName in ApplyAcrossServiceResources: required") + } + if _, ok := raw["names"]; raw != nil && !ok { + return fmt.Errorf("field names in ApplyAcrossServiceResources: required") + } + type Plain ApplyAcrossServiceResources + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ApplyAcrossServiceResources(plain) + return nil +} + +// The location of the build to use for this particular rollout. +type BuildSource struct { + // The parameters that define how to access and/or prepare the build from this + // build source. + Parameters Parameters `json:"parameters" yaml:"parameters" mapstructure:"parameters"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *BuildSource) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["parameters"]; raw != nil && !ok { + return fmt.Errorf("field parameters in BuildSource: required") + } + type Plain BuildSource + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = BuildSource(plain) + return nil +} + +// Option to use configuration specification file directly in rollout. +type Configuration struct { + // Service scope configuration setting + ServiceScope *ServiceScope `json:"serviceScope,omitempty" yaml:"serviceScope,omitempty" mapstructure:"serviceScope,omitempty"` +} + +// Email Notification definitions +type Email struct { + // Cc email addresses list separator with ',;' + Cc *string `json:"cc,omitempty" yaml:"cc,omitempty" mapstructure:"cc,omitempty"` + + // Conditions of when to sending the email, default will send on all start, error, + // complete events + Options *Options `json:"options,omitempty" yaml:"options,omitempty" mapstructure:"options,omitempty"` + + // To email addresses list separator with ',;' + To string `json:"to" yaml:"to" mapstructure:"to"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Email) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["to"]; raw != nil && !ok { + return fmt.Errorf("field to in Email: required") + } + type Plain Email + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Email(plain) + return nil +} + +// Incident notification definitions +type Incident struct { + // Conditions of when to create incidents, default will send on every error + Options *IncidentOptions `json:"options,omitempty" yaml:"options,omitempty" mapstructure:"options,omitempty"` + + // The incident properties + Properties Properties `json:"properties" yaml:"properties" mapstructure:"properties"` + + // The incident provider type + ProviderType string `json:"providerType" yaml:"providerType" mapstructure:"providerType"` +} + +// Conditions of when to create incidents, default will send on every error +type IncidentOptions struct { + // When corresponds to the JSON schema field "when". + When []string `json:"when,omitempty" yaml:"when,omitempty" mapstructure:"when,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Incident) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["properties"]; raw != nil && !ok { + return fmt.Errorf("field properties in Incident: required") + } + if _, ok := raw["providerType"]; raw != nil && !ok { + return fmt.Errorf("field providerType in Incident: required") + } + type Plain Incident + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Incident(plain) + return nil +} + +// Notification definitions +type Notification struct { + // Email Notification definitions + Email *Email `json:"email,omitempty" yaml:"email,omitempty" mapstructure:"email,omitempty"` + + // Incident notification definitions + Incident *Incident `json:"incident,omitempty" yaml:"incident,omitempty" mapstructure:"incident,omitempty"` +} + +// Conditions of when to sending the email, default will send on all start, error, +// complete events +type Options struct { + // 'All': All rollout information, default behavior, SummaryOnly': Only has + // summary table, no resource deployment details, Compact': Only show rows of + // failed resource operations. + Verbosity *string `json:"verbosity,omitempty" yaml:"verbosity,omitempty" mapstructure:"verbosity,omitempty"` + + // When corresponds to the JSON schema field "when". + When []string `json:"when,omitempty" yaml:"when,omitempty" mapstructure:"when,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Options) UnmarshalJSON(b []byte) error { + type Plain Options + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if plain.Verbosity != nil { + if matched, _ := regexp.MatchString(`(?i)(^All$|^SummaryOnly$|^Compact$)`, string(*plain.Verbosity)); !matched { + return fmt.Errorf("field %s pattern match: must match %s", `(?i)(^All$|^SummaryOnly$|^Compact$)`, "Verbosity") + } + } + *j = Options(plain) + return nil +} + +// An individual deployment step in the rollout of an Azure service. +type OrchestratedStep struct { + // The actions that must take place as part of this step. The actions will be + // executed in the order that they are declared. The action names must be unique. + // If this is an Extension action, the name of the extension must exist in the + // 'Extensions' block in RolloutParameters. + Actions []string `json:"actions,omitempty" yaml:"actions,omitempty" mapstructure:"actions,omitempty"` + + // The details of applications to be deployed. + Applications *Applications `json:"applications,omitempty" yaml:"applications,omitempty" mapstructure:"applications,omitempty"` + + // The names of the rollout steps that must be executed prior to the current step + // being executed. + DependsOn []string `json:"dependsOn,omitempty" yaml:"dependsOn,omitempty" mapstructure:"dependsOn,omitempty"` + + // The name of the rollout step. + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The unique identifier of the target that is to be updated. + TargetName *string `json:"targetName,omitempty" yaml:"targetName,omitempty" mapstructure:"targetName,omitempty"` + + // The type of the intended target of this rollout. + TargetType string `json:"targetType" yaml:"targetType" mapstructure:"targetType"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OrchestratedStep) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in OrchestratedStep: required") + } + if _, ok := raw["targetType"]; raw != nil && !ok { + return fmt.Errorf("field targetType in OrchestratedStep: required") + } + type Plain OrchestratedStep + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if len(plain.Name) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "name", 1) + } + if plain.TargetName != nil && len(*plain.TargetName) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "targetName", 1) + } + if matched, _ := regexp.MatchString(`(?i)(^ServiceResourceGroup$|^ServiceResource$|^Application$)`, string(plain.TargetType)); !matched { + return fmt.Errorf("field %s pattern match: must match %s", `(?i)(^ServiceResourceGroup$|^ServiceResource$|^Application$)`, "TargetType") + } + *j = OrchestratedStep(plain) + return nil +} + +// The parameters that define how to access and/or prepare the build from this +// build source. +type Parameters struct { + // The path relative to the Service Group Root which points to the file whose + // contents represent the version of the build being deployed. + VersionFile string `json:"versionFile" yaml:"versionFile" mapstructure:"versionFile"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Parameters) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["versionFile"]; raw != nil && !ok { + return fmt.Errorf("field versionFile in Parameters: required") + } + type Plain Parameters + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Parameters(plain) + return nil +} + +// The incident properties +type Properties struct { + // The connector Id for ICM + ConnectorId string `json:"connectorId" yaml:"connectorId" mapstructure:"connectorId"` + + // The incident correlation type. + CorrelateBy *string `json:"correlateBy,omitempty" yaml:"correlateBy,omitempty" mapstructure:"correlateBy,omitempty"` + + // The environment of the incidents raising location. + Environment *string `json:"environment,omitempty" yaml:"environment,omitempty" mapstructure:"environment,omitempty"` + + // The routing Id for ICM + RoutingId string `json:"routingId" yaml:"routingId" mapstructure:"routingId"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Properties) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["connectorId"]; raw != nil && !ok { + return fmt.Errorf("field connectorId in Properties: required") + } + if _, ok := raw["routingId"]; raw != nil && !ok { + return fmt.Errorf("field routingId in Properties: required") + } + type Plain Properties + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if plain.CorrelateBy != nil { + if matched, _ := regexp.MatchString(`(?i)(^rollout$)`, string(*plain.CorrelateBy)); !matched { + return fmt.Errorf("field %s pattern match: must match %s", `(?i)(^rollout$)`, "CorrelateBy") + } + } + if plain.Environment != nil { + if matched, _ := regexp.MatchString(`(?i)(^Dogfood$|^Int$|^Ppe$|^Prod$|^Staging$|^Test$)`, string(*plain.Environment)); !matched { + return fmt.Errorf("field %s pattern match: must match %s", `(?i)(^Dogfood$|^Int$|^Ppe$|^Prod$|^Staging$|^Test$)`, "Environment") + } + } + *j = Properties(plain) + return nil +} + +// The metadata associated with this particular rollout. +type RolloutMetadata struct { + // The location of the build to use for this particular rollout. + BuildSource BuildSource `json:"buildSource" yaml:"buildSource" mapstructure:"buildSource"` + + // Option to use configuration specification file directly in rollout. + Configuration *Configuration `json:"configuration,omitempty" yaml:"configuration,omitempty" mapstructure:"configuration,omitempty"` + + // The user-specified name of this particular rollout. + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // Notification definitions + Notification *Notification `json:"notification,omitempty" yaml:"notification,omitempty" mapstructure:"notification,omitempty"` + + // The path relative to the Service Group Root that points to the parameter + // replacements file. + ParameterReplacementsPath *string `json:"parameterReplacementsPath,omitempty" yaml:"parameterReplacementsPath,omitempty" mapstructure:"parameterReplacementsPath,omitempty"` + + // List of rollout policy references to use for the rollout. + RolloutPolicyReferences []RolloutPolicyReference `json:"rolloutPolicyReferences,omitempty" yaml:"rolloutPolicyReferences,omitempty" mapstructure:"rolloutPolicyReferences,omitempty"` + + // The scope of this particular rollout. + RolloutType string `json:"rolloutType" yaml:"rolloutType" mapstructure:"rolloutType"` + + // The path relative to the Service Group Root that points to the service model of + // the service that is being updated as part of this rollout. + ServiceModelPath string `json:"serviceModelPath" yaml:"serviceModelPath" mapstructure:"serviceModelPath"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *RolloutMetadata) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["buildSource"]; raw != nil && !ok { + return fmt.Errorf("field buildSource in RolloutMetadata: required") + } + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in RolloutMetadata: required") + } + if _, ok := raw["rolloutType"]; raw != nil && !ok { + return fmt.Errorf("field rolloutType in RolloutMetadata: required") + } + if _, ok := raw["serviceModelPath"]; raw != nil && !ok { + return fmt.Errorf("field serviceModelPath in RolloutMetadata: required") + } + type Plain RolloutMetadata + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if len(plain.Name) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "name", 1) + } + if matched, _ := regexp.MatchString(`(?i)(^Major$|^Minor$|^Hotfix$)`, string(plain.RolloutType)); !matched { + return fmt.Errorf("field %s pattern match: must match %s", `(?i)(^Major$|^Minor$|^Hotfix$)`, "RolloutType") + } + *j = RolloutMetadata(plain) + return nil +} + +// Policy reference details. +type RolloutPolicyReference struct { + // The name of the policy. + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The version of the policy to use. Specify '*' to use the latest registered + // version of the policy. + Version string `json:"version" yaml:"version" mapstructure:"version"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *RolloutPolicyReference) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in RolloutPolicyReference: required") + } + if _, ok := raw["version"]; raw != nil && !ok { + return fmt.Errorf("field version in RolloutPolicyReference: required") + } + type Plain RolloutPolicyReference + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = RolloutPolicyReference(plain) + return nil +} + +// A document that declares what actions are to be taken as part of an update to an +// Azure Service. +type RolloutSpecification struct { + // The version of the schema that a document conforms to. + ContentVersion string `json:"contentVersion" yaml:"contentVersion" mapstructure:"contentVersion"` + + // The exact sequence of steps that must be executed as part of this rollout. + OrchestratedSteps []OrchestratedStep `json:"orchestratedSteps" yaml:"orchestratedSteps" mapstructure:"orchestratedSteps"` + + // The metadata associated with this particular rollout. + RolloutMetadata RolloutMetadata `json:"rolloutMetadata" yaml:"rolloutMetadata" mapstructure:"rolloutMetadata"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *RolloutSpecification) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["contentVersion"]; raw != nil && !ok { + return fmt.Errorf("field contentVersion in RolloutSpecification: required") + } + if _, ok := raw["orchestratedSteps"]; raw != nil && !ok { + return fmt.Errorf("field orchestratedSteps in RolloutSpecification: required") + } + if _, ok := raw["rolloutMetadata"]; raw != nil && !ok { + return fmt.Errorf("field rolloutMetadata in RolloutSpecification: required") + } + type Plain RolloutSpecification + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if matched, _ := regexp.MatchString(`^([0-9]+\.)?([0-9]+\.)?([0-9]+\.)?([0-9]+){1}$`, string(plain.ContentVersion)); !matched { + return fmt.Errorf("field %s pattern match: must match %s", `^([0-9]+\.)?([0-9]+\.)?([0-9]+\.)?([0-9]+){1}$`, "ContentVersion") + } + *j = RolloutSpecification(plain) + return nil +} + +// Service scope configuration setting +type ServiceScope struct { + // The path relative to the Service Group Root that points to the service scope + // configuration specification. + SpecPath *string `json:"specPath,omitempty" yaml:"specPath,omitempty" mapstructure:"specPath,omitempty"` +} diff --git a/tests/data/deeplyNested/standalone/RolloutSpecification.json b/tests/data/deeplyNested/standalone/RolloutSpecification.json new file mode 100644 index 00000000..5dbd4b80 --- /dev/null +++ b/tests/data/deeplyNested/standalone/RolloutSpecification.json @@ -0,0 +1,282 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Rollout Specification", + "description": "A document that declares what actions are to be taken as part of an update to an Azure Service. ", + "type": "object", + "properties": { + + "contentVersion": { + "description": "The version of the schema that a document conforms to.", + "type": "string", + "pattern": "^([0-9]+\\.)?([0-9]+\\.)?([0-9]+\\.)?([0-9]+){1}$" + }, + + "rolloutMetadata": { + "description": "The metadata associated with this particular rollout.", + "type": "object", + "properties": { + "serviceModelPath": { + "description": "The path relative to the Service Group Root that points to the service model of the service that is being updated as part of this rollout.", + "type": "string" + }, + "parameterReplacementsPath": { + "description": "The path relative to the Service Group Root that points to the parameter replacements file.", + "type": "string" + }, + "name": { + "description": "The user-specified name of this particular rollout.", + "type": "string", + "minLength": 1 + }, + "rolloutType": { + "description": "The scope of this particular rollout.", + "type": "string", + "pattern": "(?i)(^Major$|^Minor$|^Hotfix$)" + }, + "buildSource": { + "description": "The location of the build to use for this particular rollout.", + "type": "object", + "properties": { + "parameters": { + "type": "object", + "description": "The parameters that define how to access and/or prepare the build from this build source.", + "properties": { + "versionFile": { + "type": "string", + "description": "The path relative to the Service Group Root which points to the file whose contents represent the version of the build being deployed. " + } + }, + "required": [ "versionFile" ] + } + }, + "required": [ "parameters" ] + }, + "notification": { + "type": "object", + "description": "Notification definitions", + "properties": { + "email": { + "type": "object", + "description": "Email Notification definitions", + "properties": { + "to": { + "type": "string", + "description": "To email addresses list separator with ',;'" + }, + "cc": { + "type": "string", + "description": "Cc email addresses list separator with ',;'" + }, + "options": { + "type": "object", + "description": "Conditions of when to sending the email, default will send on all start, error, complete events", + "properties": { + "when": { + "type": "array", + "items": { + "type": "string", + "pattern": "(onStart|onError|onComplete)" + } + }, + "verbosity": { + "type": "string", + "description": "'All': All rollout information, default behavior, SummaryOnly': Only has summary table, no resource deployment details, Compact': Only show rows of failed resource operations.", + "pattern": "(?i)(^All$|^SummaryOnly$|^Compact$)" + } + } + } + }, + "required": [ "to" ] + }, + "incident": { + "type": "object", + "description": "Incident notification definitions", + "properties": { + "providerType": { + "type": "string", + "description": "The incident provider type" + }, + "properties": { + "type": "object", + "description": "The incident properties", + "properties": { + "connectorId": { + "type": "string", + "description": "The connector Id for ICM" + }, + "routingId": { + "type": "string", + "description": "The routing Id for ICM" + }, + "environment": { + "type": "string", + "description": "The environment of the incidents raising location.", + "pattern": "(?i)(^Dogfood$|^Int$|^Ppe$|^Prod$|^Staging$|^Test$)" + }, + "correlateBy": { + "type": "string", + "description": "The incident correlation type.", + "pattern": "(?i)(^rollout$)" + } + }, + "required": [ "connectorId", "routingId" ] + }, + "options": { + "type": "object", + "description": "Conditions of when to create incidents, default will send on every error", + "properties": { + "when": { + "type": "array", + "items": { + "type": "string", + "pattern": "(?i)(^onLastAutoRestart$|^onValidationRollout$)" + } + } + } + } + }, + "required": [ "providerType", "properties" ] + } + } + }, + "rolloutPolicyReferences": { + "description": "List of rollout policy references to use for the rollout.", + "type": "array", + "items": { + "type": "object", + "description": "Policy reference details.", + "properties": { + "name": { + "type": "string", + "description": "The name of the policy." + }, + "version": { + "type": "string", + "description": "The version of the policy to use. Specify '*' to use the latest registered version of the policy." + } + }, + "required": [ "name", "version" ] + } + }, + "configuration": { + "description": "Option to use configuration specification file directly in rollout.", + "type": "object", + "properties": { + "serviceScope": { + "type": "object", + "description": "Service scope configuration setting", + "properties": { + "specPath": { + "type": "string", + "description": "The path relative to the Service Group Root that points to the service scope configuration specification." + } + }, + "serviceGroupScope": { + "type": "object", + "description": "Service Group scope configuration setting", + "properties": { + "specPath": { + "type": "string", + "description": "The path relative to the Service Group Root that points to the service group scope configuration specification." + } + } + } + } + } + } + }, + "required": [ "serviceModelPath", "name", "rolloutType", "buildSource" ] + }, + + "orchestratedSteps": { + "description": "The exact sequence of steps that must be executed as part of this rollout. ", + "type": "array", + "items": { + "type": "object", + "description": "An individual deployment step in the rollout of an Azure service. ", + "properties": { + "name": { + "description": "The name of the rollout step.", + "type": "string", + "minLength": 1 + }, + "targetType": { + "description": "The type of the intended target of this rollout.", + "type": "string", + "pattern": "(?i)(^ServiceResourceGroup$|^ServiceResource$|^Application$)" + }, + "targetName": { + "description": "The unique identifier of the target that is to be updated.", + "type": "string", + "minLength": 1 + }, + "actions": { + "description": "The actions that must take place as part of this step. The actions will be executed in the order that they are declared. The action names must be unique. If this is an Extension action, the name of the extension must exist in the 'Extensions' block in RolloutParameters.", + "type": "array", + "items": { + "type": "string", + "pattern": "(?i)^(deploy|(mdmHealthCheck|restHealthCheck|extension|shell|register|wait)\/+[\\w\\W]+)" + }, + "uniqueItems": true + }, + "dependsOn": { + "description": "The names of the rollout steps that must be executed prior to the current step being executed.", + "type": "array", + "items": { + "type": "string", + "description": "The name of an individual rollout step that the current step depends on. " + } + }, + "applications": { + "description": "The details of applications to be deployed.", + "type": "object", + "properties": { + "names": { + "description": "The list of the application instance names..", + "type": "array", + "items": { + "description": "The application instance name.", + "type": "string" + } + }, + "actions": { + "description": "The list of actions to be performed.", + "type": "array", + "items": { + "description": "The action names. Valid value is AppDeploy.", + "type": "string" + } + }, + "applyAcrossServiceResources": { + "description": "The details of the service resources across which the application has to be deployed.", + "type": "object", + "properties": { + "definitionName": { + "description": "The service resource definition name.", + "type": "string" + }, + "deployArmResources": { + "description": "Indicates if the cluster has to be deployed before application deployment.", + "type": "boolean" + }, + "names": { + "description": "The list of service resource instance names.", + "type": "array", + "items": { + "description": "The service resource instance name.", + "type": "string" + } + } + }, + "required": [ "definitionName", "names" ] + } + }, + "required": [ "names", "actions", "applyAcrossServiceResources" ] + } + }, + "required": [ "name", "targetType" ] + } + } + }, + "required": [ "contentVersion", "rolloutMetadata", "orchestratedSteps" ] +} \ No newline at end of file diff --git a/tests/generation_test.go b/tests/generation_test.go index f2dd52dd..fe72a4bb 100644 --- a/tests/generation_test.go +++ b/tests/generation_test.go @@ -174,6 +174,15 @@ func TestMinSizeInt(t *testing.T) { testExamples(t, cfg, "./data/minSizedInts") } +func TestDeeplyNestedMinimalNames(t *testing.T) { + t.Parallel() + + cfg := basicConfig + cfg.MinimalNames = true + + testExamples(t, cfg, "./data/deeplyNested") +} + func testExamples(t *testing.T, cfg generator.Config, dataDir string) { t.Helper() @@ -252,12 +261,20 @@ func testExampleFile(t *testing.T, cfg generator.Config, fileName string) { } } - if diff := cmp.Diff(string(goldenData), string(source)); diff != "" { - t.Errorf("Contents different (left is expected, right is actual):\n%s", diff) - } + if _, update := os.LookupEnv("UPDATE"); update { + t.Logf("Updating file %s", mustAbs(goldenFileName)) - if diff, ok := diffStrings(t, string(goldenData), string(source)); !ok { - t.Fatalf("Contents different (left is expected, right is actual):\n%s", *diff) + if err = os.WriteFile(goldenFileName, source, 0o655); err != nil { + t.Fatal(err) + } + } else { + if diff := cmp.Diff(string(goldenData), string(source)); diff != "" { + t.Errorf("Contents different (left is expected, right is actual):\n%s", diff) + } + + if diff, ok := diffStrings(t, string(goldenData), string(source)); !ok { + t.Fatalf("Contents different (left is expected, right is actual):\n%s", *diff) + } } } }) diff --git a/tests/go.mod b/tests/go.mod index ac2cba56..57551b5f 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -2,8 +2,6 @@ module github.com/atombender/go-jsonschema/tests go 1.22.0 -toolchain go1.22.0 - replace ( github.com/atombender/go-jsonschema => ../ github.com/atombender/go-jsonschema/tests/helpers/other => ./helpers/other @@ -13,21 +11,17 @@ require ( github.com/atombender/go-jsonschema v0.16.0 github.com/atombender/go-jsonschema/tests/helpers/other v0.0.0-20240909221408-bcba1cdc5eb2 github.com/go-viper/mapstructure/v2 v2.1.0 + github.com/google/go-cmp v0.6.0 github.com/stretchr/testify v1.9.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.17.0 // indirect - github.com/goccy/go-yaml v1.12.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/goccy/go-yaml v1.14.3 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sanity-io/litter v1.5.5 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect ) diff --git a/tests/go.sum b/tests/go.sum index 7b0ff71c..b11f635c 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -1,27 +1,12 @@ github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= -github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= +github.com/goccy/go-yaml v1.14.3 h1:8tVD+aqqPLWisSEhM+6wWoiURWXCx6BwaTKS6ZeITgM= +github.com/goccy/go-yaml v1.14.3/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -34,16 +19,8 @@ github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=