Skip to content

Commit

Permalink
feat(ExtractRouteParams): Add ExtractRouteParams (#24)
Browse files Browse the repository at this point in the history
* feat(ExtractRouteParams): Add ExtractRouteParams type

* test(ExtractRouteParams): Add ExtractRouteParams type test

* feat(ExtractRouteParams): Update ExtractRouteParams type

* test(ExtractRouteParams): Update ExtractRouteParams type test

* docs(ExtractRouteParams): Add ExtractRouteParams documentation
  • Loading branch information
haejunejung authored Aug 21, 2024
1 parent 8549d48 commit 85dc183
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 0 deletions.
9 changes: 9 additions & 0 deletions docs/.vitepress/en.mts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ export default defineConfig({
text: 'Special',
items: [{ text: 'Brand', link: '/reference/special/Brand' }],
},
{
text: 'URL Parser',
items: [
{
text: 'ExtractRouteParams',
link: '/reference/url-parser/ExtractRouteParams',
},
],
},
],
},
],
Expand Down
9 changes: 9 additions & 0 deletions docs/.vitepress/ko.mts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ export default defineConfig({
text: 'Special',
items: [{ text: 'Brand', link: '/ko/reference/special/Brand' }],
},
{
text: 'URL Parser',
items: [
{
text: 'ExtractRouteParams',
link: '/reference/url-parser/ExtractRouteParams',
},
],
},
],
},
],
Expand Down
70 changes: 70 additions & 0 deletions docs/ko/reference/url-parser/ExtractRouteParams.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# ExtractRouteParams\<Route, ParamType>

## 개요

이 타입은 다양한 제약 조건과 수정자를 고려하여 경로 패턴에서 파라미터 정의를 추출하도록 설계되었어요. 특정 패턴을 따르는 경로 문자열을 처리하고, 다양한 파라미터 수정자를 수용하여 TypeScript에서 파라미터를 정확하게 정의해요.

- **? (선택적 파라미터)**: 파라미터가 선택적이며, 존재할 수도 있고 존재하지 않을 수도 있음을 나타내요.
- **\* (0 또는 다수의 반복 파라미터)**: 파라미터가 0회 이상 반복될 수 있음을 나타내요.
- **+ (하나 이상의 반복 파라미터)**: 파라미터가 1회 이상 반복되어야 함을 나타내요.
- **'' (기본 필수 파라미터)**: 파라미터가 필수이며 정확히 한 번 나타나야 함을 나타내요.

## 문법

