-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Support attributes (nullable, format, optional) and type placeholders in .swaggo overrides #2147
Description
Problem
The .swaggo override file lets users globally replace types (e.g., replace sql.NullString string), but falls short when the replacement needs additional schema metadata — specifically nullable, optional, and format properties.
Use case: three-state PATCH fields
In REST APIs, PATCH endpoints often need to distinguish three states per field:
| State | JSON | Meaning |
|---|---|---|
| Absent | field not in payload | Don't change |
| Null | "field": null |
Clear the value |
| Value | "field": 42 |
Set the value |
A common Go solution is a generic Optional[T] with custom UnmarshalJSON. For correct OpenAPI output, each field using this type should be optional (not in required) and nullable (x-nullable: true).
Today, users must annotate every field with struct tags:
type PatchUserRequest struct {
Name Optional[string] `json:"name" validate:"optional" extensions:"x-nullable"`
Bio Optional[string] `json:"bio" validate:"optional" extensions:"x-nullable"`
}This defeats the purpose of centralized overrides — the semantics are properties of the type, not individual fields.
No generic catch-all
Each Optional[T] instantiation must be listed individually:
replace common.Optional[string] string
replace common.Optional[int] integer
replace common.Optional[models.Address] models.Address
// ... one line per type
Related
This also addresses #1852 — supporting format in global overrides (e.g., date-time for time types).
Proposed Solution
1. Override attributes (key:value syntax)
Extend replace to accept key:value attributes after the replacement type:
replace common.Optional[string] string optional:true nullable:true
replace common.Optional[time.Time] string optional:true nullable:true format:date-time
Supported attributes:
optional:true— removes the field fromrequirednullable:true— addsx-nullable: trueformat:<value>— setsformat(e.g.,date-time,uuid)
2. Type parameter placeholder ($T)
Allow $T in source and replacement types for generic catch-all rules:
replace common.Optional[$T] $T optional:true nullable:true
When swag encounters common.Optional[models.Address], it matches the pattern, captures models.Address, substitutes $T, resolves normally, and applies the attributes.
Exact matches take priority over placeholder patterns.
Complete .swaggo example
replace common.Optional[string] string optional:true nullable:true
replace common.Optional[float64] number optional:true nullable:true
replace common.Optional[time.Time] string optional:true nullable:true format:date-time
replace common.Optional[$T] $T optional:true nullable:true
Backward Compatibility
.swaggoformat: Fully backward compatible — existing lines work unchanged.SetOverridesAPI: Changes frommap[string]stringtomap[string]Override. This is a breaking change for programmatic callers ofSetOverrides, but this function is primarily used internally by thegenpackage.
Open question: attribute naming convention
The current implementation uses clean, short attribute names:
replace common.Optional[string] string optional:true nullable:true format:date-time
An alternative would be to mirror the existing struct tag names more closely:
replace common.Optional[string] string validate:optional extensions:x-nullable format:date-time
The struct-tag-aligned syntax would be more familiar to existing swag users (extensions:"x-nullable" and validate:"optional" are already well-known), but the shorter names are arguably cleaner for a config file context. Happy to adjust either way — would appreciate maintainer input on the preferred convention.
Implementation
I have a working implementation with tests ready as a PR: override struct, key:value parsing, $T matching, and attribute application. All existing tests pass.