📖 7 min read (~ 1500 words).

Sharing parameters & responses

When the same header, query parameter or error response appears on many operations, you don’t have to repeat it. codescan can publish a parameter or response once into the spec’s top-level parameters / responses maps, then reference it from each operation as a $ref. This is the OpenAPI 2.0 shared namespace (#/parameters/{name}, #/responses/{name}).

Each pane below pairs the annotated Go (left) with the exact fragment the scanner emits (right), from the test-covered docs/examples/concepts/sharedparams package. For the per-operation basics this builds on — the plain swagger:parameters <operationID> and swagger:response <name> forms — see Routes & operations.

Declaring a shared parameter

swagger:parameters * declares a struct whose fields are registered at the spec top level, #/parameters/{name}, keyed by each parameter’s resolved name. The bare * is register-only: it publishes the parameter but does not, by itself, attach it to any operation.

The convenience form swagger:parameters * <operationID>… does both at once — it registers the parameter and $refs it into the listed operations, which is handy for a small spec.

// CommonHeaders registers a reusable header parameter at the spec top
// level, #/parameters/X-Request-ID. The bare `*` target is register-only:
// it publishes the parameter but does not, by itself, attach it to any
// operation.
//
// swagger:parameters *
type CommonHeaders struct {
	// RequestID correlates a request across services.
	//
	// in: header
	RequestID string `json:"X-Request-ID"`
}

// AuthHeader registers #/parameters/X-API-Key and, in the same breath,
// $ref's it into the createPet operation — the convenient `* <opid>`
// form for a small spec.
//
// swagger:parameters * createPet
type AuthHeader struct {
	// APIKey authorises access.
	//
	// in: header
	// required: true
	APIKey string `json:"X-API-Key"`
}


// ErrorResponse is the common error envelope returned by every operation.
//
// swagger:response *
type ErrorResponse struct {
	// in: body
	Body struct {
		// Code is a machine-readable error code.
		Code int `json:"code"`
		// Message is a human-readable error message.
		Message string `json:"message"`
	} `json:"body"`
}

Full source: docs/examples/concepts/sharedparams/sharedparams.go

Both structs land in the top-level parameters map, each keyed by its parameter name (the json: tag, or a name: / swagger:name override):

{
  "X-API-Key": {
    "type": "string",
    "x-go-name": "APIKey",
    "description": "APIKey authorises access.",
    "name": "X-API-Key",
    "in": "header",
    "required": true
  },
  "X-Request-ID": {
    "type": "string",
    "x-go-name": "RequestID",
    "description": "RequestID correlates a request across services.",
    "name": "X-Request-ID",
    "in": "header"
  }
}

Full source: docs/examples/concepts/sharedparams/testdata/parameters.json

Referencing a shared parameter

Once a parameter is registered, an operation can pull it in by name. There are two reference channels:

  • swagger:parameters * <operationID> on the declaring struct — the convenience form above. AuthHeader uses it to inject X-API-Key into createPet.
  • swagger:parameters <operationID> <name>… as a standalone marker on the operation’s function — the scaling channel: the shared struct need not enumerate every operation that wants the parameter; instead each operation opts in next to its own swagger:route.
// ListPets lists pets.
//
// The standalone reference marker on the next line pulls the shared
// X-Request-ID parameter into this operation as a $ref. This is the
// scaling channel: the shared struct need not enumerate every operation
// that wants the parameter.
//
// swagger:route GET /pets pets listPets
// swagger:parameters listPets X-Request-ID
// Responses:
//
//	default: ErrorResponse
func ListPets() {}

// CreatePet creates a pet. Its X-API-Key comes from AuthHeader
// (#/parameters/X-API-Key, $ref'd via `* createPet`); its body comes from
// the inlined CreatePetParams.
//
// swagger:route POST /pets pets createPet
// Responses:
//
//	default: ErrorResponse
func CreatePet() {}

Full source: docs/examples/concepts/sharedparams/sharedparams.go

listPets opts in through the standalone marker, so its only parameter is a $ref to the shared X-Request-ID:

{
  "tags": [
    "pets"
  ],
  "operationId": "listPets",
  "parameters": [
    {
      "$ref": "#/parameters/X-Request-ID"
    }
  ],
  "responses": {
    "default": {
      "$ref": "#/responses/ErrorResponse"
    }
  }
}

Full source: docs/examples/concepts/sharedparams/testdata/listpets.json

createPet receives the $ref’d X-API-Key (from * createPet) alongside its own inlined body parameter — references and inline parameters coexist:

{
  "tags": [
    "pets"
  ],
  "operationId": "createPet",
  "parameters": [
    {
      "x-go-name": "Body",
      "name": "body",
      "in": "body",
      "required": true,
      "schema": {
        "$ref": "#/definitions/Pet"
      }
    },
    {
      "$ref": "#/parameters/X-API-Key"
    }
  ],
  "responses": {
    "default": {
      "$ref": "#/responses/ErrorResponse"
    }
  }
}

Full source: docs/examples/concepts/sharedparams/testdata/createpet.json

Path-item parameters

A parameter can also attach to a whole path rather than a single operation. swagger:parameters /path inlines a struct’s fields into the path-item’s parameters array, so every operation under that path inherits them.

// TenantHeader inlines a required header into the /pets/{id} path-item
// itself, so every operation under that exact path inherits it. The
// target is a literal path, and matching is exact — OAS2 has no path
// hierarchy, so this does NOT apply to /pets.
//
// swagger:parameters /pets/{id}
type TenantHeader struct {
	// Tenant scopes the request to a customer.
	//
	// in: header
	// required: true
	Tenant string `json:"X-Tenant"`
}

// GetPet fetches one pet. It declares no header of its own; X-Tenant
// reaches it through the /pets/{id} path-item parameter above.
//
// swagger:route GET /pets/{id} pets getPet
// Responses:
//
//	default: ErrorResponse
func GetPet() {}

Full source: docs/examples/concepts/sharedparams/sharedparams.go

X-Tenant now rides the /pets/{id} path-item; getPet inherits it without declaring a header of its own:

{
  "get": {
    "tags": [
      "pets"
    ],
    "operationId": "getPet",
    "responses": {
      "default": {
        "$ref": "#/responses/ErrorResponse"
      }
    }
  },
  "parameters": [
    {
      "type": "string",
      "x-go-name": "Tenant",
      "description": "Tenant scopes the request to a customer.",
      "name": "X-Tenant",
      "in": "header",
      "required": true
    }
  ]
}

Full source: docs/examples/concepts/sharedparams/testdata/pathitem.json

Warning

Exact path, no hierarchy. OpenAPI 2.0 has no path nesting, so the target is matched literally: swagger:parameters /pets/{id} applies to /pets/{id} only — not to /pets. Path-item parameters also co-exist with operation-level ones rather than replacing them; if an operation declares a parameter with the same (name, in), the operation’s wins at resolution time per the OAS2 rule.

To $ref an already-registered shared parameter into a path-item (instead of inlining a new one), use the reference form with a path target: swagger:parameters /path <name>… — the path-item analogue of the per-operation marker above.

Shared responses

Responses share the same way. swagger:response * registers a struct at #/responses/{name} (keyed by the Go type name). The * is a synonym for the bare/named swagger:response form — its job is to mark the response as a shared one. Operations then name it in their Responses: block and it is emitted as a $ref.

// ErrorResponse is the common error envelope returned by every operation.
//
// swagger:response *
type ErrorResponse struct {
	// in: body
	Body struct {
		// Code is a machine-readable error code.
		Code int `json:"code"`
		// Message is a human-readable error message.
		Message string `json:"message"`
	} `json:"body"`
}

Full source: docs/examples/concepts/sharedparams/sharedparams.go

The shared ErrorResponse lands in the top-level responses map:

{
  "ErrorResponse": {
    "description": "ErrorResponse is the common error envelope returned by every operation.",
    "schema": {
      "type": "object",
      "properties": {
        "code": {
          "description": "Code is a machine-readable error code.",
          "type": "integer",
          "format": "int64",
          "x-go-name": "Code"
        },
        "message": {
          "description": "Message is a human-readable error message.",
          "type": "string",
          "x-go-name": "Message"
        }
      }
    }
  }
}

Full source: docs/examples/concepts/sharedparams/testdata/responses.json

Both routes write default: ErrorResponse, which resolves to a single shared $ref (visible as responses.default.$ref in the operation panes above) — one error envelope, defined once, referenced everywhere.

Conflicts, duplicates & dangling references

The shared namespace is referenced only by short name, so codescan cannot silently rename a collision the way it deconflicts model definitions. Instead it applies a deterministic, observable policy and reports every adjustment through Options.OnDiagnostic (the scan never fails on these — it keeps a valid spec and warns):

SituationPolicyDiagnostic
Two swagger:parameters * register the same namekeep-first (sorted by package path then position; never renamed) — later one droppedscan.shared-parameter-conflict
Two swagger:response * register the same namekeep-first; later one droppedscan.shared-response-conflict
A reference names a parameter no * registeredreference dropped (no dangling $ref emitted)scan.dangling-parameter-ref
An operation names an unregistered shared responsereference droppedscan.dangling-response-ref
A * <opid>… marker repeats an operation idduplicate droppedscan.duplicate-target
A reference repeats a parameter namecollapses to a single $refscan.duplicate-ref
Note

The shared parameters, responses and definitions namespaces are independent: #/parameters/Status, #/responses/Status and #/definitions/Status can all coexist. The resolved (name:-overridden) name is the key, so references must use that name — not the Go field name. An InputSpec overlay entry seeds the namespace and wins any keep-first conflict.

What’s next