fix(core, testing): outgoing body parsing
MODIFY
packages/core/src/+internal/http/contentType.util.ts
packages/core/src/+internal/http/contentType.util.ts
@@ -42,3 +42,6 @@ export const getContentType = (headers: HttpHeaders): string => {
? Array.isArray(contentType) ? contentType[0] : String(contentType)
: '';
};
+
+export const isJsonContentType = (headerValue: string) =>
+ headerValue.includes('json');

MODIFY
packages/core/src/+internal/utils/any.util.ts
packages/core/src/+internal/utils/any.util.ts
import { pipe } from 'fp-ts/lib/pipeable';
+import { identity } from 'fp-ts/lib/function';
import * as O from 'fp-ts/lib/Option';
+import * as E from 'fp-ts/lib/Either';
+import * as qs from 'qs';
+import { isString } from './string.util';
export const isNonNullable = <T>(value: T): value is NonNullable<T> =>
value !== undefined && value !== null;
@@ -20,3 +24,20 @@ export const wait = (seconds = 1) =>
new Promise(res => {
setTimeout(() => res(), seconds * 1000);
});
+
+export const bufferFrom = (data: any): Buffer =>
+ Buffer.from(data);
+
+export const transformUrlEncoded = (data: any): string =>
+ !isString(data) ? qs.stringify(data) : data;
+
+export const stringifyJson = (data: any): string =>
+ !isString(data) ? JSON.stringify(data) : data;
+
+export const parseJson = (data: any) =>
+ pipe(
+ E.tryCatch(
+ () => JSON.parse(data),
+ () => data),
+ E.fold(identity, identity),
+ );

MODIFY
packages/core/src/http/response/http.responseBody.factory.ts
packages/core/src/http/response/http.responseBody.factory.ts
-import * as qs from 'qs';
+import { isBuffer } from 'util';
+import { Stream } from 'stream';
+import { pipe } from 'fp-ts/lib/pipeable';
import { HttpHeaders } from '../http.interface';
-import { ContentType, getContentType } from '../../+internal/http';
-import { isStream, isString } from '../../+internal/utils';
+import { ContentType, getContentType, isJsonContentType } from '../../+internal/http';
+import { isStream, stringifyJson, transformUrlEncoded, bufferFrom } from '../../+internal/utils';
-export type ResponseBodyFactory = (headers: HttpHeaders) => (body: any) => any;
-export type BodyTransformer = (body: any) => string;
+export type ResponseBodyFactory = (headers: HttpHeaders) => (body: any) => string | Stream | Buffer;
-const transformUrlEncoded: BodyTransformer = body =>
- !isString(body) ? qs.stringify(body) : body;
+export const bodyFactory: ResponseBodyFactory = headers => body => {
+ const contentType = getContentType(headers);
-const transformJson: BodyTransformer = body =>
- JSON.stringify(body);
+ if (isStream(body))
+ return body;
-export const bodyFactory: ResponseBodyFactory = headers => body => {
- if (isStream(body)) return body;
+ if (isJsonContentType(contentType))
+ return stringifyJson(body);
- switch (getContentType(headers)) {
+ switch (contentType) {
case ContentType.APPLICATION_X_WWW_FORM_URLENCODED:
return transformUrlEncoded(body);
- case ContentType.APPLICATION_JSON:
- return transformJson(body);
+ case ContentType.APPLICATION_OCTET_STREAM:
+ return !isBuffer(body)
+ ? pipe(body, stringifyJson, bufferFrom)
+ : body;
case ContentType.TEXT_PLAIN:
return String(body);
default:

MODIFY
packages/middleware-body/test/bodyParser.integration.spec.ts
packages/middleware-body/test/bodyParser.integration.spec.ts
@@ -59,7 +59,7 @@ describe('@marblejs/middleware-body - integration', () => {
.post('/multiple-parsers')
.set({ 'Content-Type': ContentType.TEXT_PLAIN })
.send(text)
- .expect(200, `"${text}"`)
+ .expect(200, text)
);
test(`parses ${ContentType.APPLICATION_OCTET_STREAM} content-type`, async () =>
@@ -68,7 +68,7 @@ describe('@marblejs/middleware-body - integration', () => {
.set({ 'Content-Type': ContentType.APPLICATION_OCTET_STREAM })
.send(text)
.expect(200)
- .then(({ body }) => expect(body.type).toBe('Buffer'))
+ .then(({ body }) => expect(body).toEqual(Buffer.from(body)))
);
});

MODIFY
packages/middleware-body/test/bodyParser.integration.ts
packages/middleware-body/test/bodyParser.integration.ts
import { EffectFactory, httpListener, combineRoutes } from '@marblejs/core';
-import { ContentType } from '@marblejs/core/dist/+internal/http';
+import { ContentType, getContentType } from '@marblejs/core/dist/+internal/http';
import { map } from 'rxjs/operators';
import { bodyParser$, urlEncodedParser, jsonParser, textParser, rawParser } from '../src';
@@ -7,7 +7,16 @@ const effect$ = EffectFactory
.matchPath('/')
.matchType('POST')
.use(req$ => req$.pipe(
- map(req => ({ body: req.body })),
+ map(req => {
+ const incomingContentType = getContentType(req.headers) as ContentType;
+ const outgoingContentType = [ContentType.APPLICATION_OCTET_STREAM, ContentType.TEXT_PLAIN].includes(incomingContentType)
+ ? incomingContentType
+ : ContentType.APPLICATION_JSON;
+ return {
+ body: req.body,
+ headers: { 'Content-Type': outgoingContentType },
+ };
+ }),
));
const defaultParser$ = combineRoutes('/default-parser', {

MODIFY
packages/testing/src/testBed/http/http.testBed.response.ts
packages/testing/src/testBed/http/http.testBed.response.ts
import * as O from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';
import { HttpStatus } from '@marblejs/core';
+import { parseJson } from '@marblejs/core/dist/+internal/utils';
import { getContentType, ContentType } from '@marblejs/core/dist/+internal/http/contentType.util';
import { HttpTestBedResponseProps, HttpTestBedResponse } from './http.testBed.response.interface';
import { HttpTestBedRequest } from './http.testBed.request.interface';
@@ -12,7 +13,7 @@ const parseResponseBody = (props: HttpTestBedResponseProps): string | Array<any>
O.map(body => {
switch (getContentType(props.headers)) {
case ContentType.APPLICATION_JSON:
- return JSON.parse(body);
+ return parseJson(body);
default:
return body;
}

fix(core, testing): outgoing body parsing