diff --git a/website/docs/explanation/_category_.json b/website/docs/explanation/_category_.json new file mode 100644 index 00000000..75965541 --- /dev/null +++ b/website/docs/explanation/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Explanation", + "position": 5, + "link": { + "type": "generated-index", + "description": "Conceptual guides understanding API design philosophy" + } +} diff --git a/website/docs/explanation/parsing-codec-chaining.md b/website/docs/explanation/parsing-codec-chaining.md new file mode 100644 index 00000000..a8201d6e --- /dev/null +++ b/website/docs/explanation/parsing-codec-chaining.md @@ -0,0 +1,102 @@ +# Decoding JSON from Headers, Query Parameters, and URL Parameters + +Though we know headers, URL parameters, and query parameters will be received as a +`string` or `string[]` value, due to a limitation in api-ts, `httpRequest` only accepts +codecs that decode values starting from the `unknown` type. Consequently, decoding a +header, URL parameter, or query parameter with a codec like `JsonFromString`, which can +only decode values typed as `string`, will produce a error like: + +``` +Type 'Type' is not assignable to type 'Mixed'. + Types of property 'validate' are incompatible. + Type 'Validate' is not assignable to type 'Validate'. + Type 'unknown' is not assignable to type 'string'. +``` + +There's a straightforward pattern you can use when you have a value typed as `unknown` +but need to decode it with a codec that can only decode a narrower type. This pattern is +called codec chaining: + +```typescript +declare const JsonFromString: t.Type; +declare const t.string: t.Type; + +const myCodec: t.Type = t.string.pipe(JsonFromString); +``` + +Here, `t.string` decodes a value from `unknown` to `string`, and then `JsonFromString` +decodes the same value from `string` to `Json`. + +For example: + +```typescript +import * as t from 'io-ts'; +import { nonEmptyArray, JsonFromString, NumberFromString } from 'io-ts-types'; +import { httpRequest, optional } from '@api-ts/io-ts-http'; + +// Define the Filter type for the JSON string +const Filter = t.type({ + category: t.string, + tags: t.array(t.string), + price: t.type({ + min: t.number, + max: t.number, + }), +}); + +// Define the SearchRequest codec +const SearchRequest = httpRequest({ + params: { + userId: NumberFromString, + }, + query: { + q: t.string, + filter: t.string.pipe(JsonFromString).pipe(Filter), + tags: nonEmptyArray(t.string), + sort: optional(t.string), + }, + headers: { + authorization: t.string, + }, +}); + +// Example request object +const example = { + params: { + userId: '84938492', + }, + query: { + q: 'test', + filter: + '{"category":"books","tags":["crypto","trading"],"price":{"min":10,"max":50}}', + tags: ['tag1', 'tag2', 'tag3'], + sort: 'price', + }, + headers: { + authorization: 'Bearer token', + }, +}; + +// Decode the example +const decoded = SearchRequest.decode(example); +if (decoded._tag === 'Right') { + console.log(decoded); + /* + Expected decoded output + { + userId: 84938492, + q: 'test', + filter: { + category: 'books', + tags: ['crypto', 'trading'], + price: { min: 10, max: 50 }, + }, + tags: ['tag1', 'tag2', 'tag3'], + sort: 'price', + authorization: 'Bearer token', + }; + */ +} else { + console.error('Decoding failed:', decoded.left); +} +``` diff --git a/website/docs/reference/_category_.json b/website/docs/reference/_category_.json new file mode 100644 index 00000000..a8e9f72e --- /dev/null +++ b/website/docs/reference/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Reference", + "position": 4, + "link": { + "type": "generated-index", + "description": "Technical specifications and API reference documentation" + } +} diff --git a/website/docs/tutorial-basics/_category_.json b/website/docs/tutorial-basics/_category_.json index 34c2b28a..b48dd0c6 100644 --- a/website/docs/tutorial-basics/_category_.json +++ b/website/docs/tutorial-basics/_category_.json @@ -3,6 +3,6 @@ "position": 2, "link": { "type": "generated-index", - "description": "5 minutes to learn the most important api-ts concepts." + "description": "Hands-on learning experiences for API specification fundamentals" } }