# ADR-0003: DTI response type-ийн хүрээ

## Status

Accepted

## Context

DTI server нь default үед JSON response envelope ашиглана. Гэхдээ зарим REST API endpoint нь text эсвэл file response буцаах шаардлагатай.

Non-JSON response буюу file download, stream response зэрэг use case дээр DTI response envelope хэрхэн хэрэглэх нь өмнө нь тодорхойгүй байсан.

## Decision

DTI response type нь дараах утгуудтай байна:

- `json`
- `text`
- `file`

`stream` response support хийхгүй. DTI library stream response-ийг огт support хийхгүй.

`json` response нь default response type байна.

`json` response дээр success result дараах envelope ашиглана:

```json
{
  "success": true,
  "data": {}
}
```

`text` response дээр success result нь raw text байна.

Text response-ийн default content type:

```text
text/plain; charset=utf-8
```

Custom content type шаардлагатай бол action contract дээр static тодорхойлохгүй. Handler-ийн `dtiText()` response дээр dynamic `contentType` өгнө.

```ts
return dtiText(body, {
    contentType: "text/csv; charset=utf-8",
});
```

`contentType` өгсөн бол library тухайн утгыг ашиглаж charset автоматаар нэмэх эсвэл өөрчлөхгүй. Text encoding болон зарласан charset хоорондын нийцлийг application хариуцна.

`file` response дээр success result нь raw file/binary response байна. Client талд `file` response-ийг `Blob` төрлөөр хүлээж авна.

### File response metadata

File response-ийн `filename` болон `Content-Type` metadata-г action contract дээр static тодорхойлохгүй. Metadata нь request болон generated file-аас хамаарч өөрчлөгдөж болох тул handler-ийн буцаах `dtiFile()` response дээр dynamic байна.

```ts
dti.action(reportDownload, async ({ query }) => {
    const report = await createReport(query.id);

    return dtiFile({
        body: report.body,
        filename: report.filename,
        contentType: report.contentType,
    });
});
```

Action contract зөвхөн `responseType: "file"` гэж response body-ийн төрөл тодорхойлно. Нэг action request бүр дээр өөр filename болон media type буцааж болно.

`dtiFile()` metadata дараах дүрэмтэй:

1. `contentType` optional байна.
2. `contentType` өгөөгүй бол `application/octet-stream` ашиглана.
3. Library filename extension-оос media type таахгүй.
4. `filename` optional байна.
5. `filename` өгсөн бол `Content-Disposition: attachment` header үүсгэнэ.
6. `filename` өгөөгүй бол library `Content-Disposition` header автоматаар үүсгэхгүй.
7. Success status default `200` байна. Өөр status болон нэмэлт headers-ийг application `dtiFile()` response дээр удирдаж болно.
8. Resolved `contentType` нь library-owned `Content-Type` болно. Raw headers доторх давхардсан `Content-Type`-ийг override хийнэ.
9. `filename` өгсөн үед generated `Content-Disposition` нь library-owned байна. Raw headers доторх давхардсан `Content-Disposition`-ийг override хийнэ.
10. `filename` өгөөгүй үед application raw `Content-Disposition` header өөрөө тохируулж болно.

### Filename validation болон encoding

`filename` нь client-д санал болгох download filename бөгөөд server filesystem path биш байна.

Filename дараах validation-ийг хангана:

- non-empty string байна;
- Unicode утгыг NFC normalization хийнэ;
- `/` болон `\\` path separator агуулахгүй;
- `CR`, `LF`, `NUL` болон бусад ASCII control character агуулахгүй;
- `.` болон `..` утгыг зөвшөөрөхгүй.

Invalid filename-ийг header рүү чимээгүй sanitize хийхгүй. File response илгээхээс өмнө reject хийнэ.

Valid filename өгсөн үед library дараах хоёр parameter-ийг ижил `Content-Disposition` header дээр үүсгэнэ:

