Skip to content

Conversation

@mjbonifacio
Copy link

@mjbonifacio mjbonifacio commented Nov 15, 2025

Description

TL;DR

Review these 4 PRs in order in my fork for smaller chunks, but add discussion here so that they can live throughout the project's history:

  1. Pre-validation Error Cleanup
  2. JSON Pointer Helpers
  3. Parameter Schema Context
  4. Unify Context and Centralize

Overview

Continues on the work in #188 to fully improve on validation error reporting.

This PR aims to make validation error reporting fully consistent so that library users can fully answer the following questions in 4xx API responses to client requests:

  • Where did validation fail?
  • What failed (i.e. field/param)?
  • What is the path to where validation failed (if applicable)?
  • a human readable error message (already exists)

All locations are noted from a keyword or constraint location in an OpenAPI schema.

Major Changes

  1. Inconsistent SchemaValidationFailure usage: Pre-validation errors (JSON decode failures, schema compilation errors) included SchemaValidationFailure objects, while actual schema violations (on specific parameters etc.) sometimes lacked them.

  2. Incomplete location information: KeywordLocation was often empty or contained relative paths instead of absolute RFC 6901 JSON Pointer paths from the OpenAPI document root, making it hard to identify which part of the OpenAPI spec was violated.

  3. Inconsistent field population: Critical fields like ReferenceSchema, Context, and KeywordLocation were missing or inconsistent across different validation types.

  4. Redundant/unused fields: AbsoluteKeywordLocation was always empty due to schema inlining, and the Location field grew to be used in different ways over the life of the repo.

Solution

This PR establishes clear patterns for error reporting:

1. Clear Error Type Separation

  • Pre-validation errors (schema compilation, JSON decode, missing schemas) → NO SchemaValidationFailure
  • Schema constraint violations (type mismatches, enum violations, range errors) → ALWAYS include SchemaValidationFailure with complete context

2. Complete OpenAPI-Aware Location Information

  • KeywordLocation contains full JSON Pointer path from OpenAPI document root to the exact schema keyword
  • Format: /paths/{escaped-path}/{operation}/parameters/{name}/schema/{keyword}
  • Example: /paths/~1users~1{id}/get/parameters/id/schema/minimum
  • RFC 6901 compliant escaping (~~0, /~1)

3. Consistent Field Population

  • ReferenceSchema: Rendered schema as JSON string (for human consumption)
  • Context: Raw *base.Schema object (for programmatic access)
  • KeywordLocation: Full OpenAPI path to failed schema keyword
  • FieldName, FieldPath, InstancePath: Consistently populated across all validation types

4. Cleanup

  • Removed Location field: Ambiguous and superseded by KeywordLocation + FieldPath
  • Removed AbsoluteKeywordLocation: Never populated due to schema inlining

Benefits

API consumers can now:

  1. ✅ Distinguish between pre-validation failures and actual schema violations
  2. ✅ Programmatically locate the exact schema keyword violated in their OpenAPI spec
  3. ✅ Access both human-readable (ReferenceSchema) and programmatic (Context) schema representations
  4. ✅ Build better error messages and debugging tools
  5. ✅ Implement automated spec correction or validation guidance

Breaking Changes

⚠️ Removed Field: Location

The deprecated Location field has been completely removed from SchemaValidationFailure.

Migration Guide:

  • If you were using Location for the schema location → use KeywordLocation
  • If you were using Location for the instance location → use FieldPath

Example:

// Before
if err.SchemaValidationErrors[0].Location == "/properties/email/format" {
    // schema location usage
}

// After  
if err.SchemaValidationErrors[0].KeywordLocation == "/properties/email/format" {
    // schema location usage - more explicit
}

// OR
if err.SchemaValidationErrors[0].FieldPath == "$.email" {
    // instance location usage - use FieldPath instead
}

⚠️ Removed Field: AbsoluteKeywordLocation

This field was always empty because RenderInline() resolves all $ref references before validation.

Validation Types Covered

  • ✅ Request body validation
  • ✅ Response body validation
  • ✅ Response header validation
  • ✅ Path parameter validation (8 error functions)
  • ✅ Query parameter validation (14 error functions)
  • ✅ Header parameter validation (7 error functions)
  • ✅ Cookie parameter validation (6 error functions)
  • ✅ Schema/document validation

Detailed Breakdown

