Skip to content

Commit

Permalink
feat(middleware-logger): swapped middleware entrypoints (#98)
Browse files Browse the repository at this point in the history
* feat(middleware-logger): swapped middleware entrypoints
* imp(middleware-body): possibility to pass additional config object to middleware entrypoint
  • Loading branch information
JozefFlakus committed Jan 31, 2019
1 parent e888138 commit 6324b82
Show file tree
Hide file tree
Showing 19 changed files with 163 additions and 128 deletions.
2 changes: 1 addition & 1 deletion packages/@integration/src/http.listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const httpServer = httpListener({
middlewares: [
loggerDev$,
loggerFile$,
bodyParser$,
bodyParser$(),
],
effects: [api$]
});
6 changes: 3 additions & 3 deletions packages/@integration/src/middlewares/logger.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { loggerWithOpts$ } from '@marblejs/middleware-logger';
import { logger$ } from '@marblejs/middleware-logger';
import { createWriteStream } from 'fs';
import { join } from 'path';

const silent = process.env.NODE_ENV === 'test';
const writePath = join(__dirname, '../../', 'access.log');
const stream = createWriteStream(writePath, { flags: 'a' });

export const loggerDev$ = loggerWithOpts$({ silent });
export const loggerFile$ = loggerWithOpts$({ silent, stream });
export const loggerDev$ = logger$({ silent });
export const loggerFile$ = logger$({ silent, stream });
20 changes: 15 additions & 5 deletions packages/core/src/+internal/testing/http.helper.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import * as http from 'http';
import { HttpRequest, HttpHeaders, RouteParameters, QueryParameters, HttpResponse } from '../../http.interface';
import {
HttpRequest,
HttpResponse,
HttpHeaders,
HttpMethod,
RouteParameters,
QueryParameters,
} from '../../http.interface';
import { EventEmitter } from 'events';

interface HttpRequestMockParams {
url: string;
body?: any;
params?: RouteParameters;
query?: QueryParameters;
headers?: HttpHeaders;
method?: HttpMethod;
[key: string]: any;
}

Expand All @@ -28,12 +37,13 @@ export const createHttpRequest = (data: HttpRequestMockParams = { url: '/' }) =>
params: data.params || {},
query: data.query || {},
headers: data.headers || {},
method: data.method || 'GET',
}) as HttpRequest;

export const createHttpResponse = (data: HttpResponseMockParams = {}) => ({
...data,
statusCode: data.statusCode,
}) as HttpResponse;
export const createHttpResponse = (data: HttpResponseMockParams = {}) =>
new class extends EventEmitter {
statusCode = data.statusCode;
} as any as HttpResponse;

