Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-httpclient

Named HTTP clients for Pudu: a client factory, delegating handlers, pooled keep-alive connections, handler lifetimes, logging, and resilience

0.1.1Apache-2.01

InstallClose

Endpoint.pudu

Pudu89 lines3.8 KB

GitHub ↗
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 }1920/// The media type endpoint calls ask for.21const ACCEPT_JSON: Str = "application/json"2223/// A `GET` endpoint.24export fn get(template: Str) -> Endpoint { Endpoint{method: Http.Get, template: template} }2526/// A `POST` endpoint.27export fn post(template: Str) -> Endpoint { Endpoint{method: Http.Post, template: template} }2829/// A `PUT` endpoint.30export fn put(template: Str) -> Endpoint { Endpoint{method: Http.Put, template: template} }3132/// A `PATCH` endpoint.33export fn patch(template: Str) -> Endpoint { Endpoint{method: Http.Patch, template: template} }3435/// A `DELETE` endpoint.36export fn delete(template: Str) -> Endpoint { Endpoint{method: Http.Delete, template: template} }3738/// The address with every `:name` slot replaced by its argument, percent-encoded as a component; a39/// slot is a colon followed by a letter and then letters, digits, or underscores, and a slot without40/// an argument is refused.41export 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}6566/// The request an endpoint makes with its arguments through the client, asking for JSON.67export 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}7172/// The response to calling the endpoint through the client, whatever its status.73export fn send(client: &Client.Client, endpoint: &Endpoint, arguments: &Array[(Str, Str)]) -> HttpClient.Outcome[Response.Response] {74  Client.send(client, request(client, endpoint, arguments) ?)75}7677/// The value a successful call answers, read from JSON.78export 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}8283/// The value a successful call carrying `body` as JSON answers, read from JSON.84export 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