Repository: deepmap/oapi-codegen
Pull request #154 (2020-03-15 12:46:22)

feat: prune unused components by default
## Changes

- Added [`.editorconfig`](https://editorconfig.org/) file (can remove this if you want but IMO it's handy to have and helped fix my whitespace formatting).
- Added a `skip-prune` flag that is similar to `skip-fmt` in that it can be used to prevent running the pruning logic when generating code.
- Rather than making the test code generation use the `skip-prune` flag (which would be a quick way to make the tests pass without updating the specs) I thought it was better to update them and make sure than any components that would have been pruned are referred to in at least one operation (so they don't get pruned). This means that `test/components/components.yaml`, `test/schemas/schemas.yaml` and `test/test-schema.yaml` have a number of additions to the generated code but they're not **missing** anything apart from this https://github.com/deepmap/oapi-codegen/pull/154/files#r392671169
- I moved the filtering logic of the `openapi3.Swagger` from `codegen.go` into `filter.go` (can move these back if it's preferred).
- I have two main types of test in `prune_test.go`
  - `pruneComprehensiveTestFixture` has a fixture where with every type of component and none of them are referred to by an operation. This is to test that it's correctly removing all types of components if they're not referenced.
  - `pruneSpecTestFixture` has a `GET /cat` operation tagged with `cat` and a `GET /dog` operation tagged with `dog`. Both operations share a reference to `#/components/schemas/Error`. The test ensures that when you filter by one tag you don't get models that are only used in the other.

## Notes

Fixes https://github.com/deepmap/oapi-codegen/issues/151

StevenACoffman: This needs a rebase!
StevenACoffman: @deepmap-marcinr Ok, this is clear for merge now!
deepmap-marcinr: Could we invert the flag, call it prune-unused? Have it maintain current behavior without the flag, and you can pass it in if you want the pruning?
pseudo-su: @deepmap-marcinr I can do that, but before I do I just wanted to mention why I decided on prune by default when I was thinking about it:

1. Keeps the flag name consistent alongside `skip-fmt`
2. It doesn't **seem** like a breaking change on upgrade because if schemas are never referenced elsewhere in the spec then generated code from this library never would've needed it. The only situation where types would dissapear when consumers of this library upgrade is when users have "bugs" in their OpenAPI specs. On top of that, in order to manifest as an actual issue they would have to be relying on the generated types in code they've manually written.
3. The pruning is actually pretty useful in detecting errors/missing-references in OpenAPI specs.

deepmap-marcinr: Ok, lets try it like it is.

Patch:
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..09bfbf1
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,13 @@
+root = true
+
+[Makefile]
+indent_size = 8
+indent_style = tab
+
+[{go.mod,go.sum,*.go}]
+indent_size = 4
+indent_style = tab
+
+[{*.yml,*.yaml}]
+indent_size = 2
+indent_style = space
diff --git a/README.md b/README.md
index 8be9ebb..b30b013 100644
--- a/README.md
+++ b/README.md
@@ -398,6 +398,8 @@ you can specify any combination of those.
 - `spec`: embed the OpenAPI spec into the generated code as a gzipped blob. This
 - `skip-fmt`: skip running `go fmt` on the generated code. This is useful for debugging
  the generated file in case the spec contains weird strings.
+- `skip-prune`: skip pruning unused components from the spec prior to generating
+ the code.
 
 So, for example, if you would like to produce only the server code, you could
 run `oapi-generate -generate types,server`. You could generate `types` and
diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go
index 164c49a..b578e9f 100644
--- a/cmd/oapi-codegen/oapi-codegen.go
+++ b/cmd/oapi-codegen/oapi-codegen.go
@@ -42,7 +42,7 @@ func main() {
 	)
 	flag.StringVar(&packageName, "package", "", "The package name for generated code")
 	flag.StringVar(&generate, "generate", "types,client,server,spec",
-		`Comma-separated list of code to generate; valid options: "types", "client", "chi-server", "server", "skip-fmt", "spec"`)
+		`Comma-separated list of code to generate; valid options: "types", "client", "chi-server", "server", "spec", "skip-fmt", "skip-prune"`)
 	flag.StringVar(&outputFile, "o", "", "Where to output generated code, stdout is default")
 	flag.StringVar(&includeTags, "include-tags", "", "Only include operations with the given tags. Comma-separated list of tags.")
 	flag.StringVar(&excludeTags, "exclude-tags", "", "Exclude operations that are tagged with the given tags. Comma-separated list of tags.")
@@ -79,6 +79,8 @@ func main() {
 			opts.EmbedSpec = true
 		case "skip-fmt":
 			opts.SkipFmt = true
+		case "skip-prune":
+			opts.SkipPrune = true
 		default:
 			fmt.Printf("unknown generate option %s\n", g)
 			flag.PrintDefaults()
diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go
index 10ca40f..9f12de5 100644
--- a/pkg/codegen/codegen.go
+++ b/pkg/codegen/codegen.go
@@ -38,6 +38,7 @@ type Options struct {
 	GenerateTypes      bool              // GenerateTypes specifies whether to generate type definitions
 	EmbedSpec          bool              // Whether to embed the swagger spec in the generated code
 	SkipFmt            bool              // Whether to skip go fmt on the generated code
+	SkipPrune          bool              // Whether to skip pruning unused components on the generated code
 	IncludeTags        []string          // Only include operations that have one of these tags. Ignored when empty.
 	ExcludeTags        []string          // Exclude operations that have one of these tags. Ignored when empty.
 	UserTemplates      map[string]string // Override built-in templates from user-provided files
@@ -90,6 +91,9 @@ var (
 // opts defines
 func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (string, error) {
 	filterOperationsByTag(swagger, opts)
+	if !opts.SkipPrune {
+		pruneUnusedComponents(swagger)
+	}
 
 	// This creates the golang templates text package
 	t := template.New("oapi-codegen").Funcs(TemplateFunctions)
@@ -511,46 +515,3 @@ func SanitizeCode(goCode string) string {
 	// See: https://groups.google.com/forum/#!topic/golang-nuts/OToNIPdfkks
 	return strings.Replace(goCode, "\uFEFF", "", -1)
 }
-
-func filterOperationsByTag(swagger *openapi3.Swagger, opts Options) {
-	if len(opts.ExcludeTags) > 0 {
-		excludeOperationsWithTags(swagger.Paths, opts.ExcludeTags)
-	}
-	if len(opts.IncludeTags) > 0 {
-		includeOperationsWithTags(swagger.Paths, opts.IncludeTags, false)
-	}
-}
-
-func excludeOperationsWithTags(paths openapi3.Paths, tags []string) {
-	includeOperationsWithTags(paths, tags, true)
-}
-
-func includeOperationsWithTags(paths openapi3.Paths, tags []string, exclude bool) {
-	for _, pathItem := range paths {
-		ops := pathItem.Operations()
-		names := make([]string, 0, len(ops))
-		for name, op := range ops {
-			if operationHasTag(op, tags) == exclude {
-				names = append(names, name)
-			}
-		}
-		for _, name := range names {
-			pathItem.SetOperation(name, nil)
-		}
-	}
-}
-
-//operationHasTag returns true if the operation is tagged with any of tags
-func operationHasTag(op *openapi3.Operation, tags []string) bool {
-	if op == nil {
-		return false
-	}
-	for _, hasTag := range op.Tags {
-		for _, wantTag := range tags {
-			if hasTag == wantTag {
-				return true
-			}
-		}
-	}
-	return false
-}
diff --git a/pkg/codegen/filter.go b/pkg/codegen/filter.go
new file mode 100644
index 0000000..61857d2
--- /dev/null
+++ b/pkg/codegen/filter.go
@@ -0,0 +1,46 @@
+package codegen
+
+import "github.com/getkin/kin-openapi/openapi3"
+
+func filterOperationsByTag(swagger *openapi3.Swagger, opts Options) {
+	if len(opts.ExcludeTags) > 0 {
+		excludeOperationsWithTags(swagger.Paths, opts.ExcludeTags)
+	}
+	if len(opts.IncludeTags) > 0 {
+		includeOperationsWithTags(swagger.Paths, opts.IncludeTags, false)
+	}
+}
+
+func excludeOperationsWithTags(paths openapi3.Paths, tags []string) {
+	includeOperationsWithTags(paths, tags, true)
+}
+
+func includeOperationsWithTags(paths openapi3.Paths, tags []string, exclude bool) {
+	for _, pathItem := range paths {
+		ops := pathItem.Operations()
+		names := make([]string, 0, len(ops))
+		for name, op := range ops {
+			if operationHasTag(op, tags) == exclude {
+				names = append(names, name)
+			}
+		}
+		for _, name := range names {
+			pathItem.SetOperation(name, nil)
+		}
+	}
+}
+
+//operationHasTag returns true if the operation is tagged with any of tags
+func operationHasTag(op *openapi3.Operation, tags []string) bool {
+	if op == nil {
+		return false
+	}
+	for _, hasTag := range op.Tags {
+		for _, wantTag := range tags {
+			if hasTag == wantTag {
+				return true
+			}
+		}
+	}
+	return false
+}
diff --git a/pkg/codegen/prune.go b/pkg/codegen/prune.go
new file mode 100644
index 0000000..41783ad
--- /dev/null
+++ b/pkg/codegen/prune.go
@@ -0,0 +1,480 @@
+package codegen
+
+import (
+	"fmt"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+func stringInSlice(a string, list []string) bool {
+	for _, b := range list {
+		if b == a {
+			return true
+		}
+	}
+	return false
+}
+
+type RefWrapper struct {
+	Ref       string
+	HasValue  bool
+	SourceRef interface{}
+}
+
+func walkSwagger(swagger *openapi3.Swagger, doFn func(RefWrapper) (bool, error)) error {
+	if swagger == nil {
+		return nil
+	}
+
+	for _, p := range swagger.Paths {
+		for _, op := range p.Operations() {
+			walkOperation(op, doFn)
+		}
+	}
+
+	walkComponents(&swagger.Components, doFn)
+
+	return nil
+}
+
+func walkOperation(op *openapi3.Operation, doFn func(RefWrapper) (bool, error)) error {
+	// Not a valid ref, ignore it and continue
+	if op == nil {
+		return nil
+	}
+
+	for _, param := range op.Parameters {
+		_ = walkParameterRef(param, doFn)
+	}
+
+	_ = walkRequestBodyRef(op.RequestBody, doFn)
+
+	for _, response := range op.Responses {
+		walkResponseRef(response, doFn)
+	}
+
+	for _, callback := range op.Callbacks {
+		walkCallbackRef(callback, doFn)
+	}
+
+	return nil
+}
+
+func walkComponents(components *openapi3.Components, doFn func(RefWrapper) (bool, error)) error {
+	// Not a valid ref, ignore it and continue
+	if components == nil {
+		return nil
+	}
+
+	for _, schema := range components.Schemas {
+		_ = walkSchemaRef(schema, doFn)
+	}
+
+	for _, param := range components.Parameters {
+		_ = walkParameterRef(param, doFn)
+	}
+
+	for _, header := range components.Headers {
+		_ = walkHeaderRef(header, doFn)
+	}
+
+	for _, requestBody := range components.RequestBodies {
+		_ = walkRequestBodyRef(requestBody, doFn)
+	}
+
+	for _, response := range components.Responses {
+		_ = walkResponseRef(response, doFn)
+	}
+
+	for _, securityScheme := range components.SecuritySchemes {
+		_ = walkSecuritySchemeRef(securityScheme, doFn)
+	}
+
+	for _, example := range components.Examples {
+		_ = walkExampleRef(example, doFn)
+	}
+
+	for _, link := range components.Links {
+		_ = walkLinkRef(link, doFn)
+	}
+
+	for _, callback := range components.Callbacks {
+		_ = walkCallbackRef(callback, doFn)
+	}
+
+	return nil
+}
+
+func walkSchemaRef(ref *openapi3.SchemaRef, doFn func(RefWrapper) (bool, error)) error {
+	// Not a valid ref, ignore it and continue
+	if ref == nil {
+		return nil
+	}
+	refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
+	shouldContinue, err := doFn(refWrapper)
+	if err != nil {
+		return err
+	}
+	if !shouldContinue {
+		return nil
+	}
+	if ref.Value == nil {
+		return nil
+	}
+
+	for _, ref := range ref.Value.OneOf {
+		walkSchemaRef(ref, doFn)
+	}
+
+	for _, ref := range ref.Value.AnyOf {
+		walkSchemaRef(ref, doFn)
+	}
+
+	for _, ref := range ref.Value.AllOf {
+		walkSchemaRef(ref, doFn)
+	}
+
+	walkSchemaRef(ref.Value.Not, doFn)
+	walkSchemaRef(ref.Value.Items, doFn)
+
+	for _, ref := range ref.Value.Properties {
+		walkSchemaRef(ref, doFn)
+	}
+
+	walkSchemaRef(ref.Value.AdditionalProperties, doFn)
+
+	return nil
+}
+
+func walkParameterRef(ref *openapi3.ParameterRef, doFn func(RefWrapper) (bool, error)) error {
+	// Not a valid ref, ignore it and continue
+	if ref == nil {
+		return nil
+	}
+	refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
+	shouldContinue, err := doFn(refWrapper)
+	if err != nil {
+		return err
+	}
+	if !shouldContinue {
+		return nil
+	}
+	if ref.Value == nil {
+		return nil
+	}
+
+	walkSchemaRef(ref.Value.Schema, doFn)
+
+	for _, example := range ref.Value.Examples {
+		walkExampleRef(example, doFn)
+	}
+
+	for _, mediaType := range ref.Value.Content {
+		if mediaType == nil {
+			continue
+		}
+		walkSchemaRef(mediaType.Schema, doFn)
+
+		for _, example := range mediaType.Examples {
+			walkExampleRef(example, doFn)
+		}
+	}
+
+	return nil
+}
+
+func walkRequestBodyRef(ref *openapi3.RequestBodyRef, doFn func(RefWrapper) (bool, error)) error {
+	// Not a valid ref, ignore it and continue
+	if ref == nil {
+		return nil
+	}
+	refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
+	shouldContinue, err := doFn(refWrapper)
+	if err != nil {
+		return err
+	}
+	if !shouldContinue {
+		return nil
+	}
+	if ref.Value == nil {
+		return nil
+	}
+
+	for _, mediaType := range ref.Value.Content {
+		if mediaType == nil {
+			continue
+		}
+		walkSchemaRef(mediaType.Schema, doFn)
+
+		for _, example := range mediaType.Examples {
+			walkExampleRef(example, doFn)
+		}
+	}
+
+	return nil
+}
+
+func walkResponseRef(ref *openapi3.ResponseRef, doFn func(RefWrapper) (bool, error)) error {
+	// Not a valid ref, ignore it and continue
+	if ref == nil {
+		return nil
+	}
+	refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
+	shouldContinue, err := doFn(refWrapper)
+	if err != nil {
+		return err
+	}
+	if !shouldContinue {
+		return nil
+	}
+	if ref.Value == nil {
+		return nil
+	}
+
+	for _, header := range ref.Value.Headers {
+		walkHeaderRef(header, doFn)
+	}
+
+	for _, mediaType := range ref.Value.Content {
+		if mediaType == nil {
+			continue
+		}
+		walkSchemaRef(mediaType.Schema, doFn)
+
+		for _, example := range mediaType.Examples {
+			walkExampleRef(example, doFn)
+		}
+	}
+
+	for _, link := range ref.Value.Links {
+		walkLinkRef(link, doFn)
+	}
+
+	return nil
+}
+
+func walkCallbackRef(ref *openapi3.CallbackRef, doFn func(RefWrapper) (bool, error)) error {
+	// Not a valid ref, ignore it and continue
+	if ref == nil {
+		return nil
+	}
+	refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
+	shouldContinue, err := doFn(refWrapper)
+	if err != nil {
+		return err
+	}
+	if !shouldContinue {
+		return nil
+	}
+	if ref.Value == nil {
+		return nil
+	}
+
+	for _, pathItem := range *ref.Value {
+		for _, parameter := range pathItem.Parameters {
+			walkParameterRef(parameter, doFn)
+		}
+		walkOperation(pathItem.Connect, doFn)
+		walkOperation(pathItem.Delete, doFn)
+		walkOperation(pathItem.Get, doFn)
+		walkOperation(pathItem.Head, doFn)
+		walkOperation(pathItem.Options, doFn)
+		walkOperation(pathItem.Patch, doFn)
+		walkOperation(pathItem.Post, doFn)
+		walkOperation(pathItem.Put, doFn)
+		walkOperation(pathItem.Trace, doFn)
+	}
+
+	return nil
+}
+
+func walkHeaderRef(ref *openapi3.HeaderRef, doFn func(RefWrapper) (bool, error)) error {
+	// Not a valid ref, ignore it and continue
+	if ref == nil {
+		return nil
+	}
+	refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
+	shouldContinue, err := doFn(refWrapper)
+	if err != nil {
+		return err
+	}
+	if !shouldContinue {
+		return nil
+	}
+	if ref.Value == nil {
+		return nil
+	}
+
+	walkSchemaRef(ref.Value.Schema, doFn)
+
+	return nil
+}
+
+func walkSecuritySchemeRef(ref *openapi3.SecuritySchemeRef, doFn func(RefWrapper) (bool, error)) error {
+	// Not a valid ref, ignore it and continue
+	if ref == nil {
+		return nil
+	}
+	refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
+	shouldContinue, err := doFn(refWrapper)
+	if err != nil {
+		return err
+	}
+	if !shouldContinue {
+		return nil
+	}
+	if ref.Value == nil {
+		return nil
+	}
+
+	// NOTE: `SecuritySchemeRef`s don't contain any children that can contain refs
+
+	return nil
+}
+
+func walkLinkRef(ref *openapi3.LinkRef, doFn func(RefWrapper) (bool, error)) error {
+	// Not a valid ref, ignore it and continue
+	if ref == nil {
+		return nil
+	}
+	refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
+	shouldContinue, err := doFn(refWrapper)
+	if err != nil {
+		return err
+	}
+	if !shouldContinue {
+		return nil
+	}
+	if ref.Value == nil {
+		return nil
+	}
+
+	return nil
+}
+
+func walkExampleRef(ref *openapi3.ExampleRef, doFn func(RefWrapper) (bool, error)) error {
+	// Not a valid ref, ignore it and continue
+	if ref == nil {
+		return nil
+	}
+	refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
+	shouldContinue, err := doFn(refWrapper)
+	if err != nil {
+		return err
+	}
+	if !shouldContinue {
+		return nil
+	}
+	if ref.Value == nil {
+		return nil
+	}
+
+	// NOTE: `ExampleRef`s don't contain any children that can contain refs
+
+	return nil
+}
+
+func findComponentRefs(swagger *openapi3.Swagger) []string {
+	refs := []string{}
+
+	walkSwagger(swagger, func(ref RefWrapper) (bool, error) {
+		if ref.Ref != "" {
+			refs = append(refs, ref.Ref)
+			return false, nil
+		}
+		return true, nil
+	})
+
+	return refs
+}
+
+func removeOrphanedComponents(swagger *openapi3.Swagger, refs []string) int {
+	countRemoved := 0
+
+	for key, _ := range swagger.Components.Schemas {
+		ref := fmt.Sprintf("#/components/schemas/%s", key)
+		if !stringInSlice(ref, refs) {
+			countRemoved++
+			delete(swagger.Components.Schemas, key)
+		}
+	}
+
+	for key, _ := range swagger.Components.Parameters {
+		ref := fmt.Sprintf("#/components/parameters/%s", key)
+		if !stringInSlice(ref, refs) {
+			countRemoved++
+			delete(swagger.Components.Parameters, key)
+		}
+	}
+
+	// securitySchemes are an exception. definitions in securitySchemes
+	// are referenced directly by name. and not by $ref
+
+	// for key, _ := range swagger.Components.SecuritySchemes {
+	// 	ref := fmt.Sprintf("#/components/securitySchemes/%s", key)
+	// 	if !stringInSlice(ref, refs) {
+	// 		countRemoved++
+	// 		delete(swagger.Components.SecuritySchemes, key)
+	// 	}
+	// }
+
+	for key, _ := range swagger.Components.RequestBodies {
+		ref := fmt.Sprintf("#/components/requestBodies/%s", key)
+		if !stringInSlice(ref, refs) {
+			countRemoved++
+			delete(swagger.Components.RequestBodies, key)
+		}
+	}
+
+	for key, _ := range swagger.Components.Responses {
+		ref := fmt.Sprintf("#/components/responses/%s", key)
+		if !stringInSlice(ref, refs) {
+			countRemoved++
+			delete(swagger.Components.Responses, key)
+		}
+	}
+
+	for key, _ := range swagger.Components.Headers {
+		ref := fmt.Sprintf("#/components/headers/%s", key)
+		if !stringInSlice(ref, refs) {
+			countRemoved++
+			delete(swagger.Components.Headers, key)
+		}
+	}
+
+	for key, _ := range swagger.Components.Examples {
+		ref := fmt.Sprintf("#/components/examples/%s", key)
+		if !stringInSlice(ref, refs) {
+			countRemoved++
+			delete(swagger.Components.Examples, key)
+		}
+	}
+
+	for key, _ := range swagger.Components.Links {
+		ref := fmt.Sprintf("#/components/links/%s", key)
+		if !stringInSlice(ref, refs) {
+			countRemoved++
+			delete(swagger.Components.Links, key)
+		}
+	}
+
+	for key, _ := range swagger.Components.Callbacks {
+		ref := fmt.Sprintf("#/components/callbacks/%s", key)
+		if !stringInSlice(ref, refs) {
+			countRemoved++
+			delete(swagger.Components.Callbacks, key)
+		}
+	}
+
+	return countRemoved
+}
+
+func pruneUnusedComponents(swagger *openapi3.Swagger) {
+	for {
+		refs := findComponentRefs(swagger)
+		countRemoved := removeOrphanedComponents(swagger, refs)
+		if countRemoved < 1 {
+			break
+		}
+	}
+}


Test patch:
diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go
index 3cf0059..2cb55d3 100644
--- a/internal/test/components/components.gen.go
+++ b/internal/test/components/components.gen.go
@@ -72,9 +72,6 @@ type SchemaObject struct {
 	Role      string `json:"role"`
 }
 
-// ParameterObject defines model for ParameterObject.
-type ParameterObject string
-
 // ResponseObject defines model for ResponseObject.
 type ResponseObject struct {
 	Field SchemaObject `json:"Field"`
@@ -120,6 +117,9 @@ type BodyWithAddPropsJSONBody_Inner struct {
 	AdditionalProperties map[string]int `json:"-"`
 }
 
+// EnsureEverythingIsReferencedRequestBody defines body for EnsureEverythingIsReferenced for application/json ContentType.
+type EnsureEverythingIsReferencedJSONRequestBody RequestBody
+
 // BodyWithAddPropsRequestBody defines body for BodyWithAddProps for application/json ContentType.
 type BodyWithAddPropsJSONRequestBody BodyWithAddPropsJSONBody
 
@@ -790,6 +790,11 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption {
 
 // The interface specification for the client above.
 type ClientInterface interface {
+	// EnsureEverythingIsReferenced request  with any body
+	EnsureEverythingIsReferencedWithBody(ctx context.Context, contentType string, body io.Reader) (*http.Response, error)
+
+	EnsureEverythingIsReferenced(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody) (*http.Response, error)
+
 	// ParamsWithAddProps request
 	ParamsWithAddProps(ctx context.Context, params *ParamsWithAddPropsParams) (*http.Response, error)
 
@@ -799,6 +804,36 @@ type ClientInterface interface {
 	BodyWithAddProps(ctx context.Context, body BodyWithAddPropsJSONRequestBody) (*http.Response, error)
 }
 
+func (c *Client) EnsureEverythingIsReferencedWithBody(ctx context.Context, contentType string, body io.Reader) (*http.Response, error) {
+	req, err := NewEnsureEverythingIsReferencedRequestWithBody(c.Server, contentType, body)
+	if err != nil {
+		return nil, err
+	}
+	req = req.WithContext(ctx)
+	if c.RequestEditor != nil {
+		err = c.RequestEditor(ctx, req)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return c.Client.Do(req)
+}
+
+func (c *Client) EnsureEverythingIsReferenced(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody) (*http.Response, error) {
+	req, err := NewEnsureEverythingIsReferencedRequest(c.Server, body)
+	if err != nil {
+		return nil, err
+	}
+	req = req.WithContext(ctx)
+	if c.RequestEditor != nil {
+		err = c.RequestEditor(ctx, req)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return c.Client.Do(req)
+}
+
 func (c *Client) ParamsWithAddProps(ctx context.Context, params *ParamsWithAddPropsParams) (*http.Response, error) {
 	req, err := NewParamsWithAddPropsRequest(c.Server, params)
 	if err != nil {
@@ -844,6 +879,45 @@ func (c *Client) BodyWithAddProps(ctx context.Context, body BodyWithAddPropsJSON
 	return c.Client.Do(req)
 }
 
+// NewEnsureEverythingIsReferencedRequest calls the generic EnsureEverythingIsReferenced builder with application/json body
+func NewEnsureEverythingIsReferencedRequest(server string, body EnsureEverythingIsReferencedJSONRequestBody) (*http.Request, error) {
+	var bodyReader io.Reader
+	buf, err := json.Marshal(body)
+	if err != nil {
+		return nil, err
+	}
+	bodyReader = bytes.NewReader(buf)
+	return NewEnsureEverythingIsReferencedRequestWithBody(server, "application/json", bodyReader)
+}
+
+// NewEnsureEverythingIsReferencedRequestWithBody generates requests for EnsureEverythingIsReferenced with any type of body
+func NewEnsureEverythingIsReferencedRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
+	var err error
+
+	queryUrl, err := url.Parse(server)
+	if err != nil {
+		return nil, err
+	}
+
+	basePath := fmt.Sprintf("/ensure-everything-is-referenced")
+	if basePath[0] == '/' {
+		basePath = basePath[1:]
+	}
+
+	queryUrl, err = queryUrl.Parse(basePath)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("GET", queryUrl.String(), body)
+	if err != nil {
+		return nil, err
+	}
+
+	req.Header.Add("Content-Type", contentType)
+	return req, nil
+}
+
 // NewParamsWithAddPropsRequest generates requests for ParamsWithAddProps
 func NewParamsWithAddPropsRequest(server string, params *ParamsWithAddPropsParams) (*http.Request, error) {
 	var err error
@@ -965,6 +1039,48 @@ func WithBaseURL(baseURL string) ClientOption {
 	}
 }
 
+type ensureEverythingIsReferencedResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+	JSON200      *struct {
+
+		// Has additional properties with schema for dictionaries
+		Five *AdditionalPropertiesObject5 `json:"five,omitempty"`
+
+		// Has anonymous field which has additional properties
+		Four      *AdditionalPropertiesObject4 `json:"four,omitempty"`
+		JsonField *ObjectWithJsonField         `json:"jsonField,omitempty"`
+
+		// Has additional properties of type int
+		One *AdditionalPropertiesObject1 `json:"one,omitempty"`
+
+		// Allows any additional property
+		Three *AdditionalPropertiesObject3 `json:"three,omitempty"`
+
+		// Does not allow additional properties
+		Two *AdditionalPropertiesObject2 `json:"two,omitempty"`
+	}
+	JSONDefault *struct {
+		Field SchemaObject `json:"Field"`
+	}
+}
+
+// Status returns HTTPResponse.Status
+func (r ensureEverythingIsReferencedResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r ensureEverythingIsReferencedResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
 type paramsWithAddPropsResponse struct {
 	Body         []byte
 	HTTPResponse *http.Response
@@ -1007,6 +1123,23 @@ func (r bodyWithAddPropsResponse) StatusCode() int {
 	return 0
 }
 
+// EnsureEverythingIsReferencedWithBodyWithResponse request with arbitrary body returning *EnsureEverythingIsReferencedResponse
+func (c *ClientWithResponses) EnsureEverythingIsReferencedWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader) (*ensureEverythingIsReferencedResponse, error) {
+	rsp, err := c.EnsureEverythingIsReferencedWithBody(ctx, contentType, body)
+	if err != nil {
+		return nil, err
+	}
+	return ParseEnsureEverythingIsReferencedResponse(rsp)
+}
+
+func (c *ClientWithResponses) EnsureEverythingIsReferencedWithResponse(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody) (*ensureEverythingIsReferencedResponse, error) {
+	rsp, err := c.EnsureEverythingIsReferenced(ctx, body)
+	if err != nil {
+		return nil, err
+	}
+	return ParseEnsureEverythingIsReferencedResponse(rsp)
+}
+
 // ParamsWithAddPropsWithResponse request returning *ParamsWithAddPropsResponse
 func (c *ClientWithResponses) ParamsWithAddPropsWithResponse(ctx context.Context, params *ParamsWithAddPropsParams) (*paramsWithAddPropsResponse, error) {
 	rsp, err := c.ParamsWithAddProps(ctx, params)
@@ -1033,6 +1166,61 @@ func (c *ClientWithResponses) BodyWithAddPropsWithResponse(ctx context.Context,
 	return ParseBodyWithAddPropsResponse(rsp)
 }
 
+// ParseEnsureEverythingIsReferencedResponse parses an HTTP response from a EnsureEverythingIsReferencedWithResponse call
+func ParseEnsureEverythingIsReferencedResponse(rsp *http.Response) (*ensureEverythingIsReferencedResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &ensureEverythingIsReferencedResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+		var dest struct {
+
+			// Has additional properties with schema for dictionaries
+			Five *AdditionalPropertiesObject5 `json:"five,omitempty"`
+
+			// Has anonymous field which has additional properties
+			Four      *AdditionalPropertiesObject4 `json:"four,omitempty"`
+			JsonField *ObjectWithJsonField         `json:"jsonField,omitempty"`
+
+			// Has additional properties of type int
+			One *AdditionalPropertiesObject1 `json:"one,omitempty"`
+
+			// Allows any additional property
+			Three *AdditionalPropertiesObject3 `json:"three,omitempty"`
+
+			// Does not allow additional properties
+			Two *AdditionalPropertiesObject2 `json:"two,omitempty"`
+		}
+		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+			return nil, err
+		}
+		response.JSON200 = &dest
+
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json"):
+		var dest struct {
+			Field SchemaObject `json:"Field"`
+		}
+		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+			return nil, err
+		}
+		response.JSONDefault = &dest
+
+	default:
+		// Content-type (text/plain) unsupported
+
+	}
+
+	return response, nil
+}
+
 // ParseParamsWithAddPropsResponse parses an HTTP response from a ParamsWithAddPropsWithResponse call
 func ParseParamsWithAddPropsResponse(rsp *http.Response) (*paramsWithAddPropsResponse, error) {
 	bodyBytes, err := ioutil.ReadAll(rsp.Body)
@@ -1074,6 +1262,9 @@ func ParseBodyWithAddPropsResponse(rsp *http.Response) (*bodyWithAddPropsRespons
 // ServerInterface represents all server handlers.
 type ServerInterface interface {
 
+	// (GET /ensure-everything-is-referenced)
+	EnsureEverythingIsReferenced(ctx echo.Context) error
+
 	// (GET /params_with_add_props)
 	ParamsWithAddProps(ctx echo.Context, params ParamsWithAddPropsParams) error
 
@@ -1086,6 +1277,15 @@ type ServerInterfaceWrapper struct {
 	Handler ServerInterface
 }
 
+// EnsureEverythingIsReferenced converts echo context to params.
+func (w *ServerInterfaceWrapper) EnsureEverythingIsReferenced(ctx echo.Context) error {
+	var err error
+
+	// Invoke the callback with all the unmarshalled arguments
+	err = w.Handler.EnsureEverythingIsReferenced(ctx)
+	return err
+}
+
 // ParamsWithAddProps converts echo context to params.
 func (w *ServerInterfaceWrapper) ParamsWithAddProps(ctx echo.Context) error {
 	var err error
@@ -1142,6 +1342,7 @@ func RegisterHandlers(router EchoRouter, si ServerInterface) {
 		Handler: si,
 	}
 
+	router.GET("/ensure-everything-is-referenced", wrapper.EnsureEverythingIsReferenced)
 	router.GET("/params_with_add_props", wrapper.ParamsWithAddProps)
 	router.POST("/params_with_add_props", wrapper.BodyWithAddProps)
 
@@ -1150,20 +1351,23 @@ func RegisterHandlers(router EchoRouter, si ServerInterface) {
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"H4sIAAAAAAAC/9xWS2/bOBD+KwR3j4Sdx+5FNy8WRVOgbdAE6CExAkYcRUxlUiHpuEKg/14MKVm2RLly",
-	"mkt7sklxHt83zxea6lWpFShnafJCS274ChwYf7psT5/vHyF1eJVq5UD5v7wsC5lyJ7WaP1qt8M6mOay4",
-	"12R0CcZJ8JreSSgE/vnbQEYT+te8szsPQnZ+5X8bW3XNqIGntTQgaHLTaFjitYPvbl4WXPZMuqoEmlDr",
-	"jFQPtManAmxqZIk+0oRyssVHGUVx+rQGU22NgXX/adH4/GV7Uf12yIMOW2plWzDh8IdEckGsXJUFkBYk",
-	"0Z2xxgtUtBBCoggvLrcoglunHnjk8459qRw8gKED8++5JZ0s6RgiOiMoTKRylPWokyKuW/EVRFAzqstg",
-	"IEbJPqdeBUMLS9Y+bRlhB1g4G2ch44WFPvD/NViitCO8KPQmzsGv4n4jaOfj0JxZD5AtEJAlXFURVNUA",
-	"0xG+H+f2P8e57TNRaVWt9NqSDEuLbHKZ5iQfy9FhfJQC8zOzbwp/mnjwi72GxX8PVff0zjW97jfS5SQo",
-	"IZk2RMjUPzKB8IHrwcJX6fIPVqttU53EMqPPvFiD72CZNivuaEJ932YjT88mPI2XXWMpxv4eVQPfM2ms",
-	"+zQGwOhiQgL4V2xH1dKPAqkyjcKFTEFZ6JiiHy+uUbuTDtXTa7COXIF59mn0DMaGMJ7OTmYnocGC4qWk",
-	"CT2fncxOsTK4y73/c78q2DsM7B0X4g7h+S8P4OH2BxJKhjTolijClSCc3GtRNVXZwItn0S2GBQ9+FF8I",
-	"moQVzGKeLIS49C6wvS3tpu/JdS5t58J4G/DGdnagtippiTx0YQjl383mQ01ikCPWVT4SYVrTmk3xVu10",
-	"NN8Dtm24IVEBCEucJvdwq9zaKBA4cDXhzcswg7EOY96i5Eabb+MMnB1k4KjuGUn+HkvRrres6+XeCqfW",
-	"RVEzWmobyT7fl0izwe6mGy55XCr8KqSB1EUJYZint+og8ZjYMdlIzuLG3MtY88pdevpEmhqGnf3jlWOp",
-	"XUjaONX9XKmHgavr+kcAAAD//xxDQTJrDQAA",
+	"H4sIAAAAAAAC/9RXTW/jNhD9KwTboxLHzvaim4tu0RRoG+wG6GFjBLQ4ipjKQy05sldY6L8XQ8nflNdx",
+	"9rK5RJY5X49v3oy/yswuKouA5GX6VTr4XIOnX602EF582Lxo+GNmkQCJH1VVlSZTZCyOXrxFfuezAhaK",
+	"nypnK3DUe/ndQKn54WcHuUzlT6Nt2FFn5Ecfw/9/5i+QkWzbJCRjHGiZfuo9zPg1wRcaVaUyByGpqUCm",
+	"0pMz+Cxb/mMfvrLo18V0H/oYP1o9idTgM2cqzlGmciq8WVQliHWRwm6D9Vmwo6nWhk1Ueb+poktrHAqP",
+	"fL0T3yDBMzh5FP4P5cXWVmwREjYXbCwMkkwOoDM67hvVAiJVJ9JWXYAYJPuYBhcJR5gl66NrRJITKEyG",
+	"UchV6eGw8N8seIGWhCpLu4pj8Na6v1Npt8OlkauPKptyQV4obCJVNUc1vSL316X97nVpByaixWZhay9y",
+	"bi2xKkxWiGKIo8f3gwjuW2G/a/nnmXd5JZeg+Mup7j5fuc7v+5WhQnRORG6d0CYLh1wH+FHqXYR/DRV/",
+	"eosbUT0L5UQuVVlDULDcuoUimcqg28nA0ckZR+Nt10eKob8H1VHuuXGe/h4qwNnyDAKEU8mOq1kYBQZz",
+	"y8alyQA9bJGSf909sHcyxO7lA3gSH8EtA42W4Hx3jePrm+ubTmABVWVkKm+vb67H3BmKipD/CNDXDq5g",
+	"Ca6hwuDzlfFXDnJwgBmE23qGUPg+Rx4K4wWgrqxBEvDFePLCW0GFIrFlnMgUijmIzIEi0MKgoML4R/QV",
+	"ZEKhDjI7B1G5GkE/8o0xvmFK32mZyvchwfeb/O78h212yc4+0wyRfm/lGe3uO4frw+Tm5g07Q26W8K3G",
+	"O9XLbSJzW7vLXbxjFy+7jXbKT6w3mSz4hiLGgZeFgzf4uA0+VvZyD5PQYged3K9XuapLGmZKT4bRwSLZ",
+	"WY8q5dTCP7EKPimtn/j+/WCLTAW3WaeZwRIInA+kV2JuddOPsF4L4pIb6Yj7kAVf3FTr+5ACd/Q6gEw/",
+	"RZt1c2J4ZoZgvKXKzzW4Zj2UUlmN5a5mdbNy2wenJuqRoHpqgmx1q61sk3OyxZ3xHwbmZmfpQUQA7QVZ",
+	"MYdHpNphEBuyQvUnu4WVh1YsW7ZcWfffMAKTkwi8atWITIpDssZWhFnbzvYEC+uybBNZWR9hXxjiote+",
+	"XbqxuimD/K02DjKKApIwTx/xJPBM7JhthLMstweMdRf+8Dx/fTv3GnaW9Qt3uPX2vr6n9pAr7fHFtW37",
+	"fwAAAP//Xv36ip0PAAA=",
 }
 
 // GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/internal/test/components/components.yaml b/internal/test/components/components.yaml
index 92a7d19..03456ed 100644
--- a/internal/test/components/components.yaml
+++ b/internal/test/components/components.yaml
@@ -5,6 +5,38 @@ info:
   license:
     name: MIT
 paths:
+  /ensure-everything-is-referenced:
+    get:
+      operationId: ensureEverythingIsReferenced
+      description: |
+        This endpoint exists so that components can be created in this
+        spec and not be pruned
+    # TODO: figure out why uncommenting this causes failures
+    #   parameters:
+    #     - $ref: "#/components/parameters/ParameterObject"
+      requestBody:
+        $ref: "#/components/requestBodies/RequestBody"
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  one:
+                    $ref: "#/components/schemas/AdditionalPropertiesObject1"
+                  two:
+                    $ref: "#/components/schemas/AdditionalPropertiesObject2"
+                  three:
+                    $ref: "#/components/schemas/AdditionalPropertiesObject3"
+                  four:
+                    $ref: "#/components/schemas/AdditionalPropertiesObject4"
+                  five:
+                    $ref: "#/components/schemas/AdditionalPropertiesObject5"
+                  jsonField:
+                    $ref: "#/components/schemas/ObjectWithJsonField"
+        default:
+          $ref: "#/components/responses/ResponseObject"
   /params_with_add_props:
     get:
       operationId: ParamsWithAddProps
@@ -141,6 +173,7 @@ components:
             type: string
   parameters:
     ParameterObject:
+      name: ParameterObject
       description: a parameter
       in: query
       content:
diff --git a/internal/test/schemas/schemas.gen.go b/internal/test/schemas/schemas.gen.go
index bf7cadc..3d70e4a 100644
--- a/internal/test/schemas/schemas.gen.go
+++ b/internal/test/schemas/schemas.gen.go
@@ -119,6 +119,9 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption {
 
 // The interface specification for the client above.
 type ClientInterface interface {
+	// EnsureEverythingIsReferenced request
+	EnsureEverythingIsReferenced(ctx context.Context) (*http.Response, error)
+
 	// Issue127 request
 	Issue127(ctx context.Context) (*http.Response, error)
 
@@ -134,6 +137,21 @@ type ClientInterface interface {
 	Issue9(ctx context.Context, params *Issue9Params, body Issue9JSONRequestBody) (*http.Response, error)
 }
 
+func (c *Client) EnsureEverythingIsReferenced(ctx context.Context) (*http.Response, error) {
+	req, err := NewEnsureEverythingIsReferencedRequest(c.Server)
+	if err != nil {
+		return nil, err
+	}
+	req = req.WithContext(ctx)
+	if c.RequestEditor != nil {
+		err = c.RequestEditor(ctx, req)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return c.Client.Do(req)
+}
+
 func (c *Client) Issue127(ctx context.Context) (*http.Response, error) {
 	req, err := NewIssue127Request(c.Server)
 	if err != nil {
@@ -209,6 +227,33 @@ func (c *Client) Issue9(ctx context.Context, params *Issue9Params, body Issue9JS
 	return c.Client.Do(req)
 }
 
+// NewEnsureEverythingIsReferencedRequest generates requests for EnsureEverythingIsReferenced
+func NewEnsureEverythingIsReferencedRequest(server string) (*http.Request, error) {
+	var err error
+
+	queryUrl, err := url.Parse(server)
+	if err != nil {
+		return nil, err
+	}
+
+	basePath := fmt.Sprintf("/ensure-everything-is-referenced")
+	if basePath[0] == '/' {
+		basePath = basePath[1:]
+	}
+
+	queryUrl, err = queryUrl.Parse(basePath)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("GET", queryUrl.String(), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return req, nil
+}
+
 // NewIssue127Request generates requests for Issue127
 func NewIssue127Request(server string) (*http.Request, error) {
 	var err error
@@ -386,6 +431,34 @@ func WithBaseURL(baseURL string) ClientOption {
 	}
 }
 
+type ensureEverythingIsReferencedResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+	JSON200      *struct {
+		AnyType1 *AnyType1 `json:"anyType1,omitempty"`
+
+		// This should be an interface{}
+		AnyType2         *AnyType2         `json:"anyType2,omitempty"`
+		CustomStringType *CustomStringType `json:"customStringType,omitempty"`
+	}
+}
+
+// Status returns HTTPResponse.Status
+func (r ensureEverythingIsReferencedResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r ensureEverythingIsReferencedResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
 type issue127Response struct {
 	Body         []byte
 	HTTPResponse *http.Response
@@ -474,6 +547,15 @@ func (r issue9Response) StatusCode() int {
 	return 0
 }
 
+// EnsureEverythingIsReferencedWithResponse request returning *EnsureEverythingIsReferencedResponse
+func (c *ClientWithResponses) EnsureEverythingIsReferencedWithResponse(ctx context.Context) (*ensureEverythingIsReferencedResponse, error) {
+	rsp, err := c.EnsureEverythingIsReferenced(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return ParseEnsureEverythingIsReferencedResponse(rsp)
+}
+
 // Issue127WithResponse request returning *Issue127Response
 func (c *ClientWithResponses) Issue127WithResponse(ctx context.Context) (*issue127Response, error) {
 	rsp, err := c.Issue127(ctx)
@@ -518,6 +600,38 @@ func (c *ClientWithResponses) Issue9WithResponse(ctx context.Context, params *Is
 	return ParseIssue9Response(rsp)
 }
 
+// ParseEnsureEverythingIsReferencedResponse parses an HTTP response from a EnsureEverythingIsReferencedWithResponse call
+func ParseEnsureEverythingIsReferencedResponse(rsp *http.Response) (*ensureEverythingIsReferencedResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &ensureEverythingIsReferencedResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+		var dest struct {
+			AnyType1 *AnyType1 `json:"anyType1,omitempty"`
+
+			// This should be an interface{}
+			AnyType2         *AnyType2         `json:"anyType2,omitempty"`
+			CustomStringType *CustomStringType `json:"customStringType,omitempty"`
+		}
+		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+			return nil, err
+		}
+		response.JSON200 = &dest
+
+	}
+
+	return response, nil
+}
+
 // ParseIssue127Response parses an HTTP response from a Issue127WithResponse call
 func ParseIssue127Response(rsp *http.Response) (*issue127Response, error) {
 	bodyBytes, err := ioutil.ReadAll(rsp.Body)
@@ -631,6 +745,9 @@ func ParseIssue9Response(rsp *http.Response) (*issue9Response, error) {
 // ServerInterface represents all server handlers.
 type ServerInterface interface {
 
+	// (GET /ensure-everything-is-referenced)
+	EnsureEverythingIsReferenced(ctx echo.Context) error
+
 	// (GET /issues/127)
 	Issue127(ctx echo.Context) error
 
@@ -649,6 +766,15 @@ type ServerInterfaceWrapper struct {
 	Handler ServerInterface
 }
 
+// EnsureEverythingIsReferenced converts echo context to params.
+func (w *ServerInterfaceWrapper) EnsureEverythingIsReferenced(ctx echo.Context) error {
+	var err error
+
+	// Invoke the callback with all the unmarshalled arguments
+	err = w.Handler.EnsureEverythingIsReferenced(ctx)
+	return err
+}
+
 // Issue127 converts echo context to params.
 func (w *ServerInterfaceWrapper) Issue127(ctx echo.Context) error {
 	var err error
@@ -730,6 +856,7 @@ func RegisterHandlers(router EchoRouter, si ServerInterface) {
 		Handler: si,
 	}
 
+	router.GET("/ensure-everything-is-referenced", wrapper.EnsureEverythingIsReferenced)
 	router.GET("/issues/127", wrapper.Issue127)
 	router.GET("/issues/30/:fallthrough", wrapper.Issue30)
 	router.GET("/issues/41/:1param", wrapper.Issue41)
@@ -740,18 +867,21 @@ func RegisterHandlers(router EchoRouter, si ServerInterface) {
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"H4sIAAAAAAAC/7RVwW7jNhD9lcG0QC+CZCdbFKtbu4diD+0umgA9NDnQ4sjihiIZcuhEEPTvBSm7VuI4",
-	"RZD2ZErizLw3M+95xMb2zhoyHLAeMTQd9SIff7xi4Tn8qbj7PfYb8umlpNB45VhZgzVedyrAHAJG9AQh",
-	"h8CD4g4EmDmsQB4cYY12840axqnAn81wPThaYz0eny7OFehs1BI2BMKAMky+FQ2NU0r0KQa2/RV7ZbbX",
-	"ucqIrfW9YKyxyR+P9UO+lsJ+JUNeNV9mQPX4HOE0FahMa19ARIGhEYECtNbDTnhlYwAVQsyvopFgd+SB",
-	"VU8lfNUkAoGQEgTwITaF3hhhBtjELbTqkWR5YxJQxZoOVa7I73L7duTDXH1drspVImAdGeEU1nhZrso1",
-	"FugEd3lu1YylWl/8lB63xKcsfhN3BCF6gmhCdM56JgmNNUyPDKkZAaQ1PzA4T9Q7huOt/HWGax15kVJ+",
-	"lljj51Q3VS3QU3DWBMqALlar9JOzmwxGOKdVkyOrbyEhOmxeOn3vqcUav6uOq1nt97J6Org0pmWux16/",
-	"J1UiX/XC30n7YN6daBDvQZPSSGpF1Pw/Nu8/YjzNeA+bd7mqxlZozZ23cdtNp/v3B4W03BLuaHiwXi5F",
-	"7jxlRYAyWV5ioym7y37p9gv9wu5drp6unolaT0kZXvTE5APWf42oEoCkFiwwpcUaF2BzhvuoPEms2Ucq",
-	"Fj155iPT7YL0h3U1rnOp6azsvh6QLNxSme3sl/+45QvMPswK/zcec/1XKbw21lPHn6bb044uSH88S/WT",
-	"VmQYMqCQPQOUaaz31LAe0llHSTKbaEKbDC+3YWPlAMLIG3Pke9ZtPp5py30kPyzma+3b5jpfpsC/WDm8",
-	"RX973S478SUfhM7Mknef9HMqMMthzyB6jTV2zK6uqr3Rp7+OUhK5XrhSKJxup78DAAD//8VdLP27BwAA",
+	"H4sIAAAAAAAC/7RWwW4bNxD9lQFboBdZK8kpiuytDYLChzZBbKCH2gdqOdIy3h0y5FD2Qth/L4YrWRtL",
+	"chu4OZmWODPvzTy+0VZVrvWOkDiqcqtiVWOr8/Hna9aB41+W6z9Tu8QgHxqMVbCerSNVqpvaRhhCgHSL",
+	"EHMIPFiuQQMNYRPFnUdVKrf8jBWrfqJ+pe6m8zhX5fbw3+JcgdqlxsASQRNYYgwrXeG2l0TvUmTXXnOw",
+	"tL7JVbZq5UKrWZWqyl8e6sd8TcJ+R8Jgqw8DoHL7HGHfT5SllTuBCCNDpSNGWLkAGx2sSxFsjCl/lMiA",
+	"22AAti1O4WODOiJoY0AD72Ml9JY0dbBMa1jZRzTTWxKglhvcV7nGsMnt22CIQ/X5dDadCQHnkbS3qlSX",
+	"09l0ribKa67z3AqkmAJe4AZDx7Wl9YWNFwFXGJAqNHJnjXym2UjGO0sM+GgjR4gOuNYMB5lApUmGUQXU",
+	"jAYsAdc23lL0WIEmA+RYLviQCE3m5TwGLWWujCrV+wzw/RO+q/jpgG6iAkbvKGJms5jN5E/liJEyaO19",
+	"Y6ucrfgcBfletnLyQWqxHaL1QWfqx4ArVaofigOVYif34kmP/WQfs/iPMQuJqU7I8KXYI9mK4I402Gcd",
+	"FoO2ivnil7Oj+0PfI0hTIVFM3rsgk8lNe2SQxBGMo58YfEBsPcPhVv52emJMV1JXqr5yJC814uuHKHTH",
+	"uR7b5jWphHzR6nBv3AO9OlGnX4NG0hhc6dTwd2ze/8T4mfIuZ8V2pZuG6+DSuu6P9fcJo5iVgXvsHlww",
+	"Y9P2AbPDiVGIXeplg3lb7ES3E/QJ7V3OvpYepabpxemCbpExRFX+vVVWAIj7qYmStKpUI7A5w5dkgxgf",
+	"h4STUU+e7YX+bkT6zbzYznOp/uyz+7hHMtp+ltbD/nvafieYvRkc+994DPVfpPDSWI83eN/fHXd0RPrt",
+	"WarvGovEkAHF7BlgqXIhYMVNJ+cmGTR5KQpaWWC5DUtnOtkKt3Tge9Zt3p5py5eEoRvN17lvm+twGSP/",
+	"5kz3Le9v927HnfiQD7rJzMT8j/rZT1R+DjsGKTSqVDWzL4tit7jlp8DUIPpW+6m2qr/r/wkAAP//q2vq",
+	"DYsJAAA=",
 }
 
 // GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/internal/test/schemas/schemas.yaml b/internal/test/schemas/schemas.yaml
index b30f290..22887f2 100644
--- a/internal/test/schemas/schemas.yaml
+++ b/internal/test/schemas/schemas.yaml
@@ -8,6 +8,25 @@ info:
 servers:
   - url: http://openapitest.deepmap.ai
 paths:
+  /ensure-everything-is-referenced:
+    get:
+      operationId: ensureEverythingIsReferenced
+      description: |
+        This endpoint exists so that components can be created in this
+        spec and not be pruned
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  anyType1:
+                    $ref: "#/components/schemas/AnyType1"
+                  anyType2:
+                    $ref: "#/components/schemas/AnyType2"
+                  customStringType:
+                    $ref: "#/components/schemas/CustomStringType"
   /issues/9:
     get:
       operationId: Issue9
diff --git a/internal/test/server/server.gen.go b/internal/test/server/server.gen.go
index 5babef1..b4f50e6 100644
--- a/internal/test/server/server.gen.go
+++ b/internal/test/server/server.gen.go
@@ -126,6 +126,8 @@ type CreateResource2JSONRequestBody CreateResource2JSONBody
 type UpdateResource3JSONRequestBody UpdateResource3JSONBody
 
 type ServerInterface interface {
+	// get every type optional (GET /every-type-optional)
+	GetEveryTypeOptional(w http.ResponseWriter, r *http.Request)
 	// Get resource via simple path (GET /get-simple)
 	GetSimple(w http.ResponseWriter, r *http.Request)
 	// Getter with referenced parameter and referenced response (GET /get-with-args)
@@ -134,12 +136,25 @@ type ServerInterface interface {
 	GetWithReferences(w http.ResponseWriter, r *http.Request)
 	// Get an object by ID (GET /get-with-type/{content_type})
 	GetWithContentType(w http.ResponseWriter, r *http.Request)
+	// get with reserved keyword (GET /reserved-keyword)
+	GetReservedKeyword(w http.ResponseWriter, r *http.Request)
 	// Create a resource (POST /resource/{argument})
 	CreateResource(w http.ResponseWriter, r *http.Request)
 	// Create a resource with inline parameter (POST /resource2/{inline_argument})
 	CreateResource2(w http.ResponseWriter, r *http.Request)
 	// Update a resource with inline body. The parameter name is a reservedkeyword, so make sure that gets prefixed to avoid syntax errors (PUT /resource3/{fallthrough})
 	UpdateResource3(w http.ResponseWriter, r *http.Request)
+	// get response with reference (GET /response-with-reference)
+	GetResponseWithReference(w http.ResponseWriter, r *http.Request)
+}
+
+// GetEveryTypeOptional operation middleware
+func GetEveryTypeOptionalCtx(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		ctx := r.Context()
+
+		next.ServeHTTP(w, r.WithContext(ctx))
+	})
 }
 
 // GetSimple operation middleware
@@ -272,6 +287,15 @@ func GetWithContentTypeCtx(next http.Handler) http.Handler {
 	})
 }
 
+// GetReservedKeyword operation middleware
+func GetReservedKeywordCtx(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		ctx := r.Context()
+
+		next.ServeHTTP(w, r.WithContext(ctx))
+	})
+}
+
 // CreateResource operation middleware
 func CreateResourceCtx(next http.Handler) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -359,6 +383,15 @@ func UpdateResource3Ctx(next http.Handler) http.Handler {
 	})
 }
 
+// GetResponseWithReference operation middleware
+func GetResponseWithReferenceCtx(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		ctx := r.Context()
+
+		next.ServeHTTP(w, r.WithContext(ctx))
+	})
+}
+
 // Handler creates http.Handler with routing matching OpenAPI spec.
 func Handler(si ServerInterface) http.Handler {
 	return HandlerFromMux(si, chi.NewRouter())
@@ -366,6 +399,10 @@ func Handler(si ServerInterface) http.Handler {
 
 // HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
 func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler {
+	r.Group(func(r chi.Router) {
+		r.Use(GetEveryTypeOptionalCtx)
+		r.Get("/every-type-optional", si.GetEveryTypeOptional)
+	})
 	r.Group(func(r chi.Router) {
 		r.Use(GetSimpleCtx)
 		r.Get("/get-simple", si.GetSimple)
@@ -382,6 +419,10 @@ func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler {
 		r.Use(GetWithContentTypeCtx)
 		r.Get("/get-with-type/{content_type}", si.GetWithContentType)
 	})
+	r.Group(func(r chi.Router) {
+		r.Use(GetReservedKeywordCtx)
+		r.Get("/reserved-keyword", si.GetReservedKeyword)
+	})
 	r.Group(func(r chi.Router) {
 		r.Use(CreateResourceCtx)
 		r.Post("/resource/{argument}", si.CreateResource)
@@ -394,6 +435,10 @@ func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler {
 		r.Use(UpdateResource3Ctx)
 		r.Put("/resource3/{fallthrough}", si.UpdateResource3)
 	})
+	r.Group(func(r chi.Router) {
+		r.Use(GetResponseWithReferenceCtx)
+		r.Get("/response-with-reference", si.GetResponseWithReference)
+	})
 
 	return r
 }
diff --git a/internal/test/server/server_moq.gen.go b/internal/test/server/server_moq.gen.go
index 4e31d15..0736acc 100644
--- a/internal/test/server/server_moq.gen.go
+++ b/internal/test/server/server_moq.gen.go
@@ -9,13 +9,16 @@ import (
 )
 
 var (
-	lockServerInterfaceMockCreateResource     sync.RWMutex
-	lockServerInterfaceMockCreateResource2    sync.RWMutex
-	lockServerInterfaceMockGetSimple          sync.RWMutex
-	lockServerInterfaceMockGetWithArgs        sync.RWMutex
-	lockServerInterfaceMockGetWithContentType sync.RWMutex
-	lockServerInterfaceMockGetWithReferences  sync.RWMutex
-	lockServerInterfaceMockUpdateResource3    sync.RWMutex
+	lockServerInterfaceMockCreateResource           sync.RWMutex
+	lockServerInterfaceMockCreateResource2          sync.RWMutex
+	lockServerInterfaceMockGetEveryTypeOptional     sync.RWMutex
+	lockServerInterfaceMockGetReservedKeyword       sync.RWMutex
+	lockServerInterfaceMockGetResponseWithReference sync.RWMutex
+	lockServerInterfaceMockGetSimple                sync.RWMutex
+	lockServerInterfaceMockGetWithArgs              sync.RWMutex
+	lockServerInterfaceMockGetWithContentType       sync.RWMutex
+	lockServerInterfaceMockGetWithReferences        sync.RWMutex
+	lockServerInterfaceMockUpdateResource3          sync.RWMutex
 )
 
 // Ensure, that ServerInterfaceMock does implement ServerInterface.
@@ -34,6 +37,15 @@ var _ ServerInterface = &ServerInterfaceMock{}
 //             CreateResource2Func: func(w http.ResponseWriter, r *http.Request)  {
 // 	               panic("mock out the CreateResource2 method")
 //             },
+//             GetEveryTypeOptionalFunc: func(w http.ResponseWriter, r *http.Request)  {
+// 	               panic("mock out the GetEveryTypeOptional method")
+//             },
+//             GetReservedKeywordFunc: func(w http.ResponseWriter, r *http.Request)  {
+// 	               panic("mock out the GetReservedKeyword method")
+//             },
+//             GetResponseWithReferenceFunc: func(w http.ResponseWriter, r *http.Request)  {
+// 	               panic("mock out the GetResponseWithReference method")
+//             },
 //             GetSimpleFunc: func(w http.ResponseWriter, r *http.Request)  {
 // 	               panic("mock out the GetSimple method")
 //             },
@@ -62,6 +74,15 @@ type ServerInterfaceMock struct {
 	// CreateResource2Func mocks the CreateResource2 method.
 	CreateResource2Func func(w http.ResponseWriter, r *http.Request)
 
+	// GetEveryTypeOptionalFunc mocks the GetEveryTypeOptional method.
+	GetEveryTypeOptionalFunc func(w http.ResponseWriter, r *http.Request)
+
+	// GetReservedKeywordFunc mocks the GetReservedKeyword method.
+	GetReservedKeywordFunc func(w http.ResponseWriter, r *http.Request)
+
+	// GetResponseWithReferenceFunc mocks the GetResponseWithReference method.
+	GetResponseWithReferenceFunc func(w http.ResponseWriter, r *http.Request)
+
 	// GetSimpleFunc mocks the GetSimple method.
 	GetSimpleFunc func(w http.ResponseWriter, r *http.Request)
 
@@ -93,6 +114,27 @@ type ServerInterfaceMock struct {
 			// R is the r argument value.
 			R *http.Request
 		}
+		// GetEveryTypeOptional holds details about calls to the GetEveryTypeOptional method.
+		GetEveryTypeOptional []struct {
+			// W is the w argument value.
+			W http.ResponseWriter
+			// R is the r argument value.
+			R *http.Request
+		}
+		// GetReservedKeyword holds details about calls to the GetReservedKeyword method.
+		GetReservedKeyword []struct {
+			// W is the w argument value.
+			W http.ResponseWriter
+			// R is the r argument value.
+			R *http.Request
+		}
+		// GetResponseWithReference holds details about calls to the GetResponseWithReference method.
+		GetResponseWithReference []struct {
+			// W is the w argument value.
+			W http.ResponseWriter
+			// R is the r argument value.
+			R *http.Request
+		}
 		// GetSimple holds details about calls to the GetSimple method.
 		GetSimple []struct {
 			// W is the w argument value.
@@ -201,6 +243,111 @@ func (mock *ServerInterfaceMock) CreateResource2Calls() []struct {
 	return calls
 }
 
+// GetEveryTypeOptional calls GetEveryTypeOptionalFunc.
+func (mock *ServerInterfaceMock) GetEveryTypeOptional(w http.ResponseWriter, r *http.Request) {
+	if mock.GetEveryTypeOptionalFunc == nil {
+		panic("ServerInterfaceMock.GetEveryTypeOptionalFunc: method is nil but ServerInterface.GetEveryTypeOptional was just called")
+	}
+	callInfo := struct {
+		W http.ResponseWriter
+		R *http.Request
+	}{
+		W: w,
+		R: r,
+	}
+	lockServerInterfaceMockGetEveryTypeOptional.Lock()
+	mock.calls.GetEveryTypeOptional = append(mock.calls.GetEveryTypeOptional, callInfo)
+	lockServerInterfaceMockGetEveryTypeOptional.Unlock()
+	mock.GetEveryTypeOptionalFunc(w, r)
+}
+
+// GetEveryTypeOptionalCalls gets all the calls that were made to GetEveryTypeOptional.
+// Check the length with:
+//     len(mockedServerInterface.GetEveryTypeOptionalCalls())
+func (mock *ServerInterfaceMock) GetEveryTypeOptionalCalls() []struct {
+	W http.ResponseWriter
+	R *http.Request
+} {
+	var calls []struct {
+		W http.ResponseWriter
+		R *http.Request
+	}
+	lockServerInterfaceMockGetEveryTypeOptional.RLock()
+	calls = mock.calls.GetEveryTypeOptional
+	lockServerInterfaceMockGetEveryTypeOptional.RUnlock()
+	return calls
+}
+
+// GetReservedKeyword calls GetReservedKeywordFunc.
+func (mock *ServerInterfaceMock) GetReservedKeyword(w http.ResponseWriter, r *http.Request) {
+	if mock.GetReservedKeywordFunc == nil {
+		panic("ServerInterfaceMock.GetReservedKeywordFunc: method is nil but ServerInterface.GetReservedKeyword was just called")
+	}
+	callInfo := struct {
+		W http.ResponseWriter
+		R *http.Request
+	}{
+		W: w,
+		R: r,
+	}
+	lockServerInterfaceMockGetReservedKeyword.Lock()
+	mock.calls.GetReservedKeyword = append(mock.calls.GetReservedKeyword, callInfo)
+	lockServerInterfaceMockGetReservedKeyword.Unlock()
+	mock.GetReservedKeywordFunc(w, r)
+}
+
+// GetReservedKeywordCalls gets all the calls that were made to GetReservedKeyword.
+// Check the length with:
+//     len(mockedServerInterface.GetReservedKeywordCalls())
+func (mock *ServerInterfaceMock) GetReservedKeywordCalls() []struct {
+	W http.ResponseWriter
+	R *http.Request
+} {
+	var calls []struct {
+		W http.ResponseWriter
+		R *http.Request
+	}
+	lockServerInterfaceMockGetReservedKeyword.RLock()
+	calls = mock.calls.GetReservedKeyword
+	lockServerInterfaceMockGetReservedKeyword.RUnlock()
+	return calls
+}
+
+// GetResponseWithReference calls GetResponseWithReferenceFunc.
+func (mock *ServerInterfaceMock) GetResponseWithReference(w http.ResponseWriter, r *http.Request) {
+	if mock.GetResponseWithReferenceFunc == nil {
+		panic("ServerInterfaceMock.GetResponseWithReferenceFunc: method is nil but ServerInterface.GetResponseWithReference was just called")
+	}
+	callInfo := struct {
+		W http.ResponseWriter
+		R *http.Request
+	}{
+		W: w,
+		R: r,
+	}
+	lockServerInterfaceMockGetResponseWithReference.Lock()
+	mock.calls.GetResponseWithReference = append(mock.calls.GetResponseWithReference, callInfo)
+	lockServerInterfaceMockGetResponseWithReference.Unlock()
+	mock.GetResponseWithReferenceFunc(w, r)
+}
+
+// GetResponseWithReferenceCalls gets all the calls that were made to GetResponseWithReference.
+// Check the length with:
+//     len(mockedServerInterface.GetResponseWithReferenceCalls())
+func (mock *ServerInterfaceMock) GetResponseWithReferenceCalls() []struct {
+	W http.ResponseWriter
+	R *http.Request
+} {
+	var calls []struct {
+		W http.ResponseWriter
+		R *http.Request
+	}
+	lockServerInterfaceMockGetResponseWithReference.RLock()
+	calls = mock.calls.GetResponseWithReference
+	lockServerInterfaceMockGetResponseWithReference.RUnlock()
+	return calls
+}
+
 // GetSimple calls GetSimpleFunc.
 func (mock *ServerInterfaceMock) GetSimple(w http.ResponseWriter, r *http.Request) {
 	if mock.GetSimpleFunc == nil {
diff --git a/internal/test/test-schema.yaml b/internal/test/test-schema.yaml
index 8bf3702..a2fbf31 100644
--- a/internal/test/test-schema.yaml
+++ b/internal/test/test-schema.yaml
@@ -7,6 +7,35 @@ info:
 servers:
   - url: http://openapitest.deepmap.ai
 paths:
+  /response-with-reference:
+    get:
+      summary: get response with reference
+      operationId: getResponseWithReference
+      responses:
+        '200':
+          $ref: "#/components/responses/ResponseWithReference"
+  /reserved-keyword:
+    get:
+      summary: get with reserved keyword
+      operationId: getReservedKeyword
+      responses:
+        '200':
+          description: example of a response with a reserved keyword
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/ReservedKeyword"
+  /every-type-optional:
+    get:
+      summary: get every type optional
+      operationId: getEveryTypeOptional
+      responses:
+        '200':
+          description: a example of every type with an optional value
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/EveryTypeOptional"
   /get-simple:
     get:
       summary: Get resource via simple path
@@ -176,6 +205,10 @@ components:
       schema:
         type: string
   schemas:
+    ThisShouldBePruned:
+      properties:
+        name:
+          type: string
     # This is intentionally named in snake case
     some_object:
       properties:
diff --git a/pkg/codegen/codegen_test.go b/pkg/codegen/codegen_test.go
index caa97da..9a5327e 100644
--- a/pkg/codegen/codegen_test.go
+++ b/pkg/codegen/codegen_test.go
@@ -110,51 +110,6 @@ func TestExamplePetStoreParseFunction(t *testing.T) {
 	assert.Equal(t, "cat", *findPetByIDResponse.JSON200.Tag)
 }
 
-func TestFilterOperationsByTag(t *testing.T) {
-	packageName := "testswagger"
-	t.Run("include tags", func(t *testing.T) {
-		opts := Options{
-			GenerateClient:     true,
-			GenerateEchoServer: true,
-			GenerateTypes:      true,
-			EmbedSpec:          true,
-			IncludeTags:        []string{"hippo", "giraffe", "cat"},
-		}
-
-		// Get a spec from the test definition in this file:
-		swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(testOpenAPIDefinition))
-		assert.NoError(t, err)
-
-		// Run our code generation:
-		code, err := Generate(swagger, packageName, opts)
-		assert.NoError(t, err)
-		assert.NotEmpty(t, code)
-		assert.NotContains(t, code, `"/test/:name"`)
-		assert.Contains(t, code, `"/cat"`)
-	})
-
-	t.Run("exclude tags", func(t *testing.T) {
-		opts := Options{
-			GenerateClient:     true,
-			GenerateEchoServer: true,
-			GenerateTypes:      true,
-			EmbedSpec:          true,
-			ExcludeTags:        []string{"hippo", "giraffe", "cat"},
-		}
-
-		// Get a spec from the test definition in this file:
-		swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(testOpenAPIDefinition))
-		assert.NoError(t, err)
-
-		// Run our code generation:
-		code, err := Generate(swagger, packageName, opts)
-		assert.NoError(t, err)
-		assert.NotEmpty(t, code)
-		assert.Contains(t, code, `"/test/:name"`)
-		assert.NotContains(t, code, `"/cat"`)
-	})
-}
-
 func TestExampleOpenAPICodeGeneration(t *testing.T) {
 
 	// Input vars for code generation:
diff --git a/pkg/codegen/filter_test.go b/pkg/codegen/filter_test.go
new file mode 100644
index 0000000..66db4c9
--- /dev/null
+++ b/pkg/codegen/filter_test.go
@@ -0,0 +1,53 @@
+package codegen
+
+import (
+	"testing"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestFilterOperationsByTag(t *testing.T) {
+	packageName := "testswagger"
+	t.Run("include tags", func(t *testing.T) {
+		opts := Options{
+			GenerateClient:     true,
+			GenerateEchoServer: true,
+			GenerateTypes:      true,
+			EmbedSpec:          true,
+			IncludeTags:        []string{"hippo", "giraffe", "cat"},
+		}
+
+		// Get a spec from the test definition in this file:
+		swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(testOpenAPIDefinition))
+		assert.NoError(t, err)
+
+		// Run our code generation:
+		code, err := Generate(swagger, packageName, opts)
+		assert.NoError(t, err)
+		assert.NotEmpty(t, code)
+		assert.NotContains(t, code, `"/test/:name"`)
+		assert.Contains(t, code, `"/cat"`)
+	})
+
+	t.Run("exclude tags", func(t *testing.T) {
+		opts := Options{
+			GenerateClient:     true,
+			GenerateEchoServer: true,
+			GenerateTypes:      true,
+			EmbedSpec:          true,
+			ExcludeTags:        []string{"hippo", "giraffe", "cat"},
+		}
+
+		// Get a spec from the test definition in this file:
+		swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(testOpenAPIDefinition))
+		assert.NoError(t, err)
+
+		// Run our code generation:
+		code, err := Generate(swagger, packageName, opts)
+		assert.NoError(t, err)
+		assert.NotEmpty(t, code)
+		assert.Contains(t, code, `"/test/:name"`)
+		assert.NotContains(t, code, `"/cat"`)
+	})
+}
diff --git a/pkg/codegen/prune_test.go b/pkg/codegen/prune_test.go
new file mode 100644
index 0000000..a358126
--- /dev/null
+++ b/pkg/codegen/prune_test.go
@@ -0,0 +1,424 @@
+package codegen
+
+import (
+	"testing"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestFindReferences(t *testing.T) {
+	t.Run("unfiltered", func(t *testing.T) {
+		swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(pruneSpecTestFixture))
+		assert.NoError(t, err)
+
+		refs := findComponentRefs(swagger)
+		assert.Len(t, refs, 14)
+	})
+	t.Run("only cat", func(t *testing.T) {
+		swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(pruneSpecTestFixture))
+		assert.NoError(t, err)
+		opts := Options{
+			IncludeTags: []string{"cat"},
+		}
+
+		filterOperationsByTag(swagger, opts)
+
+		refs := findComponentRefs(swagger)
+		assert.Len(t, refs, 7)
+	})
+	t.Run("only dog", func(t *testing.T) {
+		swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(pruneSpecTestFixture))
+		assert.NoError(t, err)
+
+		opts := Options{
+			IncludeTags: []string{"dog"},
+		}
+
+		filterOperationsByTag(swagger, opts)
+
+		refs := findComponentRefs(swagger)
+		assert.Len(t, refs, 7)
+	})
+}
+
+func TestFilterOnlyCat(t *testing.T) {
+	// Get a spec from the test definition in this file:
+	swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(pruneSpecTestFixture))
+	assert.NoError(t, err)
+
+	opts := Options{
+		IncludeTags: []string{"cat"},
+	}
+
+	refs := findComponentRefs(swagger)
+	assert.Len(t, refs, 14)
+
+	assert.Len(t, swagger.Components.Schemas, 5)
+
+	filterOperationsByTag(swagger, opts)
+
+	refs = findComponentRefs(swagger)
+	assert.Len(t, refs, 7)
+
+	assert.NotEmpty(t, swagger.Paths["/cat"], "/cat path should still be in spec")
+	assert.NotEmpty(t, swagger.Paths["/cat"].Get, "GET /cat operation should still be in spec")
+	assert.Empty(t, swagger.Paths["/dog"].Get, "GET /dog should have been removed from spec")
+
+	pruneUnusedComponents(swagger)
+
+	assert.Len(t, swagger.Components.Schemas, 3)
+}
+
+func TestFilterOnlyDog(t *testing.T) {
+	// Get a spec from the test definition in this file:
+	swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(pruneSpecTestFixture))
+	assert.NoError(t, err)
+
+	opts := Options{
+		IncludeTags: []string{"dog"},
+	}
+
+	refs := findComponentRefs(swagger)
+	assert.Len(t, refs, 14)
+
+	filterOperationsByTag(swagger, opts)
+
+	refs = findComponentRefs(swagger)
+	assert.Len(t, refs, 7)
+
+	assert.Len(t, swagger.Components.Schemas, 5)
+
+	assert.NotEmpty(t, swagger.Paths["/dog"])
+	assert.NotEmpty(t, swagger.Paths["/dog"].Get)
+	assert.Empty(t, swagger.Paths["/cat"].Get)
+
+	pruneUnusedComponents(swagger)
+
+	assert.Len(t, swagger.Components.Schemas, 3)
+}
+
+func TestPruningUnusedComponents(t *testing.T) {
+	// Get a spec from the test definition in this file:
+	swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(pruneComprehensiveTestFixture))
+	assert.NoError(t, err)
+
+	assert.Len(t, swagger.Components.Schemas, 8)
+	assert.Len(t, swagger.Components.Parameters, 1)
+	assert.Len(t, swagger.Components.SecuritySchemes, 2)
+	assert.Len(t, swagger.Components.RequestBodies, 1)
+	assert.Len(t, swagger.Components.Responses, 2)
+	assert.Len(t, swagger.Components.Headers, 3)
+	assert.Len(t, swagger.Components.Examples, 1)
+	assert.Len(t, swagger.Components.Links, 1)
+	assert.Len(t, swagger.Components.Callbacks, 1)
+
+	pruneUnusedComponents(swagger)
+
+	assert.Len(t, swagger.Components.Schemas, 0)
+	assert.Len(t, swagger.Components.Parameters, 0)
+	// securitySchemes are an exception. definitions in securitySchemes
+	// are referenced directly by name. and not by $ref
+	assert.Len(t, swagger.Components.SecuritySchemes, 2)
+	assert.Len(t, swagger.Components.RequestBodies, 0)
+	assert.Len(t, swagger.Components.Responses, 0)
+	assert.Len(t, swagger.Components.Headers, 0)
+	assert.Len(t, swagger.Components.Examples, 0)
+	assert.Len(t, swagger.Components.Links, 0)
+	assert.Len(t, swagger.Components.Callbacks, 0)
+}
+
+const pruneComprehensiveTestFixture = `
+openapi: 3.0.1
+
+info:
+  title: OpenAPI-CodeGen Test
+  description: 'This is a test OpenAPI Spec'
+  version: 1.0.0
+
+servers:
+- url: https://test.oapi-codegen.com/v2
+- url: http://test.oapi-codegen.com/v2
+
+paths:
+  /test:
+    get:
+      operationId: doesNothing
+      summary: does nothing
+      tags: [nothing]
+      responses:
+        default:
+          description: returns nothing
+          content:
+            application/json:
+              schema:
+                type: object
+components:
+  schemas:
+    Object1:
+      type: object
+      properties:
+        object:
+          $ref: "#/components/schemas/Object2"
+    Object2:
+      type: object
+      properties:
+        object:
+          $ref: "#/components/schemas/Object3"
+    Object3:
+      type: object
+      properties:
+        object:
+          $ref: "#/components/schemas/Object4"
+    Object4:
+      type: object
+      properties:
+        object:
+          $ref: "#/components/schemas/Object5"
+    Object5:
+      type: object
+      properties:
+        object:
+          $ref: "#/components/schemas/Object6"
+    Object6:
+      type: object
+    Pet:
+      type: object
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
+    Error:
+      required:
+        - code
+        - message
+      properties:
+        code:
+          type: integer
+          format: int32
+          description: Error code
+        message:
+          type: string
+          description: Error message
+  parameters:
+    offsetParam:
+      name: offset
+      in: query
+      description: Number of items to skip before returning the results.
+      required: false
+      schema:
+        type: integer
+        format: int32
+        minimum: 0
+        default: 0
+  securitySchemes:
+    BasicAuth:
+      type: http
+      scheme: basic
+    BearerAuth:
+      type: http
+      scheme: bearer
+  requestBodies:
+    PetBody:
+      description: A JSON object containing pet information
+      required: true
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/Pet'
+  responses:
+    NotFound:
+      description: The specified resource was not found
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/Error'
+    Unauthorized:
+      description: Unauthorized
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/Error'
+  headers:
+    X-RateLimit-Limit:
+      schema:
+        type: integer
+      description: Request limit per hour.
+    X-RateLimit-Remaining:
+      schema:
+        type: integer
+      description: The number of requests left for the time window.
+    X-RateLimit-Reset:
+      schema:
+        type: string
+        format: date-time
+      description: The UTC date/time at which the current rate limit window resets
+  examples:
+    objectExample:
+      value:
+        id: 1
+        name: new object
+      summary: A sample object
+  links:
+    GetUserByUserId:
+      description: >
+        The id value returned in the response can be used as
+        the userId parameter in GET /users/{userId}.
+      operationId: getUser
+      parameters:
+        userId: '$response.body#/id'
+  callbacks:
+    MyCallback:
+      '{$request.body#/callbackUrl}':
+        post:
+          requestBody:
+            required: true
+            content:
+              application/json:
+                schema:
+                  type: object
+                  properties:
+                    message:
+                      type: string
+                      example: Some event happened
+                  required:
+                    - message
+          responses:
+            '200':
+              description: Your server returns this code if it accepts the callback
+`
+
+const pruneSpecTestFixture = `
+openapi: 3.0.1
+
+info:
+  title: OpenAPI-CodeGen Test
+  description: 'This is a test OpenAPI Spec'
+  version: 1.0.0
+
+servers:
+- url: https://test.oapi-codegen.com/v2
+- url: http://test.oapi-codegen.com/v2
+
+paths:
+  /cat:
+    get:
+      tags:
+        - cat
+      summary: Get cat status
+      operationId: getCatStatus
+      responses:
+        200:
+          description: Success
+          content:
+            application/json:
+              schema:
+                oneOf:
+                  - $ref: '#/components/schemas/CatAlive'
+                  - $ref: '#/components/schemas/CatDead'
+            application/xml:
+              schema:
+                anyOf:
+                  - $ref: '#/components/schemas/CatAlive'
+                  - $ref: '#/components/schemas/CatDead'
+            application/yaml:
+              schema:
+                allOf:
+                  - $ref: '#/components/schemas/CatAlive'
+                  - $ref: '#/components/schemas/CatDead'
+        default:
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+
+  /dog:
+    get:
+      tags:
+        - dog
+      summary: Get dog status
+      operationId: getDogStatus
+      responses:
+        200:
+          description: Success
+          content:
+            application/json:
+              schema:
+                oneOf:
+                  - $ref: '#/components/schemas/DogAlive'
+                  - $ref: '#/components/schemas/DogDead'
+            application/xml:
+              schema:
+                anyOf:
+                  - $ref: '#/components/schemas/DogAlive'
+                  - $ref: '#/components/schemas/DogDead'
+            application/yaml:
+              schema:
+                allOf:
+                  - $ref: '#/components/schemas/DogAlive'
+                  - $ref: '#/components/schemas/DogDead'
+        default:
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+
+components:
+  schemas:
+
+    Error:
+      properties:
+        code:
+          type: integer
+          format: int32
+        message:
+          type: string
+
+    CatAlive:
+      properties:
+        name:
+          type: string
+        alive_since:
+          type: string
+          format: date-time
+
+    CatDead:
+      properties:
+        name:
+          type: string
+        dead_since:
+          type: string
+          format: date-time
+        cause:
+          type: string
+          enum: [car, dog, oldage]
+
+    DogAlive:
+      properties:
+        name:
+          type: string
+        alive_since:
+          type: string
+          format: date-time
+
+    DogDead:
+      properties:
+        name:
+          type: string
+        dead_since:
+          type: string
+          format: date-time
+        cause:
+          type: string
+          enum: [car, cat, oldage]
+
+`