refactor: joi.middleware
DELETE
packages/marble-joi/test.ts
-import { validator$, Joi } from './src';
-import { httpListener, Effect, matchPath, matchType, combineRoutes, bodyParser$, error$ } from '@marblejs/core';
-import { use } from '@marblejs/core/dist/operators/use.operator';
-import { map, tap } from 'rxjs/operators';
-
-const root$: Effect = request$ => request$
- .pipe(
- matchPath('/'),
- matchType('GET'),
- map(req => ({
- body: `API root @ ${req.url}`,
- }))
- );
-
-export const api$ = combineRoutes(
- '/api/v1',
- [ root$ ],
-);
-
-const createServer = app => {
- const HOSTNAME = '127.0.0.1';
- const PORT = 1337;
-
- const httpServer = require('http')
- .createServer(app)
- .listen(PORT, HOSTNAME, () => {
- console.log(`Server running @ http://${HOSTNAME}:${PORT}/`);
- });
-
- httpServer.on('close', () => {
- console.log(`Connection closed`);
- });
-};
-
-
-const middlewares = [
- bodyParser$,
- validator$({ body: {
- id: Joi.string(),
- }})
-];
-
-const effect$1: Effect = req$ => req$.pipe(
- matchPath('/test/:id'),
- use(validator$({ params: {
- id: Joi.number().min(1).max(10),
- }})),
- map(req => req.route.params.id),
- map(id => ({ status: 200, body: { id } })),
-);
-
-const effects = [
- api$,
- effect$1
-];
-
-const listener = httpListener({ middlewares, effects, errorMiddleware: error$ });
-createServer(listener);

DELETE
packages/marble-joi/tsconfig.json
-{
- "compilerOptions": {
- "outDir": "./dist/",
- "emitDecoratorMetadata": true,
- "experimentalDecorators": true,
- "module": "commonjs",
- "moduleResolution": "node",
- "declaration": true,
- "noImplicitAny": false,
- "noEmitOnError": true,
- "pretty": true,
- "strict": true,
- "strictNullChecks": false,
- "target": "es6",
- "lib": [
- "dom",
- "es7"
- ]
- },
-
- "inputs": [
- "src"
- ],
-
- "include": [
- "src/**/*.ts"
- ],
-
- "exclude": [
- "**/*.spec.ts"
- ]
-}

DELETE
packages/marble-joi/yarn.lock
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-hoek@5.x.x:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.3.tgz#b71d40d943d0a95da01956b547f83c4a5b4a34ac"
-
-isemail@3.x.x:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.1.2.tgz#937cf919002077999a73ea8b1951d590e84e01dd"
- dependencies:
- punycode "2.x.x"
-
-joi@^13.3.0:
- version "13.3.0"
- resolved "https://registry.yarnpkg.com/joi/-/joi-13.3.0.tgz#4defd4333b539c5d10e444ab44f5a5583480f17c"
- dependencies:
- hoek "5.x.x"
- isemail "3.x.x"
- topo "3.x.x"
-
-punycode@2.x.x:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d"
-
-topo@3.x.x:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/topo/-/topo-3.0.0.tgz#37e48c330efeac784538e0acd3e62ca5e231fe7a"
- dependencies:
- hoek "5.x.x"

