fix(core): Effect matching hazard
MODIFY
packages/core/src/effects/effects.combiner.ts
packages/core/src/effects/effects.combiner.ts
-import { merge, of } from 'rxjs';
-import { mergeMap, switchMap } from 'rxjs/operators';
+import { concat, of } from 'rxjs';
+import { concatMap, switchMap } from 'rxjs/operators';
import { HttpRequest } from '../http.interface';
import { matchPath } from '../operators';
import { isGroup } from './effects.helpers';
@@ -10,12 +10,12 @@ export const combineEffects: EffectCombiner = effects => res => req => {
const mappedEffects = effects.map(effect => isGroup(effect)
? req$.pipe(
matchPath(effect.path, { suffix: '/:foo*', combiner: true }),
- mergeMap(combineEffects(effect.effects)(res))
+ concatMap(combineEffects(effect.effects)(res))
)
: effect(req$, res, undefined)
);
- return merge(...mappedEffects);
+ return concat(...mappedEffects);
};
export const combineMiddlewareEffects: MiddlewareCombiner = effects => res => req => {

MODIFY
packages/core/src/http.interface.ts
packages/core/src/http.interface.ts
@@ -2,9 +2,11 @@ import * as http from 'http';
export interface HttpRequest extends http.IncomingMessage {
body?: any;
- matchers?: string[];
params?: RouteParameters;
query?: QueryParameters;
+ matchers?: string[];
+ matchPath: boolean;
+ matchType: boolean;
[key: string]: any;
}

MODIFY
packages/core/src/operators/matchPath/matchPath.operator.spec.ts
packages/core/src/operators/matchPath/matchPath.operator.spec.ts
@@ -9,7 +9,8 @@ const reqMatched = (
matchers: string[] = [],
params: RouteParameters = {},
query: QueryParameters = {},
-) => ({ url, matchers, params, query } as any as HttpRequest);
+ matchPath = true,
+) => ({ url, matchers, params, query, matchPath } as any as HttpRequest);
describe('matchPath operator', () => {
let operators;
@@ -88,15 +89,13 @@ describe('matchPath operator', () => {
operators = [matchPath('/test', { combiner: true })];
Marbles.assert(operators, [
['-a--', { a: req('/test') }],
- ['-a--', { a: reqMatched('/test', ['/test']) }],
+ ['-a--', { a: reqMatched('/test', ['/test'], undefined, undefined, false) }],
]);
- operators = [
- matchPath('/test/:id/foo', { combiner: true, suffix: '/:foo*' })
- ];
+ operators = [matchPath('/test/:id/foo', { combiner: true, suffix: '/:foo*' })];
Marbles.assert(operators, [
['-a--', { a: req('/test/2/foo') }],
- ['-a--', { a: reqMatched('/test/2/foo', ['/test/:id/foo'], { id: '2' }) }],
+ ['-a--', { a: reqMatched('/test/2/foo', ['/test/:id/foo'], { id: '2' }, undefined, false) }],
]);
});
});

MODIFY
packages/core/src/operators/matchPath/matchPath.operator.ts
packages/core/src/operators/matchPath/matchPath.operator.ts
@@ -11,13 +11,15 @@ type MatcherOpts = {
combiner?: boolean;
};
-export const matchPath = (path: string, opts: MatcherOpts = {}) => (
- source$: Observable<HttpRequest>
-) =>
+const isRequestMatched = (req: HttpRequest) =>
+ !(req.matchPath && req.matchType);
+
+export const matchPath = (path: string, opts: MatcherOpts = {}) => (source$: Observable<HttpRequest>) =>
source$.pipe(
tap(req => (req.query = req.query || {})),
tap(req => (req.params = req.params || {})),
tap(req => (req.matchers = req.matchers || [])),
+ filter(isRequestMatched),
filter(req => {
const matcher = matcherFactory(req.matchers!, path, opts.suffix);
const url = removeQueryParams(req.url!);
@@ -25,5 +27,6 @@ export const matchPath = (path: string, opts: MatcherOpts = {}) => (
}),
tap(req => (req.params = urlParamsFactory(req, path))),
tap(req => (req.query = queryParamsFactory(req.url!))),
- tap(req => opts.combiner && req.matchers!.push(path))
+ tap(req => opts.combiner && req.matchers!.push(path)),
+ tap(req => (req.matchPath = !opts.combiner ? true : false)),
);

MODIFY
packages/core/src/operators/matchType/matchType.operator.spec.ts
packages/core/src/operators/matchType/matchType.operator.spec.ts
@@ -2,23 +2,24 @@ import { Marbles } from '../../../../util/marbles.spec-util';
import { HttpRequest } from '../../http.interface';
import { matchType } from './matchType.operator';
-const createMockReq = (method: string) => ({ method } as HttpRequest);
+const mockReq = (method: string) => ({ method } as HttpRequest);
+const mockReqMatched = (method: string) => ({ method, matchType: true } as HttpRequest);
describe('matchType operator', () => {
it('filters GET request stream', () => {
const operators = [matchType('GET')];
Marbles.assert(operators, [
- ['-a-b---', { a: createMockReq('GET'), b: createMockReq('POST') }],
- ['-a-----', { a: createMockReq('GET') }],
+ ['-a-b---', { a: mockReq('GET'), b: mockReq('POST') }],
+ ['-a-----', { a: mockReqMatched('GET') }],
]);
});
it('filters POST request stream', () => {
const operators = [matchType('POST')];
Marbles.assert(operators, [
- ['-a-b---', { a: createMockReq('GET'), b: createMockReq('POST') }],
- ['---b---', { b: createMockReq('POST') }],
+ ['-a-b---', { a: mockReq('GET'), b: mockReq('POST') }],
+ ['---b---', { b: mockReqMatched('POST') }],
]);
});

MODIFY
packages/core/src/operators/matchType/matchType.operator.ts
packages/core/src/operators/matchType/matchType.operator.ts
import { Observable } from 'rxjs';
-import { filter } from 'rxjs/operators';
+import { filter, tap } from 'rxjs/operators';
import { HttpMethod, HttpRequest } from '../../http.interface';
export const matchType = (method: HttpMethod) => (source$: Observable<HttpRequest>) =>
source$.pipe(
- filter(req => req.method === method)
+ filter(req => req.method === method),
+ tap(req => req.matchType = true),
);

fix(core): Effect matching hazard