For easier review, this changeset has been broken down into 4 stacked PRs in my fork. Each PR focuses on a specific aspect:

  1. PR 1: Pre-validation Error Cleanup

    • Distinguishes pre-validation errors from schema constraint violations
    • Removes AbsoluteKeywordLocation field
    • 6 commits covering schema, document, parameter, request, and response validation
  2. PR 2: JSON Pointer Helpers

    • Introduces centralized RFC 6901 JSON Pointer construction helpers
    • Demonstrates pattern with response header validation
    • 2 commits
  3. PR 3: Parameter Schema Context

    • Adds full OpenAPI context to all parameter errors (path, query, header, cookie)
    • 4 commits (one per parameter type)
  4. PR 4: Unify Context and Centralize

    • Removes deprecated Location field
    • Consolidates all JSON Pointer construction to use helpers (72+ locations)
    • Unifies Context field to use *base.Schema consistently
    • 2 commits

Each PR includes:

  • Detailed rationale for changes
  • Tables showing before/after for each validation type
  • Examples of the improvements

Testing

  • ✅ All existing tests updated to reflect new error structure
  • ✅ Schema compilation error tests correctly expect NO SchemaValidationFailure
  • ✅ Schema constraint violation tests correctly expect complete SchemaValidationFailure
  • KeywordLocation assertions updated to expect full OpenAPI paths
  • ✅ No linter errors

Scope of Changes (14 commits total)

