validations

package
v0.35.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 21, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

validations — maintainer notes

This document is the long-form companion to the validations package code. The source files keep godoc concise; complex invariants, design trade-offs, and intentionally-deferred follow-ups live here.

The validations package owns cross-builder validation and coercion concerns shared by the schema, parameters, responses, and items/headers code paths. Its two halves are:

  • Value coercion (coerce.go) — turns raw annotation text into the Go value implied by the target schema's type + format, for keywords whose payload is a primitive literal (default:, example:, enum:).
  • Shape legality (shape.go) — answers "is this keyword legal on a schema of this type?" against the JSON-Schema draft-4 domain rules that Swagger 2.0 inherits.

Table of contents


§contract — why these helpers live in the builder layer

The grammar parser produces a typed annotation block but does not know the resolved Swagger type / format of the field, parameter, or header the block is decorating — that resolution is the builder's job, and it depends on the surrounding Go type system.

Two consequences:

  1. Coercion of default: / example: / enum: payloads cannot happen at parse time. The grammar lexes default: 3 as the raw string "3"; only the builder knows whether the target is integer (so the value should be int(3)), string (so the value stays "3"), or array (so the value should json.Unmarshal into []any).
  2. Keyword-vs-type legality (pattern: only on string, multipleOf: only on number/integer, ...) similarly needs the resolved type. The grammar accepts the keyword syntactically; the builder applies the domain rule once the target's Type is known.

This package is the seam where those two concerns meet — small, type-aware helpers that the builder layer calls from its Walker callbacks.

§coercion-dispatch — CoerceValue / ParseDefault / ParseEnumValues

CoerceValue(s, schema *spec.SimpleSchema) is the primitive coercer. It dispatches on schema.TypeName() — a Swagger helper that returns Format when set, falling back to Type. This Format-wins behaviour is convenient at the items/parameter sites (where the SimpleSchema is the authoritative source) but is the wrong axis for the schema-builder paths that already hold a resolved (type, format) pair.

ParseDefault(s, schemaType, schemaFormat) is the explicit two-axis entry point. It dispatches on schemaType only, ignoring schemaFormat for routing. The schemaFormat argument is reserved for future per-format paths (e.g. size-bounded integer parsing for int32 vs int64) but is unused today.

ParseEnumValues(val, schemaType, schemaFormat) mirrors ParseDefault for enum payloads, delegating per-element typing to CoerceEnum.

Dispatch table (after stripping surrounding quotes from TypeName()):

Source label Dispatcher Coercer
integer, int, int64, int32, int16 both strconv.Atoi
bool, boolean both strconv.ParseBool
number, float64, float32 both strconv.ParseFloat (bitSize=64)
string both unquoteIfQuoted — strips a surrounding quote pair (F8)
object both json.Unmarshal into map[string]any
array both json.Unmarshal into []any
anything else / nil schema both raw string unchanged