```text
Content-Disposition: attachment; filename="{asciiFallback}"; filename*=UTF-8''{encodedFilename}
```

Library `Content-Disposition` header-ийг гараар string concatenate хийхгүй. RFC 6266 болон RFC 8187-compatible, standards-compliant serializer ашиглана.

Serializer дараах шаардлагыг хангана:

1. Unicode filename-ийг UTF-8 `filename*` parameter-аар дамжуулна.
2. Legacy client compatibility-д safe ASCII `filename` fallback үүсгэнэ.
3. Quoted-string escaping болон percent encoding-ийг serializer хариуцна.
4. ASCII fallback нь non-empty, control character болон path separator-гүй байна.
5. Fallback character replacement эсвэл transliteration-ийн яг algorithm нь public wire contract биш, implementation detail байна.

`filename*` ойлгодог client UTF-8 filename ашиглана. Зөвхөн legacy `filename` ойлгодог client ASCII fallback ашиглана.

Error result нь REST API status standard дагана. JSON response боломжтой үед error envelope ашиглана:

```json
{
  "success": false,
  "code": "FILE_NOT_FOUND",
  "message": "File not found"
}
```

## Consequences

- DTI response behavior нь `json`, `text`, `file` гэсэн тодорхой хүрээнд байна.
- Text response default `text/plain; charset=utf-8` ашиглана; custom content type нь `dtiText()` response дээр dynamic байна.
- File download use case-д JSON envelope хэрэглэхгүй тул browser болон HTTP client-ийн binary handling эвдэхгүй.
- Client тал `file` response дээр `Blob` type хүлээн авна.
- File metadata request бүр дээр `dtiFile()` response-оос dynamic тодорхойлогдоно.
- Unicode filename нь UTF-8 `filename*` болон ASCII `filename` fallback-тай байна.
- Filename validation нь header injection болон filesystem path хэлбэрийн утгыг response metadata-д оруулахгүй.
- DTI client filename болон content type хэрэгтэй бол raw response headers-ийг `callDetailed`-аар уншина.
- Stream response хэрэгтэй use case гарвал DTI library-ийн scope-оос гадуур шийднэ.
- Server болон client implementation нь stream abstraction хийх шаардлагагүй болно.

## Alternatives Considered

### `stream` response support хийх

Streaming нь runtime бүр дээр ялгаатай abstraction шаарддаг. Browser, Node.js, Express, fetch зэрэг орчинд stream handling өөр тул DTI-ийн эхний scope-д complexity нэмнэ.

### File response-ийг JSON envelope дотор base64 хэлбэрээр буцаах

Implementation энгийн боловч file size өснө, browser download behavior эвдэрнэ, memory usage нэмэгдэнэ. REST API file download-д тохиромжгүй.

### Бүх non-JSON response-г support хийхгүй байх

Library scope энгийн болох боловч text болон file download зэрэг нийтлэг REST API use case-уудыг хаана.

### Filename болон Content-Type-ийг action contract дээр static тодорхойлох

Client contract-оос metadata урьдчилан харагдана. Гэхдээ export format, locale, report name болон generated content-оос filename/media type өөрчлөгдөх use case-ийг хаана. Иймээс metadata-г dynamic `dtiFile()` response дээр тодорхойлно.

### Application зөвхөн raw headers гараар тохируулах

Library API энгийн үлдэнэ. Гэхдээ filename validation, Unicode encoding болон header precedence application бүр дээр давтагдана. `dtiFile()` дээр typed metadata ашиглах нь behavior-ийг нэг дүрэмтэй болгоно.

### Filename encoding-ийг library дотор гараар хэрэгжүүлэх

Dependency багасна. Гэхдээ quoted-string escaping, UTF-8 extended parameter болон client compatibility-ийн edge case-уудыг дахин хэрэгжүүлэх шаардлагатай болно. Иймээс standards-compliant `Content-Disposition` serializer ашиглана.
