You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Support matrix: widen the refused JSON Schema keywords (patternProperties, if/then/else, prefixItems, contains, dependent*, unevaluated*) #157
Twelve JSON Schema assertion keywords are refused at compile time as "outside the v0.1 support matrix" (SchemaCompiler::UNSUPPORTED_ASSERTION_KEYWORDS):
The backend (opis/json-schema 2.x) evaluates every one of them under draft 2020-12; the refusal is this package's own, because two things of ours do not reach them yet:
the directional readOnly/writeOnly rewrite (SchemaCheck::effective()) recurses through properties, items, additionalProperties and the composition keywords only — a schema under patternProperties or then would be judged without the rewrite, i.e. wrongly in one direction;
rasuvaeff/property-testing-openapi has no generator for them, and the two packages move together.
Fail-closed was the right call for 0.1; a document that uses patternProperties for a map of ^x- keys, or if/then/else for a conditional required, is ordinary OpenAPI today and is currently unloadable here.
Proposal
Widen the matrix one keyword (group) at a time, each step being: the rewrite reaches the keyword's subschemas in both directions (with a property test that effective() stays a fixed point and dialect-independent) → the keyword leaves the refusal list → a generator lands in property-testing-openapi → the recorded corpus (bin/record-openapi-corpus) is re-recorded.
Suggested order, by how often the keyword appears in real documents:
patternProperties (+ propertyNames)
if / then / else
prefixItems / contains (arrays as tuples)
dependentRequired / dependentSchemas
unevaluatedProperties / unevaluatedItems — last: they read annotations from sibling applicators, so the rewrite has to be provably annotation-neutral; keep refusing until that is shown.
contentSchema stays refused: it applies to a string the validator would have to decode by contentMediaType first, and non-JSON payloads are out of scope.
Context
Comparison with duyler/openapi (2026-09-20), which evaluates all of these at runtime; the gap is the one substantive feature difference against this package's fail-closed design. Compatibility: each step turns an InvalidContract into a loadable document, so it is additive — minor releases.
2. if / then / else — open: openapi-contract#172, property-testing-openapi#141.
On the contract side each of the three becomes the single subschema it is: compiled instead of refused, walked by the load-time shape check, parsed eagerly by assertParsed(), evaluated by the backend under draft 2020-12 at any depth and nested inside one another. A boolean if/then/else stays refused under OAS 3.0, which predates boolean schemas. The directional rewrite reaches all three branches — the property test was checked against the three mutants it exists for, dropping if, then or else from the walk each fails it.
On the generator side each reachable branch is built constructively: then as a conjunction of the base schema, the condition and the branch, so the condition holds by construction; else from the base schema and else, filtered by the condition the contract's own SchemaCheck judges. The zoo gained shipments.create and the corpus is re-recorded — --check against the previous fixture reported only the new cases, no moved verdicts.
Remaining, in the proposed order: prefixItems/contains, then dependentRequired/dependentSchemas, then unevaluated* last. contentSchema stays refused.
Step 2 is merged: openapi-contract#172 and property-testing-openapi#141.
Contract side, per keyword: each of if, then, else becomes the single subschema it is — compiled, walked by the load-time shape check, parsed eagerly, evaluated at any depth and nested inside one another, with a boolean form refused under OAS 3.0 as items and not already are. The directional rewrite reaches all three branches; the property test fails if any one of them is dropped from the walk (checked against all three mutants).
Generator side: each reachable branch is built constructively and checked by the contract's own SchemaCheck through the same JsonBodyEncoder the materializer uses. shipments.create in the zoo covers both sides; the corpus is re-recorded, and --check against the previous fixture reported only the new cases, no moved verdicts.
Next is step 3, prefixItems / contains. Two decisions to make there rather than discover mid-change: whether minContains / maxContains are read alongside contains (the backend applies them; refusing contains while reading nothing is silent loss, and so is reading contains while dropping the bounds), and — for the generator — that a contains over an array needs at least one element drawn from the subschema rather than a filter that exhausts.
The wave adds prefixItems/contains with minContains/maxContains, dependentRequired/dependentSchemas, and unevaluatedProperties/unevaluatedItems. contentSchema remains refused. Both packages pass composer build; merge #173 before #142 so the generator's development-contract CI sees the new contract support.
Готово. Реализовано и смержено в два PR:\n\n- #173 — валидация оставшихся ключевых слов схемы (, //, , , , ).\n- rasuvaeff/property-testing-openapi#142 — генерация для этих ключевых слов в property-testing-openapi.\n\nПоддержка в генераторе конструктивная; остаётся fail-closed ( для неподдерживаемых комбинаций). Корпус — 540 кейсов.\n\nПопутно исправлен реальный дефект: значения для компилировались один раз на весь объект, а соединял имена и значения независимо, поэтому значение, скомпилированное под одно имя, могло попасть под другое. Имена и значения теперь генерируются вместе ( по сгенерированному имени); до фикса валидатор отклонял 337 из 500 объектов, после — 0.\n\nВесь CI зелёный, включая Psalm, Rector и гейт мутации (MSI 91.14% при пороге 91).
Поддержка в генераторе конструктивная; contentSchema остаётся fail-closed (UnsupportedGeneration для неподдерживаемых комбинаций). Корпус — 540 кейсов.
Попутно исправлен реальный дефект: значения для patternProperties компилировались один раз на весь объект, а dictOf соединял имена и значения независимо, поэтому значение, скомпилированное под одно имя, могло попасть под другое. Имена и значения теперь генерируются вместе (by: по сгенерированному имени); до фикса валидатор отклонял 337 из 500 объектов, после — 0.
Весь CI зелёный, включая Psalm, Rector и гейт мутации (MSI 91.14% при пороге 91).
Activity
rasuvaeff commentedon Oct 11, 2026
Steps 1 and 2 of the proposed order are done.
1.
patternProperties(+propertyNames) — merged: openapi-contract#171, property-testing-openapi#140.2.
if/then/else— open: openapi-contract#172, property-testing-openapi#141.On the contract side each of the three becomes the single subschema it is: compiled instead of refused, walked by the load-time shape check, parsed eagerly by
assertParsed(), evaluated by the backend under draft 2020-12 at any depth and nested inside one another. A booleanif/then/elsestays refused under OAS 3.0, which predates boolean schemas. The directional rewrite reaches all three branches — the property test was checked against the three mutants it exists for, droppingif,thenorelsefrom the walk each fails it.On the generator side each reachable branch is built constructively:
thenas a conjunction of the base schema, the condition and the branch, so the condition holds by construction;elsefrom the base schema andelse, filtered by the condition the contract's ownSchemaCheckjudges. The zoo gainedshipments.createand the corpus is re-recorded —--checkagainst the previous fixture reported only the new cases, no moved verdicts.Remaining, in the proposed order:
prefixItems/contains, thendependentRequired/dependentSchemas, thenunevaluated*last.contentSchemastays refused.Support if/then/else (#157) (#172)
rasuvaeff commentedon Oct 11, 2026
Step 2 is merged: openapi-contract#172 and property-testing-openapi#141.
Contract side, per keyword: each of
if,then,elsebecomes the single subschema it is — compiled, walked by the load-time shape check, parsed eagerly, evaluated at any depth and nested inside one another, with a boolean form refused under OAS 3.0 asitemsandnotalready are. The directional rewrite reaches all three branches; the property test fails if any one of them is dropped from the walk (checked against all three mutants).Generator side: each reachable branch is built constructively and checked by the contract's own
SchemaCheckthrough the sameJsonBodyEncoderthe materializer uses.shipments.createin the zoo covers both sides; the corpus is re-recorded, and--checkagainst the previous fixture reported only the new cases, no moved verdicts.Next is step 3,
prefixItems/contains. Two decisions to make there rather than discover mid-change: whetherminContains/maxContainsare read alongsidecontains(the backend applies them; refusingcontainswhile reading nothing is silent loss, and so is readingcontainswhile dropping the bounds), and — for the generator — that acontainsover an array needs at least one element drawn from the subschema rather than a filter that exhausts.rasuvaeff commentedon Oct 11, 2026
The remaining support-matrix work is now prepared as one minor-release wave:
The wave adds
prefixItems/containswithminContains/maxContains,dependentRequired/dependentSchemas, andunevaluatedProperties/unevaluatedItems.contentSchemaremains refused. Both packages passcomposer build; merge #173 before #142 so the generator's development-contract CI sees the new contract support.Support remaining schema keywords (#157) (#173)
rasuvaeff commentedon Oct 11, 2026
Готово. Реализовано и смержено в два PR:\n\n- #173 — валидация оставшихся ключевых слов схемы (, //, , , , ).\n- rasuvaeff/property-testing-openapi#142 — генерация для этих ключевых слов в property-testing-openapi.\n\nПоддержка в генераторе конструктивная; остаётся fail-closed ( для неподдерживаемых комбинаций). Корпус — 540 кейсов.\n\nПопутно исправлен реальный дефект: значения для компилировались один раз на весь объект, а соединял имена и значения независимо, поэтому значение, скомпилированное под одно имя, могло попасть под другое. Имена и значения теперь генерируются вместе ( по сгенерированному имени); до фикса валидатор отклонял 337 из 500 объектов, после — 0.\n\nВесь CI зелёный, включая Psalm, Rector и гейт мутации (MSI 91.14% при пороге 91).
rasuvaeff commentedon Oct 11, 2026
Готово. Реализовано и смержено в два PR:
prefixItems,contains/minContains/maxContains,dependentRequired,dependentSchemas,unevaluatedProperties,unevaluatedItems).Поддержка в генераторе конструктивная;
contentSchemaостаётся fail-closed (UnsupportedGenerationдля неподдерживаемых комбинаций). Корпус — 540 кейсов.Попутно исправлен реальный дефект: значения для
patternPropertiesкомпилировались один раз на весь объект, аdictOfсоединял имена и значения независимо, поэтому значение, скомпилированное под одно имя, могло попасть под другое. Имена и значения теперь генерируются вместе (by:по сгенерированному имени); до фикса валидатор отклонял 337 из 500 объектов, после — 0.Весь CI зелёный, включая Psalm, Rector и гейт мутации (MSI 91.14% при пороге 91).
Release 0.16.0 (#157) (#174)