Skip to content

Commit d255b00

Browse files
committed
fix: correctly handle cases where null is sent as the body
1 parent 7753597 commit d255b00

File tree

2 files changed

+50
-8
lines changed

2 files changed

+50
-8
lines changed

packages/http/src/validator/__tests__/index.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,37 @@ describe('HttpValidator', () => {
180180
});
181181
assertRight(result);
182182
});
183+
184+
it('accepts null as a valid null body', () => {
185+
const result = validator.validateInput({
186+
resource: {
187+
method: 'post',
188+
path: '/',
189+
id: '1',
190+
request: {
191+
body: {
192+
id: faker.random.word(),
193+
required: true,
194+
contents: [
195+
{
196+
id: faker.random.word(),
197+
mediaType: 'application/json',
198+
schema: { type: 'null' },
199+
},
200+
],
201+
},
202+
},
203+
responses: [{ id: faker.random.word(), code: '200' }],
204+
},
205+
element: {
206+
method: 'post',
207+
url: { path: '/', query: {} },
208+
body: null,
209+
headers: { 'content-type': 'application/json', 'content-length': '4' },
210+
},
211+
});
212+
assertRight(result);
213+
});
183214
});
184215

185216
describe('headers validation in enabled', () => {

packages/http/src/validator/index.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,18 @@ import { wildcardMediaTypeMatch } from './utils/wildcardMediaTypeMatch';
2626

2727
export { validateSecurity } from './validators/security';
2828

29-
const checkRequiredBodyIsProvided = (requestBody: O.Option<IHttpOperationRequestBody>, body: unknown) =>
29+
const checkRequiredBodyIsProvided = (
30+
requestBody: O.Option<IHttpOperationRequestBody>,
31+
body: unknown,
32+
bodyIsProvided: boolean
33+
) =>
3034
pipe(
3135
requestBody,
3236
E.fromPredicate<O.Option<IHttpOperationRequestBody>, NonEmptyArray<IPrismDiagnostic>>(
33-
requestBody => O.isNone(requestBody) || !(!!requestBody.value.required && body == null),
37+
requestBody => O.isNone(requestBody) || !(!!requestBody.value.required && !bodyIsProvided),
3438
() => [{ code: 'required', message: 'Body parameter is required', severity: DiagnosticSeverity.Error }]
3539
),
36-
E.map(requestBody => [requestBody, body == null ? O.none : O.some(body)] as const)
40+
E.map(requestBody => [requestBody, bodyIsProvided ? O.some(body) : O.none] as const)
3741
);
3842

3943
const isMediaTypeSupportedInContents = (mediaType?: string, contents?: IMediaTypeContent[]): boolean =>
@@ -73,17 +77,23 @@ const validateInputBody = (
7377
bundle: unknown,
7478
body: unknown,
7579
headers: IHttpNameValue
76-
) =>
77-
pipe(
78-
checkRequiredBodyIsProvided(requestBody, body),
79-
E.map(b => [...b, caseless(headers || {})] as const),
80+
) => {
81+
const headersCaseless = caseless(headers || {});
82+
const contentLength = parseInt(headersCaseless.get('content-length')) || 0;
83+
// A body is considered "provided" if:
84+
// - body is not undefined, AND
85+
// - body is not null OR content-length > 0 (null with content-length > 0 means JSON null value)
86+
const bodyIsProvided = body !== undefined && (body !== null || contentLength > 0);
87+
88+
return pipe(
89+
checkRequiredBodyIsProvided(requestBody, body, bodyIsProvided),
90+
E.map(b => [...b, headersCaseless] as const),
8091
E.chain(([requestBody, body, headers]) => {
8192
const contentTypeHeader = headers.get('content-type');
8293
const [multipartBoundary, mediaType] = contentTypeHeader
8394
? parseMIMEHeader(contentTypeHeader)
8495
: [undefined, undefined];
8596

86-
const contentLength = parseInt(headers.get('content-length')) || 0;
8797
if (contentLength === 0) {
8898
// generously allow this content type if there isn't a body actually provided
8999
return E.right([requestBody, body, mediaType, multipartBoundary] as const);
@@ -114,6 +124,7 @@ const validateInputBody = (
114124
validateInputIfBodySpecIsProvided(body, requestBody, mediaType, multipartBoundary, bundle)
115125
)
116126
);
127+
};
117128

118129
export const validateInput: ValidatorFn<IHttpOperation, IHttpRequest> = ({ resource, element }) => {
119130
const { request } = resource;

0 commit comments

Comments
 (0)