feat(middleware-io): eventValidator$ support for event codec
MODIFY
package.json
package.json
"packages/*"
],
"resolutions": {
- "fp-ts": "^2.4.3"
+ "fp-ts": "^2.4.3",
+ "io-ts": "^2.2.4"
}
}

MODIFY
packages/middleware-io/package.json
packages/middleware-io/package.json
},
"dependencies": {
"@types/json-schema": "^7.0.3",
- "io-ts": "~2.2.1"
+ "io-ts": "^2.2.4"
},
"devDependencies": {
"@marblejs/core": "^3.2.1"

MODIFY
packages/middleware-io/src/io.event.middleware.ts
packages/middleware-io/src/io.event.middleware.ts
import { Event, EventError, ValidatedEvent } from '@marblejs/core';
import { Observable, of, throwError } from 'rxjs';
-import { mergeMap, map, catchError } from 'rxjs/operators';
+import { pipe } from 'fp-ts/lib/pipeable';
+import { mergeMap, catchError, map } from 'rxjs/operators';
import { Schema, ValidatorOptions, validator$ } from './io.middleware';
import { IOError } from './io.error';
-export const eventValidator$ = <U extends Schema, T extends Event>
- (schema: U, options?: ValidatorOptions) => {
+export const eventValidator$ = <U extends Schema>(schema: U, options?: ValidatorOptions) => {
const eventValidator$ = validator$(schema, options);
- return (event$: Observable<T>) =>
- event$.pipe(
- mergeMap(event => eventValidator$(of(event.payload as any)).pipe(
- map(payload => event as ValidatedEvent<typeof payload>),
+ const validateByEventSchema = (incomingEvent: Event) =>
+ pipe(
+ of(incomingEvent),
+ eventValidator$,
+ map(decodedEvent => ({ ...incomingEvent, ...decodedEvent }) as ValidatedEvent),
+ );
+
+ const validateByPayloadSchema = (incomingEvent: Event) =>
+ pipe(
+ of(incomingEvent.payload),
+ eventValidator$,
+ map(payload => ({ ...incomingEvent, payload }) as ValidatedEvent),
+ );
+
+ const validate = (event: Event) =>
+ pipe(
+ schema.name === 'EventSchema'
+ ? validateByEventSchema(event)
+ : validateByPayloadSchema(event),
catchError((error: IOError) => throwError(
- new EventError(event as any, error.message, error.data),
+ new EventError(event, error.message, error.data),
)),
- )),
- );
+ ) as U['_A'] extends { type: string; payload: any }
+ ? Observable<ValidatedEvent<U['_A']['payload'], U['_A']['type']>>
+ : Observable<ValidatedEvent<U['_A']>>;
+
+ return (event$: Observable<Event>) =>
+ event$.pipe(mergeMap(validate));
};

MODIFY
packages/middleware-io/src/specs/io.event.middleware.spec.ts
packages/middleware-io/src/specs/io.event.middleware.spec.ts
import * as t from 'io-ts';
import { of } from 'rxjs';
+import { event } from '@marblejs/core';
import { eventValidator$ } from '../io.event.middleware';
-import { IOError } from '../io.error';
describe('#eventValidator$', () => {
- test('passes incoming event if input is successfuly validated', done => {
+ test('validates payload schema and passes incoming event if input is successfuly validated', async () => {
// given
const input = {
type: 'TEST_EVENT',
@@ -13,29 +13,60 @@ describe('#eventValidator$', () => {
age: 100,
}
};
+
const schema = t.type({
name: t.string,
age: t.number,
});
// when
- const stream$ = eventValidator$(schema)(of(input));
+ const result = await eventValidator$(schema)(of(input)).toPromise();
+
+ // then
+ expect(result.type).toEqual(input.type);
+ expect(result.payload).toEqual(input.payload);
+ });
+
+ test('validates event schema and passes incoming event if input is successfuly validated', async () => {
+ // given
+ const input = {
+ type: 'TEST_EVENT',
+ payload: {
+ name: 'test',
+ age: 100,
+ }
+ };
+
+ const schema = event('TEST_EVENT')(t.type({
+ name: t.string,
+ age: t.number,
+ }));
+
+ // when
+ const result = await eventValidator$(schema)(of(input)).toPromise();
+
+ // then
+ expect(result.type).toEqual(input.type);
+ expect(result.payload).toEqual(input.payload);
+ });
+
+ test('validates event schema with undefined payload and passes incoming event if input is successfuly validated', async () => {
+ // given
+ const input = {
+ type: 'TEST_EVENT',
+ };
+
+ const schema = event('TEST_EVENT')();
+
+ // when
+ const result = await eventValidator$(schema)(of(input)).toPromise();
// then
- stream$.subscribe(
- outgoingData => {
- expect(outgoingData.type).toEqual(input.type);
- expect(outgoingData.payload).toEqual(input.payload);
- done();
- },
- (error: IOError) => {
- fail(error.data);
- done();
- },
- );
+ expect(result.type).toEqual(input.type);
+ expect(result.payload).toBeUndefined();
});
- test('throws WebSocket error if incoming data are not valid', done => {
+ test('throws validation error if incoming data are not valid', async () => {
// given
const input = {
type: 'TEST_EVENT',
@@ -44,30 +75,23 @@ describe('#eventValidator$', () => {
age: '100',
}
};
+
const schema = t.type({
name: t.string,
age: t.number,
});
- const expectedError = [{
- path: 'age',
- expected: 'number',
- got: '"100"',
- }];
// when
- const stream$ = eventValidator$(schema)(of(input));
+ const result = eventValidator$(schema)(of(input)).toPromise();
// then
- stream$.subscribe(
- () => {
- fail('Datas should\'t be returned');
- done();
- },
- (error: IOError) => {
- expect(error.message).toEqual('Validation error');
- expect(error.data).toEqual(expectedError);
- done();
- },
- );
+ await expect(result).rejects.toEqual(expect.objectContaining({
+ message: 'Validation error',
+ data: [{
+ path: 'age',
+ expected: 'number',
+ got: '"100"',
+ }],
+ }));
});
});

MODIFY
yarn.lock
yarn.lock
@@ -4474,16 +4474,11 @@ inquirer@^7.0.0:
strip-ansi "^5.1.0"
through "^2.3.6"
-io-ts@^2.2.4:
+io-ts@^2.2.4, io-ts@~2.2.1:
version "2.2.4"
resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.2.4.tgz#c9b5390b8c60b9976ad03f670a6b976fe50d4083"
integrity sha512-a8A/3jekTgSPfQ+OG40sTiX2gohXZz89X1+TfKsWJ/vpA2dBilBkrGYPiDE8OaKMMG7xm5ma3SAZL5eNr4Bi2w==
-io-ts@~2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.2.1.tgz#d8cb21a23cf8fbe697d78445a65bab42c0eb467f"
- integrity sha512-RufppkTV58MfDKF2gXlHVBvi+WIyL7nUcAQMkJ1O3SiQcqwlxf6dBOJP/LV19UxHRkGKJZ6ImSj7LqYcELIejQ==
-
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"

feat(middleware-io): eventValidator$ support for event codec