import gleam/http.{type Header} import gleam/http/cookie import gleam/list import gleam/option import gleam/result import gleam/string /// A HTTP response. /// /// The body of the request is parameterised. The HTTP server or client you are /// using will have a particular set of types it supports for the body. /// pub type Response(body) { Response( status: Int, /// The request headers. The keys must always be lowercase. headers: List(Header), body: body, ) } /// Update the body of a response using a given result returning function. /// /// If the given function returns an `Ok` value the body is set, if it returns /// an `Error` value then the error is returned. /// pub fn try_map( response: Response(old_body), transform: fn(old_body) -> Result(new_body, error), ) -> Result(Response(new_body), error) { use body <- result.try(transform(response.body)) Ok(set_body(response, body)) } /// Construct an empty Response. /// /// The body type of the returned response is `String` and could be set with a /// call to `set_body`. /// pub fn new(status: Int) -> Response(String) { Response(status:, headers: [], body: "") } /// Get the value for a given header. /// /// If the response does not have that header then `Error(Nil)` is returned. /// pub fn get_header(response: Response(body), key: String) -> Result(String, Nil) { list.key_find(response.headers, string.lowercase(key)) } /// Set the header with the given value under the given header key. /// /// If the response already has that key, it is replaced. /// /// Header keys are always lowercase in `gleam_http`. To use any uppercase /// letter is invalid. /// pub fn set_header( response: Response(body), key: String, value: String, ) -> Response(body) { let headers = list.key_set(response.headers, string.lowercase(key), value) Response(..response, headers:) } /// Prepend the header with the given value under the given header key. /// /// Similar to `set_header` except if the header already exists it prepends /// another header with the same key. /// /// Header keys are always lowercase in `gleam_http`. To use any uppercase /// letter is invalid. /// pub fn prepend_header( response: Response(body), key: String, value: String, ) -> Response(body) { let headers = [#(string.lowercase(key), value), ..response.headers] Response(..response, headers:) } /// Set the body of the response, overwriting any existing body. /// pub fn set_body( response: Response(old_body), body: new_body, ) -> Response(new_body) { Response(..response, body:) } /// Update the body of a response using a given function. /// pub fn map( response: Response(old_body), transform: fn(old_body) -> new_body, ) -> Response(new_body) { response.body |> transform |> set_body(response, _) } /// Create a response that redirects to the given uri. /// pub fn redirect(uri: String) -> Response(String) { Response( status: 303, headers: [#("location", uri)], body: string.append("You are being redirected to ", uri), ) } /// Fetch the cookies sent in a response. /// /// Badly formed cookies will be discarded. /// pub fn get_cookies(resp: Response(body)) -> List(#(String, String)) { let Response(headers:, ..) = resp list.flat_map(headers, fn(header) { case header { #("set-cookie", value) -> cookie.parse(value) _ -> [] } }) } /// Set a cookie value for a client /// pub fn set_cookie( response: Response(body), name: String, value: String, attributes: cookie.Attributes, ) -> Response(body) { prepend_header( response, "set-cookie", cookie.set_header(name, value, attributes), ) } /// Expire a cookie value for a client /// /// Note: The attributes value should be the same as when the response cookie was set. pub fn expire_cookie( response: Response(body), name: String, attributes: cookie.Attributes, ) -> Response(body) { let attrs = cookie.Attributes(..attributes, max_age: option.Some(0)) set_cookie(response, name, "", attrs) }