camber.bend
bend-kit-camber@0.5.0.0 · 1,086 lines · 0 definitions
1# Fixed prepared HTTP applications and bounded affine dependency bundles.
2import Base
3import bend-kit-http@0.30.0.0/http.bend as Http
4import bend-kit-router@0.2.0.0/router.bend as Router
5import bend-kit-router@0.2.0.0/target.bend as Target
6import bend-kit-json@0.5.1.0/json.bend as Json
7import bend-kit-camber@0.5.0.0/response.bend as Response
8import bend-kit-camber@0.5.0.0/input.bend as Input
9import bend-kit-http@0.30.0.0/completion.bend as TransportCompletion
10
11type ResponseError is Data:
12 InvalidResponse{}
13 InvalidRedirect{}
14
15def validate.checked(valid: Bool, response: Http.Res) -> Result<&2, &1, ResponseError, Http.Res>:
16 match valid:
17 case True{}:
18 Done{response}
19 case False{}:
20 Fail{InvalidResponse{}}
21
22def validate(response: Http.Res) -> Result<&2, &1, ResponseError, Http.Res>:
23 Http.Res{+status, +headers, body} = response
24 validate.checked(Response.valid(status, headers), Http.Res{status, headers, body})
25
26# The encoder is an explicit public projection, never record reflection.
27def json(~T: Type, ~encode: T -> Json.Val, status: U32, value: T) -> Http.Res:
28 Http.Res{status, Http.set(Http.empty(), "content-type", "application/json"), Json.encode.bytes(encode(value))}
29
30def text(status: U32, value: String) -> Http.Res:
31 Http.Res{status, Http.set(Http.empty(), "content-type", "text/plain; charset=utf-8"), Json.utf8(value)}
32
33def raw(status: U32, body: Http.Body) -> Http.Res:
34 Http.Res{status, Http.empty(), body}
35
36def empty(status: U32) -> Http.Res:
37 raw(status, Http.from_string(""))
38
39def redirect.checked(valid: Bool, status: U32, location: String) -> Result<&2, &1, ResponseError, Http.Res>:
40 match valid:
41 case False{}:
42 Fail{InvalidRedirect{}}
43 case True{}:
44 Done{Http.Res{status, Http.set(Http.empty(), "location", location), Http.from_string("")}}
45
46def redirect(+status: U32, +location: String) -> Result<&2, &1, ResponseError, Http.Res>:
47 redirect.checked((U32.is_eq(status, 301) || U32.is_eq(status, 302) || U32.is_eq(status, 303) || U32.is_eq(status, 307) || U32.is_eq(status, 308)) && Bool.not(String.eq(location, "")) && Response.value(location), status, location)
48
49type Counts is Data:
50 Counts{capacity: Nat, in_use: Nat, completed: Nat, rejected: Nat}
51
52type Outcome<-E: Data, -O: Data> is Data:
53 Success{value: O}
54 Expected{error: E}
55 Exhausted{}
56 Stopped{}
57
58type CloseResult is Data:
59 Closed{}
60 Busy{counts: Counts}
61
62type Message<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type:
63 Submit{principal: P, input: I, reply: Chan(Outcome<E, O>)}
64 Returned{resource: R}
65 Inspect{reply: Chan(Counts)}
66 Shutdown{reply: Chan(CloseResult)}
67
68# Copy only this context, never the resources. The inbox bounds queued commands;
69# callers must count blocked submissions inside their transport admission limit.
70type Context<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Data:
71 Context{messages: Chan(Message<R, P, I, E, O>)}
72
73# The completion future has one affine owner; request contexts copy only inbox.
74type Owner<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type:
75 Owner{context: Context<R, P, I, E, O>, finished: Chan(Unit)}
76
77type OwnerClose<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type:
78 OwnerClosed{}
79 OwnerBusy{owner: Owner<R, P, I, E, O>, counts: Counts}
80
81def owner.context(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data,
82 owner: Owner<R, P, I, E, O>
83) -> Owner<R, P, I, E, O> & Context<R, P, I, E, O>:
84 Owner{+context, finished} = owner
85 (Owner{context, finished}, context)
86
87def outcome.value(-E: Data, -O: Data, result: Result<&2, &2, E, O>) -> Outcome<E, O>:
88 match result:
89 case Fail{error}:
90 Expected{error}
91 case Done{value}:
92 Success{value}
93
94# The resources return through the private owner protocol BEFORE a disposable
95# caller reply. A closed caller reply cannot erase a handle or strand capacity.
96def work.return(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data,
97 messages: Chan(Message<R, P, I, E, O>), reply: Chan(Outcome<E, O>),
98 result: R & Result<&2, &2, E, O>
99) -> IO(Unit):
100 (returned, outcome) = result
101 do IO<Unit>:
102 accepted : Bool <- Chan.send(Message<R, P, I, E, O>, messages, Returned{returned})
103 sent : Bool <- Chan.send(Outcome<E, O>, reply, outcome.value(E, O, outcome))
104 return Unit{}
105
106def work(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
107 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>),
108 resource: R, principal: P, input: I,
109 messages: Chan(Message<R, P, I, E, O>), reply: Chan(Outcome<E, O>)
110) -> IO(Unit):
111 do IO<Unit>:
112 result : R & Result<&2, &2, E, O> <- handler(resource, principal, input)
113 work.return(R, P, I, E, O, messages, reply, result)
114
115def dispose(~R: Type, ~destroy: R -> IO(Unit), resources: List<R>) -> IO(Unit):
116 match resources:
117 case Nil{}:
118 IO.pure(Unit, Unit{})
119 case Con{resource, rest}:
120 do IO<Unit>:
121 destroy(resource)
122 dispose(~R, ~destroy, rest)
123
124# Closing Base channels retains buffered messages. Drain them and answer stopped
125# callers, rather than stranding accepted commands behind the close command.
126def drain.message(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
127 ~destroy: R -> IO(Unit), message: Message<R, P, I, E, O>
128) -> IO(Unit):
129 match message:
130 case Submit{principal, input, reply}:
131 do IO<Unit>:
132 sent : Bool <- Chan.send(Outcome<E, O>, reply, Stopped{})
133 return Unit{}
134 case Inspect{reply}:
135 Chan.close(Counts, reply)
136 case Shutdown{reply}:
137 do IO<Unit>:
138 sent : Bool <- Chan.send(CloseResult, reply, Closed{})
139 return Unit{}
140 case Returned{resource}:
141 destroy(resource)
142
143def drain.step(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
144 ~destroy: R -> IO(Unit), pending: Maybe<&1, Message<R, P, I, E, O>>,
145 next: Unit -> IO(Unit)
146) -> IO(Unit):
147 match pending:
148 case None{}:
149 IO.pure(Unit, Unit{})
150 case Some{message}:
151 do IO<Unit>:
152 drain.message(~R, ~P, ~I, ~E, ~O, ~destroy, message)
153 next(Unit{})
154
155# The closed inbox contains at most room buffered messages.
156def drain(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
157 ~destroy: R -> IO(Unit), fuel: Nat, +messages: Chan(Message<R, P, I, E, O>)
158) -> IO(Unit):
159 match fuel:
160 case 0n:
161 IO.pure(Unit, Unit{})
162 case 1n+p:
163 do IO<Unit>:
164 pending : Maybe<&1, Message<R, P, I, E, O>> <- Chan.recv(Message<R, P, I, E, O>, messages)
165 drain.step(~R, ~P, ~I, ~E, ~O, ~destroy, pending, u => drain(~R, ~P, ~I, ~E, ~O, ~destroy, p, messages))
166
167type OwnerStep<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type:
168 Waiting{resources: List<R>, counts: Counts}
169 Received{message: Message<R, P, I, E, O>, counts: Counts, resources: List<R>}
170
171# IO.join closes a one-shot channel; the owner inbox must remain reusable.
172def receive.value(-A: Type, value: Maybe<&1, A>) -> IO(A):
173 match value:
174 case None{}:
175 IO.die(A, 1, "camber internal channel closed unexpectedly")
176 case Some{message}:
177 IO.pure(A, message)
178
179def receive(-A: Type, channel: Chan(A)) -> IO(A):
180 do IO<A>:
181 value : Maybe<&1, A> <- Chan.recv(A, channel)
182 receive.value(A, value)
183
184# Only idle close ends this loop. Busy close never waits on a stalled handler.
185# @unsafe is solely the lifecycle receive loop, not a termination/proof claim.
186@unsafe def owner(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
187 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit),
188 +messages: Chan(Message<R, P, I, E, O>), +room: U32, step: OwnerStep<R, P, I, E, O>
189) -> IO(Unit):
190 match step:
191 case Waiting{resources, counts}:
192 do IO<Unit>:
193 message : Message<R, P, I, E, O> <- receive(Message<R, P, I, E, O>, messages)
194 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Received{message, counts, resources})
195 case Received{message, counts, resources}:
196 match message:
197 case Returned{resource}:
198 Counts{capacity, in_use, completed, rejected} = counts
199 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resource <> resources, Counts{capacity, Nat.sub(in_use, 1n), 1n+completed, rejected}})
200 case Inspect{reply}:
201 Counts{+capacity, +in_use, +completed, +rejected} = counts
202 do IO<Unit>:
203 sent : Bool <- Chan.send(Counts, reply, Counts{capacity, in_use, completed, rejected})
204 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, in_use, completed, rejected}})
205 case Shutdown{reply}:
206 Counts{+capacity, in_use, +completed, +rejected} = counts
207 match in_use:
208 case 0n:
209 do IO<Unit>:
210 dispose(~R, ~destroy, resources)
211 Chan.close(Message<R, P, I, E, O>, messages)
212 sent : Bool <- Chan.send(CloseResult, reply, Closed{})
213 drain(~R, ~P, ~I, ~E, ~O, ~destroy, U32.to_nat(room), messages)
214 case 1n+ +p:
215 do IO<Unit>:
216 sent : Bool <- Chan.send(CloseResult, reply, Busy{Counts{capacity, 1n+p, completed, rejected}})
217 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, 1n+p, completed, rejected}})
218 case Submit{principal, input, reply}:
219 Counts{capacity, in_use, completed, rejected} = counts
220 match resources:
221 case Nil{}:
222 do IO<Unit>:
223 sent : Bool <- Chan.send(Outcome<E, O>, reply, Exhausted{})
224 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{Nil{}, Counts{capacity, in_use, completed, 1n+rejected}})
225 case Con{resource, rest}:
226 do IO<Unit>:
227 IO.spawn(Unit, work(~R, ~P, ~I, ~E, ~O, ~handler, resource, principal, input, messages, reply))
228 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{rest, Counts{capacity, 1n+in_use, completed, rejected}})
229
230def build.step(~R: Type, ~E: Data, ~destroy: R -> IO(Unit),
231 result: Result<&2, &1, E, R>, resources: List<R>,
232 next: List<R> -> IO(Result<&2, &1, E, List<R>>)
233) -> IO(Result<&2, &1, E, List<R>>):
234 match result:
235 case Fail{error}:
236 do IO<Result<&2, &1, E, List<R>>>:
237 dispose(~R, ~destroy, resources)
238 return Fail{error}
239 case Done{resource}:
240 next(resource <> resources)
241
242def build(~K: Data, ~R: Type, ~E: Data, ~create: K -> Nat -> IO(Result<&2, &1, E, R>), ~destroy: R -> IO(Unit),
243 remaining: Nat, +config: K, resources: List<R>
244) -> IO(Result<&2, &1, E, List<R>>):
245 match remaining:
246 case 0n:
247 IO.pure(Result<&2, &1, E, List<R>>, Done{resources})
248 case 1n+ +p:
249 do IO<Result<&2, &1, E, List<R>>>:
250 result : Result<&2, &1, E, R> <- create(config, p)
251 build.step(~R, ~E, ~destroy, result, resources, next => build(~K, ~R, ~E, ~create, ~destroy, p, config, next))
252
253def start.ready(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
254 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit),
255 capacity: Nat, +room: U32, result: Result<&2, &1, E, List<R>>
256) -> IO(Result<&2, &1, E, Owner<R, P, I, E, O>>):
257 match result:
258 case Fail{error}:
259 IO.pure(Result<&2, &1, E, Owner<R, P, I, E, O>>, Fail{error})
260 case Done{resources}:
261 do IO<Result<&2, &1, E, Owner<R, P, I, E, O>>>:
262 channel : Chan(Message<R, P, I, E, O>) <- Chan.new(Message<R, P, I, E, O>, room)
263 +messages : Chan(Message<R, P, I, E, O>) = channel
264 finished : Chan(Unit) <- IO.fork(Unit, owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, 0n, 0n, 0n}}))
265 return Done{Owner{Context{messages}, finished}}
266
267# The initializer closes partial handles within a failed bundle; start closes
268# all prior whole bundles. No workers exist until the complete set opens.
269def start(~K: Data, ~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
270 ~create: K -> Nat -> IO(Result<&2, &1, E, R>),
271 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit),
272 config: K, +capacity: Nat, room: U32
273) -> IO(Result<&2, &1, E, Owner<R, P, I, E, O>>):
274 do IO<Result<&2, &1, E, Owner<R, P, I, E, O>>>:
275 result : Result<&2, &1, E, List<R>> <- build(~K, ~R, ~E, ~create, ~destroy, capacity, config, Nil{})
276 start.ready(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, capacity, room, result)
277
278def submit(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data,
279 context: Context<R, P, I, E, O>, principal: P, input: I, reply: Chan(Outcome<E, O>)
280) -> IO(Bool):
281 Context{messages} = context
282 Chan.send(Message<R, P, I, E, O>, messages, Submit{principal, input, reply})
283
284def call.accepted(-E: Data, -O: Data, accepted: Bool, +reply: Chan(Outcome<E, O>)) -> IO(Outcome<E, O>):
285 match accepted:
286 case False{}:
287 do IO<Outcome<E, O>>:
288 Chan.close(Outcome<E, O>, reply)
289 return Stopped{}
290 case True{}:
291 do IO<Outcome<E, O>>:
292 result : Outcome<E, O> <- IO.join(Outcome<E, O>, reply)
293 Chan.close(Outcome<E, O>, reply)
294 return result
295
296def call(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data,
297 context: Context<R, P, I, E, O>, principal: P, input: I
298) -> IO(Outcome<E, O>):
299 do IO<Outcome<E, O>>:
300 channel : Chan(Outcome<E, O>) <- Chan.new(Outcome<E, O>, 1)
301 +reply : Chan(Outcome<E, O>) = channel
302 accepted : Bool <- submit(R, P, I, E, O, context, principal, input, reply)
303 call.accepted(E, O, accepted, reply)
304
305def inspect.value(value: Maybe<&1, Counts>) -> Maybe<&2, Counts>:
306 match value:
307 case None{}:
308 None{}
309 case Some{counts}:
310 Some{counts}
311
312def inspect.sent(sent: Bool, +reply: Chan(Counts)) -> IO(Maybe<&2, Counts>):
313 match sent:
314 case False{}:
315 do IO<Maybe<&2, Counts>>:
316 Chan.close(Counts, reply)
317 return None{}
318 case True{}:
319 do IO<Maybe<&2, Counts>>:
320 value : Maybe<&1, Counts> <- Chan.recv(Counts, reply)
321 Chan.close(Counts, reply)
322 return inspect.value(value)
323
324def inspect(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, context: Context<R, P, I, E, O>) -> IO(Maybe<&2, Counts>):
325 Context{messages} = context
326 do IO<Maybe<&2, Counts>>:
327 channel : Chan(Counts) <- Chan.new(Counts, 1)
328 +reply : Chan(Counts) = channel
329 sent : Bool <- Chan.send(Message<R, P, I, E, O>, messages, Inspect{reply})
330 inspect.sent(sent, reply)
331
332def close.sent(sent: Bool, +reply: Chan(CloseResult)) -> IO(CloseResult):
333 match sent:
334 case False{}:
335 do IO<CloseResult>:
336 Chan.close(CloseResult, reply)
337 return Closed{}
338 case True{}:
339 do IO<CloseResult>:
340 value : CloseResult <- IO.join(CloseResult, reply)
341 Chan.close(CloseResult, reply)
342 return value
343
344def close.join(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data,
345 owner: Owner<R, P, I, E, O>
346) -> IO(OwnerClose<R, P, I, E, O>):
347 Owner{context, finished} = owner
348 do IO<OwnerClose<R, P, I, E, O>>:
349 IO.join(Unit, finished)
350 return OwnerClosed{}
351
352def close.completed(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data,
353 result: CloseResult, owner: Owner<R, P, I, E, O>
354) -> IO(OwnerClose<R, P, I, E, O>):
355 match result:
356 case Closed{}:
357 close.join(R, P, I, E, O, owner)
358 case Busy{counts}:
359 IO.pure(OwnerClose<R, P, I, E, O>, OwnerBusy{owner, counts})
360
361# Stop external admissions before close. Busy preserves live context/capacity;
362# retry after admitted operations finish. Stalled operations remain observable.
363def close(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, owner: Owner<R, P, I, E, O>) -> IO(OwnerClose<R, P, I, E, O>):
364 Owner{Context{+messages}, finished} = owner
365 do IO<OwnerClose<R, P, I, E, O>>:
366 channel : Chan(CloseResult) <- Chan.new(CloseResult, 1)
367 +reply : Chan(CloseResult) = channel
368 sent : Bool <- Chan.send(Message<R, P, I, E, O>, messages, Shutdown{reply})
369 result : CloseResult <- close.sent(sent, reply)
370 close.completed(R, P, I, E, O, result, Owner{Context{messages}, finished})
371
372# Policies are prepared Data, not callbacks. Later lifecycle stages can consume
373# these root-to-route declarations without splitting patterns or finding groups.
374type Group<-P: Data> is Data:
375 Group{id: U32, ancestors: List<&2, U32>, policy: P}
376
377type GroupPlan<-P: Data> is Data:
378 GroupPlan{ids: List<&2, U32>, policies: List<&2, P>}
379
380type Route<-A: Data, -P: Data> is Data:
381 Route{method: String, pattern: String, action: A, groups: List<&2, U32>, policy: P}
382
383type Endpoint<-A: Data, -P: Data> is Data:
384 Endpoint{action: A, policies: List<&2, P>}
385
386type Application<-A: Data, -C: Data, -P: Data> is Data:
387 Application{table: Router.Table<Endpoint<A, P>>, context: C, root: P, groups: List<&2, GroupPlan<P>>}
388
389type RegistrationError is Data:
390 RouteError{error: Router.Error}
391 InvalidGroups{groups: List<&2, U32>}
392
393type Match<-P: Data> is Data:
394 Match{target: Target.Target, params: Map<&2, String>, groups: List<&2, U32>, method: String, pattern: String, policies: List<&2, P>}
395
396def groups.equal(xs: List<&2, U32>, ys: List<&2, U32>) -> Bool:
397 match xs ys:
398 case Nil{} Nil{}:
399 True{}
400 case x <> xt y <> yt:
401 U32.is_eq(x, y) && groups.equal(xt, yt)
402 case _ _:
403 False{}
404
405def group.pick(~P: Data, same: Bool, group: Group<P>, later: Unit -> Maybe<&2, Group<P>>) -> Maybe<&2, Group<P>>:
406 match same:
407 case True{}:
408 Some{group}
409 case False{}:
410 later(Unit{})
411
412def group.find(~P: Data, groups: List<&2, Group<P>>, +id: U32) -> Maybe<&2, Group<P>>:
413 match groups:
414 case Nil{}:
415 None{}
416 case Group{+here, ancestors, policy} <> rest:
417 group.pick(~P, U32.is_eq(here, id), Group{here, ancestors, policy}, u => group.find(~P, rest, id))
418
419def group.checked(~P: Data, same: Bool, policy: P) -> Maybe<&2, P>:
420 match same:
421 case False{}:
422 None{}
423 case True{}:
424 Some{policy}
425
426def group.policy(~P: Data, found: Maybe<&2, Group<P>>, ancestors: List<&2, U32>) -> Maybe<&2, P>:
427 match found:
428 case None{}:
429 None{}
430 case Some{Group{id, expected, policy}}:
431 group.checked(~P, groups.equal(expected, ancestors), policy)
432
433def groups.policies(~P: Data, ids: List<&2, U32>, +declared: List<&2, Group<P>>, +ancestors: List<&2, U32>) -> Maybe<&2, List<&2, P>>:
434 match ids:
435 case Nil{}:
436 Some{Nil{}}
437 case +id <> rest:
438 do Maybe<&2, List<&2, P>>:
439 policy : P <- group.policy(~P, group.find(~P, declared, id), ancestors)
440 policies : List<&2, P> <- groups.policies(~P, rest, declared, List.append(&2, U32, ancestors, [id]))
441 return policy <> policies
442
443def groups.unique(~P: Data, found: Maybe<&2, Group<P>>) -> Bool:
444 match found:
445 case None{}:
446 True{}
447 case Some{group}:
448 False{}
449
450def groups.valid(~P: Data, groups: List<&2, Group<P>>, +all: List<&2, Group<P>>) -> Bool:
451 match groups:
452 case Nil{}:
453 True{}
454 case Group{+id, +ancestors, policy} <> +rest:
455 chain = groups.policies(~P, List.append(&2, U32, ancestors, [id]), all, Nil{})
456 groups.unique(~P, group.find(~P, rest, id)) && Maybe.is_some(&2, List<&2, P>, chain) && groups.valid(~P, rest, all)
457
458def routes.policies(~P: Data, result: Maybe<&2, List<&2, P>>, ids: List<&2, U32>) -> Result<&2, &2, RegistrationError, List<&2, P>>:
459 match result:
460 case None{}:
461 Fail{InvalidGroups{ids}}
462 case Some{policies}:
463 Done{policies}
464
465def routes.prepare(~A: Data, ~P: Data, routes: List<&2, Route<A, P>>, +groups: List<&2, Group<P>>, +root: P) -> Result<&2, &2, RegistrationError, List<&2, Router.Entry<Endpoint<A, P>>>>:
466 match routes:
467 case Nil{}:
468 Done{Nil{}}
469 case Route{method, pattern, action, +ids, policy} <> rest:
470 do Result<&2, &2, RegistrationError, List<&2, Router.Entry<Endpoint<A, P>>>>:
471 policies : List<&2, P> <- routes.policies(~P, groups.policies(~P, ids, groups, Nil{}), ids)
472 entries : List<&2, Router.Entry<Endpoint<A, P>>> <- routes.prepare(~A, ~P, rest, groups, root)
473 return Router.Entry{method, pattern, Endpoint{action, root <> List.append(&2, P, policies, [policy])}, ids} <> entries
474
475def groups.prepare(~P: Data, groups: List<&2, Group<P>>, +all: List<&2, Group<P>>) -> Result<&2, &2, RegistrationError, List<&2, GroupPlan<P>>>:
476 match groups:
477 case Nil{}:
478 Done{Nil{}}
479 case Group{id, ancestors, policy} <> rest:
480 +ids = List.append(&2, U32, ancestors, [id])
481 do Result<&2, &2, RegistrationError, List<&2, GroupPlan<P>>>:
482 policies : List<&2, P> <- routes.policies(~P, groups.policies(~P, ids, all, Nil{}), ids)
483 plans : List<&2, GroupPlan<P>> <- groups.prepare(~P, rest, all)
484 return GroupPlan{ids, policies} <> plans
485
486def groups.select(~P: Data, same: Bool, policies: List<&2, P>, later: Unit -> List<&2, P>) -> List<&2, P>:
487 match same:
488 case True{}: policies
489 case False{}: later(Unit{})
490
491def groups.prepared(~P: Data, plans: List<&2, GroupPlan<P>>, +ids: List<&2, U32>) -> List<&2, P>:
492 match plans:
493 case Nil{}:
494 Nil{}
495 case GroupPlan{here, policies} <> rest:
496 groups.select(~P, groups.equal(here, ids), policies, u => groups.prepared(~P, rest, ids))
497
498def application.table(~A: Data, ~C: Data, ~P: Data, result: Result<&2, &2, Router.Error, Router.Table<Endpoint<A, P>>>, context: C, root: P, groups: List<&2, GroupPlan<P>>) -> Result<&2, &2, RegistrationError, Application<A, C, P>>:
499 match result:
500 case Fail{error}:
501 Fail{RouteError{error}}
502 case Done{table}:
503 Done{Application{table, context, root, groups}}
504
505def application.valid(~A: Data, ~C: Data, ~P: Data, valid: Bool, routes: List<&2, Route<A, P>>, +groups: List<&2, Group<P>>, context: C, +root: P) -> Result<&2, &2, RegistrationError, Application<A, C, P>>:
506 match valid:
507 case False{}:
508 Fail{InvalidGroups{Nil{}}}
509 case True{}:
510 do Result<&2, &2, RegistrationError, Application<A, C, P>>:
511 entries : List<&2, Router.Entry<Endpoint<A, P>>> <- routes.prepare(~A, ~P, routes, groups, root)
512 plans : List<&2, GroupPlan<P>> <- groups.prepare(~P, groups, groups)
513 application.table(~A, ~C, ~P, Router.prepare(~Endpoint<A, P>, entries), context, root, plans)
514
515# Construction is pure and fallible; no listener or owner is started here.
516def application(~A: Data, ~C: Data, ~P: Data, routes: List<&2, Route<A, P>>, +groups: List<&2, Group<P>>, context: C, root: P) -> Result<&2, &2, RegistrationError, Application<A, C, P>>:
517 application.valid(~A, ~C, ~P, groups.valid(~P, groups, groups), routes, groups, context, root)
518
519def describe(~A: Data, ~C: Data, ~P: Data, app: Application<A, C, P>) -> List<&2, Router.Description>:
520 Application{table, context, root, groups} = app
521 Router.describe(~Endpoint<A, P>, table)
522
523# A closed template can run repeatedly; an affine callback registry cannot.
524def plain(~handler: Http.Req -> IO(Http.Res), req: Http.Req) -> IO(Result<&2, &1, ResponseError, Http.Res>):
525 do IO<Result<&2, &1, ResponseError, Http.Res>>:
526 response : Http.Res <- handler(req)
527 return validate(response)
528
529type Policy<-H: Data> is Data:
530 Policy{id: U32, before: List<&2, H>, transforms: List<&2, H>}
531
532def policy(~H: Data, id: U32, before: List<&2, H>, transforms: List<&2, H>) -> Policy<H>:
533 Policy{id, before, List.reverse(&2, H, transforms)}
534
535type Metadata is Data:
536 Metadata{method: String, target: String, headers: Map<&2, List<&2, String>>}
537
538type Error<-E: Data> is Data:
539 ApplicationError{error: E}
540 Framework{status: U32, allow: String}
541 ResponseInvalid{}
542
543# Adapt reusable input status failures to the existing one-pass dispatch mapper.
544def input(~I: Type, ~E: Data, outcome: Result<&2, &1, U32, I>) -> Result<&2, &1, Error<E>, I>:
545 match outcome:
546 case Fail{status}: Fail{Framework{status, ""}}
547 case Done{value}: Done{value}
548
549def execute.decoded(~I: Type, ~S: Type, ~K: Data, ~E: Data,
550 ~handler: S -> K -> I -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
551 outcome: Result<&2, &1, Error<E>, I>, state: S, config: K
552) -> IO(S & Result<&2, &1, Error<E>, Http.Res>):
553 match outcome:
554 case Fail{error}: IO.pure(S & Result<&2, &1, Error<E>, Http.Res>, (state, Fail{error}))
555 case Done{value}: handler(state, config, value)
556
557# An executor, not a second dispatcher: call from dispatch's run template, after
558# all applicable before hooks. Only validated typed input reaches business IO.
559def execute(~I: Type, ~S: Type, ~K: Data, ~E: Data, ~H: Data,
560 ~decoder: K -> Match<Policy<H>> -> Http.Req -> Result<&2, &1, Error<E>, I>,
561 ~handler: S -> K -> I -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
562 state: S, +config: K, selected: Match<Policy<H>>, req: Http.Req
563) -> IO(S & Result<&2, &1, Error<E>, Http.Res>):
564 execute.decoded(~I, ~S, ~K, ~E, ~handler, decoder(config, selected, req), state, config)
565
566type Step<-S: Type, -E: Data> is Type:
567 Continue{state: S}
568 Early{state: S, response: Http.Res}
569 Reject{state: S, error: Error<E>}
570
571type Completion<-S: Type, -H: Data> is Type:
572 Completed{state: S, response: Http.Res, entered: List<&2, Policy<H>>, mapped: Bool, stopped: Bool}
573
574# Application completion only: no socket write, duration, or peer-receipt claim.
575def minimal() -> Http.Res:
576 empty(500)
577
578def problem.allow(add: Bool, headers: Map<&2, List<&2, String>>, allow: String) -> Map<&2, List<&2, String>>:
579 match add:
580 case True{}: Http.set(headers, "allow", allow)
581 case False{}: headers
582
583def problem.response(allow: String, response: Http.Res) -> Http.Res:
584 Http.Res{+status, headers, body} = response
585 Http.Res{status, problem.allow(U32.is_eq(status, 405), Http.set(headers, "content-type", "application/problem+json"), allow), body}
586
587def problem(+status: U32, title: String, allow: String) -> Http.Res:
588 problem.response(allow, json(~Json.Val, ~(x => x), status, Json.Obj{[(Json.utf8("type"), Json.Str{Json.utf8("about:blank")}), (Json.utf8("title"), Json.Str{Json.utf8(title)}), (Json.utf8("status"), Json.Num{Json.utf8(U32.show(status))})]}))
589
590def default.error(~E: Data, error: Error<E>) -> Http.Res:
591 match error:
592 case Framework{status, allow}:
593 match status:
594 case 400:
595 problem(400, "Bad Request", allow)
596 case 404:
597 problem(404, "Not Found", allow)
598 case 405:
599 problem(405, "Method Not Allowed", allow)
600 case 415:
601 problem(415, "Unsupported Media Type", allow)
602 case _:
603 minimal()
604 case _:
605 minimal()
606
607def default.mapper(~S: Type, ~K: Data, ~E: Data, state: S, config: K, error: Error<E>) -> IO(S & Result<&2, &1, Error<E>, Http.Res>):
608 IO.pure(S & Result<&2, &1, Error<E>, Http.Res>, (state, Done{default.error(~E, error)}))
609
610def mapped.checked(~S: Type, ~H: Data, state: S, entered: List<&2, Policy<H>>, stopped: Bool, result: Result<&2, &1, ResponseError, Http.Res>) -> Completion<S, H>:
611 match result:
612 case Fail{error}:
613 Completed{state, minimal(), entered, True{}, True{}}
614 case Done{response}:
615 Completed{state, response, entered, True{}, stopped}
616
617def mapped.result(~S: Type, ~E: Data, ~H: Data, entered: List<&2, Policy<H>>, stopped: Bool, result: S & Result<&2, &1, Error<E>, Http.Res>) -> Completion<S, H>:
618 (state, outcome) = result
619 match outcome:
620 case Fail{error}:
621 Completed{state, minimal(), entered, True{}, True{}}
622 case Done{response}:
623 mapped.checked(~S, ~H, state, entered, stopped, validate(response))
624
625def failed(~S: Type, ~K: Data, ~E: Data, ~H: Data,
626 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
627 mapped: Bool, state: S, config: K, error: Error<E>, entered: List<&2, Policy<H>>, stopped: Bool
628) -> IO(Completion<S, H>):
629 match mapped:
630 case True{}:
631 IO.pure(Completion<S, H>, Completed{state, minimal(), entered, True{}, True{}})
632 case False{}:
633 do IO<Completion<S, H>>:
634 result : S & Result<&2, &1, Error<E>, Http.Res> <- mapper(state, config, error)
635 return mapped.result(~S, ~E, ~H, entered, stopped, result)
636
637def result(~S: Type, ~K: Data, ~E: Data, ~H: Data,
638 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
639 config: K, entered: List<&2, Policy<H>>, outcome: S & Result<&2, &1, Error<E>, Http.Res>
640) -> IO(Completion<S, H>):
641 (state, response) = outcome
642 match response:
643 case Fail{error}:
644 failed(~S, ~K, ~E, ~H, ~mapper, False{}, state, config, error, entered, False{})
645 case Done{res}:
646 IO.pure(Completion<S, H>, Completed{state, res, entered, False{}, False{}})
647
648def before.next(~S: Type, ~E: Data, step: Step<S, E>, next: S -> IO(Step<S, E>)) -> IO(Step<S, E>):
649 match step:
650 case Continue{state}:
651 next(state)
652 case Early{state, response}:
653 IO.pure(Step<S, E>, Early{state, response})
654 case Reject{state, error}:
655 IO.pure(Step<S, E>, Reject{state, error})
656
657def before(~S: Type, ~K: Data, ~E: Data, ~H: Data,
658 ~hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
659 hooks: List<&2, H>, state: S, +config: K, +meta: Metadata
660) -> IO(Step<S, E>):
661 match hooks:
662 case Nil{}:
663 IO.pure(Step<S, E>, Continue{state})
664 case h <> rest:
665 do IO<Step<S, E>>:
666 step : Step<S, E> <- hook(h, state, config, meta)
667 before.next(~S, ~E, step, s => before(~S, ~K, ~E, ~H, ~hook, rest, s, config, meta))
668
669def transformed(~S: Type, ~K: Data, ~E: Data, ~H: Data,
670 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
671 config: K, entered: List<&2, Policy<H>>, mapped: Bool, outcome: S & Result<&2, &1, Error<E>, Http.Res>
672) -> IO(Completion<S, H>):
673 (state, response) = outcome
674 match response:
675 case Fail{error}:
676 failed(~S, ~K, ~E, ~H, ~mapper, mapped, state, config, error, entered, True{})
677 case Done{res}:
678 IO.pure(Completion<S, H>, Completed{state, res, entered, mapped, False{}})
679
680def transforms(~S: Type, ~K: Data, ~E: Data, ~H: Data,
681 ~hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
682 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
683 hooks: List<&2, H>, +config: K, completion: Completion<S, H>
684) -> IO(Completion<S, H>):
685 match hooks:
686 case Nil{}:
687 IO.pure(Completion<S, H>, completion)
688 case h <> rest:
689 match completion:
690 case Completed{state, response, entered, mapped, True{}}:
691 IO.pure(Completion<S, H>, Completed{state, response, entered, mapped, True{}})
692 case Completed{state, response, +entered, +mapped, False{}}:
693 do IO<Completion<S, H>>:
694 changed : S & Result<&2, &1, Error<E>, Http.Res> <- hook(h, state, config, response)
695 next : Completion<S, H> <- transformed(~S, ~K, ~E, ~H, ~mapper, config, entered, mapped, changed)
696 transforms(~S, ~K, ~E, ~H, ~hook, ~mapper, rest, config, next)
697
698def entered.step(~S: Type, ~K: Data, ~E: Data, ~H: Data,
699 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
700 config: K, entered: List<&2, Policy<H>>, body: Http.Body, step: Step<S, E>, next: S -> List<&2, Policy<H>> -> Http.Body -> IO(Completion<S, H>)
701) -> IO(Completion<S, H>):
702 match step:
703 case Continue{state}:
704 next(state, entered, body)
705 case Early{state, response}:
706 IO.pure(Completion<S, H>, Completed{state, response, entered, False{}, False{}})
707 case Reject{state, error}:
708 failed(~S, ~K, ~E, ~H, ~mapper, False{}, state, config, error, entered, False{})
709
710def scopes(~S: Type, ~K: Data, ~E: Data, ~H: Data,
711 ~before_hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
712 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
713 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
714 policies: List<&2, Policy<H>>, state: S, +config: K, +meta: Metadata, entered: List<&2, Policy<H>>, body: Http.Body,
715 next: S -> List<&2, Policy<H>> -> Http.Body -> IO(Completion<S, H>)
716) -> IO(Completion<S, H>):
717 match policies:
718 case Nil{}:
719 next(state, entered, body)
720 case Policy{+id, +hooks, +unwind} <> rest:
721 scope = {Policy{id, hooks, unwind} : Policy<H>}
722 do IO<Completion<S, H>>:
723 step : Step<S, E> <- before(~S, ~K, ~E, ~H, ~before_hook, hooks, state, config, meta)
724 inner : Completion<S, H> <- entered.step(~S, ~K, ~E, ~H, ~mapper, config, scope <> entered, body, step, s => ids => b => scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, rest, s, config, meta, ids, b, next))
725 transforms(~S, ~K, ~E, ~H, ~after_hook, ~mapper, unwind, config, inner)
726
727def finish.checked(~S: Type, ~K: Data, ~E: Data, ~H: Data,
728 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
729 state: S, config: K, entered: List<&2, Policy<H>>, mapped: Bool, stopped: Bool, checked: Result<&2, &1, ResponseError, Http.Res>
730) -> IO(Completion<S, H>):
731 match checked:
732 case Done{response}:
733 IO.pure(Completion<S, H>, Completed{state, response, entered, mapped, stopped})
734 case Fail{error}:
735 failed(~S, ~K, ~E, ~H, ~mapper, mapped, state, config, ResponseInvalid{}, entered, True{})
736
737def finish(~S: Type, ~K: Data, ~E: Data, ~H: Data,
738 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
739 config: K, completion: Completion<S, H>
740) -> IO(Completion<S, H>):
741 Completed{state, response, entered, mapped, stopped} = completion
742 finish.checked(~S, ~K, ~E, ~H, ~mapper, state, config, entered, mapped, stopped, validate(response))
743
744def policies.tail(~H: Data, policies: List<&2, Policy<H>>) -> List<&2, Policy<H>>:
745 match policies:
746 case Nil{}:
747 Nil{}
748 case root <> rest:
749 rest
750
751
752def endpoint(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data,
753 ~run: A -> S -> K -> Match<Policy<H>> -> Http.Req -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
754 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
755 action: A, state: S, +config: K, selected: Match<Policy<H>>, meta: Metadata, entered: List<&2, Policy<H>>, body: Http.Body
756) -> IO(Completion<S, H>):
757 Metadata{method, target, headers} = meta
758 do IO<Completion<S, H>>:
759 outcome : S & Result<&2, &1, Error<E>, Http.Res> <- run(action, state, config, selected, Http.Req{method, target, headers, body})
760 result(~S, ~K, ~E, ~H, ~mapper, config, entered, outcome)
761
762def framework(~S: Type, ~K: Data, ~E: Data, ~H: Data,
763 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
764 +status: U32, allow: String, state: S, config: K, entered: List<&2, Policy<H>>, body: Http.Body
765) -> IO(Completion<S, H>):
766 match status:
767 case 204:
768 IO.pure(Completion<S, H>, Completed{state, Http.Res{204, Http.set(Http.empty(), "allow", allow), Http.from_string("")}, entered, False{}, False{}})
769 case _:
770 failed(~S, ~K, ~E, ~H, ~mapper, False{}, state, config, Framework{status, allow}, entered, False{})
771
772def choice(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data,
773 ~before_hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
774 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
775 ~run: A -> S -> K -> Match<Policy<H>> -> Http.Req -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
776 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
777 selected: Router.Choice<Endpoint<A, Policy<H>>>, target: Target.Target,
778 groups: List<&2, GroupPlan<Policy<H>>>, state: S, +config: K, +meta: Metadata, entered: List<&2, Policy<H>>, body: Http.Body
779) -> IO(Completion<S, H>):
780 match selected:
781 case Router.NotFound{}:
782 framework(~S, ~K, ~E, ~H, ~mapper, 404, "", state, config, entered, body)
783 case Router.MethodMissing{ids, allow}:
784 policies = groups.prepared(~Policy<H>, groups, ids)
785 scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, policies, state, config, meta, entered, body,
786 s => entered => b => framework(~S, ~K, ~E, ~H, ~mapper, 405, allow, s, config, entered, b))
787 case Router.Options{ids, allow}:
788 policies = groups.prepared(~Policy<H>, groups, ids)
789 scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, policies, state, config, meta, entered, body,
790 s => entered => b => framework(~S, ~K, ~E, ~H, ~mapper, 204, allow, s, config, entered, b))
791 case Router.Found{Endpoint{action, +policies}, params, ids, method, pattern}:
792 selected = {Match{target, params, ids, method, pattern, policies} : Match<Policy<H>>}
793 scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, policies.tail(~H, policies), state, config, meta, entered, body,
794 s => entered => b => endpoint(~A, ~S, ~K, ~E, ~H, ~run, ~mapper, action, s, config, selected, meta, entered, b))
795
796def metadata.authority(authority: Maybe<&2, String>, meta: Metadata) -> Metadata:
797 match authority:
798 case None{}:
799 meta
800 case Some{host}:
801 Metadata{method, target, headers} = meta
802 Metadata{method, target, Http.set(headers, "host", host)}
803
804def resolved(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data,
805 ~before_hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
806 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
807 ~run: A -> S -> K -> Match<Policy<H>> -> Http.Req -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
808 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
809 outcome: Result<&2, &2, Router.Error, Router.Resolved<Endpoint<A, Policy<H>>>>,
810 groups: List<&2, GroupPlan<Policy<H>>>, state: S, config: K, meta: Metadata, entered: List<&2, Policy<H>>, body: Http.Body
811) -> IO(Completion<S, H>):
812 match outcome:
813 case Fail{error}:
814 framework(~S, ~K, ~E, ~H, ~mapper, 400, "", state, config, entered, body)
815 case Done{Router.Resolved{+target, selected}}:
816 Target.Target{segments, query, authority, star} = target
817 choice(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper, selected, target, groups, state, config, metadata.authority(authority, meta), entered, body)
818
819def dispatch(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data,
820 ~before_hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
821 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
822 ~run: A -> S -> K -> Match<Policy<H>> -> Http.Req -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
823 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
824 app: Application<A, K, Policy<H>>, state: S, req: Http.Req
825) -> IO(Completion<S, H>):
826 Application{table, +config, root, groups} = app
827 Http.Req{+method, +path, +headers, body} = req
828 +meta = {Metadata{method, path, headers} : Metadata}
829 do IO<Completion<S, H>>:
830 completion : Completion<S, H> <- scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, [root], state, config, meta, Nil{}, body,
831 s => entered => b => resolved(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper,
832 Router.resolve(~Endpoint<A, Policy<H>>, table, method, path), groups, s, config, meta, entered, b))
833 finish(~S, ~K, ~E, ~H, ~mapper, config, completion)
834
835# The wrapper owns both pipes until its admitted operation really returns.
836# Only disposable HTTP packed buffers cross them, never external handles.
837type BufferedRequest is Data:
838 BufferedRequest{metadata: Metadata, body: Chan(Http.Body), response: Chan(Http.Res)}
839
840type ApplicationReport<-H: Data> is Data:
841 Dispatched{entered: List<&2, Policy<H>>, mapped: Bool, stopped: Bool}
842 CapacityRejected{}
843
844type ServingContext<-A: Data, -S: Type, -K: Data, -H: Data> is Data:
845 ServingContext{application: Application<A, K, Policy<H>>,
846 owner: Context<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>>}
847
848type Server<-A: Data, -S: Type, -K: Data, -H: Data> is Type:
849 Server{transport: Http.Server<ServingContext<A, S, K, H>>,
850 owner: Owner<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>>}
851
852type StartupError is Data:
853 RegistrationFailed{error: RegistrationError}
854 TransportFailed{error: Http.StartupError}
855 EmptyBundles{}
856
857type ServerExit<-A: Data, -S: Type, -K: Data, -H: Data> is Type:
858 ServerExit{transport: Http.ServerExit, application: OwnerClose<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>>}
859
860def server.config(port: U32) -> Http.ServerConfig:
861 Http.ServerConfig{"127.0.0.1", port, 1048576, 65536, 128, 128, 128, 5000, 30000, 30000, 30000}
862
863def server.bundles.cons(~S: Type, resource: S, counted: List<S> & Nat) -> List<S> & Nat:
864 (returned, count) = counted
865 (resource <> returned, 1n+count)
866
867def server.bundles(~S: Type, resources: List<S>) -> List<S> & Nat:
868 match resources:
869 case Nil{}: (Nil{}, 0n)
870 case resource <> rest:
871 server.bundles.cons(~S, resource, server.bundles(~S, rest))
872
873def server.work.completed(~S: Type, ~H: Data, response_channel: Chan(Http.Res),
874 completion: Completion<S, H>
875) -> IO(S & Result<&2, &2, Unit, ApplicationReport<H>>):
876 Completed{state, response, entered, mapped, stopped} = completion
877 do IO<S & Result<&2, &2, Unit, ApplicationReport<H>>>:
878 sent : Bool <- Chan.send(Http.Res, response_channel, response)
879 return (state, Done{Dispatched{entered, mapped, stopped}})
880
881def server.work(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data,
882 ~before_hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
883 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
884 ~run: A -> S -> K -> Match<Policy<H>> -> Http.Req -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
885 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
886 state: S, app: Application<A, K, Policy<H>>, input: BufferedRequest
887) -> IO(S & Result<&2, &2, Unit, ApplicationReport<H>>):
888 BufferedRequest{Metadata{method, target, headers}, body_channel, response_channel} = input
889 do IO<S & Result<&2, &2, Unit, ApplicationReport<H>>>:
890 body : Http.Body <- receive(Http.Body, body_channel)
891 completion : Completion<S, H> <- dispatch(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper,
892 app, state, Http.Req{method, target, headers, body})
893 server.work.completed(~S, ~H, response_channel, completion)
894
895def server.response(~H: Data,
896 ~done: TransportCompletion.Completion<ApplicationReport<H>> -> IO(Unit),
897 outcome: Outcome<Unit, ApplicationReport<H>>, +body: Chan(Http.Body), +response: Chan(Http.Res)
898) -> IO(Http.Reply):
899 match outcome:
900 case Success{report}:
901 do IO<Http.Reply>:
902 result : Http.Res <- receive(Http.Res, response)
903 Chan.close(Http.Body, body)
904 Chan.close(Http.Res, response)
905 return Http.reply.complete(~ApplicationReport<H>, ~done, report, result)
906 case _:
907 do IO<Http.Reply>:
908 # Closing alone retains buffered values in Base. Consume the rejected
909 # body before closing; the single room1 send has already completed.
910 discarded : Http.Body <- receive(Http.Body, body)
911 Chan.close(Http.Body, body)
912 Chan.close(Http.Res, response)
913 return Http.reply.complete(~ApplicationReport<H>, ~done, CapacityRejected{},
914 Http.Res{503, Http.set(Http.empty(), "connection", "close"), Http.from_string("")})
915
916def server.handle(~A: Data, ~S: Type, ~K: Data, ~H: Data,
917 ~done: TransportCompletion.Completion<ApplicationReport<H>> -> IO(Unit),
918 context: ServingContext<A, S, K, H>, req: Http.Req
919) -> IO(Http.Reply):
920 ServingContext{app, owner} = context
921 Http.Req{method, target, headers, body} = req
922 do IO<Http.Reply>:
923 +body_channel : Chan(Http.Body) <- Chan.new(Http.Body, 1)
924 +response_channel : Chan(Http.Res) <- Chan.new(Http.Res, 1)
925 sent : Bool <- Chan.send(Http.Body, body_channel, body)
926 outcome : Outcome<Unit, ApplicationReport<H>> <- call(S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>,
927 owner, app, BufferedRequest{Metadata{method, target, headers}, body_channel, response_channel})
928 server.response(~H, ~done, outcome, body_channel, response_channel)
929
930
931def server.started(~A: Data, ~S: Type, ~K: Data, ~H: Data,
932 ~work: S -> Application<A, K, Policy<H>> -> BufferedRequest -> IO(S & Result<&2, &2, Unit, ApplicationReport<H>>),
933 ~destroy: S -> IO(Unit),
934 +context: Context<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>>,
935 room: U32, capacity: Nat,
936 started: List<S> & Result<&2, &1, Http.StartupError, Http.Server<ServingContext<A, S, K, H>>>
937) -> IO(Result<&2, &1, StartupError, Server<A, S, K, H>>):
938 Context{+messages} = context
939 (resources, result) = started
940 match result:
941 case Fail{error}:
942 do IO<Result<&2, &1, StartupError, Server<A, S, K, H>>>:
943 dispose(~S, ~destroy, resources)
944 Chan.close(Message<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>>, messages)
945 return Fail{TransportFailed{error}}
946 case Done{transport}:
947 do IO<Result<&2, &1, StartupError, Server<A, S, K, H>>>:
948 finished : Chan(Unit) <- IO.fork(Unit, owner(~S, ~Application<A, K, Policy<H>>, ~BufferedRequest, ~Unit, ~ApplicationReport<H>,
949 ~work, ~destroy, messages, room, Waiting{resources, Counts{capacity, 0n, 0n, 0n}}))
950 return Done{Server{transport, Owner{Context{messages}, finished}}}
951
952def server.prepared(~A: Data, ~S: Type, ~K: Data, ~H: Data,
953 ~work: S -> Application<A, K, Policy<H>> -> BufferedRequest -> IO(S & Result<&2, &2, Unit, ApplicationReport<H>>),
954 ~destroy: S -> IO(Unit), app: Application<A, K, Policy<H>>, +config: Http.ServerConfig, resources: List<S> & Nat
955) -> IO(Result<&2, &1, StartupError, Server<A, S, K, H>>):
956 Http.ServerConfig{host, port, body, headers, connections, requests, +buffered, hm, bm, im, wm} = config
957 (bundles, capacity) = resources
958 match capacity:
959 case 0n:
960 do IO<Result<&2, &1, StartupError, Server<A, S, K, H>>>:
961 dispose(~S, ~destroy, bundles)
962 return Fail{EmptyBundles{}}
963 case 1n+ +rest:
964 do IO<Result<&2, &1, StartupError, Server<A, S, K, H>>>:
965 messages : Chan(Message<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>>) <-
966 Chan.new(Message<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>>, buffered)
967 +context : Context<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>> = Context{messages}
968 started : List<S> & Result<&2, &1, Http.StartupError, Http.Server<ServingContext<A, S, K, H>>> <-
969 Http.server.start(~ServingContext<A, S, K, H>, ~List<S>, ServingContext{app, context}, bundles, config)
970 server.started(~A, ~S, ~K, ~H, ~work, ~destroy, context, buffered, 1n+rest, started)
971
972def server.valid(~A: Data, ~S: Type, ~K: Data, ~H: Data,
973 ~work: S -> Application<A, K, Policy<H>> -> BufferedRequest -> IO(S & Result<&2, &2, Unit, ApplicationReport<H>>),
974 ~destroy: S -> IO(Unit), valid: Bool, app: Application<A, K, Policy<H>>, config: Http.ServerConfig, resources: List<S>
975) -> IO(Result<&2, &1, StartupError, Server<A, S, K, H>>):
976 match valid:
977 case False{}:
978 do IO<Result<&2, &1, StartupError, Server<A, S, K, H>>>:
979 dispose(~S, ~destroy, resources)
980 return Fail{TransportFailed{Http.InvalidConfig{}}}
981 case True{}:
982 server.prepared(~A, ~S, ~K, ~H, ~work, ~destroy, app, config, server.bundles(~S, resources))
983
984def server.registered(~A: Data, ~S: Type, ~K: Data, ~H: Data,
985 ~work: S -> Application<A, K, Policy<H>> -> BufferedRequest -> IO(S & Result<&2, &2, Unit, ApplicationReport<H>>),
986 ~destroy: S -> IO(Unit), registration: Result<&2, &2, RegistrationError, Application<A, K, Policy<H>>>,
987 +config: Http.ServerConfig, resources: List<S>
988) -> IO(Result<&2, &1, StartupError, Server<A, S, K, H>>):
989 match registration:
990 case Fail{error}:
991 do IO<Result<&2, &1, StartupError, Server<A, S, K, H>>>:
992 dispose(~S, ~destroy, resources)
993 return Fail{RegistrationFailed{error}}
994 case Done{app}:
995 server.valid(~A, ~S, ~K, ~H, ~work, ~destroy, Http.server.config.valid(config), app, config, resources)
996
997def server.start(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data,
998 ~before_hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
999 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
1000 ~run: A -> S -> K -> Match<Policy<H>> -> Http.Req -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
1001 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
1002 ~destroy: S -> IO(Unit),
1003 registration: Result<&2, &2, RegistrationError, Application<A, K, Policy<H>>>,
1004 config: Http.ServerConfig, resources: List<S>
1005) -> IO(Result<&2, &1, StartupError, Server<A, S, K, H>>):
1006 server.registered(~A, ~S, ~K, ~H,
1007 ~(state => app => input => server.work(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper, state, app, input)),
1008 ~destroy, registration, config, resources)
1009
1010def server.control.result(~A: Data, ~S: Type, ~K: Data, ~H: Data,
1011 controlled: Http.Server<ServingContext<A, S, K, H>> & Http.ServerControl,
1012 owner: Owner<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>>
1013) -> Server<A, S, K, H> & Http.ServerControl:
1014 (returned, control) = controlled
1015 (Server{returned, owner}, control)
1016
1017def server.control(~A: Data, ~S: Type, ~K: Data, ~H: Data, server: Server<A, S, K, H>) -> Server<A, S, K, H> & Http.ServerControl:
1018 Server{transport, owner} = server
1019 server.control.result(~A, ~S, ~K, ~H, Http.server.control(~ServingContext<A, S, K, H>, transport), owner)
1020
1021def server.observer.result(~A: Data, ~S: Type, ~K: Data, ~H: Data,
1022 observed: Http.Server<ServingContext<A, S, K, H>> & Http.ServerStats,
1023 owner: Owner<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>>
1024) -> Server<A, S, K, H> & Http.ServerStats:
1025 (returned, stats) = observed
1026 (Server{returned, owner}, stats)
1027
1028def server.observer(~A: Data, ~S: Type, ~K: Data, ~H: Data, server: Server<A, S, K, H>) -> Server<A, S, K, H> & Http.ServerStats:
1029 Server{transport, owner} = server
1030 server.observer.result(~A, ~S, ~K, ~H, Http.server.observer(~ServingContext<A, S, K, H>, transport), owner)
1031
1032def server.dependencies(~A: Data, ~S: Type, ~K: Data, ~H: Data, server: Server<A, S, K, H>
1033) -> Server<A, S, K, H> & Context<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>>:
1034 Server{transport, Owner{+context, finished}} = server
1035 (Server{transport, Owner{context, finished}}, context)
1036
1037def server.run(~A: Data, ~S: Type, ~K: Data, ~H: Data,
1038 ~done: TransportCompletion.Completion<ApplicationReport<H>> -> IO(Unit),
1039 ~observe: U32 -> TransportCompletion.Outcome -> IO(Unit),
1040 server: Server<A, S, K, H>
1041) -> IO(ServerExit<A, S, K, H>):
1042 Server{transport, owner} = server
1043 do IO<ServerExit<A, S, K, H>>:
1044 exited : Http.ServerExit <- Http.server.run(~ServingContext<A, S, K, H>,
1045 ~(context => req => server.handle(~A, ~S, ~K, ~H, ~done, context, req)),
1046 ~(context => status => outcome => observe(status, outcome)), transport)
1047 closed : OwnerClose<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>> <- close(S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>, owner)
1048 return ServerExit{exited, closed}
1049
1050# Without starting the run loop, the caller still explicitly drains transport
1051# and joins application cleanup. A stop request alone is never completion.
1052def server.close(~A: Data, ~S: Type, ~K: Data, ~H: Data, server: Server<A, S, K, H>) -> IO(ServerExit<A, S, K, H>):
1053 Server{transport, owner} = server
1054 do IO<ServerExit<A, S, K, H>>:
1055 counts : Http.ServerCounts <- Http.server.close(~ServingContext<A, S, K, H>, transport)
1056 closed : OwnerClose<S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>> <- close(S, Application<A, K, Policy<H>>, BufferedRequest, Unit, ApplicationReport<H>, owner)
1057 return ServerExit{Http.ServerStopped{counts}, closed}
1058
1059def serve.started(~A: Data, ~S: Type, ~K: Data, ~H: Data,
1060 ~done: TransportCompletion.Completion<ApplicationReport<H>> -> IO(Unit),
1061 ~observe: U32 -> TransportCompletion.Outcome -> IO(Unit),
1062 started: Result<&2, &1, StartupError, Server<A, S, K, H>>
1063) -> IO(Result<&2, &1, StartupError, ServerExit<A, S, K, H>>):
1064 match started:
1065 case Fail{error}: IO.pure(Result<&2, &1, StartupError, ServerExit<A, S, K, H>>, Fail{error})
1066 case Done{server}:
1067 do IO<Result<&2, &1, StartupError, ServerExit<A, S, K, H>>>:
1068 exited : ServerExit<A, S, K, H> <- server.run(~A, ~S, ~K, ~H, ~done, ~observe, server)
1069 return Done{exited}
1070
1071# Owners that need readiness/control use server.start then server.run instead.
1072def serve(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data,
1073 ~before_hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
1074 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
1075 ~run: A -> S -> K -> Match<Policy<H>> -> Http.Req -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
1076 ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
1077 ~destroy: S -> IO(Unit),
1078 ~done: TransportCompletion.Completion<ApplicationReport<H>> -> IO(Unit),
1079 ~observe: U32 -> TransportCompletion.Outcome -> IO(Unit),
1080 registration: Result<&2, &2, RegistrationError, Application<A, K, Policy<H>>>,
1081 config: Http.ServerConfig, resources: List<S>
1082) -> IO(Result<&2, &1, StartupError, ServerExit<A, S, K, H>>):
1083 do IO<Result<&2, &1, StartupError, ServerExit<A, S, K, H>>>:
1084 started : Result<&2, &1, StartupError, Server<A, S, K, H>> <-
1085 server.start(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper, ~destroy, registration, config, resources)
1086 serve.started(~A, ~S, ~K, ~H, ~done, ~observe, started)