-
Notifications
You must be signed in to change notification settings - Fork 3
/
example.ts
109 lines (89 loc) · 3.77 KB
/
example.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import * as bodyParser from 'body-parser';
import * as express from 'express';
import {
BadRequest,
JsValue,
jsValueWriteable,
Ok,
Result,
} from 'express-result-types/target/result';
import * as session from 'express-session';
import * as http from 'http';
import * as t from 'io-ts';
import { NumberFromString } from './helpers/example';
import { formatValidationErrors } from './helpers/other';
import { wrap } from './index';
const app = express();
app.use(session({ secret: 'foo' }));
// Don't parse body using middleware. Body parsing is instead handled in the request handler.
app.use(bodyParser.text({ type: 'application/json' }));
const Body = t.interface({
name: t.string,
});
const Query = t.interface({
age: NumberFromString,
});
const requestHandler = wrap(req => {
const jsonBody = req.body.asJson();
const maybeQuery = Query.decode({
age: req.query.get('age').toNullable(),
}).mapLeft(formatValidationErrors('query'));
const maybeBody = jsonBody.chain(jsValue =>
jsValue.validate(Body).mapLeft(formatValidationErrors('body')),
);
return maybeQuery
.chain(query => maybeBody.map(body => ({ query, body })))
.map(({ query, body }) =>
Ok.apply(
new JsValue({
// We defined the shape of the request body and the request query parameter
// 'age' for validation purposes, but it also gives us static types! For
// example, here the type checker knows the types:
// - `body.name` is type `string`
// - `age` is type `number`
name: body.name,
age: query.age,
}),
jsValueWriteable,
),
)
.getOrElseL(error => BadRequest.apply(new JsValue(error), jsValueWriteable));
});
const sessionRequestHandler = wrap(req => {
const maybeUserId = req.session.get('userId');
return maybeUserId.foldL(
() => Ok.apply(new JsValue({}), jsValueWriteable).withSession(new Map([['userId', 'foo']])),
userId => Ok.apply(new JsValue({ userId }), jsValueWriteable),
);
});
app.post('/', requestHandler);
app.get('/session', sessionRequestHandler);
const onListen = (server: http.Server) => {
const { port } = server.address();
console.log(`Server running on port ${port}`);
};
const httpServer = http.createServer(app);
httpServer.listen(8080, () => {
onListen(httpServer);
});
// ❯ curl --request POST --silent --header 'Content-Type: application/json' \
// --data '{ "name": "bob" }' "localhost:8080/" | jq '.'
// "Validation errors for query: Expecting NumberFromString at age but instead got: null."
// ❯ curl --request POST --silent --header 'Content-Type: application/json' \
// --data '{ "name": "bob" }' "localhost:8080/?age=foo" | jq '.'
// "Validation errors for query: Expecting NumberFromString at age but instead got: \"foo\"."
// ❯ curl --request POST --silent --header 'Content-Type: invalid' \
// --data '{ "name": "bob" }' "localhost:8080/?age=5" | jq '.'
// "Expecting request header 'Content-Type' to equal 'application/json', but instead got 'invalid'."
// ❯ curl --request POST --silent --header 'Content-Type: application/json' \
// --data 'invalid' "localhost:8080/?age=5" | jq '.'
// "JSON parsing error: Unexpected token i in JSON at position 0"
// ❯ curl --request POST --silent --header 'Content-Type: application/json' \
// --data '{ "name": 1 }' "localhost:8080/?age=5" | jq '.'
// "Validation errors for body: Expecting string at name but instead got: 1."
// ❯ curl --request POST --silent --header 'Content-Type: application/json' \
// --data '{ "name": "bob" }' "localhost:8080/?age=5" | jq '.'
// {
// "name": "bob",
// "age": 5
// }