Repository: go-swagger/go-swagger
Pull request #2333 (2020-07-03 11:47:46)

Enhanced generate model CLI: accepts definitions only specs, deprecates some options
Feature
* added --accept-definitions-only option to generate model from a spec with only definitions (not the complete swagger spec)

Deprecated CLI options
* deprecated generate model option: --skip-struct (was inoperant)
* deprecated generate model option: --all-definitions (now the default behavior, as previously documented)
* updated usage doc for generate model CLI
* added deprecation notice to README (no breaking change introduced on CLI)

Refactoring
* refactored generate model command to use standalone model codegen func generator.GenerateModels (now in line with other commands)
* refactored places where GenOpts.IncludeValidator is hard-coded to true, in order to drive this parameter from one single place (unused, but might come back)
* refactored test for full server codegen, essentially to share test code with the new GenerateModels func

Signed-off-by: Frederic BIDON <fredbi@yahoo.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/go-swagger/go-swagger/pull/2333?src=pr&el=h1) Report
> Merging [#2333](https://codecov.io/gh/go-swagger/go-swagger/pull/2333?src=pr&el=desc) into [master](https://codecov.io/gh/go-swagger/go-swagger/commit/a47e304833fabfbe7a30958733dfe74b99897584&el=desc) will **decrease** coverage by `0.25%`.
> The diff coverage is `25.00%`.

[![Impacted file tree graph](https://codecov.io/gh/go-swagger/go-swagger/pull/2333/graphs/tree.svg?width=650&height=150&src=pr&token=aQS1DepR8C)](https://codecov.io/gh/go-swagger/go-swagger/pull/2333?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #2333      +/-   ##
==========================================
- Coverage   81.43%   81.18%   -0.26%     
==========================================
  Files          50       50              
  Lines        8491     8518      +27     
==========================================
  Hits         6915     6915              
- Misses       1079     1105      +26     
- Partials      497      498       +1     
```


| [Impacted Files](https://codecov.io/gh/go-swagger/go-swagger/pull/2333?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [generator/spec.go](https://codecov.io/gh/go-swagger/go-swagger/pull/2333/diff?src=pr&el=tree#diff-Z2VuZXJhdG9yL3NwZWMuZ28=) | `68.33% <0.00%> (-12.86%)` | :arrow_down: |
| [generator/model.go](https://codecov.io/gh/go-swagger/go-swagger/pull/2333/diff?src=pr&el=tree#diff-Z2VuZXJhdG9yL21vZGVsLmdv) | `84.19% <11.11%> (-0.59%)` | :arrow_down: |
| [cmd/swagger/commands/generate/model.go](https://codecov.io/gh/go-swagger/go-swagger/pull/2333/diff?src=pr&el=tree#diff-Y21kL3N3YWdnZXIvY29tbWFuZHMvZ2VuZXJhdGUvbW9kZWwuZ28=) | `100.00% <100.00%> (ø)` | |
| [generator/operation.go](https://codecov.io/gh/go-swagger/go-swagger/pull/2333/diff?src=pr&el=tree#diff-Z2VuZXJhdG9yL29wZXJhdGlvbi5nbw==) | `93.63% <100.00%> (ø)` | |
| [generator/shared.go](https://codecov.io/gh/go-swagger/go-swagger/pull/2333/diff?src=pr&el=tree#diff-Z2VuZXJhdG9yL3NoYXJlZC5nbw==) | `89.55% <100.00%> (ø)` | |
| [generator/support.go](https://codecov.io/gh/go-swagger/go-swagger/pull/2333/diff?src=pr&el=tree#diff-Z2VuZXJhdG9yL3N1cHBvcnQuZ28=) | `87.26% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/go-swagger/go-swagger/pull/2333?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/go-swagger/go-swagger/pull/2333?src=pr&el=footer). Last update [a47e304...b5315f4](https://codecov.io/gh/go-swagger/go-swagger/pull/2333?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index e781a836..1081a4ae 100644
--- a/README.md
+++ b/README.md
@@ -221,24 +221,14 @@ Iron.io
 
 ## Note to users migrating from older releases
 
-### Using 0.5.0
+### Migrating from 0.24 to [master]
 
-Because 0.5.0 and master have diverged significantly, you should checkout the tag 0.5.0 for go-swagger when you use the currently released version.
+The options for `generate model --all-definitions` and `--skip-struct` are marked for deprecation. 
 
-### Migrating from 0.5.0 to 0.6.0
+For now, the CLI continues to accept these options. They will be removed in a future version.
 
-You will have to rename some imports:
-
-```
-github.com/go-swagger/go-swagger/httpkit/validate to github.com/go-openapi/validate
-github.com/go-swagger/go-swagger/httpkit to github.com/go-openapi/runtime
-github.com/naoina/denco to github.com/go-openapi/runtime/middleware/denco
-github.com/go-swagger/go-swagger to github.com/go-openapi
-```
-
-### Migrating from 0.12 to 0.13
-
-Spec flattening and $ref resolution brought breaking changes in model generation, since all complex things generate their own definitions.
+Generating all definitions is now the default behavior when no other option filters the generation scope.
+The `--skip-struct` option had no effect.
 
 ### Migrating from 0.14 to 0.15
 
@@ -252,3 +242,22 @@ Spec flattening now defaults to minimal changes to models and should be workable
 Users who prefer to stick to 0.13 and 0.14 default flattening mode may now use the `--with-flatten=full` option.
 
 Note that the `--skip-flatten` option has been phased out and replaced by the more explicit `--with-expand` option.
+
+### Migrating from 0.12 to 0.13
+
+Spec flattening and $ref resolution brought breaking changes in model generation, since all complex things generate their own definitions.
+
+### Migrating from 0.5.0 to 0.6.0
+
+You will have to rename some imports:
+
+```
+github.com/go-swagger/go-swagger/httpkit/validate to github.com/go-openapi/validate
+github.com/go-swagger/go-swagger/httpkit to github.com/go-openapi/runtime
+github.com/naoina/denco to github.com/go-openapi/runtime/middleware/denco
+github.com/go-swagger/go-swagger to github.com/go-openapi
+```
+
+### Using 0.5.0
+
+Because 0.5.0 and master have diverged significantly, you should checkout the tag 0.5.0 for go-swagger when you use the currently released version.
diff --git a/cmd/swagger/commands/generate/model.go b/cmd/swagger/commands/generate/model.go
index 5d1a03ea..7cbc8559 100644
--- a/cmd/swagger/commands/generate/model.go
+++ b/cmd/swagger/commands/generate/model.go
@@ -27,7 +27,7 @@ type modelOptions struct {
 	ExistingModels             string   `long:"existing-models" description:"use pre-generated models e.g. github.com/foobar/model"`
 	StrictAdditionalProperties bool     `long:"strict-additional-properties" description:"disallow extra properties when additionalProperties is set to false"`
 	KeepSpecOrder              bool     `long:"keep-spec-order" description:"keep schema properties order identical to spec file"`
-	AllDefinitions             bool     `long:"all-definitions" description:"generate all model definitions regardless of usage in operations"`
+	AllDefinitions             bool     `long:"all-definitions" description:"generate all model definitions regardless of usage in operations" hidden:"deprecated"`
 	StructTags                 []string `long:"struct-tags" description:"the struct tags to generate, repeat for multiple (defaults to json)"`
 }
 
@@ -41,19 +41,23 @@ func (mo modelOptions) apply(opts *generator.GenOpts) {
 	opts.StructTags = mo.StructTags
 }
 
-// WithModels adds the model options group
+// WithModels adds the model options group.
+//
+// This group is available to all commands that need some model generation.
 type WithModels struct {
 	Models modelOptions `group:"Options for model generation"`
 }
 
-// Model the generate model file command
+// Model the generate model file command.
+//
+// Define the options that are specific to the "swagger generate model" command.
 type Model struct {
 	WithShared
 	WithModels
 
-	NoStruct bool `long:"skip-struct" description:"when present will not generate the model struct"`
-
-	Name []string `long:"name" short:"n" description:"the model to generate, repeat for multiple (defaults to all). Same as --models"`
+	NoStruct              bool     `long:"skip-struct" description:"when present will not generate the model struct" hidden:"deprecated"`
+	Name                  []string `long:"name" short:"n" description:"the model to generate, repeat for multiple (defaults to all). Same as --models"`
+	AcceptDefinitionsOnly bool     `long:"accept-definitions-only" description:"accepts a partial swagger spec wih only the definitions key"`
 }
 
 func (m Model) apply(opts *generator.GenOpts) {
@@ -62,6 +66,7 @@ func (m Model) apply(opts *generator.GenOpts) {
 
 	opts.IncludeModel = !m.NoStruct
 	opts.IncludeValidator = !m.NoStruct
+	opts.AcceptDefinitionsOnly = m.AcceptDefinitionsOnly
 }
 
 func (m Model) log(rp string) {
@@ -77,17 +82,13 @@ You can get these now with: go get -u -f %s/...
 }
 
 func (m *Model) generate(opts *generator.GenOpts) error {
-	// NOTE: at the moment, the model generator (generator.GenerateDefinition)
-	// is not standalone: use server generator as a proxy
-	opts.IncludeSupport = false
-	opts.IncludeMain = false
-	return generator.GenerateServer("", append(m.Name, m.Models.Models...), nil, opts)
+	return generator.GenerateModels(append(m.Name, m.Models.Models...), opts)
 }
 
 // Execute generates a model file
 func (m *Model) Execute(args []string) error {
 
-	if m.Shared.DumpData && (len(m.Name) > 1 || len(m.Models.Models) > 1) {
+	if m.Shared.DumpData && len(append(m.Name, m.Models.Models...)) > 1 {
 		return errors.New("only 1 model at a time is supported for dumping data")
 	}
 
diff --git a/docs/generate/model.md b/docs/generate/model.md
index f6c490d6..c83749b1 100644
--- a/docs/generate/model.md
+++ b/docs/generate/model.md
@@ -15,8 +15,8 @@ Help Options:
   -h, --help                                                                      Show this help message
 
 [model command options]
-          --skip-struct                                                           when present will not generate the model struct
       -n, --name=                                                                 the model to generate, repeat for multiple (defaults to all). Same as --models
+          --accept-definitions-only                                               accepts a partial swagger spec wih only the definitions key
 
     Options common to all code generation commands:
       -f, --spec=                                                                 the spec file to use (default swagger.{json,yml,yaml})
@@ -29,6 +29,7 @@ Help Options:
           --allow-template-override                                               allows overriding protected templates
           --skip-validation                                                       skips validation of spec prior to generation
           --dump-data                                                             when present dumps the json for the template generator instead of generating files
+          --strict-responders                                                     Use strict type for the handler return value
           --with-expand                                                           expands all $ref's in spec prior to generation (shorthand to --with-flatten=expand)
           --with-flatten=[minimal|full|expand|verbose|noverbose|remove-unused]    flattens all $ref's in spec prior to generation (default: minimal, verbose)
 
@@ -38,6 +39,7 @@ Help Options:
           --existing-models=                                                      use pre-generated models e.g. github.com/foobar/model
           --strict-additional-properties                                          disallow extra properties when additionalProperties is set to false
           --keep-spec-order                                                       keep schema properties order identical to spec file
+          --struct-tags=                                                          the struct tags to generate, repeat for multiple (defaults to json)
 ```
 
 Schema generation rules are detailed [here](../use/model.md).
diff --git a/docs/use/spec/meta.md b/docs/use/spec/meta.md
index 86f633de..f4d50586 100644
--- a/docs/use/spec/meta.md
+++ b/docs/use/spec/meta.md
@@ -95,6 +95,8 @@ Annotation | Format
 package classification
 ```
 
+> **NOTE:** If there is whitespace line between the comment and package (or type, function), swagger will generate an empty specification. This is due to the violation of the convention for Godoc. For more details refer [here](https://blog.golang.org/godoc)
+
 ##### Result
 
 ```yaml
diff --git a/fixtures/enhancements/2333/fixture-definitions.yaml b/fixtures/enhancements/2333/fixture-definitions.yaml
new file mode 100644
index 00000000..51e4afd6
--- /dev/null
+++ b/fixtures/enhancements/2333/fixture-definitions.yaml
@@ -0,0 +1,13 @@
+definitions:
+  modelInterface:
+    type: object
+    additionalProperties: true
+  recordsModel:
+    type: array
+    items:
+      type: object
+  recordsModelWithMax:
+    type: array
+    maxItems: 10
+    items:
+      type: object
diff --git a/generator/model.go b/generator/model.go
index 8e84ce48..aa4bc955 100644
--- a/generator/model.go
+++ b/generator/model.go
@@ -47,7 +47,23 @@ Every action that happens tracks the path which is a linked list of refs
 
 */
 
-// GenerateDefinition generates a model file for a schema definition.
+// GenerateModels generates all model files for some schema definitions
+func GenerateModels(modelNames []string, opts *GenOpts) error {
+	// overide any default or incompatible options setting
+	opts.IncludeModel = true
+	opts.IgnoreOperations = true
+	opts.ExistingModels = ""
+	opts.IncludeHandler = false
+	opts.IncludeMain = false
+	opts.IncludeSupport = false
+	generator, err := newAppGenerator("", modelNames, nil, opts)
+	if err != nil {
+		return err
+	}
+	return generator.Generate()
+}
+
+// GenerateDefinition generates a single model file for some schema definitions
 func GenerateDefinition(modelNames []string, opts *GenOpts) error {
 	if err := opts.CheckOpts(); err != nil {
 		return err
@@ -62,6 +78,7 @@ func GenerateDefinition(modelNames []string, opts *GenOpts) error {
 		return err
 	}
 
+	modelNames = pruneEmpty(modelNames)
 	if len(modelNames) == 0 {
 		for k := range specDoc.Spec().Definitions {
 			modelNames = append(modelNames, k)
diff --git a/generator/operation.go b/generator/operation.go
index 601e4d44..e9edf619 100644
--- a/generator/operation.go
+++ b/generator/operation.go
@@ -100,7 +100,7 @@ func GenerateServerOperation(operationNames []string, opts *GenOpts) error {
 			IncludeHandler:       opts.IncludeHandler,
 			IncludeParameters:    opts.IncludeParameters,
 			IncludeResponses:     opts.IncludeResponses,
-			IncludeValidator:     true, // we no more support the CLI option to disable validation
+			IncludeValidator:     opts.IncludeValidator,
 			DumpData:             opts.DumpData,
 			DefaultScheme:        opts.DefaultScheme,
 			DefaultProduces:      opts.DefaultProduces,
@@ -1074,7 +1074,7 @@ func (b *codeGenOpBuilder) buildOperationSchema(schemaPath, containerName, schem
 		TypeResolver:               rslv,
 		Named:                      false,
 		IncludeModel:               true,
-		IncludeValidator:           true,
+		IncludeValidator:           b.GenOpts.IncludeValidator,
 		StrictAdditionalProperties: b.GenOpts.StrictAdditionalProperties,
 		ExtraSchemas:               make(map[string]GenSchema),
 		StructTags:                 b.GenOpts.StructTags,
diff --git a/generator/shared.go b/generator/shared.go
index 5383faf1..9321bf00 100644
--- a/generator/shared.go
+++ b/generator/shared.go
@@ -267,6 +267,7 @@ type GenOpts struct {
 	IgnoreOperations       bool
 	AllowEnumCI            bool
 	StrictResponders       bool
+	AcceptDefinitionsOnly  bool
 }
 
 // CheckOpts carries out some global consistency checks on options.
@@ -396,7 +397,7 @@ func (g *GenOpts) EnsureDefaults() error {
 	}
 
 	// always include validator with models
-	g.IncludeValidator = g.IncludeModel
+	g.IncludeValidator = true
 
 	if g.Principal == "" {
 		g.Principal = iface
diff --git a/generator/spec.go b/generator/spec.go
index ebd28393..68e08ce4 100644
--- a/generator/spec.go
+++ b/generator/spec.go
@@ -1,6 +1,7 @@
 package generator
 
 import (
+	"encoding/json"
 	"errors"
 	"fmt"
 	"io/ioutil"
@@ -11,6 +12,7 @@ import (
 	"github.com/go-openapi/analysis"
 	swaggererrors "github.com/go-openapi/errors"
 	"github.com/go-openapi/loads"
+	"github.com/go-openapi/spec"
 	"github.com/go-openapi/strfmt"
 	"github.com/go-openapi/swag"
 	"github.com/go-openapi/validate"
@@ -24,6 +26,14 @@ func (g *GenOpts) validateAndFlattenSpec() (*loads.Document, error) {
 		return nil, err
 	}
 
+	// If accepts definitions only, add dummy swagger header to pass validation
+	if g.AcceptDefinitionsOnly {
+		specDoc, err = applyDefaultSwagger(specDoc)
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	// Validate if needed
 	if g.ValidateSpec {
 		log.Printf("validating spec %v", g.Spec)
@@ -213,3 +223,26 @@ func WithAutoXOrder(specPath string) string {
 	}
 	return tmpFile.Name()
 }
+
+func applyDefaultSwagger(doc *loads.Document) (*loads.Document, error) {
+	// bake a minimal swagger spec to pass validation
+	swspec := doc.Spec()
+	if swspec.Swagger == "" {
+		swspec.Swagger = "2.0"
+	}
+	if swspec.Info == nil {
+		info := new(spec.Info)
+		info.Version = "0.0.0"
+		info.Title = "minimal"
+		swspec.Info = info
+	}
+	if swspec.Paths == nil {
+		swspec.Paths = &spec.Paths{}
+	}
+	// rewrite the document with the new addition
+	jazon, err := json.Marshal(swspec)
+	if err != nil {
+		return nil, err
+	}
+	return loads.Analyzed(jazon, swspec.Swagger)
+}
diff --git a/generator/support.go b/generator/support.go
index 9889cd62..a1184ff8 100644
--- a/generator/support.go
+++ b/generator/support.go
@@ -144,7 +144,7 @@ func (a *appGenerator) Generate() error {
 		log.Printf("rendering %d models", len(app.Models))
 		for _, mod := range app.Models {
 			mod.IncludeModel = true
-			mod.IncludeValidator = true // we systematically include model validation code (previous CLI flag to skip this is gone)
+			mod.IncludeValidator = a.GenOpts.IncludeValidator
 			if err := a.GenOpts.renderDefinition(&mod); err != nil {
 				return err
 			}
@@ -272,7 +272,7 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
 			Operation:        *o,
 			Method:           opp.Method,
 			Path:             opp.Path,
-			IncludeValidator: true,
+			IncludeValidator: a.GenOpts.IncludeValidator,
 			APIPackage:       a.APIPackage, // defaults to main operations package
 			DefaultProduces:  a.DefaultProduces,
 			DefaultConsumes:  a.DefaultConsumes,


Test patch:
diff --git a/generator/generate_test.go b/generator/generate_test.go
index 2c31a91a..d97889bf 100644
--- a/generator/generate_test.go
+++ b/generator/generate_test.go
@@ -2,6 +2,7 @@ package generator
 
 import (
 	"bytes"
+	"fmt"
 	"io/ioutil"
 	"log"
 	"os"
@@ -14,24 +15,53 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func TestGenerateAndTest(t *testing.T) {
-	if runtime.GOOS == "windows" {
-		// don't run race tests on Appveyor CI
-		t.SkipNow()
+type generateFixture struct {
+	name   string
+	spec   string
+	target string
+	//template  string
+	wantError bool
+	prepare   func(opts *GenOpts)
+	verify    func(testing.TB, string)
+}
+
+func (f generateFixture) prepareTarget(name, base string, opts *GenOpts) func() {
+	if name == "" {
+		name = f.name
+	}
+	spec := filepath.FromSlash(f.spec)
+	if f.target == "" {
+		opts.Target = filepath.Join(filepath.Dir(spec), opts.LanguageOpts.ManglePackageName(name, base))
+	} else {
+		opts.Target = f.target
+	}
+	opts.Spec = spec
+	_ = os.Mkdir(opts.Target, 0700)
+	return func() {
+		if f.target == "" {
+			_ = os.RemoveAll(filepath.Join(opts.Target))
+			return
+		}
+		_ = os.RemoveAll(filepath.Join(f.target, defaultServerTarget))
+		_ = os.RemoveAll(filepath.Join(f.target, "cmd"))
+		_ = os.RemoveAll(filepath.Join(f.target, defaultModelsTarget))
 	}
+}
+
+func (f generateFixture) warnFailed(t testing.TB, buf fmt.Stringer) func() {
+	return func() {
+		if t.Failed() {
+			t.Logf("ERROR: generation failed:\n%s", buf.String())
+		}
+	}
+}
+
+func TestGenerateAndTest(t *testing.T) {
 	defer func() {
 		log.SetOutput(os.Stdout)
 	}()
 
-	cases := map[string]struct {
-		name      string
-		spec      string
-		target    string
-		template  string
-		wantError bool
-		prepare   func(opts *GenOpts)
-		verify    func(testing.TB, string)
-	}{
+	cases := map[string]generateFixture{
 		"issue 1943": {
 			spec:   "../fixtures/bugs/1943/fixture-1943.yaml",
 			target: "../fixtures/bugs/1943",
@@ -39,6 +69,11 @@ func TestGenerateAndTest(t *testing.T) {
 				opts.ExcludeSpec = false
 			},
 			verify: func(t testing.TB, target string) {
+				if runtime.GOOS == "windows" {
+					// don't run race tests on Appveyor CI
+					t.Logf("warn: race test skipped on windows")
+					return
+				}
 				packages := filepath.Join(target, "...")
 				testPrg := filepath.Join(target, "datarace_test.go")
 
@@ -210,35 +245,14 @@ func TestGenerateAndTest(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			var captureLog bytes.Buffer
 			log.SetOutput(&captureLog)
+			defer thisCas.warnFailed(t, &captureLog)
 
-			defer func() {
-				if t.Failed() {
-					t.Logf("ERROR: generation failed:\n%s", captureLog.String())
-				}
-			}()
-			spec := filepath.FromSlash(thisCas.spec)
 			opts := testGenOpts()
-			if thisCas.target == "" {
-				opts.Target = filepath.Join(filepath.Dir(spec), opts.LanguageOpts.ManglePackageName(name, "server_test"))
-			} else {
-				opts.Target = thisCas.target
-			}
-			_ = os.Mkdir(opts.Target, 0755)
-
-			opts.Spec = spec
+			defer thisCas.prepareTarget(name, "server_test", opts)()
 
 			if thisCas.prepare != nil {
 				thisCas.prepare(opts)
 			}
-			defer func() {
-				if thisCas.target == "" {
-					_ = os.RemoveAll(filepath.Join(opts.Target))
-				} else {
-					_ = os.RemoveAll(filepath.Join(opts.Target, defaultServerTarget))
-					_ = os.RemoveAll(filepath.Join(opts.Target, "cmd"))
-					_ = os.RemoveAll(filepath.Join(opts.Target, defaultModelsTarget))
-				}
-			}()
 
 			t.Logf("generating test server at: %s", opts.Target)
 			err := GenerateServer("", nil, nil, opts)
diff --git a/generator/model_test.go b/generator/model_test.go
index 08161233..562f19f4 100644
--- a/generator/model_test.go
+++ b/generator/model_test.go
@@ -20,6 +20,7 @@ import (
 	"io/ioutil"
 	"log"
 	"os"
+	"path/filepath"
 	"regexp"
 	"strconv"
 	"strings"
@@ -29,6 +30,7 @@ import (
 	"github.com/go-openapi/loads"
 	"github.com/go-openapi/swag"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 type templateTest struct {
@@ -2655,3 +2657,65 @@ func TestGenModel_XMLStructTags_Explicit(t *testing.T) {
 		}
 	}
 }
+
+func TestGenerateModels(t *testing.T) {
+	defer func() {
+		log.SetOutput(os.Stdout)
+	}()
+
+	cases := map[string]generateFixture{
+		"allDefinitions": {
+			spec:   "../fixtures/bugs/1042/fixture-1042.yaml",
+			target: "../fixtures/bugs/1042",
+			verify: func(t testing.TB, target string) {
+				target = filepath.Join(target, defaultModelsTarget)
+				require.True(t, fileExists(target, ""))
+				assert.True(t, fileExists(target, "a.go"))
+				assert.True(t, fileExists(target, "b.go"))
+			},
+		},
+		"acceptDefinitions": {
+			spec:   "../fixtures/enhancements/2333/fixture-definitions.yaml",
+			target: "../fixtures/enhancements/2333",
+			prepare: func(opts *GenOpts) {
+				opts.AcceptDefinitionsOnly = true
+			},
+			verify: func(t testing.TB, target string) {
+				target = filepath.Join(target, defaultModelsTarget)
+				require.True(t, fileExists(target, ""))
+				assert.True(t, fileExists(target, "model_interface.go"))
+				assert.True(t, fileExists(target, "records_model.go"))
+				assert.True(t, fileExists(target, "records_model_with_max.go"))
+				assert.False(t, fileExists(target, "restapi"))
+			},
+		},
+	}
+	for name, cas := range cases {
+		thisCas := cas
+
+		t.Run(name, func(t *testing.T) {
+			var captureLog bytes.Buffer
+			log.SetOutput(&captureLog)
+			defer thisCas.warnFailed(t, &captureLog)
+
+			opts := testGenOpts()
+			defer thisCas.prepareTarget(name, "model_test", opts)()
+
+			if thisCas.prepare != nil {
+				thisCas.prepare(opts)
+			}
+
+			t.Logf("generating test models at: %s", opts.Target)
+			err := GenerateModels([]string{"", ""}, opts) // NOTE: generate all models, ignore ""
+			if thisCas.wantError {
+				require.Errorf(t, err, "expected an error for models build fixture: %s", opts.Spec)
+			} else {
+				require.NoError(t, err, "unexpected error for models build fixture: %s", opts.Spec)
+			}
+
+			if thisCas.verify != nil {
+				thisCas.verify(t, opts.Target)
+			}
+		})
+	}
+}