
Endpoint.pudu
Pudu89 lines3.8 KB
1/** @HttpClient.Endpoint.Module — declarative endpoints: a method and an address template */2module PuduLangHttpClient.Endpoint34import Std.Char as Char5import Std.Http as Http6import Std.Json as Json7import Std.Text as Text8import Std.Url as Url9import PuduLangHttpClient.Client as Client10import PuduLangHttpClient.Constants.Messages as Messages11import PuduLangHttpClient.Content as Content12import PuduLangHttpClient as HttpClient13import PuduLangHttpClient.Request as Request14import PuduLangHttpClient.Response as Response15import PuduLangHttpClient.Utils.Template as Template1617/** @HttpClient.Endpoint.Endpoint — a method and an address whose `:name` slots are filled per call */18export type Endpoint = { method: Http.Method, template: Str }192021const ACCEPT_JSON: Str = "application/json"222324export fn get(template: Str) -> Endpoint { Endpoint{method: Http.Get, template: template} }252627export fn post(template: Str) -> Endpoint { Endpoint{method: Http.Post, template: template} }282930export fn put(template: Str) -> Endpoint { Endpoint{method: Http.Put, template: template} }313233export fn patch(template: Str) -> Endpoint { Endpoint{method: Http.Patch, template: template} }343536export fn delete(template: Str) -> Endpoint { Endpoint{method: Http.Delete, template: template} }3738394041export fn fill(endpoint: &Endpoint, arguments: &Array[(Str, Str)]) -> HttpClient.Outcome[Str] {42 var out = ""43 var rest = endpoint.template44 loop {45 match Text.find(rest, ":") {46 case None => { return Ok(out + rest) }47 case Some(colon) => {48 let after = rest.drop(colon + 1)49 let name = Text.takeWhile(after, |c: Char| Char.isAlphanumeric(c) || c == '_')50 if name.isEmpty() || !Char.isLetter(name.chars()[0]) {51 out = out + rest.slice(0, colon + 1)52 rest = after53 } else {54 let value = match arguments.filter(|pair: (Str, Str)| pair[0] == name) {55 case [first, ..] => first[1]56 case _ => { return HttpClient.invalid(Template.fill(Messages.UNFILLED_SLOT, &[name])) }57 }58 out = out + rest.slice(0, colon) + Url.encodeComponent(value)59 rest = after.drop(name.length())60 }61 }62 }63 }64}656667export fn request(client: &Client.Client, endpoint: &Endpoint, arguments: &Array[(Str, Str)]) -> HttpClient.Outcome[Request.Request] {68 let uri = fill(endpoint, arguments) ?69 Ok(Client.build(client, endpoint.method, uri).withHeader("accept", ACCEPT_JSON))70}717273export fn send(client: &Client.Client, endpoint: &Endpoint, arguments: &Array[(Str, Str)]) -> HttpClient.Outcome[Response.Response] {74 Client.send(client, request(client, endpoint, arguments) ?)75}767778export fn call[T: Json.Decode](client: &Client.Client, endpoint: &Endpoint, arguments: &Array[(Str, Str)]) -> HttpClient.Outcome[T] {79 let response = Response.ensureSuccess(send(client, endpoint, arguments) ?) ?80 Response.readAs(&response)81}828384export fn callWith[B: Json.Encode, T: Json.Decode](client: &Client.Client, endpoint: &Endpoint, arguments: &Array[(Str, Str)], body: &B) -> HttpClient.Outcome[T] {85 let made = request(client, endpoint, arguments) ?86 let response = Response.ensureSuccess(Client.send(client, made.withContent(Content.jsonOf(body))) ?) ?87 Response.readAs(&response)88}89