Skip to content

Commit

Permalink
QueryStringMatcher WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Bessonov committed Apr 20, 2024
1 parent e9d74ec commit 7a2920e
Show file tree
Hide file tree
Showing 17 changed files with 406 additions and 7 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ What's next?
Some ideas:
- `RegExpUrlMatcher` and `EndpointMatcher` will probably get an additional parameter to describe the data instead of do it with type arguments and get only string. This parameter probably will looks like `{userId: numberValidator()}`.
- `ExactQueryMatcher` will probably get validators too. This will improve extensibility and powerfulness a lot. In addition, it will probably handle PHP style arrays such as `users[]=1&users[]=2`.
- Addition of some sort of `ajv`-based JSON Schema Matcher.

Trying out
----------
While this state is highly experimental, you can try it out with `pnpm add github:Bessonov/node-http-router#next`.

After that, you can update to the latest version with `pnpm update -r @bessonovs/node-http-router`.
2 changes: 1 addition & 1 deletion dist/Router.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Matched, type Matcher } from './matchers/index.js';
import { type MatchResult, type MatchResultAny } from './matchers/MatchResult.js';
import type { MatchResult, MatchResultAny } from './matchers/MatchResult.js';
interface HandlerParams<MR extends MatchResultAny, D> {
match: Matched<MR>;
data: D;
Expand Down
1 change: 0 additions & 1 deletion dist/Router.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/Router.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './middlewares/index.js';
export { type Handler, type Route, type MatchedHandler, Router, } from './Router.js';
export { type ServerRequest, toServerRequest, } from './node/ServerRequest.js';
export { NodeHttpRouter, type NodeHttpRouterParams, } from './node/NodeHttpRouter.js';
export * from './validators.js';
1 change: 1 addition & 0 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions dist/matchers/QueryStringMatcher.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Matcher } from './Matcher.js';
import type { MatchResult } from './MatchResult.js';
import type { Validator } from '../validators.js';
export interface QueryStringMatcherInput {
req: {
url: string;
};
}
type QueryMatch = Record<string, Validator<string[], any>>;
type QueryResult<T extends QueryMatch> = {
[P in keyof T]: T[P] extends Validator<string[], infer O> ? O : never;
};
export type QueryStringMatchResult<U extends QueryMatch> = MatchResult<{
query: QueryResult<U>;
}>;
declare class QueryStringMatcher<U extends QueryMatch, P extends QueryStringMatcherInput> implements Matcher<QueryStringMatchResult<U>, P> {
private readonly listConfig;
constructor(config: U);
match: ({ req }: P) => QueryStringMatchResult<U>;
}
export declare function queryString<U extends QueryMatch, P extends QueryStringMatcherInput>(config: U): QueryStringMatcher<U, P>;
export {};
39 changes: 39 additions & 0 deletions dist/matchers/QueryStringMatcher.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dist/matchers/QueryStringMatcher.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions dist/validators.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface Validator<I, O> {
(): {
match(value: I): boolean;
parse(value: I): O;
};
}
export declare const trueVal: Validator<string[], string[]>;
export declare function requiredVal<I>(): Validator<I, NonNullable<NoInfer<I>>>;
export declare function getNthVal<I extends any>(num?: number): Validator<I[], NoInfer<I> | undefined>;
export declare function mapToNumVal<I extends string[]>(): Validator<I, number[]>;
export declare function oneOfVal<const I extends string | number | boolean>(values: I[]): Validator<I, I>;
export declare function atLeastOneVal<const I extends string | number | boolean>(expectedValues: I[]): Validator<I[], I[]>;
export declare function toNumVal<I extends string>(): Validator<I, number>;
export declare function chain<I extends any, O1 extends any, O2 extends any, O3 extends any, O extends any>(val1: Validator<I, O1>, val2: Validator<O1, O2>, val3: Validator<O2, O3>, val4: Validator<O3, O>): Validator<I, O>;
export declare function chain<I extends any, O1 extends any, O2 extends any, O extends any>(val1: Validator<I, O1>, val2: Validator<O1, O2>, val3: Validator<O2, O>): Validator<I, O>;
export declare function chain<I extends any, O1 extends any, O extends any>(val1: Validator<I, O1>, val2: Validator<O1, O>): Validator<I, O>;
export declare function chain<I extends any, O extends any>(val1: Validator<I, O>): Validator<I, O>;
109 changes: 109 additions & 0 deletions dist/validators.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dist/validators.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import {
type Matcher,
isMatched,
} from './matchers/index.js'
import {
type MatchResult,
type MatchResultAny,
import type {
MatchResult,
MatchResultAny,
} from './matchers/MatchResult.js'

interface HandlerParams<MR extends MatchResultAny, D> {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export {
NodeHttpRouter,
type NodeHttpRouterParams,
} from './node/NodeHttpRouter.js'
export * from './validators.js'
78 changes: 78 additions & 0 deletions src/matchers/QueryStringMatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import Url from 'urlite'
import type {
Matcher,
} from './Matcher.js'
import type {
MatchResult,
} from './MatchResult.js'
import type {
Validator,
} from '../validators.js'

export interface QueryStringMatcherInput {
req: {
url: string
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyValidator = Validator<string[], any>

type QueryMatch = Record<string, AnyValidator>

type QueryResult<T extends QueryMatch> = {
[P in keyof T]: T[P] extends Validator<string[], infer O> ? O : never
}

export type QueryStringMatchResult<U extends QueryMatch> = MatchResult<{
query: QueryResult<U>
}>

class QueryStringMatcher<
U extends QueryMatch,
P extends QueryStringMatcherInput
> implements Matcher<QueryStringMatchResult<U>, P> {
private readonly listConfig: [string, AnyValidator][]

constructor(config: U) {
this.listConfig = Object.entries(config)
}

match = ({ req }: P): QueryStringMatchResult<U> => {
// original URL returns '' if search is empty
const search = Url.parse(req.url).search ?? ''
const queryParams = new URLSearchParams(search)

const queryResult = {} as QueryResult<U>
for (const [key, validatorFactory] of this.listConfig) {
if (queryParams.has(key) === false) {
return {
matched: false,
}
}
const params = queryParams.getAll(key)
const validator = validatorFactory()
if (validator.match(params) === false) {
return {
matched: false,
}
}
// @ts-ignore
queryResult[key] = validator.parse(params)
}

return {
matched: true,
result: {
query: queryResult,
},
}
}
}

export function queryString<
U extends QueryMatch,
P extends QueryStringMatcherInput
>(config: U): QueryStringMatcher<U, P> {
return new QueryStringMatcher(config)
}
Loading

0 comments on commit 7a2920e

Please sign in to comment.