Fixer Package Deep Dive
Try it Online
No installation required! Try the fixer in your browser →
The fixer package provides automatic fixes for common OpenAPI Specification validation errors, supporting both OAS 2.0 and OAS 3.x documents.
Table of Contents
- Overview
- Fix Types
- API Styles
- Practical Examples
- Generic Naming Strategies
- Configuration Reference
- Package Chaining
- Best Practices
Overview
The fixer analyzes OAS documents and applies fixes for issues that would cause validation failures. It preserves the input file format (JSON or YAML) for output consistency.
Common use cases:
- Add missing path parameters automatically
- Rename schemas with names illegal for the document's OAS version (e.g.,
Response[User],pkg/Pet) and rewrite the$refs that pointed to them - Remove unused schema definitions
- Clean up empty path items
Fix Types
| Fix Type | Default | Description |
|---|---|---|
FixTypeMissingPathParameter |
✅ Enabled | Adds Parameter objects for undeclared path template variables |
FixTypePathParameterNotRequired |
✅ Enabled | Sets required: true on existing in: path parameters that omit it |
FixTypeRenamedGenericSchema |
❌ Disabled | Renames schemas containing URL-unsafe characters |
FixTypePrunedUnusedSchema |
❌ Disabled | Removes unreferenced schema definitions |
FixTypePrunedEmptyPath |
❌ Disabled | Removes paths with no HTTP operations |
FixTypeEnumCSVExpanded |
❌ Disabled | Expands CSV enum strings to typed arrays (e.g., "1,2,3" → [1, 2, 3]) |
FixTypeDuplicateOperationId |
❌ Disabled | Renames duplicate operationId values to ensure uniqueness |
FixTypeStubMissingRef |
❌ Disabled | Creates empty stubs for unresolved $ref targets |
How FixTypeMissingPathParameter decides what is missing
A path template variable counts as declared when it is declared on the
operation or on the containing path item, whether inline or as a $ref into the
document's reusable parameter definitions (OAS 2.0's root-level parameters or
OAS 3.x's components.parameters). References are resolved, including chains of
them, so a shared parameter is never added a second time.
When a $ref cannot be resolved, no parameters are added for that operation.
That covers an external file or URL, a reference that dangles or names a
component which is not a parameter, and a cycle or over-long chain. The
reference may already declare the variable, and adding a duplicate name and
location would produce an invalid document.
This is deliberately more conservative than the validator, which reports a reference naming a non-parameter component as an error. The fixer cannot know what the author intended by such a reference, so it declines to guess rather than adding a parameter beside one it does not understand.
Where FixTypePathParameterNotRequired applies
Every OAS version requires required on an in: path parameter and permits no
value other than true, so this repair involves no judgment and loses nothing.
It runs at the three sites the validator reports the defect:
| Site | Reported path |
|---|---|
OAS 2.0 root-level parameters |
parameters.{name} |
OAS 3.x components.parameters |
components.parameters.{name} |
| Path item and operation, both versions | paths.{path}[.{method}].parameters[{i}] |
Parameters that are a $ref are skipped. The defect belongs to the definition
being referenced, not to the reference — writing required beside a $ref would
add a sibling the spec does not allow there. Fixing the definition clears the
error for every use site at once.
The defect test itself, paramutil.NeedsRequiredTrue, is shared with the
validator, so what validate reports is exactly what fix repairs. A parity test
validates a defective spec, fixes it, and re-validates to assert no
required: true error survives.
Where FixTypeEnumCSVExpanded applies
An enum written as one comma joined string is expanded only where the type
resolves to integer or number. An OAS 3.1 type array counts, its first
non-null entry deciding, so ["integer", "null"] expands. A comma inside a
string enum value is legitimate, so those are left as the document wrote them.
The enum does not have to be in a schema. OAS 2.0 gives a non-body parameter no
schema at all: type and enum sit on the parameter object itself, as they do
on a response header and on either one's items chain. This pass reaches every
declaration in both versions:
| Site | Reported path |
|---|---|
OAS 2.0 definitions |
definitions.{name} |
OAS 2.0 root-level parameters and responses |
parameters.{name}, responses.{name} |
| OAS 2.0 parameter or header declaring type and enum itself | ...parameters[{i}], ...headers.{name} |
| OAS 2.0 items chain, at any depth | ...parameters[{i}].items[.items...] |
OAS 3.x components.schemas, parameters, headers, requestBodies, responses |
components.{section}.{name}... |
OAS 3.x components.pathItems |
components.pathItems.{name}... |
| Path item parameters, both versions | paths.{path}.parameters[{i}] |
| Operation parameters, request body, responses | paths.{path}.{method}... |
Parameter or header using content instead of schema |
....content.{mediaType}.schema |
Media type itemSchema and encoding headers |
....itemSchema, ....encoding.{name}.headers.{name}.schema |
| Callbacks and webhooks | ....callbacks.{name}.{expression}..., webhooks.{name}... |
| OAS 3.2 custom methods | paths.{path}.additionalOperations.{METHOD}... |
Within a schema the pass follows every keyword that can nest another schema:
properties, items (including the OAS 2.0 tuple form), additionalProperties,
additionalItems, unevaluatedItems, unevaluatedProperties, allOf, anyOf,
oneOf, not, prefixItems, contains, propertyNames, patternProperties,
dependentSchemas, if, then, else, contentSchema and $defs.
A path item's parameters are visited once, not once per operation it holds. Every
map is walked in sorted key order, so the fixes one document produces are
reported in the same sequence on every run. Under DryRun the pass still
traverses and still records each fix; only the write is suppressed.
Why are some fixes disabled by default?
Disabled fixes fall into two categories:
- Performance-sensitive: Schema renaming (
FixTypeRenamedGenericSchema) and pruning (FixTypePrunedUnusedSchema,FixTypePrunedEmptyPath) walk all references and compute unused schemas, which can significantly slow processing of large specifications. - Behavioral impact:
FixTypeDuplicateOperationIdrenames operation IDs that clients and SDK generators may already depend on.FixTypeStubMissingRefinjects synthetic placeholder content into the document. Both are opt-in to avoid unexpected breakage.
FixTypeEnumCSVExpanded belongs to both categories. It walks the whole document, including callbacks, webhooks and encoding chains, and it rewrites values a client may already send. It also encodes a guess: that a comma in a numeric enum was meant to separate values rather than to be part of one.
API Styles
Functional Options (Recommended)
result, err := fixer.FixWithOptions(
fixer.WithFilePath("openapi.yaml"),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Applied %d fixes\n", result.FixCount)
Struct-Based (Reusable)
f := fixer.New()
f.InferTypes = true
result1, _ := f.Fix("api1.yaml")
result2, _ := f.Fix("api2.yaml")
Enable Specific Fixes
result, err := fixer.FixWithOptions(
fixer.WithFilePath("api.yaml"),
fixer.WithEnabledFixes(
fixer.FixTypeMissingPathParameter,
fixer.FixTypeRenamedGenericSchema,
fixer.FixTypePrunedUnusedSchema,
),
)
Enable ALL Fixes
f := fixer.New()
f.EnabledFixes = []fixer.FixType{} // Empty slice enables all
result, _ := f.Fix("api.yaml")
Practical Examples
See also: Basic example, Functional options on pkg.go.dev
Basic Fixing
result, err := fixer.FixWithOptions(
fixer.WithFilePath("openapi.yaml"),
)
if err != nil {
log.Fatal(err)
}
for _, fix := range result.Fixes {
fmt.Printf("Fixed: %s at %s\n", fix.Type, fix.Path)
}
Type Inference
See also: Type inference example on pkg.go.dev
When enabled, the fixer infers parameter types from naming conventions:
| Pattern | Inferred Type |
|---|---|
*id, *Id, *ID |
integer |
*uuid, *guid |
string (format: uuid) |
| Everything else | string |
result, err := fixer.FixWithOptions(
fixer.WithFilePath("openapi.yaml"),
fixer.WithInferTypes(true),
)
Dry-Run Mode
See also: Dry-run example on pkg.go.dev
Preview fixes without applying them:
result, err := fixer.FixWithOptions(
fixer.WithFilePath("openapi.yaml"),
fixer.WithDryRun(true),
)
fmt.Printf("Would apply %d fixes\n", result.FixCount)
// result.Document is unchanged
Generic Schema Renaming
See also: Generic naming example on pkg.go.dev
result, err := fixer.FixWithOptions(
fixer.WithFilePath("api.yaml"),
fixer.WithEnabledFixes(fixer.FixTypeRenamedGenericSchema),
fixer.WithGenericNaming(fixer.GenericNamingOf),
)
// Response[User] → ResponseOfUser
Generic Naming Strategies
See also: Naming config example, Strategy example on pkg.go.dev
When fixing invalid schema names like Response[User]:
| Strategy | Result |
|---|---|
GenericNamingUnderscore |
Response_User_ |
GenericNamingOf |
ResponseOfUser |
GenericNamingFor |
ResponseForUser |
GenericNamingFlattened |
ResponseUser |
GenericNamingDot |
Response.User |
Configure with WithGenericNaming() or WithGenericNamingConfig().
Configuration Reference
Functional Options
| Option | Description |
|---|---|
WithFilePath(path) |
Path to specification file |
WithParsed(result) |
Pre-parsed ParseResult |
WithInferTypes(bool) |
Infer parameter types from names |
WithEnabledFixes(fixes...) |
Specific fix types to enable |
WithGenericNaming(strategy) |
Naming strategy for generic schemas |
WithGenericNamingConfig(cfg) |
Custom naming configuration |
WithDryRun(bool) |
Preview without applying |
WithMutableInput(bool) |
Skip defensive copy when caller owns input |
WithUserAgent(userAgent string) |
Custom User-Agent for HTTP requests |
WithSourceMap(sm *parser.SourceMap) |
Source map for line/column info in fixes |
WithOperationIdNamingConfig(config OperationIdNamingConfig) |
Configuration for duplicate operationId renaming |
WithStubConfig(config StubConfig) |
Configuration for missing reference stub creation |
WithStubResponseDescription(desc string) |
Default description for stubbed responses |
Fixer Fields
| Field | Type | Description |
|---|---|---|
InferTypes |
bool |
Enable type inference |
EnabledFixes |
[]FixType |
Fix types to apply (empty = all) |
UserAgent |
string |
User-Agent string for HTTP requests |
SourceMap |
*parser.SourceMap |
Source location lookup for fix issues |
GenericNamingConfig |
GenericNamingConfig |
Custom naming rules |
OperationIdNamingConfig |
OperationIdNamingConfig |
Configuration for duplicate operationId renaming |
StubConfig |
StubConfig |
Configuration for missing reference stub creation |
DryRun |
bool |
Preview mode |
MutableInput |
bool |
Skip defensive copy |
FixResult Fields
| Field | Type | Description |
|---|---|---|
Document |
any |
Fixed document |
Fixes |
[]Fix |
Applied fixes with details |
FixCount |
int |
Total fixes applied |
ParseErrors |
[]error |
Errors in the source document that no fix covers |
HasParseErrors() |
bool |
Whether any such errors remain |
SourceFormat |
SourceFormat |
Preserved format |
ToParseResult() |
*parser.ParseResult |
Converts result for package chaining, carrying ParseErrors through |
Package Chaining
The ToParseResult() method enables seamless chaining with other oastools packages by converting FixResult to a parser.ParseResult:
// Fix then validate
fixResult, err := fixer.FixWithOptions(
fixer.WithFilePath("openapi.yaml"),
fixer.WithInferTypes(true),
)
if err != nil {
log.Fatal(err)
}
// Chain to validator
v := validator.New()
valResult, _ := v.ValidateParsed(*fixResult.ToParseResult())
fmt.Printf("Valid: %v\n", valResult.Valid)
// Or chain to converter, which refuses a document whose problems no fix covers
if fixResult.HasParseErrors() {
log.Fatalf("%d error(s) no fix covers", len(fixResult.ParseErrors))
}
c := converter.New()
convResult, err := c.ConvertParsed(*fixResult.ToParseResult(), "3.1.0")
if err != nil {
log.Fatal(err)
}
ToParseResult() carries ParseErrors through, so the converter and joiner both
refuse a document that still has problems no fix covers. Check
HasParseErrors() before chaining if you are using the fixer as a gate.
This enables workflows like: parse → fix → validate → convert → join
Best Practices
- Start with defaults -
DefaultEnabledFixes()(FixTypeMissingPathParameter,FixTypePathParameterNotRequired) handles the most common issues - Enable expensive fixes only when needed - Schema pruning/renaming can be slow on large specs
- Use dry-run in CI - Verify what would change before applying
- Validate after fixing - Ensure the fixed document is valid
- Pipeline usage -
oastools fix api.yaml | oastools validate -q -
Learn More
For additional examples and complete API documentation:
- 📦 API Reference on pkg.go.dev - Complete API documentation with all examples
- 🔧 Selective fixes example - Enable specific fix types
- 🗑️ Prune unused schemas - Remove unreferenced definitions
- 📁 Prune empty paths - Clean up empty path items
- ✅ Check results example - Inspect applied fixes