이 타입의 문법은 다소 길고 복잡해요. 문법에 대해서 더 알고 싶다면 [구현](https://github.com/haejunejung/ts-typekit/blob/main/source/url-parser/ExtractRouteParams.d.ts)을 확인해주세요.

```ts
export type ExtractRouteParams<
Route extends string,
ParamType = string | number | boolean,
>;
```

- **Route**: 경로 패턴 문자열이에요.
- **ParamType**: 파라미터 값의 타입을 나타내요.

## 예제

#### 예제 #1

```ts
type T0 = ExtractRouteParams<'/users/:userId/posts/:postId'>;
// Result: { userId: string } & { postId: string }

type T1 = ExtractRouteParams<'/users/:userId/posts/:postId', number>;
// Result: { userId: number } & { postId: number }

type T2 = ExtractRouteParams<'/users/:userId(\\d+)', number>;
// Result: { userId: number }

type T3 = ExtractRouteParams<'/search/:query+'>;
// Result: { query: string }

type T4 = ExtractRouteParams<'/items/:itemId/:category?'>;
// Result: { itemId: string } & { category?: string }
```

#### 예제 #2

```ts
function buildLink<Route extends string>(
template: Route,
params: ExtractRouteParams<Route, number>
): string {
return template.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => {
const value = params[key as keyof typeof params];

if (value !== undefined && value !== null) {
return value.toString();
}

throw new Error(`Missing parameter: ${key}`);
});
}

const url = buildLink('/users/:userId/products/:productId', {
userId: 1,
productId: 2,
});
// Result: '/users/1/products/2'
```
70 changes: 70 additions & 0 deletions docs/reference/url-parser/ExtractRouteParams.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# ExtractRouteParams\<Route, ParamType>

## Overview

This type is designed to extract parameter definitions from a route pattern, considering various constraints and modifiers. It processes route strings that adhere to specific patterns and accommodates different parameter modifiers to accurately define parameters in TypeScript.

- **? (Optional Parameter)**: Indicates that the parameter is optional and may or may not be present.
- **\* (Zero or More Repeating Paramters)**: Indicates that the parameter can appear zero or more times.
- **+ (One or More Repeating Parameters)**: Indicates that the parameter must appear one or more times.
- **'' (Default Required Parameter)**: Indicates that the paramter is required and must appear exactly once.

## Syntax

This type of syntax is rather long and complex. If you want to know more, please check [Implementation](https://github.com/haejunejung/ts-typekit/blob/main/source/url-parser/ExtractRouteParams.d.ts)

```ts
export type ExtractRouteParams<
Route extends string,
ParamType = string | number | boolean,
>;
```

- **Route**: The route pattern string.
- **ParamType**: The type of the parameter values.

## Examples

#### Example #1

```ts
type T0 = ExtractRouteParams<'/users/:userId/posts/:postId'>;
// Result: { userId: string } & { postId: string }

type T1 = ExtractRouteParams<'/users/:userId/posts/:postId', number>;
// Result: { userId: number } & { postId: number }

type T2 = ExtractRouteParams<'/users/:userId(\\d+)', number>;
// Result: { userId: number }

type T3 = ExtractRouteParams<'/search/:query+'>;
// Result: { query: string }

type T4 = ExtractRouteParams<'/items/:itemId/:category?'>;
// Result: { itemId: string } & { category?: string }
```

#### Example #2

```ts
function buildLink<Route extends string>(
template: Route,
params: ExtractRouteParams<Route, number>
): string {
return template.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => {
const value = params[key as keyof typeof params];

if (value !== undefined && value !== null) {
return value.toString();
}

throw new Error(`Missing parameter: ${key}`);
});
}

const url = buildLink('/users/:userId/products/:productId', {
userId: 1,
productId: 2,
});
// Result: '/users/1/products/2'
```
1 change: 1 addition & 0 deletions source/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './basic';
export * from './string';
export * from './special';
export * from './url-parser';
74 changes: 74 additions & 0 deletions source/url-parser/ExtractRouteParams.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* @description
* Defines the different types of parameter modifiers used in URL route patterns.
* These modifiers determine the nature and constraints of parameters within the route.
*
* - `?` (Optional Parameter): Indicates that the parameter is optional and may or may not be present.
* - `*` (Zero or More Repeating Paramters): Indicates that the parameter can appear zero or more times.
* - `+` (One or More Repeating Parameters): Indicates that the parameter must appear one or more times.
* - `''` (Default Required Parameter): Indicates that the paramter is required and must appear exactly once.
*/
type ParameterModifier = '?' | '*' | '+' | '';

/**
* @description
* Extracts route optional parameters from a given Route pattern.
*
* @template {string} Route - The route pattern string.
* @template {string | number | boolean} ParamType - The type of the parameter values.
*/

type ExtractRouteOptionalParam<
Route extends string,
ParamType = string | number | boolean,
> = Route extends `${infer Param}?`
? { [K in Param]?: ParamType }
: Route extends `${infer Param}*`
? { [K in Param]?: ParamType }
: Route extends `${infer Param}+`
? { [K in Param]: ParamType }
: { [K in Route]: ParamType };

/**
* @description
* Extract parameters from a route pattern, including handling parameter constraints and modifiers.
*
* This type recursively parses a route string and extracts parameters, considering optional('?'),
* repeating('+', '*'), and required parameter modifiers.
*
* @template {string} Route - The route pattern string.
* @template {string | number | boolean} ParamType - The type of the parameter values.
*
* @example
* type T0 = ExtractRouteParams<'/users/:userId/posts/:postId'>;
* // Result: { userId: string } & { postId: string }
*
* type T1 = ExtractRouteParams<'/users/:userId/posts/:postId', number>;
* // Result: { userId: number } & { postId: number }
*
* type T2 = ExtractRouteParams<'/users/:userId(\\d+)', number>;
* // Result: { userId: number }
*
* type T3 = ExtractRouteParams<'/search/:query+'>;
* // Result: { query: string }
*
* type T4 = ExtractRouteParams<'/items/:itemId/:category?'>
* // Result: { itemId: string } & { category?: string }
*/

export type ExtractRouteParams<
Route extends string,
ParamType = string | number | boolean,
> = string extends Route
? { [K in Route]: ParamType }
: Route extends `${infer Start}:${infer ParamWithOptionalRegExp}/${infer Rest}`
? ParamWithOptionalRegExp extends `${infer Param}(${infer _RegExp})${infer Modifier extends ParameterModifier}`
? ExtractRouteOptionalParam<`${Param}${Modifier}`, ParamType> &
ExtractRouteParams<Rest, ParamType>
: ExtractRouteOptionalParam<ParamWithOptionalRegExp, ParamType> &
ExtractRouteParams<Rest, ParamType>
: Route extends `${infer Start}:${infer ParamWithOptionalRegExp}`
? ParamWithOptionalRegExp extends `${infer Param}(${infer _RegExp})${infer Modifier extends ParameterModifier}`
? ExtractRouteOptionalParam<`${Param}${Modifier}`, ParamType>
: ExtractRouteOptionalParam<`${ParamWithOptionalRegExp}`, ParamType>
: {};
1 change: 1 addition & 0 deletions source/url-parser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { ExtractRouteParams } from './ExtractRouteParams';
37 changes: 37 additions & 0 deletions test-d/url-parser/ExtractRouteParams.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ExtractRouteParams } from '@/url-parser';
import { expectType } from 'tsd';

declare function extractRouteParams<
Route extends string,
ParamType = string,
>(): ExtractRouteParams<Route, ParamType>;

// Should correclty handle basic pattern
expectType<{ userId: string } & { postId: string }>(
extractRouteParams<'/users/:userId/posts/:postId'>()
);

expectType<{ userId: number } & { postId: number }>(
extractRouteParams<'/users/:userId/posts/:postId', number>()
);

expectType<{ userId: boolean } & { postId: boolean }>(
extractRouteParams<'/users/:userId/posts/:postId', boolean>()
);

// Should correctly handle optioanl parameter
expectType<{ userId?: string }>(extractRouteParams<'/users/:userId?'>());

// Should correctly handle repetitive parameters
expectType<{ filePath: string }>(extractRouteParams<'/files/:filePath+'>());
expectType<{ filePath?: string }>(extractRouteParams<'/files/:filePath*'>());
expectType<{ username: string }>(
extractRouteParams<'/users/:username([a-z]+)'>()
);

// Should correctly handle regular expression parameters
expectType<{ userId: string }>(extractRouteParams<'/users/:userId(\\d+)'>());

// Should correctly handle complex pattern
expectType<{ userId?: string }>(extractRouteParams<'/users/:userId(\\d+)?'>());
expectType<{ filePath: string }>(extractRouteParams<'/files/:filePath(.*)+'>());

0 comments on commit 85dc183

Please sign in to comment.