Skip to content

Commit

Permalink
feat: improve PathParams type to support interface (#1219)
Browse files Browse the repository at this point in the history
* feat: improve PathParams type to support interface

* test: add

* refactor: improve PathParams type

* fix: remove optional from properties of PathParams type

* chore: name generics, move path params test

Co-authored-by: Artem Zakharchenko <kettanaito@gmail.com>
  • Loading branch information
kotarella1110 and kettanaito committed May 17, 2022
1 parent 05e5a13 commit b70266f
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 65 deletions.
2 changes: 1 addition & 1 deletion src/rest.ts
Expand Up @@ -12,7 +12,7 @@ function createRestHandler<Method extends RESTMethods | RegExp>(
) {
return <
RequestBodyType extends DefaultBodyType = DefaultBodyType,
Params extends PathParams = PathParams,
Params extends PathParams<keyof Params> = PathParams,
ResponseBody extends DefaultBodyType = DefaultBodyType,
>(
path: Path,
Expand Down
4 changes: 3 additions & 1 deletion src/utils/matching/matchRequestUrl.ts
Expand Up @@ -3,7 +3,9 @@ import { getCleanUrl } from '@mswjs/interceptors/lib/utils/getCleanUrl'
import { normalizePath } from './normalizePath'

export type Path = string | RegExp
export type PathParams = Record<string, string | ReadonlyArray<string>>
export type PathParams<KeyType extends keyof any = string> = {
[ParamName in KeyType]: string | ReadonlyArray<string>
}

export interface Match {
matches: boolean
Expand Down
83 changes: 46 additions & 37 deletions test/msw-api/setup-server/scenarios/generator.node.test.ts
Expand Up @@ -3,52 +3,61 @@ import { rest } from 'msw'
import { setupServer } from 'msw/node'

const server = setupServer(
rest.get('/polling/:maxCount', function* (req, res, ctx) {
const maxCount = parseInt(req.params.maxCount)
let count = 0
rest.get<never, { maxCount: string }>(
'/polling/:maxCount',
function* (req, res, ctx) {
const maxCount = parseInt(req.params.maxCount)
let count = 0

while (count < maxCount) {
count += 1
yield res(
ctx.json({
status: 'pending',
count,
}),
)
}

while (count < maxCount) {
count += 1
yield res(
return res(
ctx.json({
status: 'pending',
status: 'complete',
count,
}),
)
}

return res(
ctx.json({
status: 'complete',
count,
}),
)
}),

rest.get('/polling/once/:maxCount', function* (req, res, ctx) {
const maxCount = parseInt(req.params.maxCount)
let count = 0

while (count < maxCount) {
count += 1
yield res(
},
),

rest.get<never, { maxCount: string }>(
'/polling/once/:maxCount',
function* (req, res, ctx) {
const maxCount = parseInt(req.params.maxCount)
let count = 0

while (count < maxCount) {
count += 1
yield res(
ctx.json({
status: 'pending',
count,
}),
)
}

return res.once(
ctx.json({
status: 'pending',
status: 'complete',
count,
}),
)
}

return res.once(
ctx.json({
status: 'complete',
count,
}),
)
}),
rest.get('/polling/once/:maxCount', (req, res, ctx) => {
return res(ctx.json({ status: 'done' }))
}),
},
),
rest.get<never, { maxCount: string }>(
'/polling/once/:maxCount',
(req, res, ctx) => {
return res(ctx.json({ status: 'done' }))
},
),
)

beforeAll(() => {
Expand Down
55 changes: 55 additions & 0 deletions test/typings/path-params.test-d.ts
@@ -0,0 +1,55 @@
import { rest } from 'msw'

rest.get<never, { userId: string }>('/user/:userId', (req) => {
req.params.userId

// @ts-expect-error `unknown` is not defined in the request params type.
req.params.unknown
})

rest.get<never>('/user/:id', (req, res, ctx) => {
const { userId } = req.params

return res(
ctx.body(
// @ts-expect-error "userId" parameter is not annotated
// and is ambiguous (string | string[]).
userId,
),
)
})

rest.get<
never,
// @ts-expect-error Path parameters are always strings.
// Parse them to numbers in the resolver if necessary.
{ id: number }
>('/posts/:id', () => null)

/**
* Using interface as path parameters type.
*/
interface UserParamsInterface {
userId: string
}

rest.get<never, UserParamsInterface>('/user/:userId', (req) => {
req.params.userId.toUpperCase()

// @ts-expect-error Unknown path parameter "foo".
req.params.foo
})

/**
* Using type as path parameters type.
*/
type UserParamsType = {
userId: string
}

rest.get<never, UserParamsType>('/user/:userId', (req) => {
req.params.userId.toUpperCase()

// @ts-expect-error Unknown path parameter "foo".
req.params.foo
})
26 changes: 0 additions & 26 deletions test/typings/rest.test-d.ts
Expand Up @@ -17,13 +17,6 @@ rest.get<never, never, { postCount: number }>('/user', (req, res, ctx) => {
return res(ctx.json({ postCount: 2 }))
})

rest.get<never, { userId: string }>('/user/:userId', (req) => {
req.params.userId

// @ts-expect-error `unknown` is not defined in the request params type.
req.params.unknown
})

rest.post<// @ts-expect-error `null` is not a valid request body type.
null>('/submit', () => null)

Expand All @@ -43,25 +36,6 @@ rest.get<never, never, string | string[]>('/user', (req, res, ctx) =>
res(ctx.json('hello')),
)

rest.get<never>('/user/:id', (req, res, ctx) => {
const { userId } = req.params

return res(
ctx.body(
// @ts-expect-error "userId" parameter is not annotated
// and is ambiguous (string | string[]).
userId,
),
)
})

rest.get<
never,
// @ts-expect-error Path parameters are always strings.
// Parse them to numbers in the resolver if necessary.
{ id: number }
>('/posts/:id', () => null)

rest.head('/user', (req) => {
// @ts-expect-error GET requests cannot have body.
req.body.toString()
Expand Down

0 comments on commit b70266f

Please sign in to comment.