openapi3: typed context errors for Validate() wrapper chain#1183
Merged
Conversation
Replaces the 14 fmt.Errorf wrap sites in Validate() with three typed
error types carrying their context as structured fields:
- SectionContextError{Section, Cause} — wraps an error inside one of
the top-level document sections (info, paths, components, security,
servers, tags, externalDocs, webhooks, jsonSchemaDialect)
- PathContextError{Path, Cause} — wraps an error inside a
specific path
- OperationContextError{Method, Cause} — wraps an error inside a
specific HTTP-method operation
Continues the typed-validation-error work from getkin#1166 and getkin#1180:
those PRs typed the leaf errors; this one types the wrapper layers
that carry doc-tree position around them.
Why: today, callers that want to render context separately (which
section a finding lives in, which path, which operation) have to
parse the rendered error string with regex. errors.As against typed
wrappers is the structured equivalent.
Backward compatibility: Error() strings are byte-identical to the
fmt.Errorf wrappers they replace. Existing consumers parsing the
rendered text continue to work unchanged. The typed extraction is
purely additive.
Wrap sites converted:
- openapi3.go: 9 sections (info, paths, components, security,
servers, tags, externalDocs, webhooks, jsonSchemaDialect)
- paths.go: per-path wrapper (PathContextError)
- path_item.go: per-operation wrapper (OperationContextError)
- operation.go, tag.go, schema.go: 3 additional externalDocs sites
(also use SectionContextError{Section: "external docs"})
Tests cover Error() format byte-stability, Unwrap chain walking,
errors.As extraction from a three-layer chain (section + path +
operation), and arbitrary non-typed inner causes. All existing
tests pass unchanged across openapi2, openapi3, openapi3conv,
openapi3filter, openapi3gen, and the routers — confirming the
rendered strings are stable.
.github/docs/openapi3.txt regenerated via docs.sh.
reuvenharrison
added a commit
to oasdiff/oasdiff
that referenced
this pull request
May 14, 2026
This reverts commit f9bfe39. The reverted commit used regex to strip kin's "invalid paths: invalid path X: invalid operation Y:" prefixes off the rendered error message. That was always meant to be interim — message-text parsing is exactly the brittleness kin's typed-error work eliminates. kin PR getkin/kin-openapi#1183 adds typed SectionContextError / PathContextError / OperationContextError wrappers that carry the context as structured fields. Once that merges and a kin release is cut, the extraction becomes three errors.As calls with no string parsing. Carrying the regex in the interim isn't worth it: the whole feat/validate-command branch is gated on a kin release anyway, so there's no window where the regex would ship to users. Reverting now keeps the branch honest and avoids a "delete the regex" follow-up commit later. The Finding struct keeps its Operation / Path fields (added in the schema-alignment commit, not here) — they're simply unpopulated until the typed-error extraction lands. omitempty elides them from output in the meantime. Validate findings temporarily render the full wrapped message inline again, as they did before f9bfe39. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
|
LGTM but:
thanks |
Addresses review feedback on getkin#1183: - SectionContextError -> SectionValidationError - PathContextError -> PathValidationError - OperationContextError -> OperationValidationError The new names tie these positional wrappers to the ValidationError family they belong to (base ValidationError, cluster types, leaves). Also renames the file to sit in that namespace: section_context_error.go -> validation_error_context.go (+ _test.go). No behavior change: Error() strings and Unwrap() chains are untouched. .github/docs/openapi3.txt regenerated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
reuvenharrison
added a commit
to oasdiff/kin-openapi
that referenced
this pull request
May 17, 2026
…idate
Completes the bare-error sweep on security_scheme.go after the three
sites added earlier in this PR (InvalidSecuritySchemeTypeError,
InvalidHTTPSchemeError, and the no-OIDC-URL case below). None of
these surfaced in the corpus top-10 catchall list (so they're rare
in the wild) but typing them lets oasdiff and other downstream
consumers dispatch every code path in this file under the same
errors.As shape.
Mapping:
* line 172 'no OIDC URL found for openIdConnect security scheme X'
-> RequiredFieldError wrapping OpenIDConnectURLRequired leaf,
Field 'openIdConnectUrl'.
* line 187 'apiKey should have in. It can be query, header or
cookie, not X' -> new typed cluster APIKeyInInvalidError mirroring
InvalidParameterInError's shape (single-value rejection, preserves
the multi-clause original message).
* lines 193, 195, 201, 214 (four 'type X can't have Y' cases) ->
ForbiddenFieldError wrapping SecuritySchemeInForbidden,
SecuritySchemeNameForbidden, SecuritySchemeBearerFormatForbidden,
SecuritySchemeFlowsForbidden leaves respectively.
* line 208 'type X should have flows' -> RequiredFieldError wrapping
SecuritySchemeFlowsRequired leaf, Field 'flows'.
The single remaining wrap at line 211 ('security scheme flow is
invalid: %w') is deliberately left as-is: the inner flow.Validate()
returns typed errors that the %w preserves via the chain, so
errors.As against any inner cluster still works. Replacing the
prefix with a typed context wrapper would mirror SectionValidationError
from getkin#1183 but is a structurally different change and is out of
scope here.
All seven sites carry Origin via the existing newRequiredField /
newForbiddenField constructors that thread ss.Origin through, or
directly for APIKeyInInvalidError. Error() strings byte-identical
to the original fmt.Errorf calls.
Tests cover all seven dispatches: the cluster + the leaf are both
reachable via errors.As, and the field name on RequiredFieldError /
ForbiddenFieldError matches the spec field name.
reuvenharrison
added a commit
to oasdiff/kin-openapi
that referenced
this pull request
May 17, 2026
Completes the typed-error sweep started earlier in this PR by replacing the remaining bare fmt.Errorf-with-%w wrap sites with typed context wrappers. Mirrors the SectionValidationError / PathValidationError / OperationValidationError pattern from getkin#1183. After this commit, no fmt.Errorf calls remain inside any Validate function in the openapi3 package - every error path is either a typed leaf (cluster + leaf, per the earlier commits in this PR) or a typed context wrapper. New typed wrappers: * ComponentValidationError (Section, Name, Cause) - components.go * ExternalDocsURLValidationError (Cause) - external_docs.go * HeaderFieldValidationError (Field, Cause) - header.go (5 sites) * MediaTypeExampleValidationError (ExampleName, Cause) - media_type.go * WebhookValidationError (Name, Cause) - openapi3.go * ParameterFieldValidationError (ParameterName, Field, Cause) - parameter.go (5 sites) * ParameterExampleValidationError (ExampleName, Cause) - parameter.go (2 sites) * SecuritySchemeFlowValidationError (Cause) - security_scheme.go * OAuthFlowValidationError (FlowKind, Cause) - security_scheme.go (4 sites) * OAuthFlowFieldValidationError (Field, Cause) - security_scheme.go Each wrapper implements Error() preserving the original rendered message format byte-for-byte and Unwrap() returning Cause so errors.As chains reach inner typed leaves transparently. fmt imports removed from external_docs.go, header.go, media_type.go via goimports (no longer needed).
reuvenharrison
added a commit
to oasdiff/kin-openapi
that referenced
this pull request
May 17, 2026
The 3-layer model (Base / Cluster / Leaf) at the top of validation_error.go did not mention context wrappers, even though getkin#1183 introduced them and this PR adds 10 more. A future contributor reading either file in isolation would miss how the families relate. * Extends the validation_error.go header to name the 4 categories, shows the canonical 3-link chain (context wrapper -> cluster -> leaf), and points at validation_error_context.go for the wrapper inventory. * Adds a file-level header to validation_error_context.go that defines what a context wrapper is, contrasts it with cluster and leaf (carries WHERE, not WHAT), shows the convention all wrappers share, and groups the inventory into document-wide vs narrow-scope wrappers. No code change; pure documentation.
reuvenharrison
added a commit
to oasdiff/kin-openapi
that referenced
this pull request
May 20, 2026
Five fmt.Errorf call sites in the openapi3 validators returned bare errors that consumers could only inspect by substring matching. Wrap each in a typed cluster so errors.As / errors.Is consumers can dispatch on the error kind and pull structured fields out: - PathParameterRequiredError (parameter.go) Param + Origin - DuplicateOperationIDError (paths.go) OperationID + Endpoint1/Endpoint2 - ExtraSiblingFieldsError (extension.go, refs) Fields []string - SchemaTypeError (schema.go) Type + Origin Error() strings are unchanged byte-for-byte (existing string-comparison consumers, apis_guru golden fixtures, validation_error_test assertions all pass without edits). The benefit is purely additive: callers gain the typed-cluster dispatch they already use for the RequiredFieldError / FieldVersionMismatchError / etc. clusters added in getkin#1183. Promised in getkin#1185's heads-up comment after running oasdiff validate across ~500 fixtures and finding these as the remaining 22% of findings that fell through to a downstream catchall.
reuvenharrison
added a commit
to oasdiff/kin-openapi
that referenced
this pull request
May 20, 2026
…idate
Completes the bare-error sweep on security_scheme.go after the three
sites added earlier in this PR (InvalidSecuritySchemeTypeError,
InvalidHTTPSchemeError, and the no-OIDC-URL case below). None of
these surfaced in the corpus top-10 catchall list (so they're rare
in the wild) but typing them lets oasdiff and other downstream
consumers dispatch every code path in this file under the same
errors.As shape.
Mapping:
* line 172 'no OIDC URL found for openIdConnect security scheme X'
-> RequiredFieldError wrapping OpenIDConnectURLRequired leaf,
Field 'openIdConnectUrl'.
* line 187 'apiKey should have in. It can be query, header or
cookie, not X' -> new typed cluster APIKeyInInvalidError mirroring
InvalidParameterInError's shape (single-value rejection, preserves
the multi-clause original message).
* lines 193, 195, 201, 214 (four 'type X can't have Y' cases) ->
ForbiddenFieldError wrapping SecuritySchemeInForbidden,
SecuritySchemeNameForbidden, SecuritySchemeBearerFormatForbidden,
SecuritySchemeFlowsForbidden leaves respectively.
* line 208 'type X should have flows' -> RequiredFieldError wrapping
SecuritySchemeFlowsRequired leaf, Field 'flows'.
The single remaining wrap at line 211 ('security scheme flow is
invalid: %w') is deliberately left as-is: the inner flow.Validate()
returns typed errors that the %w preserves via the chain, so
errors.As against any inner cluster still works. Replacing the
prefix with a typed context wrapper would mirror SectionValidationError
from getkin#1183 but is a structurally different change and is out of
scope here.
All seven sites carry Origin via the existing newRequiredField /
newForbiddenField constructors that thread ss.Origin through, or
directly for APIKeyInInvalidError. Error() strings byte-identical
to the original fmt.Errorf calls.
Tests cover all seven dispatches: the cluster + the leaf are both
reachable via errors.As, and the field name on RequiredFieldError /
ForbiddenFieldError matches the spec field name.
reuvenharrison
added a commit
to oasdiff/kin-openapi
that referenced
this pull request
May 20, 2026
Completes the typed-error sweep started earlier in this PR by replacing the remaining bare fmt.Errorf-with-%w wrap sites with typed context wrappers. Mirrors the SectionValidationError / PathValidationError / OperationValidationError pattern from getkin#1183. After this commit, no fmt.Errorf calls remain inside any Validate function in the openapi3 package - every error path is either a typed leaf (cluster + leaf, per the earlier commits in this PR) or a typed context wrapper. New typed wrappers: * ComponentValidationError (Section, Name, Cause) - components.go * ExternalDocsURLValidationError (Cause) - external_docs.go * HeaderFieldValidationError (Field, Cause) - header.go (5 sites) * MediaTypeExampleValidationError (ExampleName, Cause) - media_type.go * WebhookValidationError (Name, Cause) - openapi3.go * ParameterFieldValidationError (ParameterName, Field, Cause) - parameter.go (5 sites) * ParameterExampleValidationError (ExampleName, Cause) - parameter.go (2 sites) * SecuritySchemeFlowValidationError (Cause) - security_scheme.go * OAuthFlowValidationError (FlowKind, Cause) - security_scheme.go (4 sites) * OAuthFlowFieldValidationError (Field, Cause) - security_scheme.go Each wrapper implements Error() preserving the original rendered message format byte-for-byte and Unwrap() returning Cause so errors.As chains reach inner typed leaves transparently. fmt imports removed from external_docs.go, header.go, media_type.go via goimports (no longer needed).
reuvenharrison
added a commit
to oasdiff/kin-openapi
that referenced
this pull request
May 20, 2026
The 3-layer model (Base / Cluster / Leaf) at the top of validation_error.go did not mention context wrappers, even though getkin#1183 introduced them and this PR adds 10 more. A future contributor reading either file in isolation would miss how the families relate. * Extends the validation_error.go header to name the 4 categories, shows the canonical 3-link chain (context wrapper -> cluster -> leaf), and points at validation_error_context.go for the wrapper inventory. * Adds a file-level header to validation_error_context.go that defines what a context wrapper is, contrasts it with cluster and leaf (carries WHERE, not WHAT), shows the convention all wrappers share, and groups the inventory into document-wide vs narrow-scope wrappers. No code change; pure documentation.
reuvenharrison
added a commit
to oasdiff/oasdiff
that referenced
this pull request
May 24, 2026
* feat(validate): new subcommand wrapping kin-openapi's Validate() Adds 'oasdiff validate <spec>' that flags per-RFC OpenAPI / JSON Schema spec violations. Wraps kin-openapi's openapi3.T.Validate() walker and dispatches each typed error to a stable kebab-case rule ID via errors.As against kin's RequiredFieldError / FieldVersionMismatchError clusters (introduced in kin-openapi#1166). Output format follows the changelog command's shape (id/text/level/ source) so a single CI script can parse both. Two formats: text (default, with a header summary line + per-finding block matching ApiChange.MultiLineError style) and yaml (-f yaml). Exit code 0 on no findings, 1 if any. 10 tests cover: happy path, typed dispatch (info-version-required, openapi-required, identifier-field-for-3-1-plus, webhooks-field-for- 3-1-plus, const-field-for-3-1-plus through 3 levels of %w wrapping), untyped fallback (kin-validation-error for sites kin hasn't migrated to a typed cluster yet), text + yaml format shapes, load-failure exit code distinct from validation-finding exit code. DO NOT MERGE - depends on kin-openapi#1166 landing and a kin release shipping. The go.mod replace directive pins kin to the fork's rfc branch. Cleanup before merge: 1. Remove the replace directive 2. Bump kin-openapi to the released version 3. go mod tidy * fix(validate): use validateCmd const to silence unused-const lint * feat(validate): surface line + column from kin's typed Origin Loader now starts with IncludeOrigin=true. The Finding struct gains Line/Column fields, populated from the kin cluster errors' Origin.Key when available (info, license, server, schema). Document-root fields (openapi, webhooks, jsonSchemaDialect) have nil Origin and emit findings without line/column (yaml omitempty). Text format renders <file>:<line>:<column> when origin is set, plain <file> otherwise — matches the changelog command's location shape. Three new tests pin: line/column populated for info-version-required, both fields absent for openapi-required (doc-root), text format includes :line:column when available. * chore: drop accidentally-committed .DS_Store * fix(validate): indent every line of multi-line finding messages kin errors like *SchemaError embed newlines in their Error() output (Schema and Value dumps). Without indenting continuation lines, those broke the finding's visual grouping in text format. Every \n in the message now becomes \n\t so the whole finding stays under the same tab indent. * chore: gitignore .DS_Store * chore: gitignore .DS_Store * fix(validate): leave blank lines blank when indenting multi-line text Previous indent logic prefixed every \n with \t, including blank lines — leaving stray tabs on otherwise-empty separator lines and a trailing \t at the end of the message. Switch to a line-by-line walk that skips blanks and trims trailing whitespace. * feat(validate): dispatch SchemaValueError cluster (example/default-violates-schema) kin#1166 added a *SchemaValueError cluster wrapping the *SchemaError returned by VisitJSON when a schema's example or default doesn't satisfy its own constraints. Add a third dispatch arm so findings of that shape get a specific rule ID derived from the cluster's ValueKind: 'example' → 'example-violates-schema', 'default' → 'default-violates-schema'. The cluster also carries the parameter/media-type/schema's Origin, so Line+Column now populate for these findings as well. * deps: bump kin-openapi to include long-tail RequiredFieldError leaves * deps: switch kin-openapi from oasdiff fork to upstream master All four typed-validation-error PRs now merged into upstream getkin/kin-openapi: - #1162 (openapi3conv 3.0→3.x canonicalization) - #1166 (ValidationError framework) - #1170 (long-tail RequiredFieldError leaves) - #1180 (combined long-tail PR collapsing #1171-#1179) Drop the replace directive; pin to upstream-master pseudo-version e8145f8f4d2b (the #1180 merge commit). When getkin tags a release containing this work, the require line will move from pseudo-version to a stable v0.138.x. * feat(validate): dispatch the 8 remaining kin clusters to typed rule IDs After bumping kin to upstream master post-#1180, eight cluster types became available that the validate dispatch was still routing to the 'kin-validation-error' catchall. Wire each: - *PathParametersError -> path-parameters-mismatch (static, since the cluster carries Path/Method/Missing not a single derivable field) - *MutuallyExclusiveFieldsError -> <f1>-<f2>-mutually-exclusive - *ForbiddenFieldError -> <field>-forbidden - *ServerURLTemplateError -> server-url-template-invalid (static, the cluster carries only the offending URL) - *EitherFieldRequiredError -> <f1>-or-<f2>-required - *SchemaBothFormsExclusive -> <field>-both-forms-exclusive - *ExactlyOneFieldError -> <f1>-or-<f2>-exactly-one - *SingleEntryContentError -> <subject>-content-single-entry - *WebhookNilError -> webhook-nil keyOriginForKinError extended in the same way so line/column flow through for clusters that carry Origin (all except WebhookNilError, whose offending key sits on the document root that the loader doesn't track per-key). Updated the previously-flipping Test_ValidateCmd_UntypedKinErrorFallback (server-url-mismatched-braces is now typed) and added a test for the user-reported PathParametersError case. * deps: temporarily point kin-openapi at oasdiff fork with DisableTimestamps Switches the kin-openapi dep to the oasdiff fork's feat/yaml-disable-timestamps branch via a replace directive. This is the same branch underlying getkin/kin-openapi#1181 (still in review). It includes the typed validation errors from #1166 and #1180 plus the DisableTimestamps integration with oasdiff/yaml v0.1.0, which prevents YAML 1.1 implicit-timestamp resolution from mangling date-shaped map keys in real-world specs. Verified with the canonical case: unicourt.com/1.0.0/openapi.yaml (originally cited in invopop/yaml#10 four years ago) now loads cleanly and produces actual schema-validation findings rather than the time.Time map-key explosion. REMOVE BEFORE MERGE: replace this directive with a bump to the released kin version once #1181 lands and a kin tag is cut. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(validate): align Finding shape with locked JSON schema Restructures the validate output to match the design's locked JSON schema (mirrors oasdiff changelog --format json with a few additions). Finding struct now exposes: - source as an object {file, line, column} (was: string source + top-level line/column) - section: which top-level doc section the finding belongs to ("info", "paths", "components", "webhooks", "servers", "security", "tags", or "" for unscoped doc-root findings). Determined per-cluster with a light Field-prefix check. - fingerprint: stable 12-char sha256-prefix derived from "{id}:{operation}:{path}:{args}" — mirrors the existing formatters/changes.go:computeFingerprint scheme so the Pro PR-comment can partition findings into new/pre-existing/fixed via set membership on fingerprint across the base/revision spec pair. - comment, operation, path: present but omitempty (Phase 1 leaves operation/path empty; extracting them from kin Origins requires walking the spec structure, deferred to Phase 2). - All fields have both yaml: and json: tags. Adds --format json support (encoder + enum value); existing yaml and text paths unchanged in behaviour. Tests: - Existing yaml-format + origin-tracking tests updated to the new source-object shape. - New JSON-format test pins the locked shape. - New fingerprint-stability test pins determinism across runs (the property Pro PR-comment partitioning depends on). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(validate): colorize text output via --color flag Adds the locked --color auto|always|never flag to oasdiff validate, matching the convention used by changelog and breaking. Text output now renders the severity in red/purple/cyan (error/warning/info, via Level.StringCond) and the rule ID in yellow, matching the established color scheme. Source path stays uncolored. Auto mode disables color when stdout is piped or redirected; the auto-detect helper lives in checker/piped_output.go and is shared across subcommands so behaviour is consistent. Exports checker.IsColorEnabled as a thin wrapper around the package-internal isColorEnabled. Lets oasdiff packages outside checker (validate, future subcommands) gate their own color logic without duplicating the auto-detect convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(validate): pin findings to the offending field line, not the enclosing object kin's Origin model carries two location handles: - Origin.Key — the start of the enclosing collection (for a license-identifier error, the `license:` key) - Origin.Fields[X] — the specific scalar field X inside that collection (for a license-identifier error, the `identifier:` line) The previous locator returned Origin.Key for every cluster, so a finding in `data/validate/license-identifier-in-3-0.yaml` was pinned to line 5 (`license:`) instead of line 7 (`identifier: MIT`). Reviewers reading the output had to scan from the parent key to find the actual offender. Reworks locationForKinError (renamed from keyOriginForKinError) to prefer Origin.Fields[Field] when the cluster carries a Field, falling back to Origin.Key when the per-field entry is missing (e.g. empty values, which kin doesn't track per-field). New fieldLoc helper centralises the lookup. Per-cluster strategy: - RequiredFieldError, FieldVersionMismatchError, ForbiddenFieldError, SchemaBothFormsExclusive — use Fields[Field] - MutuallyExclusiveFieldsError — use Fields[Field1] (either field pins to the right object) - SchemaValueError — use Fields[ValueKind] (e.g. "example", "default") - SingleEntryContentError — use Fields[Subject] - EitherFieldRequiredError, ExactlyOneFieldError — use Key (cluster fires when NONE of the fields are present, so per-field lookup wouldn't match) - PathParametersError, ServerURLTemplateError — use Key (no Field metadata) - WebhookNilError — no Origin (doc root) Adds a regression test asserting the License-identifier fixture pins to line 7:5 (the identifier line) rather than 5:3 (the license: line). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(validate): extract path + operation from kin's error chain kin wraps validation errors in nested fmt.Errorf("...: %w") layers that carry path/operation context as plain text rather than typed fields: invalid paths: invalid path /thing: invalid operation GET: <inner> These three layers (or any subset) are now stripped off the rendered message and surfaced as Finding.Operation and Finding.Path, matching the changelog command's convention of presenting them as discrete fields. Finding.Text holds the cleaned inner message. Text output gains a second header line "in API <op> <path>" rendered in green when color is enabled, mirroring changelog / breaking. Doc- root findings (info, openapi, license — no path/operation) skip the line entirely. Before: error [const-field-for-3-1-plus] at spec.yaml:18:21 invalid paths: invalid path /thing: invalid operation GET: field const is for OpenAPI >=3.1 After: error [const-field-for-3-1-plus] at spec.yaml:18:21 in API GET /thing field const is for OpenAPI >=3.1 Side benefit: Finding.Operation / Finding.Path are now populated for operation-scoped findings, which feeds into the fingerprint (per formatters/changes.go scheme: sha256(id:op:path:args)) and makes Pro PR-comment partitioning stable when the same finding moves to a new path between base and revision. Component-scoped findings (kin's "invalid components: schema X:" wrapper) are still inline in Text. Section is set correctly via sectionForKinError, but extracting the schema name into a discrete field is a separate enhancement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Revert "feat(validate): extract path + operation from kin's error chain" This reverts commit f9bfe39. The reverted commit used regex to strip kin's "invalid paths: invalid path X: invalid operation Y:" prefixes off the rendered error message. That was always meant to be interim — message-text parsing is exactly the brittleness kin's typed-error work eliminates. kin PR getkin/kin-openapi#1183 adds typed SectionContextError / PathContextError / OperationContextError wrappers that carry the context as structured fields. Once that merges and a kin release is cut, the extraction becomes three errors.As calls with no string parsing. Carrying the regex in the interim isn't worth it: the whole feat/validate-command branch is gated on a kin release anyway, so there's no window where the regex would ship to users. Reverting now keeps the branch honest and avoids a "delete the regex" follow-up commit later. The Finding struct keeps its Operation / Path fields (added in the schema-alignment commit, not here) — they're simply unpopulated until the typed-error extraction lands. omitempty elides them from output in the meantime. Validate findings temporarily render the full wrapped message inline again, as they did before f9bfe39. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(validate): typed path/operation/section extraction from kin's error chain Now that getkin/kin-openapi #1183 is merged, validate lifts the structural scope of each finding out of the message text and into the typed fields: - Operation / Path come from PathValidationError + OperationValidationError via errors.As (replacing the reverted regex approach). - sectionForKinError now prefers SectionValidationError.Section, kin's authoritative section name, falling back to the pre-#1183 cluster heuristics only for doc-root errors that aren't section-wrapped. Adds data/validate/operation-missing-responses.yaml and a test asserting an in-operation error surfaces operation=GET, path=/things, section=paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(validate): strip the redundant context prefix from finding text Section/path/operation scope now lives in the Finding's typed fields, so the "invalid paths: invalid path X: invalid operation Y:" prefix that kin's context wrappers add to Error() was pure duplication in Text. unwrapContext peels those typed wrappers off the front of the chain so Text carries only the underlying leaf message. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * validate: pin kin to PR #1185 branch for multi-error + precise locations Bump kin to oasdiff/kin-openapi @ e14b38a (the open getkin/kin-openapi#1185 branch with multi-error + simple-leaf conversions + plural-examples typing fix). Lets oasdiff exercise the changes end-to-end before the kin PR merges; the replace will drop and the dep will return to upstream master once #1185 lands. Wire-up: - internal/validate.go: pass openapi3.EnableMultiError() to Validate so simple-leaf and container errors aggregate rather than fail-fast. - internal/validate.go (fieldLoc): when the dotted Field name on a cluster error (e.g. "info.version") doesn't match a Fields key, fall back to the suffix after the last dot ("version"). Kin's Origin.Fields is keyed by the leaf name as it appears in the YAML mapping, while cluster errors use a dotted form for rule-ID disambiguation; without this fallback findings under aggregated leaves resolved to the parent object's Origin.Key instead of the precise field. Concrete result on /tmp/multi-problem.yaml (empty info.title, empty info.version, missing operation.responses): three findings at the exact field lines and columns where the value is missing, instead of one finding at the info section start. Tests updated: - missing-required-info.yaml fixture: title now set, only version left empty so single-finding tests stay single. - Test_ValidateCmd_LineColumnFromOrigin: now asserts line 4 col 3 (the version line in the fixture). - Test_ValidateCmd_DocRootFieldHasLineColumn (renamed from ...HasNoLineColumn): asserts doc-root findings carry line:1 col:1 now that T.Origin is populated (kin #1184). - Test_ValidateCmd_TextFormatLocation: location string updated to 4:3. * validate: bump kin pin to 9b85457 (example value origin fix) * validate: drop the kin fork replace; bump to kin master with #1185 kin getkin/kin-openapi#1185 (multi-error aggregation + simple-leaf conversions + plural-examples typing + precise example-value Origin) merged today as e56c2c7. Drop the temporary replace directive that pointed at the oasdiff fork branch and bump to a pseudo-version of upstream master that includes the merge. * validate: dispatch typed clusters from kin #1187 Wires oasdiff validate against the four new typed validation clusters added in getkin/kin-openapi #1187 plus the Origin tracking this branch pushed back to that PR for the duplicate-operation-id and extra-sibling-fields sites. Before this change, the four corresponding error sites all fell through to the kin-validation-error catchall and lost line:column precision. After this change each surfaces under its own stable rule ID with file:line:col when the loader tracks origins. Dispatcher updates in internal/validate.go: * ruleIDForKinError — four new errors.As cases returning the stable rule IDs path-parameter-required, duplicate-operation-id, extra-sibling-fields, schema-type-unsupported. * locationForKinError — four new cases: - PathParameterRequiredError → parameter object's Key. - SchemaTypeError → the offending type field via fieldLoc. - DuplicateOperationIDError → fieldLoc(Origin, "operationId") so the finding pins to the duplicate operationId scalar inside the second operation, not the operation block start. - ExtraSiblingFieldsError → parent object's Key (parser does not track Origin for unknown sibling field names). * sectionForKinError — PathParameterRequiredError and DuplicateOperationIDError both resolve to "paths"; the other two fall through to the existing schema/generic logic which already handles them correctly. * argsForKinError — fingerprint disambiguation for all four (Param / OperationID / joined Fields / Type). Tests + fixtures: * data/validate/{path-parameter-not-required, duplicate-operation-id, extra-sibling-fields, schema-type-unsupported}.yaml — minimal specs that each trigger exactly one of the four clusters. * internal/validate_test.go — one test per fixture asserting the stable rule ID, the kin-side error message, and that Origin resolves to a file:line:col suffix. go.mod pins to the oasdiff fork's branch via pseudo-version. Swap to the upstream tag once getkin/kin-openapi cuts a release that includes #1187. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * validate: dispatch InvalidParameterInError and SchemaPatternRegexError Wires oasdiff validate against the two additional typed clusters added to getkin/kin-openapi #1187 after corpus testing surfaced them as the dominant remaining catchall sites. Corpus result on testdata/APIs-guru-openapi-directory (~4,000 specs): catchall hits dropped from 385 to 10 after this change. The remaining 10 cluster into three untyped sites (two in security_scheme.go, one in ref.go) that are out of #1187's scope. Dispatcher updates in internal/validate.go: * ruleIDForKinError — two new errors.As cases returning the stable rule IDs parameter-in-invalid and schema-pattern-regex-invalid. * locationForKinError — two new cases pinning to the offending field via fieldLoc: parameter.in for InvalidParameterIn, schema.pattern for SchemaPatternRegex. * sectionForKinError — InvalidParameterIn → "paths". The other falls through to the existing schema-deep logic. * argsForKinError — fingerprint disambiguation by the structured field (Value / Pattern). Tests + fixtures: data/validate/{parameter-in-invalid, schema-pattern-regex-invalid}.yaml minimal specs and corresponding tests in internal/validate_test.go asserting typed rule ID, the kin-side error message, and Origin file:line:col resolution. go.mod bumped to the latest oasdiff/kin-openapi fork pseudo-version v0.0.0-20260517110407-8e7311f2c94f (corresponds to #1187 HEAD with the InvalidParameterIn + SchemaPatternRegex additions and the SchemaPatternRegex message revert that preserves Error() string). * validate: dispatch remaining 8 typed clusters from kin #1187; rename catchall to spec-validation-error Adds errors.As cases (rule ID, section, args, location) for the typed clusters in kin #1187 that didn't yet have oasdiff dispatchers: InvalidSecuritySchemeTypeError, InvalidHTTPSchemeError, UnresolvedRefError, APIKeyInInvalidError, PathMustStartWithSlashError, ConflictingPathsError, DuplicateParameterError, InvalidSerializationMethodError. Also extends unwrapContext to strip the 10 new typed context wrappers kin #1187 added (ComponentValidationError, ExternalDocsURLValidationError, HeaderFieldValidationError, MediaTypeExampleValidationError, WebhookValidationError, ParameterFieldValidationError, ParameterExampleValidationError, SecuritySchemeFlowValidationError, OAuthFlowValidationError, OAuthFlowFieldValidationError) so Finding.Text carries just the underlying typed message without the wrapper prefixes. Corpus result on testdata/APIs-guru-openapi-directory (~4,000 specs): zero catchall hits across the entire corpus. Renames the catchall rule ID from 'kin-validation-error' to 'spec-validation-error'. The old name leaked an implementation detail (kin-openapi is an oasdiff dependency users don't know about); the new name is meaningful in the user's domain (their OpenAPI spec failed validation). The constant is renamed in lockstep (kinUnknownID -> unknownValidationID). go.mod bumped to kin fork pseudo-version with the full PR. * validate: dedupe defects reported once per $ref site A defect in a shared components definition (e.g. a bad example in components/schemas/Status) is reported once by kin's components walk AND once per operation that $refs it. From the user's perspective that's one thing to fix; from the Pro PR-comment workflow's perspective it should be one approve/reject decision. dedupePreferringComponents groups findings by their defect identity (Id + Source location + Text — Text carries the args discriminator) and keeps one representative per group. When a group includes a components-rooted finding (Section == "components"), prefer it: the components-rooted version points at the definition site and has empty Operation/Path, giving a stable fingerprint across reference- graph changes (adding a 7th endpoint that $refs the schema doesn't churn the fingerprint). Covers all components/* sub-sections (schemas, parameters, headers, requestBodies, responses, examples, links, callbacks, securitySchemes, and 3.1+ pathItems) because the dedup looks at Section, not the specific bucket. Path-level shared parameters don't need handling here because kin only validates them once at the PathItem level (no per-operation re-validation). Regression test pins a fixture with components/schemas/Status that has a bad example referenced from 3 operations: produces one finding located at the components-schema definition's line:col, not three finding-sized copies pointing at $ref sites. * validate: rewrite Long help text to drop implementation leaks Dropped 'kin-openapi', 'Phase 1', 'errors.As / RequiredFieldError / FieldVersionMismatchError clusters' — internal implementation details users don't care about. Replaced with user-facing content: what the command catches, the output format options, and a clear exit-code table including 102 for load failure. * text formatter: drop trailing-tab artifacts; context-aware indent in validate MultiLineError for ApiChange, SecurityChange, ComponentChange carried a trailing "\t" on the header line (in security/component, a space-tab where "at source" never went). Visible as stray whitespace. Removed. validate's text formatter now mirrors the changelog shape when an operation context exists ("in API METHOD /path" plus a two-tab message body) and stays single-indent when it does not (doc-root findings, components-rooted findings after dedup). Continuation lines (Schema:, Value:, JSON blocks in nested-schema messages) inherit the same indent so multi-line messages stay visually grouped under the operation line. indentContinuation now takes the prefix as a parameter so the first line and continuation lines always agree. Tests updated to match the corrected strings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump kin-openapi pseudo-version Pick up the latest HEAD of the upstream fork branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: track kin-openapi upstream master directly Drop the oasdiff/kin-openapi fork replace. The fork's master is 109 commits behind upstream and all the typed-cluster work (#1166, #1180, #1187) landed upstream directly via Pierre's merges, so the fork is no longer carrying anything we need. Bumps the kin pseudo-version to 8381bfc (the #1187 merge commit, 2026-05-21 14:53 UTC). Phase 1 of the validate subcommand can now errors.As against the full typed-cluster surface (clusters from #1166 + #1180 + #1187 + the 24 new clusters/wrappers from #1187). Switch back to a tagged kin release version before merging this branch to main, once Pierre cuts one containing all three PRs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * formatters: update text-changelog golden after trailing-tab removal Commit f04519e (2026-05-17) dropped the trailing "\t" artifact from ComponentChange.MultiLineError (and the parallel ApiChange and SecurityChange formatters), but didn't update the only golden test that exercises this path. The branch has been failing TestTextFormatter_RenderChangelog ever since. Update the expected string to match the now-clean output — `error\t[change_id]\n` instead of `error\t[change_id] \t\n`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * build: pin kin-openapi to released v0.139.0 #894 was wired against a master pseudo-version while the typed-validation PRs were unreleased. Pierre tagged v0.139.0 (2026-05-23), which includes them, so pin the real release. Build + full test suite green. * docs: document the validate command Add docs/VALIDATE.md (usage, output formats, flags, exit codes, rule IDs) and list `validate` in the README Commands section (eight commands now). Mirrors the existing per-command doc pattern. * validate: document git-ref as a supported input; tidy help and comment Note git refs (e.g. main:openapi.yaml) alongside file/URL/stdin in the validate Long help and docs/VALIDATE.md, since the loader supports them. Also tidies the Long help and the catchall-ID comment. * validate: render output through the formatters package; add githubactions Move validate's output off the inline yaml/json/text marshaling and onto the shared formatters, the same path changelog/breaking/flatten use: a new Formatter.RenderValidate method renders a plain formatters.Finding, looked up by format via formatters.Lookup. text, yaml, and json implement it; the command's -f options come from SupportedFormatsByContentType(OutputValidate) so the advertised and implemented format sets stay in sync. Add githubactions support so the upcoming oasdiff-action validate wrapper can emit one CI annotation per finding (anchored to file/line/column) inline on the pull request, plus error_count/warning_count/info_count step outputs. Validate and changelog now share formatters.ComputeFingerprint so a downstream tool can match findings across spec versions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * validate: classify finding severities and add --fail-on Findings were all reported as errors. Classify them by impact via severityForKinError (errors.As dispatch, default ERR so any unrecognised or newly-typed kin cluster stays an error): WARN version-portability (3.1 field in an older doc), $ref siblings that are silently ignored, conflicting paths, duplicate parameters, and a default value that violates its schema INFO an example that violates its schema (the contract itself is valid) duplicate-operation-id stays ERR: it violates the spec's uniqueness MUST and breaks code generators. Add -o, --fail-on (default ERR), mirroring changelog: the command exits 1 only when a finding is at or above the threshold, so warnings and info print but don't fail CI by default. Severity flows through to githubactions annotations (::warning / ::notice / ::error) and the text summary counts. The mapping is a hardcoded dispatch; a future per-rule override (like changelog's --severity-levels) can sit on top of it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: escape GitHub Actions annotation messages and properties The githubactions formatter only escaped newlines, so a message containing '%' (decoded by GitHub) or a property value containing ':' or ',' (e.g. a git-ref source path like main:openapi.yaml) would produce a malformed annotation. Add escapeData (% / CR / LF) and escapeProperty (+ ':' / ',') per GitHub's workflow-command rules, escaping '%' first so the introduced %0A/%0D sequences are not double-escaped. Applied to both the changelog (RenderChangelog) and validate (RenderValidate) output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(validate): add Feedback section Invite users to report issues with a [validate] title prefix, mirroring the OpenAPI 3.1 page. Pairs with the catchall spec-validation-error rule ID, which we want users to report. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * validate: reject --color with a non-text format; clarify docs wording --color only affects the text output (yaml/json/githubactions ignore it), so pairing it with a non-text format now errors instead of being silently dropped, matching the changelog command. Also soften the VALIDATE.md intro: findings are spec-defined violations classified by severity, not "hard violations only", now that some are warnings/info. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: cover validate formatter rendering and more kin clusters Codecov patch coverage was low because CI runs `go test ./...` without -coverpkg, so the new formatters code (RenderValidate, Findings helpers) got no credit from the internal tests that exercise it, and validate.go's typed dispatch had several kin clusters with no fixture. - formatters-package tests for RenderValidate (text/yaml/json), the not-implemented fallback, and the Findings helpers (GetLevelCount, HasLevelOrHigher, indentContinuation). - fixtures + tests for previously-untested clusters: conflicting-paths, duplicate-parameter, path-must-start-with-slash, and the three invalid security-scheme clusters; plus a multi-cluster test over openapi-test3 (EitherFieldRequired / ExactlyOneField via joinFieldsForRuleID, OAuth-flow required field). - a checker test for the exported IsColorEnabled. No production code change. validate.go coverage 82.8% -> 88.6%; the new formatters validate rendering is now fully covered in-package. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Jun 3, 2026
fenollp
pushed a commit
to oasdiff/kin-openapi
that referenced
this pull request
Jun 3, 2026
Follow-up to getkin#1188. Adds scope to two previously unwrapped Validate() sites, in the same vein as the context wrappers from getkin#1183: - oneOf/anyOf/allOf element failures in Schema.validate now carry a SchemaCombinatorElementValidationError naming the combinator. - Per-tag failures in Tags.Validate now carry a TagValidationError naming the tag. Both are category-4 context wrappers: they add scope via Unwrap and do not themselves report a failure, so errors.As still walks through to the inner cluster/leaf. Unlike getkin#1187 these add a scope prefix to Error(), so they are not byte-for-byte; the apis-guru golden fixtures are updated to match. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Today,
Validate()propagates context (which section, path, operation a leaf error lives in) by wrapping withfmt.Errorf:Callers that want to render the context separately have to parse the rendered text with regex. That's the brittleness the typed-validation-error work in #1166 and #1180 eliminated for leaf errors. The same reasoning applies to the wrapper layers.
What
Three typed wrappers cover all 14 wrap sites in
Validate():Each implements
Error()andUnwrap(). TheError()strings are byte-identical to thefmt.Errorfoutput they replace, so existing consumers parsing rendered text continue to work unchanged. Typed extraction is purely additive.The naming places these positional wrappers in the existing
ValidationErrorfamily (the baseValidationError, the cluster types, the per-site leaves), and they live inopenapi3/validation_error_context.goalongside the rest of that family.Wrap sites converted
SectionValidationError(12 sites: 9 in openapi3.go plus 3external docswrappers in operation.go, tag.go, schema.go):PathValidationError(1 site):paths.go:113OperationValidationError(1 site):path_item.go:212Downstream usage
Each layer is extracted independently; a typical schema-deep validation error has all three.
Backward compatibility
100% preserved at the
Error()string layer. All existing tests pass unchanged across openapi2, openapi3, openapi3conv, openapi3filter, openapi3gen, and the routers; the byte-identical rendering is confirmed by the suite.Tests
openapi3/validation_error_context_test.gocovers:Error()format byte-stability for each typed wrapper.Unwrapchain walking viaerrors.Is.errors.Asextraction from a realistic three-layer chain (section + path + operation).Files
openapi3/validation_error_context.go(new): the three types plus Error / Unwrap.openapi3/validation_error_context_test.go(new): 8 unit tests.openapi3/openapi3.go,openapi3/operation.go,openapi3/paths.go,openapi3/path_item.go,openapi3/schema.go,openapi3/tag.go: 14 wrap-site conversions; unusedfmtimports removed where only the wrap-site used them..github/docs/openapi3.txt: regenerated viadocs.sh.Reviewable as two commits: the typed wrappers, then the review-requested rename to the
*ValidationErrornames.