Skip to content

Commit

Permalink
feat: make packages importable using glitr/lustre and glitr/wisp
Browse files Browse the repository at this point in the history
  • Loading branch information
Billuc committed Oct 10, 2024
1 parent 07ce107 commit 4fdc1ed
Show file tree
Hide file tree
Showing 9 changed files with 481 additions and 476 deletions.
5 changes: 3 additions & 2 deletions glitr_lustre/gleam.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name = "glitr_lustre"
version = "0.1.6"
version = "0.1.12"
gleam = ">= 1.1.0"

# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
Expand All @@ -18,7 +19,7 @@ lustre_http = ">= 0.5.2 and < 1.0.0"
lustre = ">= 4.4.3 and < 5.0.0"
gleam_http = ">= 3.7.0 and < 4.0.0"
gleam_json = ">= 1.0.1 and < 2.0.0"
glitr = ">= 0.1.5 and < 1.0.0"
glitr = ">= 0.1.12 and < 1.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
13 changes: 7 additions & 6 deletions glitr_lustre/manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
# You typically do not need to edit this file

packages = [
{ name = "gleam_erlang", version = "0.26.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "3DF72F95F4716883FA51396FB0C550ED3D55195B541568CAF09745984FD37AD1" },
{ name = "gleam_erlang", version = "0.27.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "DE468F676D71B313C6C8C5334425CFCF827837333F8AB47B64D8A6D7AA40185D" },
{ name = "gleam_fetch", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_javascript", "gleam_stdlib"], otp_app = "gleam_fetch", source = "hex", outer_checksum = "7446410A44A1D1328F5BC1FF4FC9CBD1570479EA69349237B3F82E34521CCC10" },
{ name = "gleam_http", version = "3.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "EA66440C2269F7CED0F6845E5BD0DB68095775D627FA709A841CA78A398D6D56" },
{ name = "gleam_javascript", version = "0.12.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "6EB652538B31E852FE0A8307A8B6314DEB34930944B6DDC41CCC31CA344DA35D" },
{ name = "gleam_javascript", version = "0.13.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "F98328FCF573DA6F3A35D7F6CB3F9FF19FD5224CCBA9151FCBEAA0B983AF2F58" },
{ name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" },
{ name = "gleam_otp", version = "0.12.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "CD5FC777E99673BDB390092DF85E34EAA6B8EE1882147496290AB3F45A4960B1" },
{ name = "gleam_otp", version = "0.12.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BFACC1513410DF5A1617169A9CD7EA334973AC71D860A17574BA7B2EADD89A6F" },
{ name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
{ name = "glitr", version = "0.1.5", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_json", "gleam_stdlib"], otp_app = "glitr", source = "hex", outer_checksum = "EECC160AF63C9930A246D003B43CFCD7EC4C79CDA0585935E8B88B2F06A88EB6" },
{ name = "lustre", version = "4.4.3", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "AA54C98497C0CF8750B0247F351D7E38EB43E46B9E8AA755900E4C64103BD9F7" },
{ name = "glitr", version = "0.1.12", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_json", "gleam_stdlib", "glitr_convert"], otp_app = "glitr", source = "hex", outer_checksum = "1CC1B26F553331E660008DF134A1A822BF9EDED6288345397A6D8B59E7A1477F" },
{ name = "glitr_convert", version = "0.1.12", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "glitr_convert", source = "hex", outer_checksum = "9722783812DA6C0DEDB159363D41C35F22E580CA34DEC8492EB75AF3953C4E3A" },
{ name = "lustre", version = "4.5.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "B592DA442F6577143CAFA35D4506DB2018DAEED9C707A921E33559E09F001DF1" },
{ name = "lustre_http", version = "0.5.2", build_tools = ["gleam"], requirements = ["gleam_fetch", "gleam_http", "gleam_javascript", "gleam_json", "gleam_stdlib", "lustre"], otp_app = "lustre_http", source = "hex", outer_checksum = "FB0478CBFA6B16DBE8ECA326DAE2EC15645E04900595EF2C4F039ABFA0512ABA" },
{ name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" },
]
Expand All @@ -21,6 +22,6 @@ gleam_http = { version = ">= 3.7.0 and < 4.0.0" }
gleam_json = { version = ">= 1.0.1 and < 2.0.0" }
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
glitr = { version = ">= 0.1.5 and < 1.0.0" }
glitr = { version = ">= 0.1.12 and < 1.0.0" }
lustre = { version = ">= 4.4.3 and < 5.0.0" }
lustre_http = { version = ">= 0.5.2 and < 1.0.0" }
234 changes: 234 additions & 0 deletions glitr_lustre/src/glitr/lustre.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
//// This module helps to make the bridge between glitr Routes and lustre

import gleam/http
import gleam/http/request
import gleam/option.{type Option, None, Some}
import gleam/result
import gleam/string
import gleam/string_builder
import glitr
import glitr/body
import glitr/error
import glitr/path
import glitr/query
import glitr/route
import lustre/effect
import lustre_http

pub opaque type RequestFactory {
/// A request factory helps you create requests automatically from routes.
/// It contains data on the way to reach the backend (scheme, host and port).
/// It is meant to be reusable as usually one frontend connects mainly to one backend
RequestFactory(scheme: http.Scheme, host: String, port: Int)
}

/// Create a new default factory
/// By default, it points to "http://localhost:80"
pub fn create_factory() -> RequestFactory {
RequestFactory(http.Http, "localhost", 80)
}

/// Changes the scheme of a RequestFactory
/// Also sets the port to the default for the selected scheme (80 for http and 443 for https)
pub fn with_scheme(
factory: RequestFactory,
scheme: http.Scheme,
) -> RequestFactory {
case scheme {
http.Http -> RequestFactory(http.Http, factory.host, 80)
http.Https -> RequestFactory(http.Https, factory.host, 443)
}
}

/// Changes the host of a RequestFactory
pub fn with_host(factory: RequestFactory, host: String) -> RequestFactory {
RequestFactory(..factory, host: host)
}

/// Changes the port of a RequestFactory
pub fn with_port(factory: RequestFactory, port: Int) -> RequestFactory {
RequestFactory(..factory, port: port)
}

/// Create a RouteRequest given a RequestFactory and a Route
pub fn for_route(
factory: RequestFactory,
route: route.Route(p, q, b, c),
) -> RouteRequest(p, q, b, c) {
RouteRequest(
route,
factory.scheme,
factory.host,
factory.port,
None,
None,
None,
)
}

/// A RouteRequest contains the data required to send a request to a backend
/// This data contains a route, all the data from the factory that created the request
/// as well as path, query and body data that will have to be provided
pub opaque type RouteRequest(p, q, b, c) {
RouteRequest(
route: route.Route(p, q, b, c),
scheme: http.Scheme,
host: String,
port: Int,
path_opt: Option(p),
query_opt: Option(q),
body_opt: Option(b),
)
}

/// Set the path, query and body data all at the same time
pub fn with_options(
request: RouteRequest(p, q, b, _),
options: glitr.RouteOptions(p, q, b),
) -> RouteRequest(p, q, b, _) {
RouteRequest(
..request,
path_opt: Some(options.path),
query_opt: Some(options.query),
body_opt: Some(options.body),
)
}

/// Set the path data for this request
/// Note that, for now, you have to call this method before sending the request
pub fn with_path(
request: RouteRequest(p, _, _, _),
path: p,
) -> RouteRequest(p, _, _, _) {
RouteRequest(..request, path_opt: Some(path))
}

/// Set the query data for this request
pub fn with_query(
request: RouteRequest(_, q, _, _),
query: q,
) -> RouteRequest(_, q, _, _) {
RouteRequest(..request, query_opt: Some(query))
}

/// Set the body data for this request
pub fn with_body(
request: RouteRequest(_, _, b, _),
body: b,
) -> RouteRequest(_, _, b, _) {
RouteRequest(..request, body_opt: Some(body))
}

/// Send a RouteRequest and handle the result
/// Uses lustre_http under the hood to send the result, catch the response and transform the data
pub fn send(
rreq: RouteRequest(p, q, b, c),
as_msg: fn(Result(c, lustre_http.HttpError)) -> msg,
on_error: fn(String) -> effect.Effect(msg),
) -> effect.Effect(msg) {
let req =
request.new()
|> request.set_method(rreq.route.method)

use req <- add_path(req, rreq, on_error)
use req <- add_query(req, rreq, on_error)
use req <- add_body(req, rreq, on_error)

let req =
req
|> request.set_scheme(rreq.scheme)
|> request.set_host(rreq.host)
|> request.set_port(rreq.port)

req
|> lustre_http.send(
lustre_http.expect_text(fn(body) {
body
|> result.then(fn(value) {
rreq.route.res_body
|> body.decode(value)
|> result.map_error(glitr_to_http_error)
})
|> as_msg
}),
)
}

/// Set the path of the request based on data stored in the RouteRequest
/// It is meant to be used with use and provides the request with the path set.
/// It will call on_error if no path data is provided.
fn add_path(
req: request.Request(String),
rreq: RouteRequest(p, q, b, c),
on_error: fn(String) -> effect.Effect(msg),
then: fn(request.Request(String)) -> effect.Effect(msg),
) -> effect.Effect(msg) {
case rreq.route.path |> path.get_type, rreq.path_opt {
_, None ->
on_error("Path option is missing, please call with_path before send")
_, Some(path) ->
then(
req
|> request.set_path(
rreq.route.path |> path.encode(path) |> string.join("/"),
),
)
}
}

/// Set the query of the request based on data stored in the RouteRequest
/// It is meant to be used with use and provides the request with the query set.
/// It will call on_error if no query data is provided and is required.
fn add_query(
req: request.Request(String),
rreq: RouteRequest(p, q, b, c),
on_error: fn(String) -> effect.Effect(msg),
then: fn(request.Request(String)) -> effect.Effect(msg),
) -> effect.Effect(msg) {
case rreq.route.query |> query.get_type, rreq.query_opt {
query.ComplexQuery, None ->
on_error("Query option is missing, please call with_query before send")
query.EmptyQuery, _ -> then(req)
query.ComplexQuery, Some(query) ->
then(req |> request.set_query(rreq.route.query |> query.encode(query)))
}
}

/// Set the body of the request based on data stored in the RouteRequest
/// It is meant to be used with use and provides the request with the body set.
/// It will call on_error if no body data is provided and is required.
fn add_body(
req: request.Request(String),
rreq: RouteRequest(p, q, b, c),
on_error: fn(String) -> effect.Effect(msg),
then: fn(request.Request(String)) -> effect.Effect(msg),
) -> effect.Effect(msg) {
case rreq.route.req_body |> body.get_type, rreq.body_opt {
body.JsonBody, None | body.StringBody, None ->
on_error("Body option is missing, please call with_body before send")
body.EmptyBody, _ -> then(req)
body.JsonBody, Some(body) ->
then(
req
|> request.set_body(
rreq.route.req_body |> body.encode(body) |> string_builder.to_string,
)
|> request.set_header("Content-Type", "application/json"),
)
body.StringBody, Some(body) ->
then(
req
|> request.set_body(
rreq.route.req_body |> body.encode(body) |> string_builder.to_string,
),
)
}
}

/// Mapper function between GlitrError and HttpError
fn glitr_to_http_error(err: error.GlitrError) -> lustre_http.HttpError {
case err {
error.RouteError(msg) -> lustre_http.OtherError(500, msg)
error.JsonDecodeError(json_err) -> lustre_http.JsonError(json_err)
}
}
Loading

0 comments on commit 4fdc1ed

Please sign in to comment.