-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.almd
More file actions
121 lines (106 loc) · 4.42 KB
/
Copy pathapi.almd
File metadata and controls
121 lines (106 loc) · 4.42 KB
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
110
111
112
113
114
115
116
117
118
119
120
121
// Shared, synchronous application logic. No sockets, environment, or provider SDK.
// Storage is described, not performed: `step` asks the host for the keys it needs
// to read and returns the write the host must make before answering.
import json
fn reply(status: Int, body: Value) -> String = json.stringify(value.object([
("status", value.int(status)),
("body", body),
]))
fn error(status: Int, message: String) -> String = reply(status, value.object([
("error", value.str(message)),
]))
fn method_error(allow: String) -> String = json.stringify(value.object([
("status", value.int(405)),
("body", value.object([("error", value.str("method_not_allowed"))])),
("allow", value.str(allow)),
]))
fn greet(body: String) -> String = match json.parse(body) {
err(_) => error(400, "invalid_json"),
ok(data) => match json.get_string(data, "name") {
none => error(400, "name_required"),
some(name) => if string.len(name) == 0 or string.len(name) > 100 then
error(400, "name_length")
else reply(200, value.object([("message", value.str("Hello, " + name + "!"))])),
},
}
pub fn handle(method: String, target: String, body: String) -> String = {
let path = string.split(target, "?") |> list.get(0) ?? target
if string.len(body) > 8192 then error(413, "body_too_large")
else if path == "/health" then {
if method == "GET" then reply(200, value.object([("ok", value.bool(true))]))
else method_error("GET")
} else if path == "/greet" then {
if method == "POST" then greet(body)
else method_error("POST")
} else error(404, "not_found")
}
// --- /notes: a small list kept by the host under one key ---------------------
fn read_request(key: String) -> String = json.stringify(value.object([
("read", value.array([value.str(key)])),
]))
// none: the host has not read `key` yet; some(none): it did, and nothing is stored.
fn lookup(reads: Value, key: String) -> Option[Option[String]] =
match json.get_path(reads, json.field(json.root(), key)) {
none => none,
some(v) => match value.as_string(v) {
ok(text) => some(some(text)),
err(_) => some(none),
},
}
fn stored_notes(doc: Option[String]) -> Result[List[Value], String] = match doc {
none => ok([]),
some(text) => match json.parse(text) {
err(e) => err(e),
ok(v) => value.as_array(v),
},
}
fn note_text(body: String) -> Result[String, String] = match json.parse(body) {
err(_) => err("invalid_json"),
ok(data) => match json.get_string(data, "text") {
none => err("text_required"),
some(text) => if string.len(text) == 0 or string.len(text) > 280 then err("text_length")
else ok(text),
},
}
fn list_notes(notes: List[Value]) -> String =
reply(200, value.object([("notes", value.array(list.reverse(notes)))]))
fn add_note(notes: List[Value], text: String) -> String = {
let n = match list.last(notes) {
some(last) => (json.get_int(last, "n") ?? 0) + 1,
none => 1,
}
let note = value.object([("n", value.int(n)), ("text", value.str(text))])
let kept = list.take_end(list.flatten([notes, [note]]), 50)
json.stringify(value.object([
("status", value.int(201)),
("body", value.object([("note", note)])),
("write", value.object([
("key", value.str("notes")),
("value", value.str(json.stringify(value.array(kept)))),
])),
]))
}
fn with_notes(reads: Value, f: (List[Value]) -> String) -> String = match lookup(reads, "notes") {
none => read_request("notes"),
some(doc) => match stored_notes(doc) {
ok(notes) => f(notes),
err(_) => error(500, "store_corrupt"),
},
}
fn notes(method: String, body: String, reads: Value) -> String =
if method == "GET" then with_notes(reads, (notes) => list_notes(notes))
else if method == "POST" then match note_text(body) {
err(code) => error(400, code),
ok(text) => with_notes(reads, (notes) => add_note(notes, text)),
}
else method_error("GET, POST")
// The host calls step with reads = "{}", performs any "read" it gets back (a
// JSON object of key -> stored string or null), and calls again; a reply with
// "status" is final, and its optional "write" is made before the reply is sent.
pub fn step(method: String, target: String, body: String, reads: String) -> String = {
let path = string.split(target, "?") |> list.get(0) ?? target
if path == "/notes" and string.len(body) <= 8192 then match json.parse(reads) {
ok(r) => notes(method, body, r),
err(_) => error(500, "invalid_reads"),
} else handle(method, target, body)
}