Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions website/docs/explanation/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Explanation",
"position": 5,
"link": {
"type": "generated-index",
"description": "Conceptual guides understanding API design philosophy"
}
}
102 changes: 102 additions & 0 deletions website/docs/explanation/parsing-codec-chaining.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@youngjungithub This duplicates content between the how-to guide and explanation. I've heard our technical writers mention how this can increase the maintenance burden, because now we have to keep two copies of the same data in sync over time.

Is this intentional, or do you have any ideas on how we can duplicate only the essential information?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is intentional. To minimize maintenance burden we:

  1. Keep procedural steps in How-To Guides
  2. Store explanatory context in shared components
  3. Use cross-references instead of duplications

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@youngjungithub youngjungithub Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the plan I have:

parsing.json-strings file

## Implementation Steps

+ [Why this pattern works](/explanation/parsing-codec-chaining)

### 1. Chain decoders for type safety
- Existing code 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,
  }),
});...Example continues......

parsing-codec-chaining file

# Type Safety in I/O Decoding

I am going to ask Designers to draw up a codec chaining diagram and post it here.

## Design Rationale

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..... Continue...
but need to decode it with a codec that can only decode a narrower type. This pattern is
called codec chaining.

## Tradeoffs
- **Pros**: Enables gradual type refinement

Original file line number Diff line number Diff line change
@@ -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<Json, string, string>' is not assignable to type 'Mixed'.
Types of property 'validate' are incompatible.
Type 'Validate<string, Json>' is not assignable to type 'Validate<unknown, any>'.
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 <em>codec chaining</em>:

```typescript
declare const JsonFromString: t.Type<Json, string, string>;
declare const t.string: t.Type<string, string, unknown>;

const myCodec: t.Type<Json, string, unknown> = 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);
}
```
8 changes: 8 additions & 0 deletions website/docs/reference/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Reference",
"position": 4,
"link": {
"type": "generated-index",
"description": "Technical specifications and API reference documentation"
}
}
2 changes: 1 addition & 1 deletion website/docs/tutorial-basics/_category_.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}