From 5e9839363830725148214e2f200d7dcc8503376a Mon Sep 17 00:00:00 2001 From: Anton Bessonov Date: Wed, 17 Apr 2024 20:16:25 +0000 Subject: [PATCH] rework and matcher and regexp matcher --- package.json | 2 +- src/matchers/AndMatcher.ts | 98 +++++++++++-------- src/matchers/EndpointMatcher.ts | 7 +- src/matchers/RegExpUrlMatcher.ts | 21 ++-- src/matchers/__tests__/AndMatcher.test.ts | 16 +++ .../__tests__/RegExpUrlMatcher.test.ts | 26 +++++ 6 files changed, 119 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index 3ffbb05..30fb5d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bessonovs/node-http-router", - "version": "2.3.0", + "version": "3.0.0", "description": "Extensible http router for node and micro", "keywords": [ "router", diff --git a/src/matchers/AndMatcher.ts b/src/matchers/AndMatcher.ts index 349a4f9..e48d7c0 100644 --- a/src/matchers/AndMatcher.ts +++ b/src/matchers/AndMatcher.ts @@ -9,61 +9,81 @@ import { } from './MatchResult' export type AndMatcherResult< - MR1 extends MatchResultAny, - MR2 extends MatchResultAny, - MR3 extends MatchResultAny | never = never + MR1 extends MatchResultAny | never = never, + MR2 extends MatchResultAny | never = never, + MR3 extends MatchResultAny | never = never, + MR4 extends MatchResultAny | never = never, + MR5 extends MatchResultAny | never = never, > = MatchResult<{ - and: [Matched, Matched, MR3 extends void ? void : Matched] + and: [ + MR1 extends MatchResultAny ? Matched : Partial, + MR2 extends MatchResultAny ? Matched : Partial, + MR3 extends MatchResultAny ? Matched : Partial, + MR4 extends MatchResultAny ? Matched : Partial, + MR5 extends MatchResultAny ? Matched : Partial, + ] }> /** * Match if every matcher matches */ export class AndMatcher< - MR1 extends MatchResultAny, - MR2 extends MatchResultAny, - P1, - P2, + MR1 extends MatchResultAny | never = never, + P1 = unknown, + MR2 extends MatchResultAny | never = never, + P2 = unknown, MR3 extends MatchResultAny | never = never, - P3 = unknown> -implements Matcher, P1 & P2 & P3> { - constructor(private readonly matchers: [Matcher, Matcher, Matcher?]) { + P3 = unknown, + MR4 extends MatchResultAny | never = never, + P4 = unknown, + MR5 extends MatchResultAny | never = never, + P5 = unknown, +> +implements Matcher, P1 & P2 & P3 & P4 & P5> { + constructor(private readonly matchers: [ + Matcher?, + Matcher?, + Matcher?, + Matcher?, + Matcher? + ]) { this.match = this.match.bind(this) } - match(params: P1 & P2 & P3): AndMatcherResult { - const [matcher1, matcher2, matcher3] = this.matchers - - const result1 = matcher1.match(params) - if (isMatched(result1)) { - const result2 = matcher2.match(params) - if (isMatched(result2)) { - if (matcher3) { - const result3 = matcher3.match(params) - if (isMatched(result3)) { - return { - matched: true, - result: { - and: [result1, result2, result3 as never], - }, - } + match(params: P1 & P2 & P3 & P4 & P5): AndMatcherResult< + MR1 | never, + MR2 | never, + MR3 | never, + MR4 | never, + MR5 | never + > { + type Result = MR1 | MR2 | MR3 | MR4 | MR5 | undefined + const results: Result[] = [] + for (const matcher of this.matchers) { + let result: Result + if (matcher) { + result = matcher.match(params) + if (isMatched(result) === false) { + return { + matched: false, } } - - return { - matched: true, - result: { - and: [result1, result2] as unknown as [ - Matched, - Matched, - MR3 extends void ? void : Matched - ], - }, - } } + results.push(result) } + return { - matched: false, + matched: true, + result: { + // @ts-expect-error + and: results, + }, } } } diff --git a/src/matchers/EndpointMatcher.ts b/src/matchers/EndpointMatcher.ts index a11d361..b6395a9 100644 --- a/src/matchers/EndpointMatcher.ts +++ b/src/matchers/EndpointMatcher.ts @@ -45,7 +45,12 @@ export class EndpointMatcher< P extends EndpointMatcherInput = EndpointMatcherInput > implements Matcher, P> { - private readonly matcher: AndMatcher, RegExpUrlMatchResult, P, P> + private readonly matcher: AndMatcher< + MethodMatchResult<[Method]>, + P, + RegExpUrlMatchResult, + P + > constructor(methods: Method | Method[], url: RegExp) { this.match = this.match.bind(this) this.matcher = new AndMatcher([ diff --git a/src/matchers/RegExpUrlMatcher.ts b/src/matchers/RegExpUrlMatcher.ts index 6be47ee..f11d094 100644 --- a/src/matchers/RegExpUrlMatcher.ts +++ b/src/matchers/RegExpUrlMatcher.ts @@ -1,3 +1,4 @@ +import Url from 'urlite' import type { Matcher, } from './Matcher' @@ -33,16 +34,16 @@ implements Matcher, P> { } match({ req }: RegExpUrlMatcherInput): RegExpUrlMatchResult { - if (req.url) { - for (const url of this.urls) { - const result = url.exec(req.url) as never as RegExpExecGroupArray - if (result !== null) { - return { - matched: true, - result: { - match: result, - }, - } + // original URL returns '/' if pathname is empty + const pathname = Url.parse(req.url).pathname ?? '/' + for (const url of this.urls) { + const result = url.exec(pathname) as unknown as RegExpExecGroupArray + if (result !== null) { + return { + matched: true, + result: { + match: result, + }, } } } diff --git a/src/matchers/__tests__/AndMatcher.test.ts b/src/matchers/__tests__/AndMatcher.test.ts index 3f3e912..2a93e43 100644 --- a/src/matchers/__tests__/AndMatcher.test.ts +++ b/src/matchers/__tests__/AndMatcher.test.ts @@ -80,6 +80,22 @@ it('both match', () => { }) }) +it('three not a match', () => { + const req = createRequest({ + url: '/test', + }) + + const result = new AndMatcher([ + new MethodMatcher(['GET']), + new ExactUrlPathnameMatcher(['/test']), + new BooleanMatcher(false), + ]).match({ req }) + + expect(result).toStrictEqual({ + matched: false, + }) +}) + it('three match', () => { const req = createRequest({ url: '/test', diff --git a/src/matchers/__tests__/RegExpUrlMatcher.test.ts b/src/matchers/__tests__/RegExpUrlMatcher.test.ts index fda6f56..209878c 100644 --- a/src/matchers/__tests__/RegExpUrlMatcher.test.ts +++ b/src/matchers/__tests__/RegExpUrlMatcher.test.ts @@ -68,3 +68,29 @@ it('match group', () => { expect(result.result.match.groups.groupId).toBe('123') } }) + +it(`url is empty`, () => { + const result = new RegExpUrlMatcher([/^\/$/]) + .match({ + req: createRequest({ + url: '', + }), + }) + expect(result.matched).toBe(true) + if (result.matched) { + expect(result.result.match.input).toBe('/') + } +}) + +it(`query string isn't matched`, () => { + const result = new RegExpUrlMatcher([/^\/test$/]) + .match({ + req: createRequest({ + url: '/test?query=not-matched', + }), + }) + expect(result.matched).toBe(true) + if (result.matched) { + expect(result.result.match.input).toBe('/test') + } +})