Repository navigation
v0.15.0
The 1.0-readiness review of 2026-09-18 (#117–#129). A minor on 0.x: the
constructor of ResponseMaterializer narrows, the OperationPropertyFailed
factories take one more argument, the RequestCaseData /
NegativeRequestCaseData psalm aliases are gone, and case-shape errors are a
type of their own.
- Changed. Requires
rasuvaeff/openapi-contract^0.12and
rasuvaeff/property-testing-core^0.10 || ^0.11(develops against
rasuvaeff/property-testing-testo^0.10 || ^0.11): the compile-time
probes catchGenerationExhaustedExceptionby name, which exists since the
core's 0.10 contract freeze, so^0.5–^0.9are no longer accepted. The
directional schema
rewrite is delegated toSchemaCheck::effective()— this package's own
copy never recursed intoadditionalProperties— and the request body and
responses arrive as the contract's typed shapes, so the guards that
re-checked them are gone. - Fixed. A numeric parameter reached the wire through
(string), which
rounds toprecision=14and put a different number there for any value
that needed more digits (846608010056.187went as846608010056.19). A
float is spelled the wayjson_encodespells it now. ThemultipleOf
generator also roundedk × mback to the decimal it meant, which from
about64upwards is one ulp away from the product the validator
computes and tolerates, so a third of the draws for0.1were rejected;
the decimal spelling is kept only where the validator agrees with it.
Underext-bcmaththe contract evaluatesmultipleOfin decimal
arithmetic and its verdict is its own — openapi-contract#151 (#117). - Fixed.
missing-requiredno longer targets a path parameter, and
negativeCoverage()no longer lists it: omitting one leaves the template
literal in the request target, which astringschema accepts, so the
negative phase failed on any/{slug}operation with
unexpectedlyValidRequest. An operation whose only required component is a
path parameter skips the category and names the reason (#118). - Fixed.
+stays%2BunderallowReserved: a raw plus in a query is
a space to the validator and to every SAPI (#119). - Fixed. A form body property with an object schema and
explode: true
is generated without undeclared members — its flatmember=valuepairs can
carry only the declared ones, and an extra landed as a top-level member of
the body. A nested object whoseminPropertiesits declared properties
cannot meet fails closed at compile time (#120). - Fixed.
oneOfover anintegerand anumberbranch was compiled as a
plain choice between disjoint branches, and half the draws matched both.
The number branch is generated without integral values, the integer branch
keeps only what the number branch's bounds and multiple refuse, and a
number branch that admits every value outside the integer branch fails
closed.1.0is an integer to JSON Schema, and is treated as one (#121). - Fixed. A multipart part declared under a JSON media type carries the
JSON encoding of its value;text/*andapplication/octet-streamparts
carry it verbatim; any other part media type fails closed instead of
sending a body the generator cannot vouch for (#122). - Fixed. Three legal documents reached a run-time
GenerationExhausted:
a header enum outside ASCII (isHeaderSafe()admits obs-text now, as RFC
9110 and the validator do), a path pattern that always carries a slash
(probed at compile time and refused by name, as is a header pattern none
of whose strings survives the wire), and an object whose twelve optionals
were drawn as independent presence choices againstmaxProperties: 1
(minProperties/maxPropertiesare met by construction: an optional past
the ceiling is left out, one needed for the floor brought in).uniqueItems
withminItemsover a bounded integer domain smaller than it fails closed
at compile time (#123). - Fixed. A header enum member with an interior space (
New York) was
refused at compile time although the validator accepts it: a scalar
header's members are refused only for whitespace at an end or a control
character, a list or object header's additionally for a comma (#129). - Added.
ContractSuite::redaction(RedactionPolicy)configures the policy
every rendering goes through:reproduce()by default (its policy
argument is optional now) and the minimal caseOperationPropertyFailed
prints, which used to be the unredacted counterexample JSON.
ContractSuite::redact(array $case): arrayapplies it to a case. The
READMEs andllms.txtclaimedCookiewas in the default redacted header
set; it is not, by design since #73, and they say so now (#124). - Added.
OpenApiPropertyTestingException, an empty marker interface
implemented by every exception of the package;InvalidCase
(\InvalidArgumentException) for a case that does not have the exported
shape — the materializer and the serializer threwUnsupportedGeneration
for it, which is a document limitation, not a caller error; a
Psr15Transportconfiguration error is aSuiteConfigurationErrorrather
than a bare\LogicException.UnsupportedGeneration::forSchema()
refusals name the operation and the parameter or body being compiled
(Unsupported OpenAPI schema generation for operation "pets.list", query parameter "limit": minLength exceeds maxLength) through
inOperation()(#127). - Changed. The case shape is declared once, on
ContractSuite
(CaseDatawithParameterMap,BodyData,PartData,MisuseData), and
imported everywhere else; theRequestCaseDataand
NegativeRequestCaseDataaliases and the inline copies are gone.
checkValid(),checkNegative(),reproduce(),redact()and
RequestMaterializer::materialize()refuse a case missing a key with
InvalidCasenaming it — a hand-written case withoutmisuseused to
raise a PHP warning and passcheckValid()(#128). - Changed.
ResponseMaterializer::__construct()no longer takes the
@internalJsonBodyEncoderandParameterSerializeras defaulted
parameters; it builds them, asRequestMaterializerdoes (#125).
CheckFailed::$resultisreadonly, assigned through the constructor by
the factories (#126).OperationPropertyFailed::forCounterExample()and
forExample()take the redacted case to print as their last argument. - Zoo:
labels.get,sparse.create,amounts.create,settings.create,
precise.get; the contract's generated corpus is re-recorded from it
(openapi-contract#152).