RENAME
packages/marble-joi/.editorconfig
packages/middleware-joi/.editorconfig
RENAME
packages/marble-joi/LICENSE
packages/middleware-joi/LICENSE
ADD
packages/middleware-joi/README.md
+Middleware Joi
+=======
+
+A [joi](https://github.com/hapijs/joi) validation middleware for [Marble.js](https://github.com/marblejs/marble).
+
+## Usage
+
+Example of using this middleware on a GET route to validate params.
+
+```javascript
+import { validator$, Joi } from '@marblejs/middleware-joi';
+const foo$: Effect = request$ => request$
+ .pipe(
+ matchPath('/foo/:id'),
+ matchType('GET'),
+ use(validator$({
+ params: Joi.object({
+ id: Joi.number().min(1).max(10),
+ })
+ }));
+ // ...
+ );
+```
+
+Example to validate all incoming requests.
+
+```javascript
+import { validator$, Joi } from '@marblejs/middleware-joi';
+
+const middlewares = [
+ logger$,
+ validator$({
+ headers: Joi.object({
+ sign: Joi.string(),
+ accept: Joi.string().default('application/json'),
+ }),
+ params: Joi.object({
+ apiKey: Joi.string().token().required(),
+ })
+ })
+];
+
+const effects = [
+ endpoint1$,
+ endpoint2$,
+ ...
+];
+
+const app = httpListener({ middlewares, effects });
+```
+
+License: MIT

RENAME
packages/marble-joi/package-lock.json
packages/middleware-joi/package-lock.json
ADD
packages/middleware-joi/src/index.ts
+export * from './joi.middleware';

ADD
packages/middleware-joi/src/joi.middleware.spec.ts
+import { of } from 'rxjs';
+import { HttpRequest, HttpResponse, HttpError, bodyParser$ } from '@marblejs/core';
+import { Marbles } from '@marblejs/core/dist/util/marbles.spec-util';
+import { validator$, Joi } from './joi.middleware';
+import { take } from 'rxjs/operators';
+
+const MockReq = require('mock-req');
+
+describe('Joi middleware', () => {
+
+ beforeEach(() => {
+ spyOn(console, 'log').and.stub();
+ spyOn(console, 'error').and.stub();
+ });
+
+ it('validator$ throws an error if using an invalid schema', done => {
+ expect.assertions(1);
+
+ const request = new MockReq({
+ method: 'GET',
+ });
+
+ const req$ = of(request as HttpRequest);
+ const res = {} as HttpResponse;
+ const schema = { headers: 'foo' };
+ const http$ = validator$(schema)(req$, res, {});
+
+ http$.subscribe(() => {
+ fail('Exceptions should be thrown');
+ done();
+ },
+ error => {
+ expect(error).toBeDefined();
+ done();
+ });
+ });
+
+ it('validator$ throws an error if pass a invalid header', done => {
+ expect.assertions(1);
+
+ const request = new MockReq({
+ method: 'GET',
+ headers: {
+ token: '@@@'
+ }
+ });
+
+ const req$ = of(request as HttpRequest);
+ const res = {} as HttpResponse;
+ const schema = { headers: Joi.object({
+ token: Joi.string().token().required(),
+ }) };
+ const http$ = validator$(schema)(req$, res, {});
+
+ http$.subscribe(() => {
+ fail('Exceptions should be thrown');
+ done();
+ },
+ error => {
+ expect(error).toBeDefined();
+ done();
+ });
+ });
+
+ it('validator$ validates header', done => {
+ expect.assertions(1);
+
+ const request = new MockReq({
+ method: 'GET',
+ headers: {
+ token: '181782881DB38D84',
+ }
+ });
+
+ const req$ = of(request as HttpRequest);
+ const res = {} as HttpResponse;
+ const schema = { headers: Joi.object({
+ token: Joi.string().token().required(),
+ }) };
+ const http$ = validator$(schema)(req$, res, {});
+
+ http$.subscribe(data => {
+ expect(data).toBeTruthy();
+ done();
+ });
+ });
+
+ it('validator$ validates header and body', done => {
+ expect.assertions(2);
+
+ const request = new MockReq({
+ method: 'POST',
+ headers: {
+ token: '181782881DB38D84',
+ }
+ });
+
+ const req$ = of(request as HttpRequest);
+ const res = {} as HttpResponse;
+ const schema = {
+ headers: Joi.object({
+ token: Joi.string().token().required(),
+ }),
+ body: Joi.object({
+ id: Joi.number().min(1).max(4),
+ role: Joi.string().default('admin'),
+ }),
+ };
+ const http$ = bodyParser$(req$, res, {});
+ const valid$ = validator$(schema)(http$, res, {});
+
+ valid$.subscribe(data => {
+ expect(data).toBeTruthy();
+ expect(data.body.role).toBe('admin'),
+ done();
+ });
+
+ request.write({ id: 4 });
+ request.end();
+ });
+});

RENAME
packages/marble-joi/src/index.ts
packages/middleware-joi/src/joi.middleware.ts
import * as Joi from 'joi';
import { Schema, SchemaValidator } from './schema';
import { Effect, HttpRequest, HttpError, HttpStatus } from '@marblejs/core';
-import { of, Observable, throwError, from, empty } from 'rxjs';
-import { tap, switchMap, toArray, map, catchError, mapTo, mergeMap, switchMapTo } from 'rxjs/operators';
-import { filter } from 'minimatch';
+import { from, of, throwError } from 'rxjs';
+import { mergeMap, flatMap, catchError, mapTo, tap, switchMap, toArray, map } from 'rxjs/operators';
const validateSource = (req: HttpRequest, rules: Map<string, any>, options) => from(rules.keys()).pipe(
- switchMap(rule => of(req[rule]).pipe(
- mergeMap(item => {
- return typeof item !== 'undefined'
- ? of(item)
- : empty();
- }),
- mergeMap(item => from(Joi.validate(item, rules.get(rule), options))),
+ mergeMap(rule => of(req[rule]).pipe(
+ flatMap(item => from(Joi.validate(item || {}, rules.get(rule), options))),
catchError((err: Error) => throwError(new HttpError(err.message, HttpStatus.BAD_REQUEST))),
+ map(result => (req[rule] = result)),
)),
+ toArray(),
mapTo(req),
);

RENAME
packages/marble-joi/src/schema.ts
packages/middleware-joi/src/schema.ts
const Joi = require('joi');
export const SchemaValidator = Joi.object().keys({
- headers: Joi.any(),
- params: Joi.any(),
- query: Joi.any(),
- body: Joi.any(),
+ headers: Joi.object(),
+ params: Joi.object(),
+ query: Joi.object(),
+ body: Joi.object(),
}).min(1);
export interface Schema {

ADD
packages/middleware-joi/tsconfig.json
+{
+ "extends": "../../tsconfig.json",
+
+ "compilerOptions": {
+ "outDir": "./dist/",
+ "declaration": true,
+ "strictNullChecks": false,
+ "typeRoots": [
+ "./node_modules/@types",
+ "../../node_modules/@types"
+ ]
+ },
+
+ "inputs": [
+ "src"
+ ],
+
+ "include": [
+ "node_modules/@types",
+ "src/**/*.ts"
+ ],
+}

refactor: joi.middleware