Skip to content
This repository has been archived by the owner on Sep 11, 2023. It is now read-only.

Commit

Permalink
allow for empty request bodies to skip validation, fixes stoplightio#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel A. White authored Feb 17, 2022
1 parent 705fca1 commit 03b46ff
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 7 deletions.
43 changes: 43 additions & 0 deletions packages/http-server/src/__tests__/body-params-validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,37 @@ describe('body params validation', () => {
},
},
},
{
id: '?http-operation-id?',
method: 'get',
path: '/empty-body',
responses: [
{
code: '200',
headers: [],
contents: [
{
mediaType: 'text/plain',
schema: {
type: 'string',
$schema: 'http://json-schema.org/draft-07/schema#',
},
examples: [],
encodings: [],
},
],
},
],
servers: [],
request: {
headers: [],
query: [],
cookie: [],
path: [],
},
tags: [],
security: [],
},
]);
});

Expand Down Expand Up @@ -452,13 +483,24 @@ describe('body params validation', () => {
const response = await makeRequest('/json-body-optional', { method: 'POST' });
expect(response.status).toBe(200);
});

describe('and no content is specified', () => {
test('returns 200', async () => {
const response = await makeRequest('/empty-body', {
method: 'GET',
headers: { 'content-type': 'application/json' },
});
expect(response.status).toBe(200);
});
});
});

describe('when body with unsupported content-type is used', () => {
test('returns 415', async () => {
const response = await makeRequest('/json-body-optional', {
method: 'POST',
headers: { 'content-type': 'application/xml' },
body: 'some xml',
});
expect(response.status).toBe(415);
});
Expand All @@ -472,6 +514,7 @@ describe('body params validation', () => {
headers: {
'content-type': 'application/csv',
},
body: 'type,name\nfoo,foobar',
});
expect(response.status).toBe(415);
await expect(response.json()).resolves.toMatchObject({ type: 'foo' });
Expand Down
35 changes: 28 additions & 7 deletions packages/http/src/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { sequenceOption, sequenceValidation } from '../combinators';
import { pipe } from 'fp-ts/function';
import { inRange, isMatch } from 'lodash';
import { URI } from 'uri-template-lite';
import { IHttpRequest, IHttpResponse } from '../types';
import { IHttpRequest, IHttpResponse, IHttpNameValue } from '../types';
import { findOperationResponse } from './utils/spec';
import { validateBody, validateHeaders, validatePath, validateQuery } from './validators';
import { NonEmptyArray } from 'fp-ts/NonEmptyArray';
Expand Down Expand Up @@ -68,29 +68,50 @@ const tryValidateInputBody = (
requestBody: IHttpOperationRequestBody,
bundle: unknown,
body: unknown,
mediaType: string
headers: IHttpNameValue
) =>
pipe(
checkBodyIsProvided(requestBody, body),
E.chain(() => {
const headersNormalized = caseless(headers || {});

const contentLength = parseInt(headersNormalized.get('content-length')) || 0;
if (contentLength === 0) {
// generously allow this content type if there isn't a body actually provided
return E.right(body);
}

const mediaType = headersNormalized.get('content-type');
if (isMediaTypeSupportedInContents(mediaType, requestBody.contents)) {
return E.right(body);
}

const supportedContentTypes = (requestBody.contents || []).map(x => x.mediaType);
const specRequestBodyContents = requestBody.contents || [];
let message: string;

if (specRequestBodyContents.length === 0) {
message = 'No supported content types, but request included a non-empty body';
} else {
const supportedContentTypes = specRequestBodyContents.map(x => x.mediaType);
message = `Supported content types: ${supportedContentTypes.join(',')}`;
}

return E.left<NonEmptyArray<IPrismDiagnostic>>([
{
message: `Supported content types: ${supportedContentTypes.join(',')}`,
message,
code: 415,
severity: DiagnosticSeverity.Error,
},
]);
}),
E.chain(() => validateInputIfBodySpecIsProvided(body, mediaType, requestBody.contents, bundle))
E.chain(() => {
const mediaType = caseless(headers || {}).get('content-type');

return validateInputIfBodySpecIsProvided(body, mediaType, requestBody.contents, bundle);
})
);

export const validateInput: ValidatorFn<IHttpOperation, IHttpRequest> = ({ resource, element }) => {
const mediaType = caseless(element.headers || {}).get('content-type');
const { request } = resource;
const { body } = element;

Expand All @@ -101,7 +122,7 @@ export const validateInput: ValidatorFn<IHttpOperation, IHttpRequest> = ({ resou
e => E.right<NonEmptyArray<IPrismDiagnostic>, unknown>(e),
request =>
sequenceValidation(
request.body ? tryValidateInputBody(request.body, bundle, body, mediaType) : E.right(undefined),
request.body ? tryValidateInputBody(request.body, bundle, body, element.headers || {}) : E.right(undefined),
request.headers ? validateHeaders(element.headers || {}, request.headers, bundle) : E.right(undefined),
request.query ? validateQuery(element.url.query || {}, request.query, bundle) : E.right(undefined),
request.path
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
====test====
Given a request body when the spec hasn't specified one
then return 415
====spec====
openapi: '3.1.0'
paths:
/path:
post:
responses:
200:
content:
text/plain:
example: ok
415:
content:
text/plain:
example: no body allowed
====server====
mock -p 4010 ${document}
====command====
curl -i -X POST http://localhost:4010/path -H "Content-Type: text/plain" --data "empty"
====expect====
HTTP/1.1 415 Bad Request
content-type: text/plain

no body allowed
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
====test====
Given a request declaring a content-type but has no body
then return 200
====spec====
openapi: '3.1.0'
paths:
/path:
post:
responses:
200:
content:
text/plain:
example: ok
====server====
mock -p 4010 ${document}
====command====
curl -i -X POST http://localhost:4010/path -H "Content-Type: text/plain"
====expect====
HTTP/1.1 200 OK
content-type: text/plain

ok

0 comments on commit 03b46ff

Please sign in to comment.