The string arm strips one pair of surrounding double quotes from a quoted literal (example: "Foo"Foo, example: "" → the empty string) while leaving a bare value (example: Foo) untouched — the quotes are delimiters, not content (quirk F8; go-swagger#2547 / #2899). Only the plain string type is unquoted here; string formats (date, uuid, …) surface their format name as the dispatch label and fall to the raw-string arm.

Numeric and boolean parse errors are surfaced to the caller so the consumer can decide whether to emit a diagnostic. JSON parse failures on object / array are absorbed and the raw string is returned — the assumption is that an author who wrote default: notjson against an object target intended a textual placeholder rather than a machine-readable default. A future strict-mode option could turn this into a diagnostic.

§enum-shapes — JSON-array form vs comma-list form

CoerceEnum accepts two input shapes for the enum: annotation:

  • JSON-array formenum: ["a", "b", "c"]. Detected by attempting json.Unmarshal into []json.RawMessage. Each element is strconv.Unquoted (so the literal "a" becomes a before per-value coercion) and then routed through CoerceValue.
  • Comma-list formenum: a, b, c. Triggered when the JSON-array unmarshal fails. Each comma-separated token is TrimSpaced before per-value coercion so enum: a, b produces ["a", "b"], not ["a", " b"]. A surrounding [ ] pair is stripped first: the bracketed enum: [a, b, c] form has unquoted values, so it is not valid JSON and lands here — without the strip the brackets glue onto the first/last value (go-swagger#2396). The quoted (["a","b"]) and numeric ([1,2]) bracketed variants are valid JSON and take the JSON-array path instead.

Per-element coercion is the same CoerceValue path as default: / example:, so type-aware typing applies uniformly across the three keywords.

§format-axis — Format is reserved but not routed

ParseDefault and ParseEnumValues accept a schemaFormat argument that is currently discarded — the helpers underscore- assign it explicitly so the surface stays stable for callers while the format-aware paths are deferred.

Two paths could exercise it later:

  • Size-bounded integer parsing. int32 could parse via strconv.ParseInt(s, 10, 32) and surface overflow as a diagnostic rather than the silent truncation strconv.Atoi performs on int32 targets.
  • Float precision. float32 could parse with bitSize=32 to match the target's range; today both float widths share the bitSize=64 path.

Neither is strictly required for spec correctness — the emitted Swagger document carries the value via interface{} and downstream consumers re-validate against (type, format) themselves. They are tagged here as straightforward refinements once a concrete consumer asks for them.

§format-compat — IsFormatCompatible type × format legality

IsFormatCompatible(schemaType, format) (in format.go) is a sibling of IsLegalForType on the format axis: it answers "may this format ride on a schema already typed schemaType?" It exists for the swagger:type + swagger:strfmt combination, where swagger:type wins on the type axis and the strfmt format is applied as a supplementary hint only when it is consistent with that type (the F3 reconciliation — see .claude/plans/quirks-F-series-fix.md). It is not used for the strfmt-alone path, where strfmt still forces {type: string, format: X} (go-swagger#1512).

Resolved type Formats accepted
string any (strfmt is string-oriented)
integer int{n} (int,int8int64) + the swagger-extension uint{n}
number the integer set + float widths (float,double,float32,float64)
boolean / object / array / file none

int32/int64 are the only OAS-2-official integer formats and float/double the only official number formats; the wider Go-spelled set round-trips back to Go (the same vendor convention #1512 documents). An empty format is trivially compatible (nothing to apply); an empty/unknown schemaType is accepted best-effort, mirroring §empty-type. A false result returns a diagnostic-ready hint naming the offending format and type.

§type-domain-table — keyword × Swagger type legality

keywordTypeRules (in shape.go) carries the per-keyword type-domain table sourced from JSON-Schema draft-4 (the dialect Swagger 2.0 inherits):

Keyword family Legal on
pattern, minLength, maxLength string
maximum, minimum, multipleOf integer, number
minItems, maxItems, uniqueItems array
minProperties, maxProperties, patternProperties object

Keywords intentionally absent from the table:

  • required, readOnly, deprecated, discriminator — the rule is type-independent (or, in the case of discriminator, the OAS-level legality check happens elsewhere). The table returns "no rule" and IsLegalForType accepts the keyword for any type.
  • default, example, enum — coerced via CoerceValue / CoerceEnum, so they are legal on any type and the value conforms by construction.

The table is returned by a function rather than held as a package variable to keep the package gochecknoglobals-clean and to leave room for a future WithRules(...) constructor that lets callers extend the table for custom keywords.

§empty-type — schemaType == "" is accepted

IsLegalForType treats an empty schemaType as "type unknown" and returns ok=true with no hint. Two situations produce an empty type at the call site:

  • The grammar parsed a keyword before the type has been resolved (the typeless preamble case).
  • The target's type is genuinely indeterminate — a free-form schema such as additionalProperties: true.

In both cases the caller — typically a Walker callback in the schema or parameters builder — is responsible for deciding whether to apply the keyword. The package-local rule is "best-effort apply": never block on an unknown type from this seam.

Format is intentionally not consulted by IsLegalForType. Format is a refinement of type (int32 is an integer-typed field with format: int32); the domain rules apply at the type level. A future IsLegalForFormat sibling could add format-specific constraints (e.g. pattern: only on format: regex strings) without disturbing this surface.

§quirks-open — deferred follow-ups

  • Format-aware numeric parsing. ParseDefault ignores schemaFormat today; per-bit-size integer parsing (int32 via ParseInt(_, 10, 32)) and per-bit-size float parsing (float32 via ParseFloat(_, 32)) are the obvious next refinements once a consumer surfaces the need.
  • Strict JSON for object / array defaults. Invalid JSON on an object- or array-typed default: / example: currently falls back to the raw string. A strict mode could emit a diagnostic and drop the value rather than silently retain it.
  • Custom keyword rule extension. keywordTypeRules is built per call so a WithRules(...) constructor (or a RegisterKeyword hook) could let downstream tools extend the legality table for vendor-extension keywords without patching this package.

Documentation

Overview

Package validations owns cross-builder validation and coercion concerns shared by the schema, parameters, responses, and items/headers code paths.

The package has two halves:

  • Value coercion (this file) — turns raw annotation text into the Go value implied by the target schema's type+format, for keywords whose payload is a primitive literal (default:, example:, enum:).
  • Shape legality (shape.go) — answers whether a given keyword is legal on a schema of a given Swagger type.

Details

See [§contract](./README.md#contract) — why these helpers live in the builder layer rather than in the grammar parser.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CoerceEnum

func CoerceEnum(val string, s *spec.SimpleSchema) []any

CoerceEnum turns an `enum: …` annotation value into a typed []any. Accepts the JSON-array form (`enum: ["a","b"]`) and the comma-list form (`enum: a, b`). Per-value typing is applied via CoerceValue against the target schema.

Details

See [§enum-shapes](./README.md#enum-shapes) — how the two input forms are detected and how each element is normalised before per-value coercion.

func CoerceJSONOrString added in v0.35.0

func CoerceJSONOrString(s string) any

CoerceJSONOrString coerces a default:/example: value whose target type is unknown — the case of a $ref override arm, which carries no Type of its own (the type lives on the referenced definition, not on the allOf sibling). A JSON object- or array-literal body is parsed structurally so it matches the direct-field path (quirk G3): `example: {"k":"v"}` yields an object on a $ref'd field just as it does on a plain map field. Any other value is treated as a string scalar, with surrounding double quotes stripped (mirroring CoerceValue's "string" arm).

Never errors: a malformed JSON literal falls back to the unquoted string. Scalar literals (numbers, booleans) are intentionally NOT coerced here — the referenced type is unknown, so a bare `example: 42` against a string-format $ref must not be silently turned into a number.

func CoerceValue

func CoerceValue(s string, schema *spec.SimpleSchema) (any, error)

CoerceValue converts a raw annotation value to the Go representation implied by the target schema's Type/Format. Used by default:/example: setters where the annotation body is a primitive literal whose meaning depends on the target: `default: 3` becomes int(3) against `Type: "integer"`, "3" against `Type: "string"`, and so on.

A nil schema yields the raw string unchanged. Numeric and boolean parsing errors are surfaced to the caller; JSON parse failures on object/array targets are absorbed and the raw string is returned.

Dispatch is on spec.SimpleSchema.TypeName — Format wins when set, otherwise Type is consulted.

Details

See [§coercion-dispatch](./README.md#coercion-dispatch) — the per-type dispatch table, and the rationale for absorbing object/array JSON parse failures.

func IsFormatCompatible added in v0.35.0

func IsFormatCompatible(schemaType, format string) (ok bool, hint string)

IsFormatCompatible reports whether a `format` is meaningful on a schema whose resolved Swagger `type` is already fixed (e.g. by a `swagger:type` override). It answers the question "may this strfmt format ride on top of this type?" — the supplementary-format rule for the swagger:type + swagger:strfmt combination, where swagger:type wins on the type axis and the format applies only when it is consistent with that type.

Rules (see README §format-compat):

  • type "string": any format applies (strfmt is string-oriented).
  • type "integer": the integer formats int{n} and the swagger-extension uint{n} apply.
  • type "number": the integer formats, the uint{n} extension, AND the float widths apply.
  • any other type (boolean / object / array / file): no format applies.

An empty format is trivially compatible (there is nothing to apply). An empty/unknown schemaType is accepted best-effort, mirroring IsLegalForType — the caller has no type to check against. A false result carries a human-readable hint suitable for a diagnostic message.

func IsLegalForType

func IsLegalForType(keyword grammar.Keyword, schemaType string) (ok bool, hint string)

IsLegalForType reports whether keyword is legal on a schema with the given resolved Swagger type. Returns ok=true with empty hint when the keyword has no type constraint or the type matches. Returns ok=false with a human-readable hint when the type mismatches the keyword's domain.

Empty schemaType is treated as "type unknown" and accepted; the caller decides whether to apply the keyword to a typeless schema. Format is not consulted — the domain rules apply at the type level.

Details

See [§empty-type](./README.md#empty-type) — the best-effort-apply rule for unknown schema types, and why Format is intentionally kept off this axis.

func ParseDefault

func ParseDefault(s, schemaType, schemaFormat string) (any, error)

ParseDefault is the two-axis entry point for default:/example: coercion. It dispatches on schemaType alone — schemaFormat is accepted for surface stability but not consulted today.

Details

See [§coercion-dispatch](./README.md#coercion-dispatch) and [§format-axis](./README.md#format-axis) — why the two-axis surface exists alongside CoerceValue and which refinements the schemaFormat argument is reserved for.

func ParseEnumValues

func ParseEnumValues(val, schemaType, schemaFormat string) []any

ParseEnumValues is the two-axis entry point for enum: coercion. Mirrors ParseDefault — dispatches on schemaType, with schemaFormat reserved for future per-format paths.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL