Skip to content

feat: enable cross-type constraint interpretation by default#1580

Merged
ernado merged 5 commits intoogen-go:mainfrom
lanej:feature/lenient-validation-mode
Nov 25, 2025
Merged

feat: enable cross-type constraint interpretation by default#1580
ernado merged 5 commits intoogen-go:mainfrom
lanej:feature/lenient-validation-mode

Conversation

@lanej
Copy link
Contributor

@lanej lanej commented Nov 23, 2025

Summary

Enables cross-type constraint interpretation by default, allowing ogen to generate code from real-world vendor specs that have minor OpenAPI violations (pattern on numbers, min/max on strings).

Fixes #5

Changes

  • Parser accepts cross-type constraints instead of rejecting them
  • Validation code generation interprets constraints correctly:
    • Pattern on numbers: validates string representation against regex
    • Min/max on strings: parses as number and validates bounds
  • Added --strict flag to disable interpretation (restore old behavior)
  • Config option: allow_cross_type_constraints (default: true)

API Example

Before: Generation fails

$ ogen --target ./client spec.yaml
openapi.yaml:6124:24 -> maximum: unexpected field for type "string"

After: Interprets constraints and generates validation

$ ogen --target ./client spec.yaml  # works!
$ ogen --strict --target ./client spec.yaml  # rejects if needed

Generated validator enforces numeric bounds on strings:

// Validates that string parses as number within bounds
if v.MaxNumericSet && val > v.MaxNumeric {
    return errors.Errorf("value %f greater than maximum %f", val, v.MaxNumeric)
}

Why Interpretation > Ignoring

Originally considered silently ignoring invalid constraints. This implementation interprets them instead, preserving validation intent:

  • type: string, maximum: 1000 → validates string parses as number ≤ 1000
  • type: number, pattern: '^\d+(\.\d{1,2})?$' → validates at most 2 decimal places

Spec authors chose correct types but used wrong constraint keywords. Interpretation is objectively better than ignoring.

Why Default ON

Real-world vendor specs (Royal Mail, Hermes, others) have these constraints. Making interpretation the default means:

  • ogen works with vendor APIs out of the box
  • No flag required for the common case
  • --strict available for pure JSON Schema compliance

Testing

  • 197 lines of comprehensive test coverage
  • Tests both default (interpretation) and strict (reject) modes
  • Tests match exact issue examples
  • All existing tests pass unchanged

Implementation

Cross-type validation in 3 layers:

  1. Parser: Allows constraints through (when enabled)
  2. IR: Interprets constraints into validation logic
  3. Runtime: Executes validation at runtime

Clean separation of concerns, minimal performance impact, full backward compatibility.

@lanej lanej marked this pull request as draft November 23, 2025 20:52
@lanej lanej force-pushed the feature/lenient-validation-mode branch 3 times, most recently from 540df33 to b37b7d0 Compare November 23, 2025 21:41
@lanej lanej changed the title Add lenient validation mode for schema constraint violations feat: enable cross-type constraint interpretation by default Nov 23, 2025
@lanej lanej marked this pull request as ready for review November 23, 2025 21:45
Adds support for interpreting schema constraints that cross type boundaries,
which is common in real-world vendor OpenAPI specs.

When enabled (default), the parser and code generator correctly interpret:
- pattern constraints on numeric types (validates string representation)
- minimum/maximum constraints on string types (parses as number and validates)

This feature is ON by default because these constraints represent the spec
author's clear intent - they chose the correct types but used constraints
from a different type. Rather than rejecting these specs, we interpret the
constraints correctly.

Key Changes:
- Added AllowCrossTypeConstraints option (default: true)
- Added --strict CLI flag to disable interpretation and reject cross-type constraints
- Extended validation logic in validate package to support cross-type constraints
- Added comprehensive test coverage for both modes

Implementation Details:
- Pattern on numbers: Formats number as string, validates against regex
- Min/max on strings: Parses string as float64, validates numeric bounds
- Uses pointer type (*bool) to distinguish unset from explicitly false
- Full backward compatibility: existing behavior unchanged unless --strict used

Fixes #5
…ction

The RegexStrings() function was only collecting String.Regex and MapPattern,
but the lenient-validation-mode feature added Pattern support to Int and Float
validators. This caused regexMap to be undefined when generating code with
pattern constraints on numeric types.

This fix adds typ.Validators.Int.Pattern and typ.Validators.Float.Pattern
to the list of regex patterns collected, ensuring regexMap is properly
populated for all pattern validators.

Fixes the error:
  ./oas_validators_gen.go:2673:21: cannot use regexMap[pattern] (map index
  expression of type *regexp.Regexp) as ogenregex.Regexp value in struct
  literal: *regexp.Regexp does not implement ogenregex.Regexp
Add comprehensive tests to ensure RegexStrings() collects regex patterns from
all validator types (String.Regex, Int.Pattern, Float.Pattern, MapPattern).

These tests prevent regression of the bug where Int.Pattern and Float.Pattern
were not being collected, causing undefined regexMap errors when generating
code with pattern constraints on numeric types.

Tests cover:
- Collection of all pattern types
- Handling of nil patterns
- Deduplication of duplicate patterns
Generated code updated to include cross-type constraint validation
logic for patterns on numbers and min/max on strings.
@lanej lanej force-pushed the feature/lenient-validation-mode branch from 69ba432 to dcbb634 Compare November 24, 2025 19:43
@ernado ernado merged commit 4b3acf1 into ogen-go:main Nov 25, 2025
15 checks passed
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.

2 participants