🧚 A practical web framework for Gleam

Rescue crashes

+37 -10
+2 -2
action/manifest.toml
··· 3 3 4 4 packages = [ 5 5 { name = "esqlite", version = "0.8.6", build_tools = ["rebar3"], requirements = [], otp_app = "esqlite", source = "hex", outer_checksum = "607E45F4DA42601D8F530979417F57A4CD629AB49085891849302057E68EA188" }, 6 - { name = "framework", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "mist"], source = "local", path = "/Users/louis/src/gleam/action/framework" }, 6 + { name = "framework", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "mist"], source = "local", path = "../framework" }, 7 7 { name = "gleam_erlang", version = "0.20.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "F216A80C8FDFF774447B494D5E08AE4E9A911E971727B9A78FEBF5F300914584" }, 8 8 { name = "gleam_http", version = "3.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "B6EB76D304E0E66267485983E6B7BC28F3BFA6795BB2BF90FC411F6903AF6A1A" }, 9 9 { name = "gleam_otp", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_erlang"], otp_app = "gleam_otp", source = "hex", outer_checksum = "E31B158857E3D2AF946FE6E90E0CB21699AF226F4630E93FBEAC5DB4515F8920" }, 10 10 { name = "gleam_stdlib", version = "0.30.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "03710B3DA047A3683117591707FCA19D32B980229DD8CE8B0603EB5B5144F6C3" }, 11 11 { name = "gleeunit", version = "0.10.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "ECEA2DE4BE6528D36AFE74F42A21CDF99966EC36D7F25DEB34D47DD0F7977BAF" }, 12 12 { name = "glisten", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_erlang", "gleam_otp"], otp_app = "glisten", source = "hex", outer_checksum = "6DDE276F8A2E3C79E5A580DEA05C7D87FCDE3A37FF69F607770D92686E193531" }, 13 - { name = "htmb", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "/Users/louis/src/gleam/action/htmb" }, 13 + { name = "htmb", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "../htmb" }, 14 14 { name = "ids", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_otp", "gleam_erlang"], otp_app = "ids", source = "hex", outer_checksum = "7A378014D40E848326FBEE8AC0C9B35EB9C8094DC4414D89F9A5AA99397A6042" }, 15 15 { name = "mist", version = "0.13.0", build_tools = ["gleam"], requirements = ["glisten", "gleam_erlang", "gleam_stdlib", "gleam_http"], otp_app = "mist", source = "hex", outer_checksum = "9A374CA245D682E2C08A5224B4420DDA252EF553AE5FD0ED7BAD33F86ACF7C98" }, 16 16 { name = "sqlight", version = "0.7.0", build_tools = ["gleam"], requirements = ["esqlite", "gleam_stdlib"], otp_app = "sqlight", source = "hex", outer_checksum = "BDBAA35B58E11B6DE20DC869EAA247F447268B8F398E0677F25444C9F7AE54EA" },
+1 -1
action/src/action/feature/applications.gleam action/src/action/applications.gleam
··· 1 1 // TODO: Sign the application id so that bad actors can't generate them. 2 2 3 3 import action/database 4 - import action/feature/applications/form.{ 4 + import action/applications/form.{ 5 5 BoolSubmitButtons, Checkboxes, Email, Phone, Question, Radio, Step, Text, 6 6 } 7 7 import action/html
action/src/action/feature/applications/form.gleam action/src/action/applications/form.gleam
+4 -4
action/src/action/router.gleam
··· 1 - import action/feature/applications 1 + import action/applications 2 2 import action/html.{h, text} 3 3 import action/web.{Context} 4 4 import framework.{Request, Response} ··· 6 6 import gleam/http.{Get} 7 7 8 8 pub fn handle_request(req: Request, ctx: Context) -> Response { 9 - let req = framework.method_override(req) 9 + use req <- web.middleware(req) 10 10 11 11 case framework.path_segments(req) { 12 12 ["take-action"] -> applications.resource(req, ctx) 13 13 [] -> home_page(req) 14 14 _ -> framework.not_found() 15 15 } 16 - |> web.default_responses 17 16 } 18 17 19 18 pub fn home_page(request: Request) -> Response { 20 19 use <- framework.require_method(request, Get) 21 - framework.html_response(home_html(), 200) 20 + home_html() 21 + |> framework.html_response(200) 22 22 } 23 23 24 24 fn home_html() {
+11 -2
action/src/action/web.gleam
··· 1 1 import sqlight 2 - import framework.{Response} 2 + import framework.{Request, Response} 3 3 import htmb.{h, text} 4 4 import gleam/bool 5 5 ··· 7 7 Context(db: sqlight.Connection) 8 8 } 9 9 10 - pub fn default_responses(response: Response) -> Response { 10 + pub fn middleware(req: Request, service: fn(Request) -> Response) -> Response { 11 + let req = framework.method_override(req) 12 + use <- serve_default_responses 13 + use <- framework.rescue_crashes 14 + 15 + service(req) 16 + } 17 + 18 + fn serve_default_responses(service: fn() -> Response) -> Response { 19 + let response = service() 11 20 use <- bool.guard(response.body != framework.Empty, return: response) 12 21 13 22 case response.status {
+1 -1
action/test/action/feature/applications/form_test.gleam action/test/action/applications/form_test.gleam
··· 1 - import action/feature/applications/form.{ 1 + import action/applications/form.{ 2 2 Answer, BoolAnswer, BoolSubmitButtons, Checkboxes, Email, MultipleChoiceAnswer, 3 3 NoAnswer, Phone, Question, Radio, Text, TextAnswer, 4 4 }
+18
framework/src/framework.gleam
··· 23 23 import gleam/string_builder.{StringBuilder} 24 24 import gleam/bit_builder.{BitBuilder} 25 25 import gleam/bit_string 26 + import gleam/erlang 26 27 import gleam/bool 27 28 import gleam/http.{Method} 28 29 import gleam/http/request.{Request as HttpRequest} ··· 143 144 // TODO: document 144 145 pub fn entity_too_large() -> Response { 145 146 HttpResponse(413, [], Empty) 147 + } 148 + 149 + // TODO: test 150 + // TODO: document 151 + pub fn internal_server_error() -> Response { 152 + HttpResponse(500, [], Empty) 146 153 } 147 154 148 155 // TODO: test ··· 324 331 case result { 325 332 Ok(value) -> next(value) 326 333 Error(_) -> bad_request() 334 + } 335 + } 336 + 337 + // 338 + // Middleware 339 + // 340 + 341 + pub fn rescue_crashes(service: fn() -> Response) -> Response { 342 + case erlang.rescue(service) { 343 + Ok(response) -> response 344 + Error(_) -> internal_server_error() 327 345 } 328 346 } 329 347