Limits and approximations

Schematrix generates data shapes, not validators. This page states what the output does not do, and where it accepts more than the schema requires. Shape compilation widens unsupported constraints. Format conversion can reject schema-valid data, and enum conversion rejects unknown members as described below.

Compilation work limit

Compilation raises Schematrix::CompilationLimitError when it exceeds 100,000 work units. All roots and referenced documents share one budget. Schema visits, reference expansion, model copying, and composition merges consume units, including repeated visits to the same inline target. This stops compact shared-reference graphs from expanding without a bound. The pipeline finishes compilation before writing generated files.

For large trusted schemas, pass a larger positive integer as max_compilation_work: to Schematrix.generate or Schematrix::Compiler.new. Units are internal accounting, not a count of input schemas or bytes. This limit does not bound document parsing, recursion depth, or rendering.

No validation

Constraint keywords never reach the output. Generated classes check no pattern, minimum, maxLength, multipleOf, or similar. from_h accepts a hash that violates the schema and builds an instance from it. Validate data separately when you need to.

Format conversion

format is the one annotation that reaches the output. For the scalar formats the scalar type table maps (date, date-time, and whatever the project adds through scalar_types:), every slot the type algebra renders from a mapped format converts on load and dump: plain scalar properties, array elements and tuple positions, map and catch-all values, and branches of a union. The conversion can fail where the schema does not: a string the coercer cannot parse raises from from_h (Date::Error, ArgumentError, or whatever the coercer raises). The spec reads format as an annotation, so a document with an unparseable date string is valid; loading it into a generated class is not. Validate documents before loading when that distinction matters.

One shape cannot convert: a union whose branches would convert the same JSON type through different coercers, such as anyOf of date and date-time. A loaded value carries no mark of the branch it came from, so rendering either branch’s type alone would name loaded values the other branch never produces. Generation fails with CoercionConflictError instead. Give the branches distinct JSON types, or map them to one coercer.

Enums

Only enums with more than one distinct non-null value, all strings or all integers, qualify. Mixed, single-value, boolean, and float enums, and const, stay scalar. Null is a nullable slot term, not a member. In :unions mode, Sorbet uses scalar types because it cannot spell string or integer literal unions; RBS uses literal unions.

In :classes mode, standalone enum conversion raises KeyError for an unknown raw value. Values of other Ruby types still pass through. This is a conversion contract, not a general schema validator. A coercing format entry converts document values before enum membership is checked. Members store those converted values. On dump, the same coercer converts the member’s serialized value back to a document value. Format parsing errors propagate; a successfully parsed value outside the enum raises KeyError.

When enum union branches share a JSON type with another enum or an uncoerced branch, the helper tries conversion and rescues KeyError. A trailing :pass keeps values that no enum accepts. This can accept values outside every schema branch. The first successful conversion wins; dumped strings are not fed through later enum conversions. An enum and a builtin coercer claiming the same JSON type, such as a string enum unioned with format: "date", raise CoercionConflictError during generation.

Distinct values that fold to the same member constant raise OutputCollisionError. The Sorbet Ruby and RBI enum generators also raise it when a folded member equals the enclosing class leaf, such as Affiliation::Affiliation. Plain Ruby and RBS permit that name. See Enum serialization spike for the Sorbet evidence. The pipeline checks these names before writing any files.

Class usage without an enum writer only warns. The run succeeds but the application must supply the constants. DSL and partial generators never write enum files.

Ignored keywords

These keywords are skipped, and each skip is logged once per document at debug level:

Skipping them keeps shapes open: extra keys land in the catch-all where one exists.

format, examples, default, title, description, and deprecated are annotations or constraints with no output effect, so their absence from the list above is expected: default, description, and mapped format values do reach the output, the rest do not.

Accepted approximations

Each item below widens acceptance. None of them narrows it.

Signatures are shapes, not proofs

The Sorbet and RBS output types every slot, including slots the schema left unconstrained: those render as T.anything or top. Reads from such a slot need narrowing before the type checker accepts them. That is the honest statement about data whose shape the schema does not constrain.

Builders

The instance DSL builders (The instance DSL) inherit every approximation above and add their own: