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
A requestBody that is a Reference Object is silently discarded during parsing, and the request body is then not validated at all. This fails open: malformed bodies are accepted and required: true is not enforced. No exception, no warning.
Parameter and Response both support $ref; RequestBody is the one Reference-Object-capable member of the Operation Object that does not.
Reproduce
<?phpuseDuyler\OpenApi\Builder\OpenApiValidatorBuilder;
useNyholm\Psr7\Factory\Psr17Factory;
$spec = <<<'YAML'
openapi: 3.1.0info: title: Ref RequestBody API version: 1.0.0paths: /data: post: requestBody: $ref: '#/components/requestBodies/UserBody' responses: '201': description: Createdcomponents: requestBodies: UserBody: required: true content: application/json: schema: type: object required: [name] properties: name: type: string
YAML;
$validator = OpenApiValidatorBuilder::create()->fromYamlString($spec)->build();
$factory = newPsr17Factory();
// `name` must be a string, and the body is required.$request = $factory->createServerRequest('POST', '/data')
->withHeader('Content-Type', 'application/json')
->withBody($factory->createStream('{"name": 12345}'));
$validator->validateRequest($request); // passes — expected a validation error
Expected
A validation error for name (integer where a string is required), and a MissingRequestBodyException for an empty body.
Actual
Both requests pass. Inlining the same requestBody instead of referencing it produces the expected errors, so the schema itself is fine — the reference is the problem.
$document->paths->paths['/data']->post->requestBody is:
Compare ResponseTreeBuilder::buildResponse() and PathItemBuilder::buildParameter(), which both open with an isset($data['$ref']) branch. Because $ref matches none of the three keys read above, a Reference Object parses to an empty RequestBody — a silent drop rather than an error.
Navigator — Validator\Schema\Internal\DocumentNavigator::navigate() returns Schema|Parameter|Response and rejects anything else with Target is not a Schema, Parameter, or Response. A #/components/requestBodies/* pointer cannot be resolved even though Components::$requestBodies is parsed and populated:
$navigator->navigate($document, ['components', 'requestBodies', 'UserBody']);
// UnresolvableRefException: Target is not a Schema, Parameter, or Response
RefCache::$map is typed to the same three-class union.
Resolver — Validator\Schema\RefResolverInterface declares resolveParameter() / resolveResponse() and resolveParameterWithOverride() / resolveResponseWithOverride(). There is no RequestBody counterpart.
Validator — Validator\Request\RequestBodyValidatorWithContext::validate() dereferences the media type's schema ($content->schema->ref) but never the request body object itself.
Layer 2 is what makes this fail open rather than throw. The empty RequestBody reaches validate(), which returns at its null === $requestBody->content early exit:
if (null === $requestBody->content) {
if ($requestBody->required) { // false — the ref's `required: true` was droppedthrownewMissingRequestBodyException();
}
return; // body never validated
}
So the fix needs all five layers: without 2 the pointer never reaches the validator, and without 3 and 4 the validator has no way to resolve it.
Notes
Affects #/components/requestBodies/* and external/file $ref targets alike, and applies to webhooks and callbacks, which build operations through the same PathItemBuilder::buildOperation().
OAS 3.1+ allows summary and description as siblings of $ref in a Reference Object. Response and Parameter model these as refSummary / refDescription and let them override the resolved values; RequestBody should behave the same way for consistency.
A
requestBodythat is a Reference Object is silently discarded during parsing, and the request body is then not validated at all. This fails open: malformed bodies are accepted andrequired: trueis not enforced. No exception, no warning.ParameterandResponseboth support$ref;RequestBodyis the one Reference-Object-capable member of the Operation Object that does not.Reproduce
Expected
A validation error for
name(integer where a string is required), and aMissingRequestBodyExceptionfor an empty body.Actual
Both requests pass. Inlining the same
requestBodyinstead of referencing it produces the expected errors, so the schema itself is fine — the reference is the problem.$document->paths->paths['/data']->post->requestBodyis:The
$refis gone, and with it thecontentandrequired: trueit pointed at.Root cause
Reference Object support for
requestBodyis absent at every layer of the pipeline, whereParameterandResponsehave it:Model —
Schema\Model\RequestBodyhas noreffield.Schema\Model\ResponseandSchema\Model\Parameterboth carryref/refSummary/refDescription.Parser —
Schema\Parser\Internal\ComponentTreeBuilder::buildRequestBody()never inspects$ref:Compare
ResponseTreeBuilder::buildResponse()andPathItemBuilder::buildParameter(), which both open with anisset($data['$ref'])branch. Because$refmatches none of the three keys read above, a Reference Object parses to an emptyRequestBody— a silent drop rather than an error.Navigator —
Validator\Schema\Internal\DocumentNavigator::navigate()returnsSchema|Parameter|Responseand rejects anything else withTarget is not a Schema, Parameter, or Response. A#/components/requestBodies/*pointer cannot be resolved even thoughComponents::$requestBodiesis parsed and populated:RefCache::$mapis typed to the same three-class union.Resolver —
Validator\Schema\RefResolverInterfacedeclaresresolveParameter()/resolveResponse()andresolveParameterWithOverride()/resolveResponseWithOverride(). There is noRequestBodycounterpart.Validator —
Validator\Request\RequestBodyValidatorWithContext::validate()dereferences the media type's schema ($content->schema->ref) but never the request body object itself.Layer 2 is what makes this fail open rather than throw. The empty
RequestBodyreachesvalidate(), which returns at itsnull === $requestBody->contentearly exit:So the fix needs all five layers: without 2 the pointer never reaches the validator, and without 3 and 4 the validator has no way to resolve it.
Notes
#/components/requestBodies/*and external/file$reftargets alike, and applies to webhooks and callbacks, which build operations through the samePathItemBuilder::buildOperation().summaryanddescriptionas siblings of$refin a Reference Object.ResponseandParametermodel these asrefSummary/refDescriptionand let them override the resolved values;RequestBodyshould behave the same way for consistency.Environment
duyler/openapi— reproduced ondevat 8718cbc