diff --git a/CHANGES.md b/CHANGES.md index 5f35a3d..9296a6e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -84,10 +84,12 @@ #### Yocaml_unix +- Improve rendering of 500 error pages on server (by [Linda-Njau](https://github.com/Linda-Njau)) - Adapt runtime to `is_file` (by [xvw](https://xvw.lol)) #### Yocaml_eio +- Improve rendering of 500 error pages on server (by [Linda-Njau](https://github.com/Linda-Njau)) - Adapt runtime to `is_file` (by [xvw](https://xvw.lol)) #### Yocaml_git diff --git a/lib/core/runtime.ml b/lib/core/runtime.ml index 97aab05..76a1ba2 100644 --- a/lib/core/runtime.ml +++ b/lib/core/runtime.ml @@ -22,7 +22,7 @@ module Make (Runtime : Required.RUNTIME) = struct ~in_exception_handler:true) exn in - msg |> Runtime.log `Error |> Runtime.bind (fun () -> raise Exit) + msg |> Runtime.log `Error |> Runtime.bind (fun () -> raise exn) let runtimec error = let error = Runtime.runtime_error_to_string error in diff --git a/lib/runtime/server.ml b/lib/runtime/server.ml index a8e57b8..df27d44 100644 --- a/lib/runtime/server.ml +++ b/lib/runtime/server.ml @@ -107,6 +107,12 @@ module Pages = struct |> String.concat "" in Format.asprintf "
The build failed while \ + refreshing the site.
%s" + msg end let prompt port = diff --git a/lib/runtime/server.mli b/lib/runtime/server.mli index d03642d..445e672 100644 --- a/lib/runtime/server.mli +++ b/lib/runtime/server.mli @@ -54,6 +54,7 @@ module Pages : sig val error404 : string -> string val directory : string list -> Kind.t list -> string + val error500 : string -> string end (** {1 Helpers} *) diff --git a/plugins/yocaml_eio/server.ml b/plugins/yocaml_eio/server.ml index 5c489e0..3539087 100644 --- a/plugins/yocaml_eio/server.ml +++ b/plugins/yocaml_eio/server.ml @@ -60,11 +60,22 @@ let dir path lpath = render_html @@ Yocaml_runtime.Server.Pages.directory lpath children let handler htdoc refresh _socket request _body = - let () = refresh () in - match get_requested_uri htdoc request with - | Error404 -> error404 htdoc - | File (path, str) -> file path str - | Dir (path, lpath) -> dir path lpath + try + refresh (); + match get_requested_uri htdoc request with + | Error404 -> error404 htdoc + | File (path, str) -> file path str + | Dir (path, lpath) -> dir path lpath + with exn -> + let msg = + Format.asprintf "%a" + (fun ppf exn -> + Yocaml.Diagnostic.exception_to_diagnostic ~in_exception_handler:true + ppf exn) + exn + in + render_html ~status:`Internal_server_error + (Yocaml_runtime.Server.Pages.error500 msg) let run ?custom_error_handler directory port program env = Eio.Switch.run (fun sw -> diff --git a/plugins/yocaml_unix/server.ml b/plugins/yocaml_unix/server.ml index 133daf6..768dc8f 100644 --- a/plugins/yocaml_unix/server.ml +++ b/plugins/yocaml_unix/server.ml @@ -82,11 +82,22 @@ let dir reqd path lpath = let[@warning "-8"] handler htdoc refresh _socket (`V1 reqd : Httpcats.Server.reqd) = - let () = refresh () in - match get_requested_uri htdoc reqd with - | Error404 -> error404 reqd htdoc - | File (path, _) -> file reqd path - | Dir (path, lpath) -> dir reqd path lpath + try + refresh (); + match get_requested_uri htdoc reqd with + | Error404 -> error404 reqd htdoc + | File (path, _) -> file reqd path + | Dir (path, lpath) -> dir reqd path lpath + with exn -> + let msg = + Format.asprintf "%a" + (fun ppf exn -> + Yocaml.Diagnostic.exception_to_diagnostic ~in_exception_handler:true + ppf exn) + exn + in + render_html ~status:`Internal_server_error reqd + (Yocaml_runtime.Server.Pages.error500 msg) let run ?custom_error_handler directory port program = let refresh () = Runner.run ?custom_error_handler program in diff --git a/test/e2e/bin/dune b/test/e2e/bin/dune index bb93ba8..78375c8 100644 --- a/test/e2e/bin/dune +++ b/test/e2e/bin/dune @@ -29,3 +29,25 @@ yocaml_jingoo yocaml_cmarkit yocaml_markdown)) + +(executable + (name unix_server_error) + (modules server_error unix_server_error test_article) + (libraries + yocaml + yocaml_unix + yocaml_yaml + yocaml_jingoo + yocaml_cmarkit + yocaml_markdown)) + +(executable + (name eio_server_error) + (modules server_error eio_server_error test_article) + (libraries + yocaml + yocaml_eio + yocaml_yaml + yocaml_jingoo + yocaml_cmarkit + yocaml_markdown)) diff --git a/test/e2e/bin/eio_server_error.ml b/test/e2e/bin/eio_server_error.ml new file mode 100644 index 0000000..b0dfe12 --- /dev/null +++ b/test/e2e/bin/eio_server_error.ml @@ -0,0 +1,4 @@ +let () = + let port, resolver, file = Server_error.setup () in + Yocaml_eio.serve ~target:resolver#target ~port + (Server_error.program resolver file) diff --git a/test/e2e/bin/server_error.ml b/test/e2e/bin/server_error.ml new file mode 100644 index 0000000..c56ab20 --- /dev/null +++ b/test/e2e/bin/server_error.ml @@ -0,0 +1,45 @@ +module Test_Article = Test_article + +class resolver ~source ~target = + object (self) + val get_source : Yocaml.Path.t = source + val get_target : Yocaml.Path.t = target + method source = get_source + method target = Yocaml.Path.(get_target / "_www") + method cache = Yocaml.Path.(self#target / ".cache") + method templates = Yocaml.Path.(self#source / "content" / "templates") + method articles = Yocaml.Path.(self#target / "articles") + + method as_article path = + path + |> Yocaml.Path.move ~into:self#articles + |> Yocaml.Path.change_extension "html" + + method as_template path = Yocaml.Path.(self#templates / path) + end + +let article (resolver : resolver) file = + Yocaml.Action.Static.write_file_with_metadata (resolver#as_article file) + Yocaml.Task.( + Yocaml_yaml.Pipeline.read_file_with_metadata (module Test_Article) file + >>> Yocaml_cmarkit.content_to_html () + >>> Yocaml_jingoo.Pipeline.as_template + (module Test_Article) + (resolver#as_template "article.html") + >>> Yocaml_jingoo.Pipeline.as_template + (module Test_Article) + (resolver#as_template "layout.html")) + +let program (resolver : resolver) file () = + Yocaml.Action.with_cache ~on:`Target resolver#cache (article resolver file) + +let setup () = + let port, file = + match Array.to_list Sys.argv with + | _ :: port_str :: file :: _ -> (int_of_string port_str, file) + | _ -> failwith "Usage: server_error.exe
The build failed while refreshing the site.
--- Oh dear, an error has occurred --- + Unable to write to target ./_www/articles/content_pp_errors/required_metadata.html: + + Required metadata in: ./content_pp_errors/required_metadata.md + Entity: `Test_article` + + --- ++ +Cleanup + $ pkill -f "eio_server_error.exe 8081" 2>/dev/null || true + $ fuser -k 8081/tcp >/dev/null 2>&1 || true diff --git a/test/e2e/eio_server_parsing.t b/test/e2e/eio_server_parsing.t new file mode 100644 index 0000000..a947ad1 --- /dev/null +++ b/test/e2e/eio_server_parsing.t @@ -0,0 +1,26 @@ +Eio server returns 500 and detailed parsing error + + $ pkill -f "eio_server_error.exe 8080" 2>/dev/null || true + $ fuser -k 8080/tcp >/dev/null 2>&1 || true + + $ ./bin/eio_server_error.exe 8080 content_pp_errors/parsing_error.md >/dev/null 2>&1 & + + $ sleep 1 + + $ curl -sS http://127.0.0.1:8080/ +
The build failed while refreshing the site.
--- Oh dear, an error has occurred --- + Unable to write to target ./_www/articles/content_pp_errors/parsing_error.html: + + Parsing error in: ./content_pp_errors/parsing_error.md + + Given: + title: A broken article + date 2025-06-08 + + Message: `Yaml: error calling parser: could not find expected ':' character 0 position 0 returned: 0` + --- ++ +Cleanup + $ pkill -f "eio_server_error.exe 8080" 2>/dev/null || true + $ fuser -k 8080/tcp >/dev/null 2>&1 || true diff --git a/test/e2e/eio_server_validation.t b/test/e2e/eio_server_validation.t new file mode 100644 index 0000000..eaf762a --- /dev/null +++ b/test/e2e/eio_server_validation.t @@ -0,0 +1,32 @@ +Eio server returns 500 and detailed metadata validation error + + $ pkill -f "eio_server_error.exe 8082" 2>/dev/null || true + $ fuser -k 8082/tcp >/dev/null 2>&1 || true + + $ ./bin/eio_server_error.exe 8082 content_pp_errors/validation_error.md >/dev/null 2>&1 & + + $ sleep 1 + + $ curl -sS http://127.0.0.1:8082/ +
The build failed while refreshing the site.
--- Oh dear, an error has occurred --- + Unable to write to target ./_www/articles/content_pp_errors/validation_error.html: + + Validation error in: ./content_pp_errors/validation_error.md + Entity: `Test_article` + + + Invalid record: + Errors (1): + 1) Invalid field `date`: + Invalid shape: + Expected: strict-string + Given: `[1, 2, 3]` + + Given record: + title = `"Valid title"` + date = `[1, 2, 3]`--- ++ +Cleanup + $ pkill -f "eio_server_error.exe 8082" 2>/dev/null || true + $ fuser -k 8082/tcp >/dev/null 2>&1 || true diff --git a/test/e2e/parsing_error.t b/test/e2e/parsing_error.t index 25d55df..b48e235 100644 --- a/test/e2e/parsing_error.t +++ b/test/e2e/parsing_error.t @@ -16,6 +16,6 @@ Parsing error diagnostic Message: `Yaml: error calling parser: could not find expected ':' character 0 position 0 returned: 0` --- - Fatal error: exception Stdlib.Exit + Fatal error: exception Yocaml__Eff.Provider_error(_, _, _) [2] diff --git a/test/e2e/required_metadata.t b/test/e2e/required_metadata.t index 99c365e..44f1473 100644 --- a/test/e2e/required_metadata.t +++ b/test/e2e/required_metadata.t @@ -12,5 +12,5 @@ Required metadata diagnostic --- - Fatal error: exception Stdlib.Exit + Fatal error: exception Yocaml__Eff.Provider_error(_, _, _) [2] diff --git a/test/e2e/unix_server_metadata.t b/test/e2e/unix_server_metadata.t new file mode 100644 index 0000000..79aca04 --- /dev/null +++ b/test/e2e/unix_server_metadata.t @@ -0,0 +1,25 @@ +Unix server returns 500 and detailed required metadata error + + $ pkill -f "unix_server_error.exe 8089" 2>/dev/null || true + $ fuser -k 8089/tcp >/dev/null 2>&1 || true + + $ ./bin/unix_server_error.exe 8089 content_pp_errors/required_metadata.md >/dev/null 2>&1 & + + $ sleep 1 + + $ curl -sS http://127.0.0.1:8089/ +
The build failed while refreshing the site.
--- Oh dear, an error has occurred --- + Unable to write to target ./_www/articles/content_pp_errors/required_metadata.html: + + Required metadata in: ./content_pp_errors/required_metadata.md + Entity: `Test_article` + + --- ++ +Cleanup + $ pkill -f "unix_server_error.exe 8089" 2>/dev/null || true + $ fuser -k 8089/tcp >/dev/null 2>&1 || true + + + diff --git a/test/e2e/unix_server_parsing.t b/test/e2e/unix_server_parsing.t new file mode 100644 index 0000000..f9989e2 --- /dev/null +++ b/test/e2e/unix_server_parsing.t @@ -0,0 +1,26 @@ +Unix server returns 500 and detailed parsing error + + $ pkill -f "unix_server_error.exe 8090" 2>/dev/null || true + $ fuser -k 8090/tcp >/dev/null 2>&1 || true + + $ ./bin/unix_server_error.exe 8090 content_pp_errors/parsing_error.md >/dev/null 2>&1 & + + $ sleep 1 + + $ curl -sS http://127.0.0.1:8090/ +
The build failed while refreshing the site.
--- Oh dear, an error has occurred --- + Unable to write to target ./_www/articles/content_pp_errors/parsing_error.html: + + Parsing error in: ./content_pp_errors/parsing_error.md + + Given: + title: A broken article + date 2025-06-08 + + Message: `Yaml: error calling parser: could not find expected ':' character 0 position 0 returned: 0` + --- ++ +Cleanup + $ pkill -f "unix_server_error.exe 8090" 2>/dev/null || true + $ fuser -k 8090/tcp >/dev/null 2>&1 || true diff --git a/test/e2e/unix_server_validation.t b/test/e2e/unix_server_validation.t new file mode 100644 index 0000000..41960b8 --- /dev/null +++ b/test/e2e/unix_server_validation.t @@ -0,0 +1,32 @@ +Unix server returns 500 and detailed metadata validation error + + $ pkill -f "unix_server_error.exe 8091" 2>/dev/null || true + $ fuser -k 8091/tcp >/dev/null 2>&1 || true + + $ ./bin/unix_server_error.exe 8091 content_pp_errors/validation_error.md >/dev/null 2>&1 & + + $ sleep 1 + + $ curl -sS http://127.0.0.1:8091/ +
The build failed while refreshing the site.
--- Oh dear, an error has occurred --- + Unable to write to target ./_www/articles/content_pp_errors/validation_error.html: + + Validation error in: ./content_pp_errors/validation_error.md + Entity: `Test_article` + + + Invalid record: + Errors (1): + 1) Invalid field `date`: + Invalid shape: + Expected: strict-string + Given: `[1, 2, 3]` + + Given record: + title = `"Valid title"` + date = `[1, 2, 3]`--- ++ +Cleanup + $ pkill -f "unix_server_error.exe 8091" 2>/dev/null || true + $ fuser -k 8091/tcp >/dev/null 2>&1 || true diff --git a/test/e2e/validation_error.t b/test/e2e/validation_error.t index 7547aa7..9739014 100644 --- a/test/e2e/validation_error.t +++ b/test/e2e/validation_error.t @@ -22,6 +22,6 @@ Validation error diagnostic title = `"Valid title"` date = `[1, 2, 3]`--- - Fatal error: exception Stdlib.Exit + Fatal error: exception Yocaml__Eff.Provider_error(_, _, _) [2]