camber.bend

bend-kit-camber@0.3.0.0 · 776 lines · 0 definitions

1# Fixed prepared HTTP applications and bounded affine dependency bundles.
2import Base
3import bend-kit-http@0.25.0.2/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.3.0.0/response.bend as Response
8
9type ResponseError is Data:
10  InvalidResponse{}
11  InvalidRedirect{}
12
13def validate.checked(valid: Bool, response: Http.Res) -> Result<&2, &1, ResponseError, Http.Res>:
14  match valid:
15    case True{}:
16      Done{response}
17    case False{}:
18      Fail{InvalidResponse{}}
19
20def validate(response: Http.Res) -> Result<&2, &1, ResponseError, Http.Res>:
21  Http.Res{+status, +headers, body} = response
22  validate.checked(Response.valid(status, headers), Http.Res{status, headers, body})
23
24# The encoder is an explicit public projection, never record reflection.
25def json(~T: Type, ~encode: T -> Json.Val, status: U32, value: T) -> Http.Res:
26  Http.Res{status, Http.set(Http.empty(), "content-type", "application/json"), Json.encode.bytes(encode(value))}
27
28def text(status: U32, value: String) -> Http.Res:
29  Http.Res{status, Http.set(Http.empty(), "content-type", "text/plain; charset=utf-8"), Json.utf8(value)}
30
31def raw(status: U32, body: Http.Body) -> Http.Res:
32  Http.Res{status, Http.empty(), body}
33
34def empty(status: U32) -> Http.Res:
35  raw(status, Http.from_string(""))
36
37def redirect.checked(valid: Bool, status: U32, location: String) -> Result<&2, &1, ResponseError, Http.Res>:
38  match valid:
39    case False{}:
40      Fail{InvalidRedirect{}}
41    case True{}:
42      Done{Http.Res{status, Http.set(Http.empty(), "location", location), Http.from_string("")}}
43
44def redirect(+status: U32, +location: String) -> Result<&2, &1, ResponseError, Http.Res>:
45  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)
46
47type Counts is Data:
48  Counts{capacity: Nat, in_use: Nat, completed: Nat, rejected: Nat}
49
50type Outcome<-E: Data, -O: Data> is Data:
51  Success{value: O}
52  Expected{error: E}
53  Exhausted{}
54  Stopped{}
55
56type CloseResult is Data:
57  Closed{}
58  Busy{counts: Counts}
59
60type Message<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type:
61  Submit{principal: P, input: I, reply: Chan(Outcome<E, O>)}
62  Returned{resource: R}
63  Inspect{reply: Chan(Counts)}
64  Shutdown{reply: Chan(CloseResult)}
65
66# Copy only this context, never the resources. The inbox bounds queued commands;
67# callers must count blocked submissions inside their transport admission limit.
68type Context<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Data:
69  Context{messages: Chan(Message<R, P, I, E, O>)}
70
71def outcome.value(-E: Data, -O: Data, result: Result<&2, &2, E, O>) -> Outcome<E, O>:
72  match result:
73    case Fail{error}:
74      Expected{error}
75    case Done{value}:
76      Success{value}
77
78# The resources return through the private owner protocol BEFORE a disposable
79# caller reply. A closed caller reply cannot erase a handle or strand capacity.
80def work.return(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data,
81  messages: Chan(Message<R, P, I, E, O>), reply: Chan(Outcome<E, O>),
82  result: R & Result<&2, &2, E, O>
83) -> IO(Unit):
84  (returned, outcome) = result
85  do IO<Unit>:
86    accepted : Bool <- Chan.send(Message<R, P, I, E, O>, messages, Returned{returned})
87    sent : Bool <- Chan.send(Outcome<E, O>, reply, outcome.value(E, O, outcome))
88    return Unit{}
89
90def work(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
91  ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>),
92  resource: R, principal: P, input: I,
93  messages: Chan(Message<R, P, I, E, O>), reply: Chan(Outcome<E, O>)
94) -> IO(Unit):
95  do IO<Unit>:
96    result : R & Result<&2, &2, E, O> <- handler(resource, principal, input)
97    work.return(R, P, I, E, O, messages, reply, result)
98
99def dispose(~R: Type, ~destroy: R -> IO(Unit), resources: List<R>) -> IO(Unit):
100  match resources:
101    case Nil{}:
102      IO.pure(Unit, Unit{})
103    case Con{resource, rest}:
104      do IO<Unit>:
105        destroy(resource)
106        dispose(~R, ~destroy, rest)
107
108# Closing Base channels retains buffered messages. Drain them and answer stopped
109# callers, rather than stranding accepted commands behind the close command.
110def drain.message(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
111  ~destroy: R -> IO(Unit), message: Message<R, P, I, E, O>
112) -> IO(Unit):
113  match message:
114    case Submit{principal, input, reply}:
115      do IO<Unit>:
116        sent : Bool <- Chan.send(Outcome<E, O>, reply, Stopped{})
117        return Unit{}
118    case Inspect{reply}:
119      Chan.close(Counts, reply)
120    case Shutdown{reply}:
121      do IO<Unit>:
122        sent : Bool <- Chan.send(CloseResult, reply, Closed{})
123        return Unit{}
124    case Returned{resource}:
125      destroy(resource)
126
127def drain.step(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
128  ~destroy: R -> IO(Unit), pending: Maybe<&1, Message<R, P, I, E, O>>,
129  next: Unit -> IO(Unit)
130) -> IO(Unit):
131  match pending:
132    case None{}:
133      IO.pure(Unit, Unit{})
134    case Some{message}:
135      do IO<Unit>:
136        drain.message(~R, ~P, ~I, ~E, ~O, ~destroy, message)
137        next(Unit{})
138
139# The closed inbox contains at most room buffered messages.
140def drain(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
141  ~destroy: R -> IO(Unit), fuel: Nat, +messages: Chan(Message<R, P, I, E, O>)
142) -> IO(Unit):
143  match fuel:
144    case 0n:
145      IO.pure(Unit, Unit{})
146    case 1n+p:
147      do IO<Unit>:
148        pending : Maybe<&1, Message<R, P, I, E, O>> <- Chan.recv(Message<R, P, I, E, O>, messages)
149        drain.step(~R, ~P, ~I, ~E, ~O, ~destroy, pending, u => drain(~R, ~P, ~I, ~E, ~O, ~destroy, p, messages))
150
151type OwnerStep<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type:
152  Waiting{resources: List<R>, counts: Counts}
153  Received{message: Message<R, P, I, E, O>, counts: Counts, resources: List<R>}
154
155# IO.join closes a one-shot channel; the owner inbox must remain reusable.
156def receive.value(-A: Type, value: Maybe<&1, A>) -> IO(A):
157  match value:
158    case None{}:
159      IO.die(A, 1, "camber internal channel closed unexpectedly")
160    case Some{message}:
161      IO.pure(A, message)
162
163def receive(-A: Type, channel: Chan(A)) -> IO(A):
164  do IO<A>:
165    value : Maybe<&1, A> <- Chan.recv(A, channel)
166    receive.value(A, value)
167
168# Only idle close ends this loop. Busy close never waits on a stalled handler.
169# @unsafe is solely the lifecycle receive loop, not a termination/proof claim.
170@unsafe def owner(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
171  ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit),
172  +messages: Chan(Message<R, P, I, E, O>), +room: U32, step: OwnerStep<R, P, I, E, O>
173) -> IO(Unit):
174  match step:
175    case Waiting{resources, counts}:
176      do IO<Unit>:
177        message : Message<R, P, I, E, O> <- receive(Message<R, P, I, E, O>, messages)
178        owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Received{message, counts, resources})
179    case Received{message, counts, resources}:
180      match message:
181        case Returned{resource}:
182          Counts{capacity, in_use, completed, rejected} = counts
183          owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resource <> resources, Counts{capacity, Nat.sub(in_use, 1n), 1n+completed, rejected}})
184        case Inspect{reply}:
185          Counts{+capacity, +in_use, +completed, +rejected} = counts
186          do IO<Unit>:
187            sent : Bool <- Chan.send(Counts, reply, Counts{capacity, in_use, completed, rejected})
188            owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, in_use, completed, rejected}})
189        case Shutdown{reply}:
190          Counts{+capacity, in_use, +completed, +rejected} = counts
191          match in_use:
192            case 0n:
193              do IO<Unit>:
194                dispose(~R, ~destroy, resources)
195                Chan.close(Message<R, P, I, E, O>, messages)
196                sent : Bool <- Chan.send(CloseResult, reply, Closed{})
197                drain(~R, ~P, ~I, ~E, ~O, ~destroy, U32.to_nat(room), messages)
198            case 1n+ +p:
199              do IO<Unit>:
200                sent : Bool <- Chan.send(CloseResult, reply, Busy{Counts{capacity, 1n+p, completed, rejected}})
201                owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, 1n+p, completed, rejected}})
202        case Submit{principal, input, reply}:
203          Counts{capacity, in_use, completed, rejected} = counts
204          match resources:
205            case Nil{}:
206              do IO<Unit>:
207                sent : Bool <- Chan.send(Outcome<E, O>, reply, Exhausted{})
208                owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{Nil{}, Counts{capacity, in_use, completed, 1n+rejected}})
209            case Con{resource, rest}:
210              do IO<Unit>:
211                IO.spawn(Unit, work(~R, ~P, ~I, ~E, ~O, ~handler, resource, principal, input, messages, reply))
212                owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{rest, Counts{capacity, 1n+in_use, completed, rejected}})
213
214def build.step(~R: Type, ~E: Data, ~destroy: R -> IO(Unit),
215  result: Result<&2, &1, E, R>, resources: List<R>,
216  next: List<R> -> IO(Result<&2, &1, E, List<R>>)
217) -> IO(Result<&2, &1, E, List<R>>):
218  match result:
219    case Fail{error}:
220      do IO<Result<&2, &1, E, List<R>>>:
221        dispose(~R, ~destroy, resources)
222        return Fail{error}
223    case Done{resource}:
224      next(resource <> resources)
225
226def build(~K: Data, ~R: Type, ~E: Data, ~create: K -> Nat -> IO(Result<&2, &1, E, R>), ~destroy: R -> IO(Unit),
227  remaining: Nat, +config: K, resources: List<R>
228) -> IO(Result<&2, &1, E, List<R>>):
229  match remaining:
230    case 0n:
231      IO.pure(Result<&2, &1, E, List<R>>, Done{resources})
232    case 1n+ +p:
233      do IO<Result<&2, &1, E, List<R>>>:
234        result : Result<&2, &1, E, R> <- create(config, p)
235        build.step(~R, ~E, ~destroy, result, resources, next => build(~K, ~R, ~E, ~create, ~destroy, p, config, next))
236
237def start.ready(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
238  ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit),
239  capacity: Nat, +room: U32, result: Result<&2, &1, E, List<R>>
240) -> IO(Result<&2, &2, E, Context<R, P, I, E, O>>):
241  match result:
242    case Fail{error}:
243      IO.pure(Result<&2, &2, E, Context<R, P, I, E, O>>, Fail{error})
244    case Done{resources}:
245      do IO<Result<&2, &2, E, Context<R, P, I, E, O>>>:
246        channel : Chan(Message<R, P, I, E, O>) <- Chan.new(Message<R, P, I, E, O>, room)
247        +messages : Chan(Message<R, P, I, E, O>) = channel
248        IO.spawn(Unit, owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, 0n, 0n, 0n}}))
249        return Done{Context{messages}}
250
251# The initializer closes partial handles within a failed bundle; start closes
252# all prior whole bundles. No workers exist until the complete set opens.
253def start(~K: Data, ~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data,
254  ~create: K -> Nat -> IO(Result<&2, &1, E, R>),
255  ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit),
256  config: K, +capacity: Nat, room: U32
257) -> IO(Result<&2, &2, E, Context<R, P, I, E, O>>):
258  do IO<Result<&2, &2, E, Context<R, P, I, E, O>>>:
259    result : Result<&2, &1, E, List<R>> <- build(~K, ~R, ~E, ~create, ~destroy, capacity, config, Nil{})
260    start.ready(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, capacity, room, result)
261
262def submit(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data,
263  context: Context<R, P, I, E, O>, principal: P, input: I, reply: Chan(Outcome<E, O>)
264) -> IO(Bool):
265  Context{messages} = context
266  Chan.send(Message<R, P, I, E, O>, messages, Submit{principal, input, reply})
267
268def call.accepted(-E: Data, -O: Data, accepted: Bool, +reply: Chan(Outcome<E, O>)) -> IO(Outcome<E, O>):
269  match accepted:
270    case False{}:
271      do IO<Outcome<E, O>>:
272        Chan.close(Outcome<E, O>, reply)
273        return Stopped{}
274    case True{}:
275      do IO<Outcome<E, O>>:
276        result : Outcome<E, O> <- IO.join(Outcome<E, O>, reply)
277        Chan.close(Outcome<E, O>, reply)
278        return result
279
280def call(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data,
281  context: Context<R, P, I, E, O>, principal: P, input: I
282) -> IO(Outcome<E, O>):
283  do IO<Outcome<E, O>>:
284    channel : Chan(Outcome<E, O>) <- Chan.new(Outcome<E, O>, 1)
285    +reply : Chan(Outcome<E, O>) = channel
286    accepted : Bool <- submit(R, P, I, E, O, context, principal, input, reply)
287    call.accepted(E, O, accepted, reply)
288
289def inspect.value(value: Maybe<&1, Counts>) -> Maybe<&2, Counts>:
290  match value:
291    case None{}:
292      None{}
293    case Some{counts}:
294      Some{counts}
295
296def inspect.sent(sent: Bool, +reply: Chan(Counts)) -> IO(Maybe<&2, Counts>):
297  match sent:
298    case False{}:
299      do IO<Maybe<&2, Counts>>:
300        Chan.close(Counts, reply)
301        return None{}
302    case True{}:
303      do IO<Maybe<&2, Counts>>:
304        value : Maybe<&1, Counts> <- Chan.recv(Counts, reply)
305        Chan.close(Counts, reply)
306        return inspect.value(value)
307
308def inspect(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, context: Context<R, P, I, E, O>) -> IO(Maybe<&2, Counts>):
309  Context{messages} = context
310  do IO<Maybe<&2, Counts>>:
311    channel : Chan(Counts) <- Chan.new(Counts, 1)
312    +reply : Chan(Counts) = channel
313    sent : Bool <- Chan.send(Message<R, P, I, E, O>, messages, Inspect{reply})
314    inspect.sent(sent, reply)
315
316def close.sent(sent: Bool, +reply: Chan(CloseResult)) -> IO(CloseResult):
317  match sent:
318    case False{}:
319      do IO<CloseResult>:
320        Chan.close(CloseResult, reply)
321        return Closed{}
322    case True{}:
323      do IO<CloseResult>:
324        value : CloseResult <- IO.join(CloseResult, reply)
325        Chan.close(CloseResult, reply)
326        return value
327
328# Stop external admissions before close. Busy preserves live context/capacity;
329# retry after admitted operations finish. Stalled operations remain observable.
330def close(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, context: Context<R, P, I, E, O>) -> IO(CloseResult):
331  Context{messages} = context
332  do IO<CloseResult>:
333    channel : Chan(CloseResult) <- Chan.new(CloseResult, 1)
334    +reply : Chan(CloseResult) = channel
335    sent : Bool <- Chan.send(Message<R, P, I, E, O>, messages, Shutdown{reply})
336    close.sent(sent, reply)
337
338# Policies are prepared Data, not callbacks. Later lifecycle stages can consume
339# these root-to-route declarations without splitting patterns or finding groups.
340type Group<-P: Data> is Data:
341  Group{id: U32, ancestors: List<&2, U32>, policy: P}
342
343type GroupPlan<-P: Data> is Data:
344  GroupPlan{ids: List<&2, U32>, policies: List<&2, P>}
345
346type Route<-A: Data, -P: Data> is Data:
347  Route{method: String, pattern: String, action: A, groups: List<&2, U32>, policy: P}
348
349type Endpoint<-A: Data, -P: Data> is Data:
350  Endpoint{action: A, policies: List<&2, P>}
351
352type Application<-A: Data, -C: Data, -P: Data> is Data:
353  Application{table: Router.Table<Endpoint<A, P>>, context: C, root: P, groups: List<&2, GroupPlan<P>>}
354
355type RegistrationError is Data:
356  RouteError{error: Router.Error}
357  InvalidGroups{groups: List<&2, U32>}
358
359type Match<-P: Data> is Data:
360  Match{target: Target.Target, params: Map<&2, String>, groups: List<&2, U32>, method: String, pattern: String, policies: List<&2, P>}
361
362def groups.equal(xs: List<&2, U32>, ys: List<&2, U32>) -> Bool:
363  match xs ys:
364    case Nil{} Nil{}:
365      True{}
366    case x <> xt y <> yt:
367      U32.is_eq(x, y) && groups.equal(xt, yt)
368    case _ _:
369      False{}
370
371def group.pick(~P: Data, same: Bool, group: Group<P>, later: Unit -> Maybe<&2, Group<P>>) -> Maybe<&2, Group<P>>:
372  match same:
373    case True{}:
374      Some{group}
375    case False{}:
376      later(Unit{})
377
378def group.find(~P: Data, groups: List<&2, Group<P>>, +id: U32) -> Maybe<&2, Group<P>>:
379  match groups:
380    case Nil{}:
381      None{}
382    case Group{+here, ancestors, policy} <> rest:
383      group.pick(~P, U32.is_eq(here, id), Group{here, ancestors, policy}, u => group.find(~P, rest, id))
384
385def group.checked(~P: Data, same: Bool, policy: P) -> Maybe<&2, P>:
386  match same:
387    case False{}:
388      None{}
389    case True{}:
390      Some{policy}
391
392def group.policy(~P: Data, found: Maybe<&2, Group<P>>, ancestors: List<&2, U32>) -> Maybe<&2, P>:
393  match found:
394    case None{}:
395      None{}
396    case Some{Group{id, expected, policy}}:
397      group.checked(~P, groups.equal(expected, ancestors), policy)
398
399def groups.policies(~P: Data, ids: List<&2, U32>, +declared: List<&2, Group<P>>, +ancestors: List<&2, U32>) -> Maybe<&2, List<&2, P>>:
400  match ids:
401    case Nil{}:
402      Some{Nil{}}
403    case +id <> rest:
404      do Maybe<&2, List<&2, P>>:
405        policy : P <- group.policy(~P, group.find(~P, declared, id), ancestors)
406        policies : List<&2, P> <- groups.policies(~P, rest, declared, List.append(&2, U32, ancestors, [id]))
407        return policy <> policies
408
409def groups.unique(~P: Data, found: Maybe<&2, Group<P>>) -> Bool:
410  match found:
411    case None{}:
412      True{}
413    case Some{group}:
414      False{}
415
416def groups.valid(~P: Data, groups: List<&2, Group<P>>, +all: List<&2, Group<P>>) -> Bool:
417  match groups:
418    case Nil{}:
419      True{}
420    case Group{+id, +ancestors, policy} <> +rest:
421      chain = groups.policies(~P, List.append(&2, U32, ancestors, [id]), all, Nil{})
422      groups.unique(~P, group.find(~P, rest, id)) && Maybe.is_some(&2, List<&2, P>, chain) && groups.valid(~P, rest, all)
423
424def routes.policies(~P: Data, result: Maybe<&2, List<&2, P>>, ids: List<&2, U32>) -> Result<&2, &2, RegistrationError, List<&2, P>>:
425  match result:
426    case None{}:
427      Fail{InvalidGroups{ids}}
428    case Some{policies}:
429      Done{policies}
430
431def 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>>>>:
432  match routes:
433    case Nil{}:
434      Done{Nil{}}
435    case Route{method, pattern, action, +ids, policy} <> rest:
436      do Result<&2, &2, RegistrationError, List<&2, Router.Entry<Endpoint<A, P>>>>:
437        policies : List<&2, P> <- routes.policies(~P, groups.policies(~P, ids, groups, Nil{}), ids)
438        entries : List<&2, Router.Entry<Endpoint<A, P>>> <- routes.prepare(~A, ~P, rest, groups, root)
439        return Router.Entry{method, pattern, Endpoint{action, root <> List.append(&2, P, policies, [policy])}, ids} <> entries
440
441def groups.prepare(~P: Data, groups: List<&2, Group<P>>, +all: List<&2, Group<P>>) -> Result<&2, &2, RegistrationError, List<&2, GroupPlan<P>>>:
442  match groups:
443    case Nil{}:
444      Done{Nil{}}
445    case Group{id, ancestors, policy} <> rest:
446      +ids = List.append(&2, U32, ancestors, [id])
447      do Result<&2, &2, RegistrationError, List<&2, GroupPlan<P>>>:
448        policies : List<&2, P> <- routes.policies(~P, groups.policies(~P, ids, all, Nil{}), ids)
449        plans : List<&2, GroupPlan<P>> <- groups.prepare(~P, rest, all)
450        return GroupPlan{ids, policies} <> plans
451
452def groups.select(~P: Data, same: Bool, policies: List<&2, P>, later: Unit -> List<&2, P>) -> List<&2, P>:
453  match same:
454    case True{}: policies
455    case False{}: later(Unit{})
456
457def groups.prepared(~P: Data, plans: List<&2, GroupPlan<P>>, +ids: List<&2, U32>) -> List<&2, P>:
458  match plans:
459    case Nil{}:
460      Nil{}
461    case GroupPlan{here, policies} <> rest:
462      groups.select(~P, groups.equal(here, ids), policies, u => groups.prepared(~P, rest, ids))
463
464def 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>>:
465  match result:
466    case Fail{error}:
467      Fail{RouteError{error}}
468    case Done{table}:
469      Done{Application{table, context, root, groups}}
470
471def 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>>:
472  match valid:
473    case False{}:
474      Fail{InvalidGroups{Nil{}}}
475    case True{}:
476      do Result<&2, &2, RegistrationError, Application<A, C, P>>:
477        entries : List<&2, Router.Entry<Endpoint<A, P>>> <- routes.prepare(~A, ~P, routes, groups, root)
478        plans : List<&2, GroupPlan<P>> <- groups.prepare(~P, groups, groups)
479        application.table(~A, ~C, ~P, Router.prepare(~Endpoint<A, P>, entries), context, root, plans)
480
481# Construction is pure and fallible; no listener or owner is started here.
482def 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>>:
483  application.valid(~A, ~C, ~P, groups.valid(~P, groups, groups), routes, groups, context, root)
484
485def describe(~A: Data, ~C: Data, ~P: Data, app: Application<A, C, P>) -> List<&2, Router.Description>:
486  Application{table, context, root, groups} = app
487  Router.describe(~Endpoint<A, P>, table)
488
489# A closed template can run repeatedly; an affine callback registry cannot.
490def plain(~handler: Http.Req -> IO(Http.Res), req: Http.Req) -> IO(Result<&2, &1, ResponseError, Http.Res>):
491  do IO<Result<&2, &1, ResponseError, Http.Res>>:
492    response : Http.Res <- handler(req)
493    return validate(response)
494
495type Policy<-H: Data> is Data:
496  Policy{id: U32, before: List<&2, H>, transforms: List<&2, H>}
497
498def policy(~H: Data, id: U32, before: List<&2, H>, transforms: List<&2, H>) -> Policy<H>:
499  Policy{id, before, List.reverse(&2, H, transforms)}
500
501type Metadata is Data:
502  Metadata{method: String, target: String, headers: Map<&2, List<&2, String>>}
503
504type Error<-E: Data> is Data:
505  ApplicationError{error: E}
506  Framework{status: U32, allow: String}
507  ResponseInvalid{}
508
509type Step<-S: Data, -E: Data> is Type:
510  Continue{state: S}
511  Early{state: S, response: Http.Res}
512  Reject{state: S, error: Error<E>}
513
514type Completion<-S: Data, -H: Data> is Type:
515  Completed{state: S, response: Http.Res, entered: List<&2, Policy<H>>, mapped: Bool, stopped: Bool}
516
517# Application completion only: no socket write, duration, or peer-receipt claim.
518def minimal() -> Http.Res:
519  empty(500)
520
521def problem.allow(add: Bool, headers: Map<&2, List<&2, String>>, allow: String) -> Map<&2, List<&2, String>>:
522  match add:
523    case True{}: Http.set(headers, "allow", allow)
524    case False{}: headers
525
526def problem.response(allow: String, response: Http.Res) -> Http.Res:
527  Http.Res{+status, headers, body} = response
528  Http.Res{status, problem.allow(U32.is_eq(status, 405), Http.set(headers, "content-type", "application/problem+json"), allow), body}
529
530def problem(+status: U32, title: String, allow: String) -> Http.Res:
531  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))})]}))
532
533def default.error(~E: Data, error: Error<E>) -> Http.Res:
534  match error:
535    case Framework{status, allow}:
536      match status:
537        case 400:
538          problem(400, "Bad Request", allow)
539        case 404:
540          problem(404, "Not Found", allow)
541        case 405:
542          problem(405, "Method Not Allowed", allow)
543        case 415:
544          problem(415, "Unsupported Media Type", allow)
545        case _:
546          minimal()
547    case _:
548      minimal()
549
550def default.mapper(~S: Data, ~K: Data, ~E: Data, state: S, config: K, error: Error<E>) -> IO(S & Result<&2, &1, Error<E>, Http.Res>):
551  IO.pure(S & Result<&2, &1, Error<E>, Http.Res>, (state, Done{default.error(~E, error)}))
552
553def mapped.checked(~S: Data, ~H: Data, state: S, entered: List<&2, Policy<H>>, stopped: Bool, result: Result<&2, &1, ResponseError, Http.Res>) -> Completion<S, H>:
554  match result:
555    case Fail{error}:
556      Completed{state, minimal(), entered, True{}, True{}}
557    case Done{response}:
558      Completed{state, response, entered, True{}, stopped}
559
560def mapped.result(~S: Data, ~E: Data, ~H: Data, entered: List<&2, Policy<H>>, stopped: Bool, result: S & Result<&2, &1, Error<E>, Http.Res>) -> Completion<S, H>:
561  (state, outcome) = result
562  match outcome:
563    case Fail{error}:
564      Completed{state, minimal(), entered, True{}, True{}}
565    case Done{response}:
566      mapped.checked(~S, ~H, state, entered, stopped, validate(response))
567
568def failed(~S: Data, ~K: Data, ~E: Data, ~H: Data,
569  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
570  mapped: Bool, state: S, config: K, error: Error<E>, entered: List<&2, Policy<H>>, stopped: Bool
571) -> IO(Completion<S, H>):
572  match mapped:
573    case True{}:
574      IO.pure(Completion<S, H>, Completed{state, minimal(), entered, True{}, True{}})
575    case False{}:
576      do IO<Completion<S, H>>:
577        result : S & Result<&2, &1, Error<E>, Http.Res> <- mapper(state, config, error)
578        return mapped.result(~S, ~E, ~H, entered, stopped, result)
579
580def result(~S: Data, ~K: Data, ~E: Data, ~H: Data,
581  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
582  config: K, entered: List<&2, Policy<H>>, outcome: S & Result<&2, &1, Error<E>, Http.Res>
583) -> IO(Completion<S, H>):
584  (state, response) = outcome
585  match response:
586    case Fail{error}:
587      failed(~S, ~K, ~E, ~H, ~mapper, False{}, state, config, error, entered, False{})
588    case Done{res}:
589      IO.pure(Completion<S, H>, Completed{state, res, entered, False{}, False{}})
590
591def before.next(~S: Data, ~E: Data, step: Step<S, E>, next: S -> IO(Step<S, E>)) -> IO(Step<S, E>):
592  match step:
593    case Continue{state}:
594      next(state)
595    case Early{state, response}:
596      IO.pure(Step<S, E>, Early{state, response})
597    case Reject{state, error}:
598      IO.pure(Step<S, E>, Reject{state, error})
599
600def before(~S: Data, ~K: Data, ~E: Data, ~H: Data,
601  ~hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
602  hooks: List<&2, H>, state: S, +config: K, +meta: Metadata
603) -> IO(Step<S, E>):
604  match hooks:
605    case Nil{}:
606      IO.pure(Step<S, E>, Continue{state})
607    case h <> rest:
608      do IO<Step<S, E>>:
609        step : Step<S, E> <- hook(h, state, config, meta)
610        before.next(~S, ~E, step, s => before(~S, ~K, ~E, ~H, ~hook, rest, s, config, meta))
611
612def transformed(~S: Data, ~K: Data, ~E: Data, ~H: Data,
613  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
614  config: K, entered: List<&2, Policy<H>>, mapped: Bool, outcome: S & Result<&2, &1, Error<E>, Http.Res>
615) -> IO(Completion<S, H>):
616  (state, response) = outcome
617  match response:
618    case Fail{error}:
619      failed(~S, ~K, ~E, ~H, ~mapper, mapped, state, config, error, entered, True{})
620    case Done{res}:
621      IO.pure(Completion<S, H>, Completed{state, res, entered, mapped, False{}})
622
623def transforms(~S: Data, ~K: Data, ~E: Data, ~H: Data,
624  ~hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
625  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
626  hooks: List<&2, H>, +config: K, completion: Completion<S, H>
627) -> IO(Completion<S, H>):
628  match hooks:
629    case Nil{}:
630      IO.pure(Completion<S, H>, completion)
631    case h <> rest:
632      match completion:
633        case Completed{state, response, entered, mapped, True{}}:
634          IO.pure(Completion<S, H>, Completed{state, response, entered, mapped, True{}})
635        case Completed{state, response, +entered, +mapped, False{}}:
636          do IO<Completion<S, H>>:
637            changed : S & Result<&2, &1, Error<E>, Http.Res> <- hook(h, state, config, response)
638            next : Completion<S, H> <- transformed(~S, ~K, ~E, ~H, ~mapper, config, entered, mapped, changed)
639            transforms(~S, ~K, ~E, ~H, ~hook, ~mapper, rest, config, next)
640
641def entered.step(~S: Data, ~K: Data, ~E: Data, ~H: Data,
642  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
643  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>)
644) -> IO(Completion<S, H>):
645  match step:
646    case Continue{state}:
647      next(state, entered, body)
648    case Early{state, response}:
649      IO.pure(Completion<S, H>, Completed{state, response, entered, False{}, False{}})
650    case Reject{state, error}:
651      failed(~S, ~K, ~E, ~H, ~mapper, False{}, state, config, error, entered, False{})
652
653def scopes(~S: Data, ~K: Data, ~E: Data, ~H: Data,
654  ~before_hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
655  ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
656  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
657  policies: List<&2, Policy<H>>, state: S, +config: K, +meta: Metadata, entered: List<&2, Policy<H>>, body: Http.Body,
658  next: S -> List<&2, Policy<H>> -> Http.Body -> IO(Completion<S, H>)
659) -> IO(Completion<S, H>):
660  match policies:
661    case Nil{}:
662      next(state, entered, body)
663    case Policy{+id, +hooks, +unwind} <> rest:
664      scope = {Policy{id, hooks, unwind} : Policy<H>}
665      do IO<Completion<S, H>>:
666        step : Step<S, E> <- before(~S, ~K, ~E, ~H, ~before_hook, hooks, state, config, meta)
667        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))
668        transforms(~S, ~K, ~E, ~H, ~after_hook, ~mapper, unwind, config, inner)
669
670def finish.checked(~S: Data, ~K: Data, ~E: Data, ~H: Data,
671  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
672  state: S, config: K, entered: List<&2, Policy<H>>, mapped: Bool, stopped: Bool, checked: Result<&2, &1, ResponseError, Http.Res>
673) -> IO(Completion<S, H>):
674  match checked:
675    case Done{response}:
676      IO.pure(Completion<S, H>, Completed{state, response, entered, mapped, stopped})
677    case Fail{error}:
678      failed(~S, ~K, ~E, ~H, ~mapper, mapped, state, config, ResponseInvalid{}, entered, True{})
679
680def finish(~S: Data, ~K: Data, ~E: Data, ~H: Data,
681  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
682  config: K, completion: Completion<S, H>
683) -> IO(Completion<S, H>):
684  Completed{state, response, entered, mapped, stopped} = completion
685  finish.checked(~S, ~K, ~E, ~H, ~mapper, state, config, entered, mapped, stopped, validate(response))
686
687def policies.tail(~H: Data, policies: List<&2, Policy<H>>) -> List<&2, Policy<H>>:
688  match policies:
689    case Nil{}:
690      Nil{}
691    case root <> rest:
692      rest
693
694
695def endpoint(~A: Data, ~S: Data, ~K: Data, ~E: Data, ~H: Data,
696  ~run: A -> S -> K -> Match<Policy<H>> -> Http.Req -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
697  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
698  action: A, state: S, +config: K, selected: Match<Policy<H>>, meta: Metadata, entered: List<&2, Policy<H>>, body: Http.Body
699) -> IO(Completion<S, H>):
700  Metadata{method, target, headers} = meta
701  do IO<Completion<S, H>>:
702    outcome : S & Result<&2, &1, Error<E>, Http.Res> <- run(action, state, config, selected, Http.Req{method, target, headers, body})
703    result(~S, ~K, ~E, ~H, ~mapper, config, entered, outcome)
704
705def framework(~S: Data, ~K: Data, ~E: Data, ~H: Data,
706  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
707  +status: U32, allow: String, state: S, config: K, entered: List<&2, Policy<H>>, body: Http.Body
708) -> IO(Completion<S, H>):
709  match status:
710    case 204:
711      IO.pure(Completion<S, H>, Completed{state, Http.Res{204, Http.set(Http.empty(), "allow", allow), Http.from_string("")}, entered, False{}, False{}})
712    case _:
713      failed(~S, ~K, ~E, ~H, ~mapper, False{}, state, config, Framework{status, allow}, entered, False{})
714
715def choice(~A: Data, ~S: Data, ~K: Data, ~E: Data, ~H: Data,
716  ~before_hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
717  ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
718  ~run: A -> S -> K -> Match<Policy<H>> -> Http.Req -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
719  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
720  selected: Router.Choice<Endpoint<A, Policy<H>>>, target: Target.Target,
721  groups: List<&2, GroupPlan<Policy<H>>>, state: S, +config: K, +meta: Metadata, entered: List<&2, Policy<H>>, body: Http.Body
722) -> IO(Completion<S, H>):
723  match selected:
724    case Router.NotFound{}:
725      framework(~S, ~K, ~E, ~H, ~mapper, 404, "", state, config, entered, body)
726    case Router.MethodMissing{ids, allow}:
727      policies = groups.prepared(~Policy<H>, groups, ids)
728      scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, policies, state, config, meta, entered, body,
729        s => entered => b => framework(~S, ~K, ~E, ~H, ~mapper, 405, allow, s, config, entered, b))
730    case Router.Options{ids, allow}:
731      policies = groups.prepared(~Policy<H>, groups, ids)
732      scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, policies, state, config, meta, entered, body,
733        s => entered => b => framework(~S, ~K, ~E, ~H, ~mapper, 204, allow, s, config, entered, b))
734    case Router.Found{Endpoint{action, +policies}, params, ids, method, pattern}:
735      selected = {Match{target, params, ids, method, pattern, policies} : Match<Policy<H>>}
736      scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, policies.tail(~H, policies), state, config, meta, entered, body,
737        s => entered => b => endpoint(~A, ~S, ~K, ~E, ~H, ~run, ~mapper, action, s, config, selected, meta, entered, b))
738
739def metadata.authority(authority: Maybe<&2, String>, meta: Metadata) -> Metadata:
740  match authority:
741    case None{}:
742      meta
743    case Some{host}:
744      Metadata{method, target, headers} = meta
745      Metadata{method, target, Http.set(headers, "host", host)}
746
747def resolved(~A: Data, ~S: Data, ~K: Data, ~E: Data, ~H: Data,
748  ~before_hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
749  ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
750  ~run: A -> S -> K -> Match<Policy<H>> -> Http.Req -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
751  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
752  outcome: Result<&2, &2, Router.Error, Router.Resolved<Endpoint<A, Policy<H>>>>,
753  groups: List<&2, GroupPlan<Policy<H>>>, state: S, config: K, meta: Metadata, entered: List<&2, Policy<H>>, body: Http.Body
754) -> IO(Completion<S, H>):
755  match outcome:
756    case Fail{error}:
757      framework(~S, ~K, ~E, ~H, ~mapper, 400, "", state, config, entered, body)
758    case Done{Router.Resolved{+target, selected}}:
759      Target.Target{segments, query, authority, star} = target
760      choice(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper, selected, target, groups, state, config, metadata.authority(authority, meta), entered, body)
761
762def dispatch(~A: Data, ~S: Data, ~K: Data, ~E: Data, ~H: Data,
763  ~before_hook: H -> S -> K -> Metadata -> IO(Step<S, E>),
764  ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
765  ~run: A -> S -> K -> Match<Policy<H>> -> Http.Req -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
766  ~mapper: S -> K -> Error<E> -> IO(S & Result<&2, &1, Error<E>, Http.Res>),
767  app: Application<A, K, Policy<H>>, state: S, req: Http.Req
768) -> IO(Completion<S, H>):
769  Application{table, +config, root, groups} = app
770  Http.Req{+method, +path, +headers, body} = req
771  +meta = {Metadata{method, path, headers} : Metadata}
772  do IO<Completion<S, H>>:
773    completion : Completion<S, H> <- scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, [root], state, config, meta, Nil{}, body,
774      s => entered => b => resolved(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper,
775        Router.resolve(~Endpoint<A, Policy<H>>, table, method, path), groups, s, config, meta, entered, b))
776    finish(~S, ~K, ~E, ~H, ~mapper, config, completion)