# ADR-0007: DTI typed path params дэмжих

## Status

Accepted

## Context

DTI action contract нь `path`, `query`, `body`, `result` тодорхойлдог. Гэхдээ `/users/:userId` болон `/tenants/:tenantId/users/:userId` хэлбэрийн REST resource identifier-үүдийг typed contract-аар дамжуулах боломж одоогоор байхгүй.

Үүний улмаас:

- client URL-ийг гараар interpolate хийдэг;
- server `req.params`-ийг action contract-оос тусдаа parse хийдэг;
- path values client/server type inference-д орохгүй;
- path values runtime validation болон request signing-д нэг дүрмээр оролцохгүй.

Энэ ADR нь single болон nested resource endpoint-д нэг эсвэл олон required named path params нэмэх шийдвэрийг тодорхойлно. Wildcard path param дэмжихгүй.

## Decision

### Public contract

`createAction`-ийн schema object дээр `params` field нэмнэ.

```ts
import { z } from "zod";
import { createAction } from "@napp/dti-core";

export const tenantUserRead = createAction("tenantUserRead", {
    params: z.object({
        tenantId: z.string().uuid(),
        userId: z.string().uuid(),
    }),
    result: z.object({
        id: z.string(),
        name: z.string(),
    }),
}, {
    path: "/tenants/:tenantId/users/:userId",
    method: "GET",
});
```

`params` schema байгаа үед client call болон server handler-ийн `params` field required, typed байна. Route дээрх placeholder бүр `params` schema-д ижил нэртэй required field байна.

### Client usage

```ts
const user = await client.call(tenantUserRead, {
    params: {
        tenantId: "123e4567-e89b-12d3-a456-426614174000",
        userId: "550e8400-e29b-41d4-a716-446655440000",
    },
});
```

Client дараах URL-ийг үүсгэнэ:

```text
GET /tenants/123e4567-e89b-12d3-a456-426614174000/users/550e8400-e29b-41d4-a716-446655440000
```

Client behavior:

1. `params` object-ийг Zod schema-аар parse хийнэ.
2. Parsed field бүрийг ижил нэртэй route placeholder-д оруулна.
3. Field бүрийн value-г `encodeURIComponent` ашиглан тусад нь URL encode хийнэ.
4. Query байгаа бол resolved path-ийн дараа query string нэмнэ.
5. Parsed `params`-ийг `validateParam` болон `signature` callback-д дамжуулна.

Client validation fail үед HTTP request илгээхгүй.

### Server usage

```ts
dti.action(tenantUserRead, async ({ params }) => {
    return {
        id: params.userId,
        name: "Bat",
    };
});
```

Server behavior:

1. Express `req.params`-ийг action contract-ийн `params` schema-аар parse хийнэ.
2. Parsed object-ийг handler-ийн typed `params` field-д дамжуулна.
3. Parsed `params` нь existing request lifecycle дотор `query` болон `body`-тай адил action param болно.
4. Request signing идэвхтэй үед `signature(param)` callback parsed `params`-ийг авна.

HTTP path param бүр анх string байдлаар ирнэ. Number хэрэгтэй field дээр contract-д `z.coerce.number()` ашиглана.

```ts
params: z.object({
    tenantId: z.string(),
    userId: z.coerce.number().int().positive(),
})
```

### Route rules

Action бүр explicit `path` тодорхойлох үндсэн дүрмийг `ADR-0009-require-explicit-action-path.md` тогтооно. Энэ хэсэг нь explicit path дотор typed placeholder ашиглах нэмэлт дүрмийг тодорхойлно.

Нэг action path дараах дүрэмтэй байна:

1. Path дээр placeholder байвал `params` schema заавал байна.
2. `params` schema байвал path дээр дор хаяж нэг placeholder заавал байна.
3. Нэг эсвэл олон required named path placeholder дэмжинэ.
4. Placeholder path-ийн аль ч segment-д байрлаж болно.
5. Placeholder нь `:identifier` syntax ашиглана.
6. Identifier нь English үсэг эсвэл `_`-ээр эхэлнэ.
7. Identifier-ийн үргэлжлэлд English үсэг, тоо, `_` ашиглаж болно.
8. Нэг placeholder name route дотор давхардахгүй.
9. `params` нь placeholder бүрт ижил нэртэй required field бүхий `z.object(...)` schema байна.
10. Route placeholder name-үүд болон schema field name-үүд яг таарна.

Static segment-ийг placeholder-ийн өмнө, хооронд болон дараа ашиглаж болно.

| Path | Төлөв | Тайлбар |
| --- | --- | --- |
| `/users/:userId` | Дэмжинэ | Нэг resource identifier |
| `/v1/users/:userId` | Дэмжинэ | Static prefix болон нэг identifier |
| `/tenants/:tenantId/users/:userId` | Дэмжинэ | Nested resource, олон identifier |
| `/users/:userId/posts` | Дэмжинэ | Nested collection resource |
| `/users/:userId/posts/:postId` | Дэмжинэ | Nested resource, олон identifier |
| `/users/:userId?` | Дэмжихгүй | Optional path param |
| `/files/*path` | Дэмжихгүй | Wildcard path param |

