1 # Fixed prepared HTTP applications and bounded affine dependency bundles. 2 import Base 3 import bend-kit-http@0.25.0.2/http.bend as Http 4 import bend-kit-router@0.2.0.0/router.bend as Router 5 import bend-kit-router@0.2.0.0/target.bend as Target 6 import bend-kit-json@0.5.1.0/json.bend as Json 7 import ./response.bend as Response 8 9 type ResponseError is Data: 10 InvalidResponse{} 11 InvalidRedirect{} 12 13 def 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 20 def 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. 25 def 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 28 def 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 31 def raw(status: U32, body: Http.Body) -> Http.Res: 32 Http.Res{status, Http.empty(), body} 33 34 def empty(status: U32) -> Http.Res: 35 raw(status, Http.from_string("")) 36 37 def 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 44 def 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 47 type Counts is Data: 48 Counts{capacity: Nat, in_use: Nat, completed: Nat, rejected: Nat} 49 50 type Outcome<-E: Data, -O: Data> is Data: 51 Success{value: O} 52 Expected{error: E} 53 Exhausted{} 54 Stopped{} 55 56 type CloseResult is Data: 57 Closed{} 58 Busy{counts: Counts} 59 60 type Message<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type: 61 Submit{principal: P, input: I, reply: Chan(Outcome)} 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. 68 type Context<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Data: 69 Context{messages: Chan(Message)} 70 71 def outcome.value(-E: Data, -O: Data, result: Result<&2, &2, E, O>) -> Outcome: 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. 80 def work.return(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 81 messages: Chan(Message), reply: Chan(Outcome), 82 result: R & Result<&2, &2, E, O> 83 ) -> IO(Unit): 84 (returned, outcome) = result 85 do IO: 86 accepted : Bool <- Chan.send(Message, messages, Returned{returned}) 87 sent : Bool <- Chan.send(Outcome, reply, outcome.value(E, O, outcome)) 88 return Unit{} 89 90 def 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), reply: Chan(Outcome) 94 ) -> IO(Unit): 95 do IO: 96 result : R & Result<&2, &2, E, O> <- handler(resource, principal, input) 97 work.return(R, P, I, E, O, messages, reply, result) 98 99 def dispose(~R: Type, ~destroy: R -> IO(Unit), resources: List) -> IO(Unit): 100 match resources: 101 case Nil{}: 102 IO.pure(Unit, Unit{}) 103 case Con{resource, rest}: 104 do IO: 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. 110 def drain.message(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 111 ~destroy: R -> IO(Unit), message: Message 112 ) -> IO(Unit): 113 match message: 114 case Submit{principal, input, reply}: 115 do IO: 116 sent : Bool <- Chan.send(Outcome, reply, Stopped{}) 117 return Unit{} 118 case Inspect{reply}: 119 Chan.close(Counts, reply) 120 case Shutdown{reply}: 121 do IO: 122 sent : Bool <- Chan.send(CloseResult, reply, Closed{}) 123 return Unit{} 124 case Returned{resource}: 125 destroy(resource) 126 127 def drain.step(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 128 ~destroy: R -> IO(Unit), pending: Maybe<&1, Message>, 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: 136 drain.message(~R, ~P, ~I, ~E, ~O, ~destroy, message) 137 next(Unit{}) 138 139 # The closed inbox contains at most room buffered messages. 140 def drain(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 141 ~destroy: R -> IO(Unit), fuel: Nat, +messages: Chan(Message) 142 ) -> IO(Unit): 143 match fuel: 144 case 0n: 145 IO.pure(Unit, Unit{}) 146 case 1n+p: 147 do IO: 148 pending : Maybe<&1, Message> <- Chan.recv(Message, messages) 149 drain.step(~R, ~P, ~I, ~E, ~O, ~destroy, pending, u => drain(~R, ~P, ~I, ~E, ~O, ~destroy, p, messages)) 150 151 type OwnerStep<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type: 152 Waiting{resources: List, counts: Counts} 153 Received{message: Message, counts: Counts, resources: List} 154 155 # IO.join closes a one-shot channel; the owner inbox must remain reusable. 156 def 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 163 def receive(-A: Type, channel: Chan(A)) -> IO(A): 164 do IO: 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), +room: U32, step: OwnerStep 173 ) -> IO(Unit): 174 match step: 175 case Waiting{resources, counts}: 176 do IO: 177 message : Message <- receive(Message, 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: 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: 194 dispose(~R, ~destroy, resources) 195 Chan.close(Message, 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: 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: 207 sent : Bool <- Chan.send(Outcome, 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: 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 214 def build.step(~R: Type, ~E: Data, ~destroy: R -> IO(Unit), 215 result: Result<&2, &1, E, R>, resources: List, 216 next: List -> IO(Result<&2, &1, E, List>) 217 ) -> IO(Result<&2, &1, E, List>): 218 match result: 219 case Fail{error}: 220 do IO>>: 221 dispose(~R, ~destroy, resources) 222 return Fail{error} 223 case Done{resource}: 224 next(resource <> resources) 225 226 def 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 228 ) -> IO(Result<&2, &1, E, List>): 229 match remaining: 230 case 0n: 231 IO.pure(Result<&2, &1, E, List>, Done{resources}) 232 case 1n+ +p: 233 do IO>>: 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 237 def 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> 240 ) -> IO(Result<&2, &2, E, Context>): 241 match result: 242 case Fail{error}: 243 IO.pure(Result<&2, &2, E, Context>, Fail{error}) 244 case Done{resources}: 245 do IO>>: 246 channel : Chan(Message) <- Chan.new(Message, room) 247 +messages : Chan(Message) = 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. 253 def 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>): 258 do IO>>: 259 result : Result<&2, &1, E, List> <- build(~K, ~R, ~E, ~create, ~destroy, capacity, config, Nil{}) 260 start.ready(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, capacity, room, result) 261 262 def submit(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 263 context: Context, principal: P, input: I, reply: Chan(Outcome) 264 ) -> IO(Bool): 265 Context{messages} = context 266 Chan.send(Message, messages, Submit{principal, input, reply}) 267 268 def call.accepted(-E: Data, -O: Data, accepted: Bool, +reply: Chan(Outcome)) -> IO(Outcome): 269 match accepted: 270 case False{}: 271 do IO>: 272 Chan.close(Outcome, reply) 273 return Stopped{} 274 case True{}: 275 do IO>: 276 result : Outcome <- IO.join(Outcome, reply) 277 Chan.close(Outcome, reply) 278 return result 279 280 def call(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 281 context: Context, principal: P, input: I 282 ) -> IO(Outcome): 283 do IO>: 284 channel : Chan(Outcome) <- Chan.new(Outcome, 1) 285 +reply : Chan(Outcome) = channel 286 accepted : Bool <- submit(R, P, I, E, O, context, principal, input, reply) 287 call.accepted(E, O, accepted, reply) 288 289 def 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 296 def inspect.sent(sent: Bool, +reply: Chan(Counts)) -> IO(Maybe<&2, Counts>): 297 match sent: 298 case False{}: 299 do IO>: 300 Chan.close(Counts, reply) 301 return None{} 302 case True{}: 303 do IO>: 304 value : Maybe<&1, Counts> <- Chan.recv(Counts, reply) 305 Chan.close(Counts, reply) 306 return inspect.value(value) 307 308 def inspect(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, context: Context) -> IO(Maybe<&2, Counts>): 309 Context{messages} = context 310 do IO>: 311 channel : Chan(Counts) <- Chan.new(Counts, 1) 312 +reply : Chan(Counts) = channel 313 sent : Bool <- Chan.send(Message, messages, Inspect{reply}) 314 inspect.sent(sent, reply) 315 316 def close.sent(sent: Bool, +reply: Chan(CloseResult)) -> IO(CloseResult): 317 match sent: 318 case False{}: 319 do IO: 320 Chan.close(CloseResult, reply) 321 return Closed{} 322 case True{}: 323 do IO: 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. 330 def close(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, context: Context) -> IO(CloseResult): 331 Context{messages} = context 332 do IO: 333 channel : Chan(CloseResult) <- Chan.new(CloseResult, 1) 334 +reply : Chan(CloseResult) = channel 335 sent : Bool <- Chan.send(Message, 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. 340 type Group<-P: Data> is Data: 341 Group{id: U32, ancestors: List<&2, U32>, policy: P} 342 343 type GroupPlan<-P: Data> is Data: 344 GroupPlan{ids: List<&2, U32>, policies: List<&2, P>} 345 346 type Route<-A: Data, -P: Data> is Data: 347 Route{method: String, pattern: String, action: A, groups: List<&2, U32>, policy: P} 348 349 type Endpoint<-A: Data, -P: Data> is Data: 350 Endpoint{action: A, policies: List<&2, P>} 351 352 type Application<-A: Data, -C: Data, -P: Data> is Data: 353 Application{table: Router.Table>, context: C, root: P, groups: List<&2, GroupPlan

