From 3e6d3730f6956444a07324289a4aeb15c3ff3cc4 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Thu, 17 Apr 2025 11:56:28 -0400 Subject: [PATCH] feat(docs): Add core @api-ts/io-ts-http docs to Docusaurus Introduces initial Docusaurus documentation files (.mdx) for key components of the `@api-ts/io-ts-http` package, including: `apiSpec`, `httpRoute`, `httpRequest`, `GenericHttpRequest`, `flattened`, `optional`, and `optionalize`. This is part of the effort to consolidate fragmented documentation (READMEs, Confluence, etc.) into a single source of truth on the new Docusaurus website. chore: add vivian's feedback --- website/docs/reference/_category_.json | 2 +- website/docs/reference/io-ts-http/apispec.md | 52 +++++++ .../io-ts-http/combinators/_category_.json | 8 ++ .../io-ts-http/combinators/flattened.md | 66 +++++++++ .../reference/io-ts-http/combinators/index.md | 8 ++ .../io-ts-http/combinators/optional.md | 24 ++++ .../io-ts-http/combinators/optionalize.md | 38 +++++ .../docs/reference/io-ts-http/httpRequest.md | 100 +++++++++++++ .../docs/reference/io-ts-http/httpRoute.md | 131 ++++++++++++++++++ website/docs/reference/io-ts-http/index.md | 9 ++ .../docs/reference/io-ts-http/interfaces.md | 25 ++++ website/sidebars.js | 57 ++++++-- 12 files changed, 511 insertions(+), 9 deletions(-) create mode 100644 website/docs/reference/io-ts-http/apispec.md create mode 100644 website/docs/reference/io-ts-http/combinators/_category_.json create mode 100644 website/docs/reference/io-ts-http/combinators/flattened.md create mode 100644 website/docs/reference/io-ts-http/combinators/index.md create mode 100644 website/docs/reference/io-ts-http/combinators/optional.md create mode 100644 website/docs/reference/io-ts-http/combinators/optionalize.md create mode 100644 website/docs/reference/io-ts-http/httpRequest.md create mode 100644 website/docs/reference/io-ts-http/httpRoute.md create mode 100644 website/docs/reference/io-ts-http/index.md create mode 100644 website/docs/reference/io-ts-http/interfaces.md diff --git a/website/docs/reference/_category_.json b/website/docs/reference/_category_.json index a8e9f72e..6bd561bc 100644 --- a/website/docs/reference/_category_.json +++ b/website/docs/reference/_category_.json @@ -1,6 +1,6 @@ { "label": "Reference", - "position": 4, + "position": 5, "link": { "type": "generated-index", "description": "Technical specifications and API reference documentation" diff --git a/website/docs/reference/io-ts-http/apispec.md b/website/docs/reference/io-ts-http/apispec.md new file mode 100644 index 00000000..2549455f --- /dev/null +++ b/website/docs/reference/io-ts-http/apispec.md @@ -0,0 +1,52 @@ +--- +sidebar_position: 2 +--- + +# `apiSpec` + +### Overview + +A helper function that defines a collection of HTTP routes, associating them with +operation names to represent a service's complete API contract. Primarily serves as a +typed container for `httpRoute` definitions. + +### Specification + +Accepts a single argument: an object where keys represent operation names (e.g., +`'api.v1.user'`) and values are objects that map HTTP methods (lowercase strings like +`'get'`, `'post'`) to corresponding `httpRoute` definitions. + +### Metadata + +`@version`: A JSDoc tag added to the exported `apiSpec` variable declaration that will +be used as the API `version` property when generating artifacts like OpenAPI schemas. + +### Usage Example + +```typescript +import { apiSpec } from '@api-ts/io-ts-http'; +import * as t from 'io-ts'; // Assuming base io-ts types are needed elsewhere +import { httpRoute, httpRequest } from '@api-ts/io-ts-http'; // Assuming httpRoute/Request defined here or imported + +// Assume GetUser, CreateUser etc. are pre-defined httpRoute objects +import { GetUser, CreateUser, UpdateUser, DeleteUser, PatchUser } from './routes/user'; +import { GetMessage, CreateMessage } from './routes/message'; + +/** + * Example service API definition. + * @version 1.0.0 + */ +export const API = apiSpec({ + 'api.v1.message': { + get: GetMessage, + post: CreateMessage, + }, + 'api.v1.user': { + get: GetUser, + post: CreateUser, + put: UpdateUser, + delete: DeleteUser, + patch: PatchUser, + }, +}); +``` diff --git a/website/docs/reference/io-ts-http/combinators/_category_.json b/website/docs/reference/io-ts-http/combinators/_category_.json new file mode 100644 index 00000000..3b827490 --- /dev/null +++ b/website/docs/reference/io-ts-http/combinators/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Combinators", + "position": 5, + "link": { + "type": "generated-index", + "description": "Technical specifications and API reference documentation" + } +} diff --git a/website/docs/reference/io-ts-http/combinators/flattened.md b/website/docs/reference/io-ts-http/combinators/flattened.md new file mode 100644 index 00000000..e52d6c74 --- /dev/null +++ b/website/docs/reference/io-ts-http/combinators/flattened.md @@ -0,0 +1,66 @@ +# `flattened` + +### Overview + +The `flattened` combinator creates a codec that decodes a nested structure into a flat +object back into a nested structure. + +### Specification + +Accepts two arguments: + +- `name`: (`string`) A name for the codec, used in error messages. +- `nestedProps`: (`object`) An object that defines the encoded nested structure. Each + key represents a top-level property in the encoded form, and its value is an object + that maps the nested keys to their `io-ts` codecs. + +Returns a new codec. + +### Behavior + +- Decoding: Takes the nested input (matching the `nestedProps` structure) and outputs a + flat object that contains all the inner properties. +- Encoding: Takes the flat object (containing keys from the inner structures) and + outputs the nested structure defined by `nestedProps`. + +### Caveats + +- Defining multiple nested properties with the same name across different top-level keys + can lead to undefined behavior. The library tries to prevent this where statically. + +### Usage Example + +```typescript +import * as t from 'io-ts'; +import { flattened } from '@api-ts/io-ts-http'; + +const FlatCodec = flattened('FlatCodec', { + metadata: { + // Top-level key in encoded form + id: t.string, + createdAt: t.string, // Assume DateFromString etc. if needed + }, + payload: { + // Another top-level key in encoded form + value: t.number, + }, +}); + +// Decoded type: +// type Decoded = { +// id: string; +// createdAt: string; +// value: number; +// }; + +// Encoded type (Input for decode, Output for encode): +// type Encoded = { +// metadata: { +// id: string; +// createdAt: string; +// }; +// payload: { +// value: number; +// }; +// }; +``` diff --git a/website/docs/reference/io-ts-http/combinators/index.md b/website/docs/reference/io-ts-http/combinators/index.md new file mode 100644 index 00000000..3889b33f --- /dev/null +++ b/website/docs/reference/io-ts-http/combinators/index.md @@ -0,0 +1,8 @@ +--- +sidebar_position: 5 +--- + +# Combinators + +Helper functions that construct `io-ts` codecs, often used within `httpRequest` +definitions. diff --git a/website/docs/reference/io-ts-http/combinators/optional.md b/website/docs/reference/io-ts-http/combinators/optional.md new file mode 100644 index 00000000..b7b4cf68 --- /dev/null +++ b/website/docs/reference/io-ts-http/combinators/optional.md @@ -0,0 +1,24 @@ +# `optional` + +### Overview + +`optional` creates a codec that represents a value of a specified type or `undefined`. +This is useful for marking properties as optional when used with `optionalize`. + +### Specification + +Accepts one argument: + +- `codec`: (`io-ts` Codec) The base codec for the type. + +Returns a new codec that represents `t.union([codec, t.undefined])`. + +### Usage Example + +```typescript +import * as t from 'io-ts'; +import { optional } from '@api-ts/io-ts-http'; + +// Represents: string | undefined +const MaybeString = optional(t.string); +``` diff --git a/website/docs/reference/io-ts-http/combinators/optionalize.md b/website/docs/reference/io-ts-http/combinators/optionalize.md new file mode 100644 index 00000000..4be83372 --- /dev/null +++ b/website/docs/reference/io-ts-http/combinators/optionalize.md @@ -0,0 +1,38 @@ +# `optionalize` + +### Overview + +`optionalize` creates a codec for an object type where properties whose codecs can +resolve to `undefined` (typically created using `optional` or +`t.union([..., t.undefined])`) are marked as optional (`?`) in the resulting TypeScript +type. + +### Specification + +Accepts one argument: + +- `props`: (`object`) An object that maps property names to `io-ts` codecs, similar to + `t.type` or `t.partial`. + +Returns a new object codec. The codec effectively combines `t.type` (for required +properties) and `t.partial` (for properties whose codecs include `t.undefined`). + +### Usage Example + +```typescript +import * as t from 'io-ts'; +import { optional, optionalize } from '@api-ts/io-ts-http'; + +const ItemCodec = optionalize({ + requiredId: t.string, + optionalValue: optional(t.number), // Uses optional combinator + maybeDefined: t.union([t.string, t.undefined]), // Also becomes optional +}); + +// Resulting type: +// type Item = { +// requiredId: string; +// optionalValue?: number; +// maybeDefined?: string; +// } +``` diff --git a/website/docs/reference/io-ts-http/httpRequest.md b/website/docs/reference/io-ts-http/httpRequest.md new file mode 100644 index 00000000..222fc74c --- /dev/null +++ b/website/docs/reference/io-ts-http/httpRequest.md @@ -0,0 +1,100 @@ +--- +sidebar_position: 4 +--- + +# `httpRequest` + +### Overview + +`httpRequest` is a helper function that builds `io-ts` codecs specifically for HTTP +requests. It defines the expected structure of path parameters, query parameters, +headers, and the request body. The resulting codec flattens these distinct parts into a +single object upon successful decoding. + +### Specification + +Accepts a single optional argument: an object that can contain the following optional +properties: + +- `params`: (`object`) An object that maps path parameter names (strings matching + `{name}` syntax in `httpRoute` path) to `io-ts` codecs for validation and type + conversion. +- `query`: (`object`) An object that maps query parameter names (strings) to `io-ts` + codecs. +- `headers`: (`object`) An object that maps HTTP header names (lowercase strings) to + `io-ts` codecs. +- `body`: (`object` | `io-ts` Codec) An object that maps field names within the request + body to `io-ts` codecs. Assumes an object structure by default. (See Limitations). + +The function returns an `io-ts` codec. + +### Behavior + +- Decoding: Takes an input object that conforms to `GenericHttpRequest` (see below). + Validates and parses the `params`, `query`, `headers`, and `body` based on the + provided codecs. If successful, returns a flattened object that contains all decoded + properties directly. +- Encoding: Takes a flattened object (matching the decoded type). Encodes the properties + back into the structured `GenericHttpRequest` format, suitable for sending as a + request. + +### Decoded Type Structure + +The `t.TypeOf` of the resulting codec is a flat object that contains properties from +`params`, `query`, `headers`, and `body` combined. + +```typescript +import * as t from 'io-ts'; +import { httpRequest } from '@api-ts/io-ts-http'; +import { NumberFromString, DateFromISOString } from 'io-ts-types'; // Example types + +const ExampleRequestCodec = httpRequest({ + params: { + id: NumberFromString, // from path '/.../{id}' + }, + query: { + filter: t.string, // from query '?filter=...' + }, + body: { + content: t.string, + timestamp: DateFromISOString, + }, +}); + +// The resulting decoded type: +type ExampleDecoded = t.TypeOf; +// Equivalent to: +// type ExampleDecoded = { +// id: number; // from params +// filter: string; // from query +// content: string; // from body +// timestamp: Date; // from body +// }; +``` + +### Limitations + +Assumes the request `body`, if present and defined via the shorthand object syntax, is +an object whose properties can be flattened. For non-object bodies, see the advanced +usage notes under `httpRoute`. + +### Usage Examples + +- Query Parameters Only: + +```typescript +const RequestWithQuery = httpRequest({ + query: { message: t.string, count: NumberFromString }, +}); +// Decoded type: { message: string; count: number } +``` + +- Path and body parameters: + +```typescript +const RequestWithPathAndBody = httpRequest({ + params: { userId: t.string }, + body: { data: t.unknown }, +}); +// Decoded type: { userId: string; data: unknown } +``` diff --git a/website/docs/reference/io-ts-http/httpRoute.md b/website/docs/reference/io-ts-http/httpRoute.md new file mode 100644 index 00000000..1fdc07ff --- /dev/null +++ b/website/docs/reference/io-ts-http/httpRoute.md @@ -0,0 +1,131 @@ +--- +sidebar_position: 3 +--- + +# `httpRoute` + +### Overview + +`httpRoute` is a helper function that defines a single HTTP route, specifying its path, +method, request structure, and possible responses. + +### Specification + +Accepts a single argument: an object with the following properties: + +- `path`: (`string`) The URL path for the route. Path parameters are denoted using curly + braces (e.g., `'/users/{userId}'`). These parameters must be defined in the `request` + codec's `params` property. +- `method`: (`string`) The HTTP request method (e.g., `'GET'`, `'POST'`, `'PUT'`, + `'DELETE'`, `'PATCH'`). +- `request`: (`io-ts` Codec) An `io-ts` codec that decodes incoming requests + (server-side) and encodes outgoing requests (client-side). Typically created using + `httpRequest`. The codec must decode from an object that conforms to the + `GenericHttpRequest` interface (see below). +- `response`: (`object`) An object where keys are HTTP status codes (e.g., `200`, `404`, + as numbers or strings) and values are `io-ts` codecs that define the structure of the + corresponding response body. Responses are assumed to be JSON. + +### Usage Examples + +- Route with path parameter and multiple responses: + +```typescript +import { httpRoute, httpRequest } from '@api-ts/io-ts-http'; +import * as t from 'io-ts'; +import { NumberFromString } from 'io-ts-types'; // Example type + +const GetMessageRoute = httpRoute({ + path: '/message/{id}', + method: 'GET', + request: httpRequest({ + params: { + id: NumberFromString, // Path param '{id}' defined here + }, + }), + response: { + 200: t.type({ + // Codec for 200 OK response body + id: t.string, + message: t.string, + }), + 404: t.type({ + // Codec for 404 Not Found response body + error: t.string, + }), + }, +}); +``` + +- Route with request body: + +```typescript +import { httpRoute, httpRequest } from '@api-ts/io-ts-http'; +import * as t from 'io-ts'; + +const CreateMessageRoute = httpRoute({ + path: '/message', + method: 'POST', + request: httpRequest({ + body: { + // Defines the structure of the request body + message: t.string, + }, + }), + response: { + 201: t.type({ + // Codec for 201 Created response body + id: t.string, + message: t.string, + }), + 400: t.type({ + // Codec for 400 Bad Request + error: t.string, + }), + }, +}); +``` + +### Advanced Usage Notes + +- Non-Object Request Body: If a route requires a non-object body (e.g., a raw string), + `httpRequest` can't define this directly. Use `t.intersection` to combine an + `httpRequest` (for params, query, headers) with a `t.type({ body: YourCodec })`. The + decoded type will include a `body` property. + +```typescript +// Example for a string body request +const StringBodyRoute = httpRoute({ + path: '/example/{id}', + method: 'POST', + request: t.intersection([ + // Combine httpRequest with explicit body type + httpRequest({ + params: { id: t.string }, + }), + t.type({ + body: t.string, // Define the body type explicitly + }), + ]), + response: { 200: t.string }, +}); +// Decoded type: { id: string; body: string } +``` + +- Conditional Parameters: For requests where parameters depend on each other, use + `t.union` with multiple `httpRequest` definitions. + +```typescript +// Example for conditional query parameters +const UnionRoute = httpRoute({ + path: '/example', + method: 'GET', + request: t.union([ + // Union of possible request structures + httpRequest({ query: { type: t.literal('ping') } }), + httpRequest({ query: { type: t.literal('message'), message: t.string } }), + ]), + response: { 200: t.string }, +}); +// Decoded type: { type: 'ping' } | { type: 'message'; message: string } +``` diff --git a/website/docs/reference/io-ts-http/index.md b/website/docs/reference/io-ts-http/index.md new file mode 100644 index 00000000..3b336649 --- /dev/null +++ b/website/docs/reference/io-ts-http/index.md @@ -0,0 +1,9 @@ +--- +sidebar_position: 1 +--- + +# io-ts-http + +Technical descriptions of the core components and utilities within the +`@api-ts/io-ts-http` library. This section details their parameters, behavior, and usage +patterns. Consult this section for authoritative information on the library's API. diff --git a/website/docs/reference/io-ts-http/interfaces.md b/website/docs/reference/io-ts-http/interfaces.md new file mode 100644 index 00000000..e61f2797 --- /dev/null +++ b/website/docs/reference/io-ts-http/interfaces.md @@ -0,0 +1,25 @@ +# `GenericHttpRequest` + +### Overview + +An internal interface that represents the expected input structure for codecs generated +by `httpRequest` during the decoding process. It represents a minimally parsed HTTP +request before type-specific validation and parsing occur. + +### Specification + +```typescript +interface GenericHttpRequest { + params: { + [K: string]: string; // Raw path parameters + }; + query: { + // Minimally parsed query params (split, urlDecoded) + [K: string]: string | string[]; + }; + headers: { + [K: string]: string; // Raw headers (typically lowercase keys) + }; + body?: unknown; // Raw request body, type unknown before parsing +} +``` diff --git a/website/sidebars.js b/website/sidebars.js index 54ba6094..35b149ae 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -13,19 +13,60 @@ /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { - // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], - - // But you can create a sidebar manually - /* tutorialSidebar: [ + 'intro', + { + type: 'category', + label: 'Tutorial - Basics', + items: [ + 'tutorial-basics/create-an-api-spec', + 'tutorial-basics/create-an-http-server', + 'tutorial-basics/create-an-http-client', + 'tutorial-basics/render-an-open-api-spec', + ], + }, + { + type: 'category', + label: 'How-To Guides', + items: [ + 'how-to-guides/intermediate-semantic-analysis', + 'how-to-guides/parsing.json-strings', + ], + }, { type: 'category', - label: 'Tutorial', - items: ['hello'], + label: 'Reference', + items: [ + { + type: 'category', + label: 'io-ts-http', + link: { + type: 'doc', + id: 'reference/io-ts-http/index', + }, + items: [ + 'reference/io-ts-http/apispec', + 'reference/io-ts-http/httpRoute', + 'reference/io-ts-http/httpRequest', + { + type: 'category', + label: 'Combinators', + link: { + type: 'doc', + id: 'reference/io-ts-http/combinators/index', + }, + items: [ + 'reference/io-ts-http/combinators/optional', + 'reference/io-ts-http/combinators/optionalize', + 'reference/io-ts-http/combinators/flattened', + ], + }, + 'reference/io-ts-http/interfaces', + ], + }, + ], }, ], - */ }; module.exports = sidebars;