Nested resource болон олон required named path params дэмжинэ. Optional param, repeated placeholder name, wildcard болон custom regular expression syntax энэ ADR-ийн scope-д орохгүй.

### Value rules

Parsed path param field бүрийн value дараах scalar type-ийн аль нэг байна:

- `string`
- `number`
- `boolean`
- `bigint`

Object, array, `null`, `undefined` утгыг URL path segment болгон serialize хийхгүй.

### Error contract

Action үүсэх үед route болон schema rule зөрчигдвөл:

```text
DTI_PATH_PARAMS_CONTRACT_ERROR
```

Client эсвэл server path params schema-д нийцэхгүй бол:

```text
DTI_PATH_PARAMS_VALIDATE_ERROR
```

Server validation error нь `400` status буцаана:

```json
{
  "success": false,
  "code": "DTI_PATH_PARAMS_VALIDATE_ERROR",
  "message": "Invalid action path params"
}
```

Contract error-ийн `details` дотор `path`, `placeholderNames`, `schemaFieldNames` байна. Validation error-ийн public response-д Zod internal error-ийг default-аар задлахгүй.

### REST compatibility

DTI ашигладаггүй client resolved REST URL-ээр endpoint-ийг хэвийн дуудаж болно:

```bash
curl http://localhost:3000/tenants/123e4567-e89b-12d3-a456-426614174000/users/550e8400-e29b-41d4-a716-446655440000
```

Unsigned endpoint дээр DTI-specific header эсвэл DTI client шаардахгүй. Router дээр auth/sign enabled бол raw HTTP client тухайн configured security protocol-ийг хэрэгжүүлнэ. Энэ нь path params-ийн REST URL болон schema validation дүрмийг өөрчлөхгүй.

### Acceptance criteria

Implementation complete гэж үзэхийн тулд:

1. Single болон nested route-ийн `params` type contract-оос client/server талд infer хийгддэг байна.
2. Client placeholder бүрийн value-г тусад нь URL encode хийдэг байна.
3. Client invalid value дээр request илгээдэггүй байна.
4. Server `req.params`-ийг schema-аар parse болон transform хийдэг байна.
5. Server invalid value дээр `400 / DTI_PATH_PARAMS_VALIDATE_ERROR` буцаадаг байна.
6. Placeholder болон `params` schema-ийн аль нэг байхгүй, эсвэл тэдгээрийн field set зөрвөл `DTI_PATH_PARAMS_CONTRACT_ERROR` гардаг байна.
7. Nested resource болон олон required path params зөв resolve хийгддэг байна.
8. Wildcard болон бусад unsupported route syntax reject хийгддэг байна.
9. DTI ашигладаггүй REST client endpoint-ийг дуудаж чаддаг байна.
10. `params`-гүй action explicit `path` өгсөн нөхцөлд өмнөх request behavior-оо хадгална.
11. Client, server, contract validation test-ийг TypeScript дээр `node:test`, `node:assert/strict` ашиглан нэмсэн байна.

## Consequences

- Single болон nested resource identifier-үүд client/server shared contract-оос type inference болон runtime validation авна.
- Client resource path-ийг гараар interpolate хийх шаардлагагүй болно.
- Path params `validateParam` болон request signing-д typed байдлаар орно.
- Existing `params`-гүй action request behavior хадгалагдана. Гэхдээ `ADR-0009`-ийн дагуу action бүр explicit `path` өгөх migration хийнэ.
- Nested resource route-г shared DTI contract-аар тодорхойлох боломжтой болно.
- Route placeholder set болон schema field set-ийн relation-ийг action үүсэх үед шалгах implementation шаардлагатай болно.
- Wildcard route хэрэгтэй endpoint-ийг DTI scope-оос гадуурх Express route-аар шийднэ.

## Alternatives Considered

### Path params-ийг query дотор дамжуулах

Implementation энгийн боловч resource identity болон query filter холилдоно. REST resource path typed болохгүй.

### Client URL-ийг гараар бүрдүүлэх

Contract болон runtime URL салж type safety, validation, signing consistency алдагдана.

### Express req.params-ийг validation-гүй дамжуулах

Server handler string dictionary хүлээж авах бөгөөд shared contract-ийн зорилготой нийцэхгүй.

### Зөвхөн нэг path param дэмжих

Implementation энгийн боловч tenant-scoped болон parent-child resource endpoint-үүдийг typed contract-аар тодорхойлох боломжгүй болно.

### Wildcard path param дэмжих

Wildcard нь нэг эсвэл олон segment барих боломжтой тул scalar `params` field болон deterministic client interpolation-тэй нийцэхгүй. Иймээс wildcard дэмжихгүй.

### Optional болон custom regex syntax дэмжих

Express route parser болон client value shape тусдаа contract шаарддаг тул энэ ADR-ийн scope-д оруулахгүй.
