Skip to content

Commit

Permalink
rework and matcher and regexp matcher
Browse files Browse the repository at this point in the history
  • Loading branch information
Bessonov committed Apr 17, 2024
1 parent 9ff4841 commit 5e98393
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 51 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
98 changes: 59 additions & 39 deletions src/matchers/AndMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<MR1>, Matched<MR2>, MR3 extends void ? void : Matched<MR3>]
and: [
MR1 extends MatchResultAny ? Matched<MR1> : Partial<void>,
MR2 extends MatchResultAny ? Matched<MR2> : Partial<void>,
MR3 extends MatchResultAny ? Matched<MR3> : Partial<void>,
MR4 extends MatchResultAny ? Matched<MR4> : Partial<void>,
MR5 extends MatchResultAny ? Matched<MR5> : Partial<void>,
]
}>

/**
* 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<AndMatcherResult<MR1, MR2, MR3>, P1 & P2 & P3> {
constructor(private readonly matchers: [Matcher<MR1, P1>, Matcher<MR2, P2>, Matcher<MR3, P3>?]) {
P3 = unknown,
MR4 extends MatchResultAny | never = never,
P4 = unknown,
MR5 extends MatchResultAny | never = never,
P5 = unknown,
>
implements Matcher<AndMatcherResult<
MR1,
MR2,
MR3,
MR4,
MR5
>, P1 & P2 & P3 & P4 & P5> {
constructor(private readonly matchers: [
Matcher<MR1, P1>?,
Matcher<MR2, P2>?,
Matcher<MR3, P3>?,
Matcher<MR4, P4>?,
Matcher<MR5, P5>?
]) {
this.match = this.match.bind(this)
}

match(params: P1 & P2 & P3): AndMatcherResult<MR1, MR2, MR3> {
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<MR1>,
Matched<MR2>,
MR3 extends void ? void : Matched<MR3>
],
},
}
}
results.push(result)
}

return {
matched: false,
matched: true,
result: {
// @ts-expect-error
and: results,
},
}
}
}
7 changes: 6 additions & 1 deletion src/matchers/EndpointMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ export class EndpointMatcher<
P extends EndpointMatcherInput = EndpointMatcherInput
>
implements Matcher<EndpointMatchResult<R>, P> {
private readonly matcher: AndMatcher<MethodMatchResult<[Method]>, RegExpUrlMatchResult<R>, P, P>
private readonly matcher: AndMatcher<
MethodMatchResult<[Method]>,
P,
RegExpUrlMatchResult<R>,
P
>
constructor(methods: Method | Method[], url: RegExp) {
this.match = this.match.bind(this)
this.matcher = new AndMatcher([
Expand Down
21 changes: 11 additions & 10 deletions src/matchers/RegExpUrlMatcher.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Url from 'urlite'
import type {
Matcher,
} from './Matcher'
Expand Down Expand Up @@ -33,16 +34,16 @@ implements Matcher<RegExpUrlMatchResult<R>, P> {
}

match({ req }: RegExpUrlMatcherInput): RegExpUrlMatchResult<R> {
if (req.url) {
for (const url of this.urls) {
const result = url.exec(req.url) as never as RegExpExecGroupArray<R>
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<R>
if (result !== null) {
return {
matched: true,
result: {
match: result,
},
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/matchers/__tests__/AndMatcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ it('both match', () => {
})
})

it('three not a match', () => {
const req = createRequest<ServerRequest>({
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<ServerRequest>({
url: '/test',
Expand Down
26 changes: 26 additions & 0 deletions src/matchers/__tests__/RegExpUrlMatcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ServerRequest>({
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<ServerRequest>({
url: '/test?query=not-matched',
}),
})
expect(result.matched).toBe(true)
if (result.matched) {
expect(result.result.match.input).toBe('/test')
}
})

0 comments on commit 5e98393

Please sign in to comment.