The changeset touches error reporting across the entire library:

  • errors/validation_error.go - Struct definitions
  • errors/parameter_errors.go - 35 parameter error functions updated
  • helpers/json_pointer.go - New centralized JSON Pointer helpers
  • parameters/*.go - All parameter validation (path, query, header, cookie)
  • requests/validate_request.go - Request body validation
  • responses/*.go - Response body and header validation
  • schema_validation/*.go - Schema and document validation
  • All corresponding test files

Commit Phases

Phase 1: Pre-validation Error Cleanup (6 commits)

  1. Remove SchemaValidationFailure from schema pre-validation errors
  2. Remove SchemaValidationFailure from document compilation errors
  3. Parameters: add KeywordLocation when formatting JSON schema errors, remove SchemaValidationFailure when compilation fails
  4. Remove AbsoluteKeywordLocation field - never populated due to schema inlining
  5. Request body validation: remove SchemaValidationFailure from pre-validation errors
  6. Response body validation: remove SchemaValidationFailure from pre-validation errors

Phase 2: Centralized JSON Pointer Helpers (2 commits)
7. Add centralized JSON Pointer construction helpers
8. Response headers: add SchemaValidationFailure with full OpenAPI path (using helpers)

Phase 3: Parameter Schema Context (4 commits)
9. Path parameters: render schema once, pass to error functions
10. Query parameters: add full OpenAPI context + missing required param fix
11. Header parameters: add full OpenAPI context
12. Cookie parameters: add full OpenAPI context

Phase 4: Unify and Centralize (2 commits)
13. Remove deprecated Location field from SchemaValidationFailure (+ Context field unification)
14. Refactor: use centralized JSON Pointer helpers across codebase (72+ locations)

Review Notes

📋 For easier review, I recommend reviewing the stacked PRs in my fork sequentially:

  1. Start with PR Path not found error handling #4 to understand the conceptual changes
  2. Review PR v0.0.2 #5 to see the helper function approach
  3. Review PR Added support for base paths defined by server definitions. #6 to see the pattern applied to parameters
  4. Review PR A number of fixes after implementing in vacuum #7 to see the final cleanup and consolidation

Each PR builds on the previous one and can be reviewed independently for logic and correctness.

Requested blessing

  1. AbsoluteKeywordLocation removal: This field was always empty due to RenderInline() resolving refs. Is this observation correct, or are there use cases where this field should be populated?

Michael Bonifacio added 21 commits October 8, 2025 18:10
…emove SchemaValidationFailure from when json schema compilation fails
…inlining

AbsoluteKeywordLocation was never populated because libopenapi's RenderInline()
method resolves and inlines all $ref references before schemas reach the JSON
Schema validator. Since the validator never encounters $ref pointers, this field
remained empty in all cases and served no purpose.

Removed from:
- SchemaValidationFailure struct definition
- All instantiation sites (schema_validation, parameters, requests)
- Improved KeywordLocation documentation with JSON Pointer reference
…dation errors, add KeywordLocation to schema violations

Pre-validation errors (compilation, JSON decode, empty body) now correctly
omit SchemaValidationFailure objects, as they don't represent actual schema
constraint violations.

Actual schema violations now include KeywordLocation (JSON Pointer path to
the failing keyword) for better error context.

Also fixed Location field to use er.InstanceLocation for consistency with
schema_validation/validate_schema.go.
…idation errors, add KeywordLocation to schema violations

Pre-validation errors (compilation, missing response, IO read, JSON decode) now
correctly omit SchemaValidationFailure objects, as they don't represent actual
schema constraint violations.

Actual schema violations now include KeywordLocation (JSON Pointer path to the
failing keyword) for better error context.

Also fixed Location field to use er.InstanceLocation for consistency with
request validation and schema validation.
Creates helper functions in helpers package for constructing RFC 6901-compliant
JSON Pointer paths to OpenAPI specification locations.

New functions:
- EscapeJSONPointerSegment: Escapes ~  and / characters per RFC 6901
- ConstructParameterJSONPointer: Builds paths for parameter schemas
- ConstructResponseHeaderJSONPointer: Builds paths for response headers

This eliminates duplication of the escaping logic across 72+ locations in
the codebase and provides a single source of truth for JSON Pointer construction.

Pattern:
  Before: Manual escaping in each error function (3 lines of code each)
  After: Single function call with semantic naming

Next step: Refactor all existing inline JSON Pointer construction to use
these helpers.
When a response header is marked as required in the OpenAPI schema and is
missing from the response, this is a schema constraint violation. Added
SchemaValidationFailure with full OpenAPI path context for KeywordLocation.

Updated ValidateResponseHeaders signature to accept pathTemplate and statusCode
to construct full JSON Pointer paths like:
  /paths/~1health/get/responses/200/headers/chicken-nuggets/required

This makes header validation consistent with request/response body validation,
which also uses full OpenAPI document paths for KeywordLocation.

Note: Considered using relative paths (/header-name/required) but chose full
paths for consistency with body validation patterns. Both approaches have
tradeoffs documented in PR description.
…r ReferenceSchema

- Render parameter schema once in path_parameters.go instead of in each error function
- Pass renderedSchema to all 8 path parameter error functions (bool, enum, integer, number, array variants)
- Update Context field to use raw base.Schema (programmatic access)
- Update ReferenceSchema field to use rendered JSON string (API consumers)
- Use full OpenAPI JSON Pointer paths for KeywordLocation (e.g., /paths/~1users~1{id}/parameters/id/schema/type)
- Serialize full schema objects for ReferenceSchema instead of just type strings
- Update resolveNumber and resolveInteger helpers to accept and pass renderedSchema

Note: This approach (Context=raw schema, ReferenceSchema=rendered string) will be reviewed later for consistency across the codebase
Changes:
- Remove 'Build full OpenAPI path for KeywordLocation' comments
- Remove inline comments from previous commits
- Add renderedSchema parameter to QueryParameterMissing
- Set ReferenceSchema field in QueryParameterMissing SchemaValidationFailure
- Render schema for missing required parameters before creating error
- Update tests to pass renderedSchema parameter
Adds full OpenAPI context to all 7 header parameter error functions:
- HeaderParameterMissing
- HeaderParameterCannotBeDecoded (now includes SchemaValidationFailure)
- IncorrectHeaderParamEnum
- InvalidHeaderParamInteger
- InvalidHeaderParamNumber
- IncorrectHeaderParamBool
- IncorrectHeaderParamArrayBoolean
- IncorrectHeaderParamArrayNumber

All errors now include:
- KeywordLocation: Full JSON Pointer from OpenAPI root (e.g., /paths/{path}/{method}/parameters/{name}/schema/type)
- ReferenceSchema: Rendered schema as JSON string
- Context: Raw base.Schema object
- Proper FieldName and InstancePath

Updated ValidateHeaderArray to accept and pass path/operation/schema context.
Updated all test cases to pass new required parameters.
Adds full OpenAPI context to all 6 cookie parameter error functions:
- InvalidCookieParamInteger
- InvalidCookieParamNumber
- IncorrectCookieParamBool
- IncorrectCookieParamEnum
- IncorrectCookieParamArrayBoolean
- IncorrectCookieParamArrayNumber

All errors now include:
- KeywordLocation: Full JSON Pointer from OpenAPI root (e.g., /paths/{path}/{method}/parameters/{name}/schema/type)
- ReferenceSchema: Rendered schema as JSON string
- Context: Raw base.Schema object
- Proper FieldName and InstancePath

Updated ValidateCookieArray to accept and pass path/operation/schema context.
Updated all test cases to pass new required parameters.

This completes consistent SchemaValidationFailure population across all parameter types (path, query, header, cookie).
Removed the deprecated Location field entirely from SchemaValidationFailure struct
and updated all code and tests to use KeywordLocation instead.

Changes:
- Removed Location field from SchemaValidationFailure struct
- Updated Error() method to use FieldPath instead of Location
- Removed .Location assignment in schema_validation/validate_document.go
- Updated all test assertions to use KeywordLocation instead of Location
- Updated tests to reflect that schema compilation errors do not have
  SchemaValidationFailure objects (they were removed in earlier commits)

This completes the transition to the new error reporting model where:
- KeywordLocation: Full JSON Pointer to schema keyword (for all schema violations)
- FieldPath: JSONPath to the failing instance (for body validation)
- InstancePath: Structured path segments to failing instance
- Location field: Removed entirely
Replaces all manual JSON Pointer construction with calls to the new helper
functions, eliminating 72+ instances of duplicated escaping logic.

Changes:
- errors/parameter_errors.go: Replaced all manual escaping with
  helpers.ConstructParameterJSONPointer() calls
  - All 35 parameter error functions now use helper
  - Handles type, enum, items/type, items/enum, maxItems, minItems, uniqueItems
- responses/validate_headers.go: Replaced manual escaping with
  helpers.ConstructResponseHeaderJSONPointer()
- errors/validation_error_test.go: Updated tests to use FieldPath instead of
  deprecated Location field

Benefits:
- Single source of truth for JSON Pointer construction
- Reduced code duplication (3 lines → 1 line per usage)
- More maintainable and less error-prone
- Semantic function names make intent clearer

Each function call reduced from:
  escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
  escapedPath = strings.ReplaceAll(escapedPath, "/", "~1")
  escapedPath = strings.TrimPrefix(escapedPath, "~1")
  keywordLocation := fmt.Sprintf("/paths/%s/%s/...", escapedPath, ...)

To:
  keywordLocation := helpers.ConstructParameterJSONPointer(path, method, param, keyword)
- Added missing files from upstream: property_locator.go, xml_validator.go, etc.
- Fixed Location field references (removed in our changes)
- Fixed enrichSchemaValidationFailure signature to remove location parameter
- Added OriginalError field back for backwards compatibility
- Fixed XML validation API compatibility with goxml2json
- Added missing dependency: github.com/basgys/goxml2json

This commit prepares for merging origin/main into our branch.
Resolved conflicts by keeping our versions with Location field removed and XML API fixed.
@codecov
Copy link

codecov bot commented Nov 15, 2025

Codecov Report

❌ Patch coverage is 99.05363% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.50%. Comparing base (bde0446) to head (d750797).

Files with missing lines Patch % Lines
parameters/query_parameters.go 87.50% 3 Missing and 1 partial ⚠️
errors/validation_error.go 66.66% 1 Missing ⚠️
parameters/path_parameters.go 97.22% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #200      +/-   ##
==========================================
+ Coverage   97.41%   97.50%   +0.09%     
==========================================
  Files          45       46       +1     
  Lines        3987     4332     +345     
==========================================
+ Hits         3884     4224     +340     
- Misses        103      107       +4     
- Partials        0        1       +1     
Flag Coverage Δ
unittests 97.50% <99.05%> (+0.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- Update goxml2json to v1.1.1-0.20231018121955-e66ee54ceaad (matches upstream)
- Restore WithTypeConverter call in validate_xml.go for numeric type conversion
- Update property_locator_test.go to use OriginalJsonSchemaError (our convention)
@mjbonifacio mjbonifacio force-pushed the unify-context-and-centralize branch from 7a314ad to fabe24d Compare November 15, 2025 22:16
Michael Bonifacio added 2 commits November 15, 2025 14:40
- Remove extra blank lines (gofumpt)
- Remove redundant nil checks before len() (staticcheck)
@mjbonifacio mjbonifacio marked this pull request as ready for review November 15, 2025 22:53
Michael Bonifacio added 2 commits November 15, 2025 15:06
- Keep only OriginalJsonSchemaError (our convention)
- OriginalError was mistakenly re-added during upstream merge
- Updated path parameters to use ValidateSingleParameterSchemaWithPath
- Updated header parameters to use ValidateSingleParameterSchemaWithPath
- Now all parameter types (query, path, header, cookie) get full OpenAPI context
- Ensures KeywordLocation is consistent across all parameter validation
@mjbonifacio mjbonifacio force-pushed the unify-context-and-centralize branch from 831984f to 6013125 Compare November 15, 2025 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant