Skip to content

Commit b1a809b

Browse files
bitgopatmclericcrosson-bitgo
authored andcommitted
task: add io-ts-http api reference docs
Ticket: BG-39132
1 parent 87c7101 commit b1a809b

File tree

6 files changed

+423
-50
lines changed

6 files changed

+423
-50
lines changed

packages/io-ts-http/README.md

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Runtime types for (de)serializing HTTP requests from both the client and server side
44

5-
## Usage
5+
## Overview
66

77
The primary function in this library is `httpRequest`, which is used to build codecs
88
which can parse a generic HTTP request into a more refined type. The generic HTTP
@@ -78,53 +78,6 @@ possible to encode the API contract (or at least as much of it that is possible
7878
express in the type system) and therefore someone calling the API can be confident that
7979
the server will correctly interpret a request if the arguments typecheck.
8080

81-
## Other functions
81+
## Documentation
8282

83-
### optional
84-
85-
The `optional` function takes a codec and wraps it in one that also accepts `undefined`
86-
(specifically, not `null`):
87-
88-
```typescript
89-
import { optional } from '@bitgo/io-ts-http';
90-
import * as t from 'io-ts';
91-
92-
// This is effectively a runtime representation of `string | undefined`
93-
const stringOrUndefined = optional(t.string);
94-
```
95-
96-
### optionalize
97-
98-
In TypeScript, an object may have some keys that are required and others that are
99-
optional:
100-
101-
```typescript
102-
type Example = {
103-
needed: string;
104-
notNeeded?: string;
105-
};
106-
```
107-
108-
To build a codec for such a type in `io-ts`, this pattern is generally used:
109-
110-
```typescript
111-
const Example = t.intersection([
112-
t.type({
113-
needed: t.string,
114-
}),
115-
t.partial({
116-
notNeeded: t.string,
117-
}),
118-
]);
119-
```
120-
121-
The `optionalize` function makes this easier to do by accepting a similar set of
122-
arguments as `t.type` does, and if any of the properties allow `undefined` they become
123-
optional:
124-
125-
```typescript
126-
const Example = optionalize({
127-
needed: t.string,
128-
notNeeded: optional(t.string),
129-
});
130-
```
83+
- [API Reference](docs/apiReference.md)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# API Reference
2+
3+
## Table of Contents
4+
5+
- [apiSpec](apiSpec.md)
6+
- [httpRoute](httpRoute.md)
7+
- [httpRequest](httpRequest.md)
8+
- [combinators](combinators.md)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# `apiSpec`
2+
3+
Helper function for defining a collection of routes and associating them with operation
4+
names.
5+
6+
## Usage
7+
8+
The intended usage is to create a top-level export that uses `apiSpec` to define a
9+
service's API as a collection of operations linked to routes. This function itself does
10+
not do anything aside from enforce the correct type of the parameter passed to it.
11+
12+
```typescript
13+
import { apiSpec } from '@bitgo/io-ts-http';
14+
15+
import { GetMessage, CreateMessage } from './routes/message';
16+
import { GetUser, CreateUser, UpdateUser, DeleteUser } from './routes/user';
17+
18+
/**
19+
* Example service
20+
*
21+
* @version 1.0.0
22+
*/
23+
export const API = apiSpec({
24+
'api.v1.message': {
25+
get: GetMessage,
26+
post: CreateMessage,
27+
},
28+
'api.v1.user': {
29+
get: GetUser,
30+
post: CreateUser,
31+
put: UpdateUser,
32+
delete: DeleteUser,
33+
},
34+
});
35+
```
36+
37+
Other packages are designed to read these API specs for various purposes such as
38+
creating a type-checked api client, binding functions to routes with automatic parsing
39+
and validating, and generating things such as an OpenAPI schema.
40+
41+
## Metadata
42+
43+
Currently, if a `@version` JSDoc tag is added to the exported API spec, it will be used
44+
as the API `version` when generating an OpenAPI schema. Support for other tags may be
45+
added in the future.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Combinators
2+
3+
`io-ts-http` currently exports a handful of combinators for `io-ts` codecs.
4+
5+
## `optionalize`
6+
7+
Provides an easy way to define object types with some required and some optional
8+
properties. It accepts the same props that `type` and `partial` do in `io-ts`, and
9+
behaves like a combination of the two. It works by figuring out which properties are
10+
capable of being `undefined`, and marking them as optional. For example:
11+
12+
```typescript
13+
const Item = optionalize({
14+
necessaryProperty: t.string,
15+
maybeDefined: t.union([t.string, t.undefined]),
16+
});
17+
```
18+
19+
defines a codec for the following type:
20+
21+
```typescript
22+
type Item = {
23+
necessaryProperty: string;
24+
maybeDefined?: string;
25+
};
26+
```
27+
28+
This same type could be defined with a combination of `intersection`, `type`, and
29+
`partial`, however it is much easier to read, especially when combined with the next
30+
combinator.
31+
32+
## `optional`
33+
34+
Designed to be paired with `optionalize` for readability. It accepts a codec and unions
35+
it with undefined. Thus:
36+
37+
```typescript
38+
// typeof Foo = string | undefined
39+
const Foo = optional(t.string);
40+
```
41+
42+
When used with `optionalize` it becomes easy to see which parameters are optional.
43+
44+
## `flattened`
45+
46+
Allows codecs to be defined where properties are flattened on decode and nested on
47+
encode. To illustrate:
48+
49+
```typescript
50+
const Item = flattened({
51+
first: {
52+
second: t.string,
53+
},
54+
});
55+
56+
type DecodedType = {
57+
second: string;
58+
};
59+
60+
type EncodedType = {
61+
first: {
62+
second: string;
63+
};
64+
};
65+
```
66+
67+
You can have multiple top-level properties flattened into one object.
68+
69+
```typescript
70+
const Item = flattened({
71+
foo: {
72+
fizz: t.string,
73+
},
74+
bar: {
75+
buzz: t.number,
76+
},
77+
});
78+
79+
type DecodedType = {
80+
fizz: string;
81+
buzz: number;
82+
};
83+
84+
type EncodedType = {
85+
foo: {
86+
fizz: string;
87+
};
88+
bar: {
89+
buzz: number;
90+
};
91+
};
92+
```
93+
94+
The library tries to statically prevent defining multiple nested properties with the
95+
same key, but can't in all cases. If this is worked around, then it is undefined
96+
behavior.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# `httpRequest`
2+
3+
Helper function for defining HTTP request codecs. These codecs have the property that
4+
the decoded type is a flattened combination of the query params, path params, headers,
5+
and body. It accepts an object containing codecs for query, path, header, and body
6+
params. The result is a codec where these parameters are all flattened into the result
7+
when decoded, and placed into their appropriate positions when encoded. All parameters
8+
are optional and default to the empty object `{}`
9+
10+
## Usage
11+
12+
Request with a single query parameter
13+
14+
```typescript
15+
const Request = httpRequest({
16+
query: {
17+
message: t.string,
18+
},
19+
});
20+
21+
// Decoded type
22+
type DecodedRequest = {
23+
message: string;
24+
};
25+
```
26+
27+
Request with both query and path parameters
28+
29+
```typescript
30+
const Request = httpRequest({
31+
query: {
32+
message: t.string,
33+
},
34+
params: {
35+
id: NumberFromString,
36+
},
37+
});
38+
39+
// Decoded type
40+
type DecodedRequest = {
41+
message: string;
42+
id: number;
43+
};
44+
```
45+
46+
Request with a body
47+
48+
```typescript
49+
const Request = httpRequest({
50+
params: {
51+
id: NumberFromString,
52+
},
53+
body: {
54+
content: t.string,
55+
timestamp: DateFromISOString,
56+
},
57+
});
58+
59+
// Decoded type
60+
type DecodedRequest = {
61+
id: number;
62+
content: string;
63+
timestamp: Date;
64+
};
65+
```
66+
67+
## Limitations
68+
69+
`httpRequest` currently assumes that a request body, if present, is an object type. A
70+
workaround for this is outlined in the [httpRoute](./httpRoute.md#Advanced%20Usage) doc.

0 commit comments

Comments
 (0)