export const mockHttpServer = (mocks: HttpServerMocks = {}) =>
jest.spyOn(http, 'createServer').mockImplementation(jest.fn(() => ({
Expand Down
2 changes: 1 addition & 1 deletion packages/middleware-body/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Requires `@marblejs/core` to be installed.
import { bodyParser$ } from '@marblejs/middleware-body';
const middlewares = [
bodyParser$,
bodyParser$(),
// ...
];
Expand Down
62 changes: 62 additions & 0 deletions packages/middleware-body/src/body.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { HttpError, HttpRequest, HttpStatus, HttpMiddlewareEffect } from '@marblejs/core';
import { ContentType, compose } from '@marblejs/core/dist/+internal';
import { Observable, of, throwError } from 'rxjs';
import { catchError, map, switchMap, tap, toArray, mapTo } from 'rxjs/operators';
import { serializeUrlEncoded } from './body.urlEncoded.serializer';
import { BodyParserOptions} from './body.model';

const PARSEABLE_METHODS = ['POST', 'PUT', 'PATCH'];

const fromReadableStream = (stream: HttpRequest): Observable<any> => {
stream.pause();
return new Observable(observer => {
const next = chunk => observer.next(chunk);
const complete = () => observer.complete();
const error = err => observer.error(err);

stream
.on('data', next)
.on('error', error)
.on('end', complete)
.resume();

return () => {
stream.removeListener('data', next);
stream.removeListener('error', error);
stream.removeListener('end', complete);
};
});
};

const getBody = (req: HttpRequest) =>
fromReadableStream(req).pipe(
toArray(),
map(chunks => Buffer.concat(chunks)),
map(buffer => buffer.toString()),
map(body => {
switch (req.headers['content-type']) {
case ContentType.APPLICATION_JSON:
return JSON.parse(body);
case ContentType.APPLICATION_X_WWW_FORM_URLENCODED:
return compose(serializeUrlEncoded, decodeURIComponent)(body);
default:
return body;
}
})
);

export const bodyParser$ = (opts: BodyParserOptions = {}): HttpMiddlewareEffect => (req$) =>
req$.pipe(
switchMap(req =>
PARSEABLE_METHODS.includes(req.method)
? of(req).pipe(
switchMap(getBody),
tap(body => (req.body = body)),
mapTo(req),
catchError(() => throwError(
new HttpError('Request body parse error', HttpStatus.BAD_REQUEST),
))
)
: of(req)
)
);
3 changes: 3 additions & 0 deletions packages/middleware-body/src/body.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface BodyParserOptions {
// Add dedicated middlewares options when needed
}
7 changes: 7 additions & 0 deletions packages/middleware-body/src/body.urlEncoded.serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const serializeUrlEncoded = (formData: string) => formData
.split('&')
.map(x => x.split('='))
.reduce((data, [key, value]) => ({
...data,
[key]: isNaN(+value) ? value : +value,
}), {});
60 changes: 1 addition & 59 deletions packages/middleware-body/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1 @@
import { HttpError, HttpRequest, HttpStatus, HttpMiddlewareEffect } from '@marblejs/core';
import { ContentType } from '@marblejs/core/dist/+internal';
import { Observable, of, throwError } from 'rxjs';
import { catchError, map, switchMap, tap, toArray, mapTo } from 'rxjs/operators';
import { serializeUrlEncoded } from './urlEncoded.serializer';

const PARSEABLE_METHODS = ['POST', 'PUT', 'PATCH'];

const fromReadableStream = (stream: HttpRequest): Observable<any> => {
stream.pause();
return new Observable(observer => {
const next = chunk => observer.next(chunk);
const complete = () => observer.complete();
const error = err => observer.error(err);

stream
.on('data', next)
.on('error', error)
.on('end', complete)
.resume();

return () => {
stream.removeListener('data', next);
stream.removeListener('error', error);
stream.removeListener('end', complete);
};
});
};

const getBody = (req: HttpRequest) =>
fromReadableStream(req).pipe(
toArray(),
map(chunks => Buffer.concat(chunks)),
map(buffer => buffer.toString()),
map(body => {
switch (req.headers['content-type']) {
case ContentType.APPLICATION_JSON:
return JSON.parse(body);
case ContentType.APPLICATION_X_WWW_FORM_URLENCODED:
return serializeUrlEncoded(decodeURIComponent(body));
default:
return body;
}
})
);

export const bodyParser$: HttpMiddlewareEffect = req$ =>
req$.pipe(
switchMap(req =>
PARSEABLE_METHODS.includes(req.method)
? of(req).pipe(
switchMap(getBody),
tap(body => (req.body = body)),
mapTo(req),
catchError(() => throwError(new HttpError('Request body parse error', HttpStatus.BAD_REQUEST)))
)
: of(req)
)
);
export { bodyParser$ } from './body.middleware';
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { HttpRequest, HttpResponse, createStaticInjectionContainer, createEffectMetadata } from '@marblejs/core';
import { Marbles } from '@marblejs/core/dist/+internal';
import { of } from 'rxjs';
import { bodyParser$ } from '.';
import { bodyParser$ } from '../body.middleware';

const MockReq = require('mock-req');

describe('BodyParser middleware', () => {
describe('bodyParser$ middleware', () => {
const injector = createStaticInjectionContainer();
const effectMeta = createEffectMetadata({ inject: injector.get });

Expand All @@ -14,25 +14,25 @@ describe('BodyParser middleware', () => {
spyOn(console, 'error').and.stub();
});

it('bodyParser$ passes through non POST || PATCH || PUT requests', () => {
test('passes through non POST || PATCH || PUT requests', () => {
const request = new MockReq({
method: 'GET',
});

Marbles.assertEffect(bodyParser$, [
Marbles.assertEffect(bodyParser$(), [
['-a-', { a: request }],
['-a-', { a: request }],
]);
});

it('bodyParser$ parses "application/json" body', done => {
test('parses "application/json" body', done => {
const request = new MockReq({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
const req$ = of(request as HttpRequest);
const res = {} as HttpResponse;
const http$ = bodyParser$(req$, res, effectMeta);
const http$ = bodyParser$()(req$, res, effectMeta);

http$.subscribe(data => {
expect(data.body).toEqual({ test: 'test' });
Expand All @@ -43,14 +43,14 @@ describe('BodyParser middleware', () => {
request.end();
});

it('bodyParser$ parses "x-www-form-urlencoded" body', done => {
test('parses "x-www-form-urlencoded" body', done => {
const request = new MockReq({
method: 'POST',
headers: { 'Content-Type': 'x-www-form-urlencoded' },
});
const req$ = of(request as HttpRequest);
const res = {} as HttpResponse;
const http$ = bodyParser$(req$, res, effectMeta);
const http$ = bodyParser$()(req$, res, effectMeta);

http$.subscribe(data => {
expect(data.body).toEqual({
Expand All @@ -66,14 +66,14 @@ describe('BodyParser middleware', () => {
});


it('bodyParser$ throws exception on "application/json" parse', done => {
test('throws exception on "application/json" parse', done => {
const request = new MockReq({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
const req$ = of(request as HttpRequest);
const res = {} as HttpResponse;
const http$ = bodyParser$(req$, res, effectMeta);
const http$ = bodyParser$()(req$, res, effectMeta);

http$.subscribe(
() => {
Expand All @@ -91,14 +91,14 @@ describe('BodyParser middleware', () => {
request.end();
});

it('bodyParser$ parses "text/plain" body', done => {
test('parses "text/plain" body', done => {
const request = new MockReq({
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
});
const req$ = of(request as HttpRequest);
const res = {} as HttpResponse;
const http$ = bodyParser$(req$, res, effectMeta);
const http$ = bodyParser$()(req$, res, effectMeta);

http$.subscribe(data => {
expect(data.body).toEqual('test');
Expand All @@ -109,14 +109,14 @@ describe('BodyParser middleware', () => {
request.end();
});

it('bodyParser$ throws exception on EventEmitter "error" event', done => {
test('throws exception on EventEmitter "error" event', done => {
const request = new MockReq({
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
});
const req$ = of(request as HttpRequest);
const res = {} as HttpResponse;
const http$ = bodyParser$(req$, res, effectMeta);
const http$ = bodyParser$()(req$, res, effectMeta);

http$.subscribe(
() => {
Expand Down
7 changes: 7 additions & 0 deletions packages/middleware-body/src/specs/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as API from '../index';

describe('@marblejs/middleware-body public API', () => {
test('apis are defined', () => {
expect(API.bodyParser$).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { serializeUrlEncoded } from './urlEncoded.serializer';
import { serializeUrlEncoded } from '../body.urlEncoded.serializer';

describe('serializeUrlEncoded', () => {
test('should return object with params', () => {
test('returns object with params', () => {
// given
const formData = 'test=test&test-2=test-2&test-3=3';

Expand Down
8 changes: 0 additions & 8 deletions packages/middleware-body/src/urlEncoded.serializer.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/middleware-io/test/io-http.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ const effect$ = EffectFactory
));

export const app = httpListener({
middlewares: [bodyParser$],
middlewares: [bodyParser$()],
effects: [effect$],
});
2 changes: 1 addition & 1 deletion packages/middleware-joi/test/helpers/api.spec-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const postUser$ = EffectFactory
const api$ = combineRoutes('/api', [getPost$, getUser$, postUser$, storePost$]);

const middlewares = [
bodyParser$,
bodyParser$(),
validator$(
{ headers: { token: Joi.string().token().required() } },
{ stripUnknown: true }
Expand Down
6 changes: 3 additions & 3 deletions packages/middleware-joi/test/unit/body.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('Joi middleware - Body', () => {
})
};

const http$ = bodyParser$(req$, res, metadata);
const http$ = bodyParser$()(req$, res, metadata);
const valid$ = validator$(schema)(http$);

valid$.subscribe(
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('Joi middleware - Body', () => {
})
};

const http$ = bodyParser$(req$, res, metadata);
const http$ = bodyParser$()(req$, res, metadata);
const valid$ = validator$(schema)(http$);

valid$.subscribe(
Expand Down Expand Up @@ -96,7 +96,7 @@ describe('Joi middleware - Body', () => {
})
};

const http$ = bodyParser$(req$, res, metadata);
const http$ = bodyParser$()(req$, res, metadata);
const valid$ = validator$(schema, { allowUnknown: true })(http$);

valid$.subscribe(data => {
Expand Down
Loading

0 comments on commit 6324b82

Please sign in to comment.