acacode / acacode/swagger-typescript-api

Colon suffix in path segment (AIP-136 custom method, e.g. /v1/items:search) becomes a phantom required path param

Aberta
#1,833 0 comentários 3 reações 0 responsáveis Ver no GitHub
Linguagem predominante
TypeScript
Estrelas
4.1k
Forks
436
Métricas de merge de PRs
Nenhum PR com merge em 30d

Descrição

### Description

A colon inside a path segment is treated as an Express-style path parameter. Routes that use [AIP-136 custom methods](https://google.aip.dev/136), such as `/v1/items:search` or `/v1/items/{itemId}:archive`, come out with a required `string` argument that the spec does not declare. The colon suffix is also removed from the URL template.

This looks like the same problem as #532, which was filed as a question and is still open.

Version: 13.12.6. `parseRouteName` on `main` is unchanged.

### Reproduction

```json
{
"openapi": "3.0.3",
"info": { "title": "repro", "version": "1.0.0" },
"paths": {
"/v1/items:search": {
"post": {
"operationId": "searchItems",
"responses": { "200": { "description": "ok" } }
}
},
"/v1/items/{itemId}:archive": {
"post": {
"operationId": "archiveItem",
"parameters": [
{ "name": "itemId", "in": "path", "required": true, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "ok" } }
}
}
}
}
```

```js
await generateApi({ input: "spec.json", output: false, httpClientType: "fetch" });
```

### Actual

```ts
searchItems: (search: string, params: RequestParams = {}) =>
this.request({
path: `/v1/items${search}`,
method: "POST",
...params,
}),
archiveItem: (itemId: string, archive: string, params: RequestParams = {}) =>
this.request({
path: `/v1/items/${itemId}${archive}`,
method: "POST",
...params,
}),
```

`searchItems()` requires an argument that has no meaning in the spec. If the caller passes a value, it is appended to the URL as is. The only argument that gives the correct URL is the literal `":search"`.

### Expected

```ts
searchItems: (params: RequestParams = {}) =>
this.request({ path: `/v1/items:search`, method: "POST", ...params }),
archiveItem: (itemId: string, params: RequestParams = {}) =>
this.request({ path: `/v1/items/${itemId}:archive`, method: "POST", ...params }),
```

### Cause and a possible fix

In `src/schema-routes/schema-routes.ts`, `parseRouteName` matches a colon parameter at any position:

```js
/({[\w[\\\]^`][-_.\w]*})|(:[\w[\\\]^`][-_.\w]*:?)/g
```

An Express-style parameter always starts a path segment. Requiring a preceding `/` keeps `/users/:userId` working and stops matching custom-method suffixes:

```js
/({[\w[\\\]^`][-_.\w]*})|((?<=\/):[\w[\\\]^`][-_.\w]*:?)/g
```

| route | current | with lookbehind |
|---|---|---|
| `/v1/items:search` | `[":search"]` | none |
| `/v1/items/{itemId}:archive` | `["{itemId}", ":archive"]` | `["{itemId}"]` |
| `/users/:userId` | `[":userId"]` | `[":userId"]` |
| `/users/:userId/posts/:postId` | `[":userId", ":postId"]` | `[":userId", ":postId"]` |

### Workaround

We use an `onCreateRoute` hook. It finds path params whose `$match` starts with `:`, puts the literal back into `request.path`, and removes the param from `request.parameters` and from `requestParams.typeData.content`.

Guia de contribuição

Nenhum guia de contribuição indexado para este repositório

Avaliação

Esta issue ainda não foi avaliada.

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.