>} 354 355 type RegistrationError is Data: 356 RouteError{error: Router.Error} 357 InvalidGroups{groups: List<&2, U32>} 358 359 type 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 362 def 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 371 def group.pick(~P: Data, same: Bool, group: Group

, later: Unit -> Maybe<&2, Group

>) -> Maybe<&2, Group

>: 372 match same: 373 case True{}: 374 Some{group} 375 case False{}: 376 later(Unit{}) 377 378 def group.find(~P: Data, groups: List<&2, Group

>, +id: U32) -> Maybe<&2, Group

>: 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 385 def 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 392 def group.policy(~P: Data, found: Maybe<&2, Group

>, 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 399 def groups.policies(~P: Data, ids: List<&2, U32>, +declared: List<&2, Group

>, +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 409 def groups.unique(~P: Data, found: Maybe<&2, Group

>) -> Bool: 410 match found: 411 case None{}: 412 True{} 413 case Some{group}: 414 False{} 415 416 def groups.valid(~P: Data, groups: List<&2, Group

>, +all: List<&2, Group

>) -> 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 424 def 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 431 def routes.prepare(~A: Data, ~P: Data, routes: List<&2, Route>, +groups: List<&2, Group

>, +root: P) -> Result<&2, &2, RegistrationError, List<&2, Router.Entry>>>: 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>>>: 437 policies : List<&2, P> <- routes.policies(~P, groups.policies(~P, ids, groups, Nil{}), ids) 438 entries : List<&2, Router.Entry>> <- 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 441 def groups.prepare(~P: Data, groups: List<&2, Group

>, +all: List<&2, Group

>) -> Result<&2, &2, RegistrationError, List<&2, GroupPlan

>>: 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

>>: 448 policies : List<&2, P> <- routes.policies(~P, groups.policies(~P, ids, all, Nil{}), ids) 449 plans : List<&2, GroupPlan

> <- groups.prepare(~P, rest, all) 450 return GroupPlan{ids, policies} <> plans 451 452 def 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 457 def groups.prepared(~P: Data, plans: List<&2, GroupPlan

>, +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 464 def application.table(~A: Data, ~C: Data, ~P: Data, result: Result<&2, &2, Router.Error, Router.Table>>, context: C, root: P, groups: List<&2, GroupPlan

>) -> Result<&2, &2, RegistrationError, Application>: 465 match result: 466 case Fail{error}: 467 Fail{RouteError{error}} 468 case Done{table}: 469 Done{Application{table, context, root, groups}} 470 471 def application.valid(~A: Data, ~C: Data, ~P: Data, valid: Bool, routes: List<&2, Route>, +groups: List<&2, Group

>, context: C, +root: P) -> Result<&2, &2, RegistrationError, Application>: 472 match valid: 473 case False{}: 474 Fail{InvalidGroups{Nil{}}} 475 case True{}: 476 do Result<&2, &2, RegistrationError, Application>: 477 entries : List<&2, Router.Entry>> <- routes.prepare(~A, ~P, routes, groups, root) 478 plans : List<&2, GroupPlan

> <- groups.prepare(~P, groups, groups) 479 application.table(~A, ~C, ~P, Router.prepare(~Endpoint, entries), context, root, plans) 480 481 # Construction is pure and fallible; no listener or owner is started here. 482 def application(~A: Data, ~C: Data, ~P: Data, routes: List<&2, Route>, +groups: List<&2, Group

>, context: C, root: P) -> Result<&2, &2, RegistrationError, Application>: 483 application.valid(~A, ~C, ~P, groups.valid(~P, groups, groups), routes, groups, context, root) 484 485 def describe(~A: Data, ~C: Data, ~P: Data, app: Application) -> List<&2, Router.Description>: 486 Application{table, context, root, groups} = app 487 Router.describe(~Endpoint, table) 488 489 # A closed template can run repeatedly; an affine callback registry cannot. 490 def plain(~handler: Http.Req -> IO(Http.Res), req: Http.Req) -> IO(Result<&2, &1, ResponseError, Http.Res>): 491 do IO>: 492 response : Http.Res <- handler(req) 493 return validate(response) 494 495 type Policy<-H: Data> is Data: 496 Policy{id: U32, before: List<&2, H>, transforms: List<&2, H>} 497 498 def policy(~H: Data, id: U32, before: List<&2, H>, transforms: List<&2, H>) -> Policy: 499 Policy{id, before, List.reverse(&2, H, transforms)} 500 501 type Metadata is Data: 502 Metadata{method: String, target: String, headers: Map<&2, List<&2, String>>} 503 504 type Error<-E: Data> is Data: 505 ApplicationError{error: E} 506 Framework{status: U32, allow: String} 507 ResponseInvalid{} 508 509 type Step<-S: Data, -E: Data> is Type: 510 Continue{state: S} 511 Early{state: S, response: Http.Res} 512 Reject{state: S, error: Error} 513 514 type Completion<-S: Data, -H: Data> is Type: 515 Completed{state: S, response: Http.Res, entered: List<&2, Policy>, mapped: Bool, stopped: Bool} 516 517 # Application completion only: no socket write, duration, or peer-receipt claim. 518 def minimal() -> Http.Res: 519 empty(500) 520 521 def 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 526 def 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 530 def 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 533 def default.error(~E: Data, error: Error) -> 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 550 def default.mapper(~S: Data, ~K: Data, ~E: Data, state: S, config: K, error: Error) -> IO(S & Result<&2, &1, Error, Http.Res>): 551 IO.pure(S & Result<&2, &1, Error, Http.Res>, (state, Done{default.error(~E, error)})) 552 553 def mapped.checked(~S: Data, ~H: Data, state: S, entered: List<&2, Policy>, stopped: Bool, result: Result<&2, &1, ResponseError, Http.Res>) -> Completion: 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 560 def mapped.result(~S: Data, ~E: Data, ~H: Data, entered: List<&2, Policy>, stopped: Bool, result: S & Result<&2, &1, Error, Http.Res>) -> Completion: 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 568 def failed(~S: Data, ~K: Data, ~E: Data, ~H: Data, 569 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 570 mapped: Bool, state: S, config: K, error: Error, entered: List<&2, Policy>, stopped: Bool 571 ) -> IO(Completion): 572 match mapped: 573 case True{}: 574 IO.pure(Completion, Completed{state, minimal(), entered, True{}, True{}}) 575 case False{}: 576 do IO>: 577 result : S & Result<&2, &1, Error, Http.Res> <- mapper(state, config, error) 578 return mapped.result(~S, ~E, ~H, entered, stopped, result) 579 580 def result(~S: Data, ~K: Data, ~E: Data, ~H: Data, 581 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 582 config: K, entered: List<&2, Policy>, outcome: S & Result<&2, &1, Error, Http.Res> 583 ) -> IO(Completion): 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, Completed{state, res, entered, False{}, False{}}) 590 591 def before.next(~S: Data, ~E: Data, step: Step, next: S -> IO(Step)) -> IO(Step): 592 match step: 593 case Continue{state}: 594 next(state) 595 case Early{state, response}: 596 IO.pure(Step, Early{state, response}) 597 case Reject{state, error}: 598 IO.pure(Step, Reject{state, error}) 599 600 def before(~S: Data, ~K: Data, ~E: Data, ~H: Data, 601 ~hook: H -> S -> K -> Metadata -> IO(Step), 602 hooks: List<&2, H>, state: S, +config: K, +meta: Metadata 603 ) -> IO(Step): 604 match hooks: 605 case Nil{}: 606 IO.pure(Step, Continue{state}) 607 case h <> rest: 608 do IO>: 609 step : Step <- hook(h, state, config, meta) 610 before.next(~S, ~E, step, s => before(~S, ~K, ~E, ~H, ~hook, rest, s, config, meta)) 611 612 def transformed(~S: Data, ~K: Data, ~E: Data, ~H: Data, 613 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 614 config: K, entered: List<&2, Policy>, mapped: Bool, outcome: S & Result<&2, &1, Error, Http.Res> 615 ) -> IO(Completion): 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, Completed{state, res, entered, mapped, False{}}) 622 623 def transforms(~S: Data, ~K: Data, ~E: Data, ~H: Data, 624 ~hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 625 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 626 hooks: List<&2, H>, +config: K, completion: Completion 627 ) -> IO(Completion): 628 match hooks: 629 case Nil{}: 630 IO.pure(Completion, completion) 631 case h <> rest: 632 match completion: 633 case Completed{state, response, entered, mapped, True{}}: 634 IO.pure(Completion, Completed{state, response, entered, mapped, True{}}) 635 case Completed{state, response, +entered, +mapped, False{}}: 636 do IO>: 637 changed : S & Result<&2, &1, Error, Http.Res> <- hook(h, state, config, response) 638 next : Completion <- transformed(~S, ~K, ~E, ~H, ~mapper, config, entered, mapped, changed) 639 transforms(~S, ~K, ~E, ~H, ~hook, ~mapper, rest, config, next) 640 641 def entered.step(~S: Data, ~K: Data, ~E: Data, ~H: Data, 642 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 643 config: K, entered: List<&2, Policy>, body: Http.Body, step: Step, next: S -> List<&2, Policy> -> Http.Body -> IO(Completion) 644 ) -> IO(Completion): 645 match step: 646 case Continue{state}: 647 next(state, entered, body) 648 case Early{state, response}: 649 IO.pure(Completion, 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 653 def scopes(~S: Data, ~K: Data, ~E: Data, ~H: Data, 654 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 655 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 656 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 657 policies: List<&2, Policy>, state: S, +config: K, +meta: Metadata, entered: List<&2, Policy>, body: Http.Body, 658 next: S -> List<&2, Policy> -> Http.Body -> IO(Completion) 659 ) -> IO(Completion): 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} 665 do IO>: 666 step : Step <- before(~S, ~K, ~E, ~H, ~before_hook, hooks, state, config, meta) 667 inner : Completion <- 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 670 def finish.checked(~S: Data, ~K: Data, ~E: Data, ~H: Data, 671 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 672 state: S, config: K, entered: List<&2, Policy>, mapped: Bool, stopped: Bool, checked: Result<&2, &1, ResponseError, Http.Res> 673 ) -> IO(Completion): 674 match checked: 675 case Done{response}: 676 IO.pure(Completion, Completed{state, response, entered, mapped, stopped}) 677 case Fail{error}: 678 failed(~S, ~K, ~E, ~H, ~mapper, mapped, state, config, ResponseInvalid{}, entered, True{}) 679 680 def finish(~S: Data, ~K: Data, ~E: Data, ~H: Data, 681 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 682 config: K, completion: Completion 683 ) -> IO(Completion): 684 Completed{state, response, entered, mapped, stopped} = completion 685 finish.checked(~S, ~K, ~E, ~H, ~mapper, state, config, entered, mapped, stopped, validate(response)) 686 687 def policies.tail(~H: Data, policies: List<&2, Policy>) -> List<&2, Policy>: 688 match policies: 689 case Nil{}: 690 Nil{} 691 case root <> rest: 692 rest 693 694 695 def endpoint(~A: Data, ~S: Data, ~K: Data, ~E: Data, ~H: Data, 696 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 697 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 698 action: A, state: S, +config: K, selected: Match>, meta: Metadata, entered: List<&2, Policy>, body: Http.Body 699 ) -> IO(Completion): 700 Metadata{method, target, headers} = meta 701 do IO>: 702 outcome : S & Result<&2, &1, Error, Http.Res> <- run(action, state, config, selected, Http.Req{method, target, headers, body}) 703 result(~S, ~K, ~E, ~H, ~mapper, config, entered, outcome) 704 705 def framework(~S: Data, ~K: Data, ~E: Data, ~H: Data, 706 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 707 +status: U32, allow: String, state: S, config: K, entered: List<&2, Policy>, body: Http.Body 708 ) -> IO(Completion): 709 match status: 710 case 204: 711 IO.pure(Completion, 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 715 def choice(~A: Data, ~S: Data, ~K: Data, ~E: Data, ~H: Data, 716 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 717 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 718 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 719 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 720 selected: Router.Choice>>, target: Target.Target, 721 groups: List<&2, GroupPlan>>, state: S, +config: K, +meta: Metadata, entered: List<&2, Policy>, body: Http.Body 722 ) -> IO(Completion): 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, 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, 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>} 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 739 def 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 747 def resolved(~A: Data, ~S: Data, ~K: Data, ~E: Data, ~H: Data, 748 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 749 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 750 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 751 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 752 outcome: Result<&2, &2, Router.Error, Router.Resolved>>>, 753 groups: List<&2, GroupPlan>>, state: S, config: K, meta: Metadata, entered: List<&2, Policy>, body: Http.Body 754 ) -> IO(Completion): 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 762 def dispatch(~A: Data, ~S: Data, ~K: Data, ~E: Data, ~H: Data, 763 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 764 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 765 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 766 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 767 app: Application>, state: S, req: Http.Req 768 ) -> IO(Completion): 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>: 773 completion : Completion <- 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>, table, method, path), groups, s, config, meta, entered, b)) 776 finish(~S, ~K, ~E, ~H, ~mapper, config, completion)