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

Abierto
#1,833 0 comentarios 3 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
TypeScript
Estrellas
4.1k
Forks
436
Métricas de merge de PR
Sin PR fusionados en 30 d

Descripción

### 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`.

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.