Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/core/runtime.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions lib/runtime/server.ml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ module Pages = struct
|> String.concat ""
in
Format.asprintf "<nav><h1>%s</h1></nav><ul>%s</ul>" top listing

let error500 msg =
Format.asprintf
"<h1>500 Internal server error</h1><hr /><p>The build failed while \
refreshing the site.</p><pre>%s</pre>"
msg
end

let prompt port =
Expand Down
1 change: 1 addition & 0 deletions lib/runtime/server.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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} *)
Expand Down
21 changes: 16 additions & 5 deletions plugins/yocaml_eio/server.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand Down
21 changes: 16 additions & 5 deletions plugins/yocaml_unix/server.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions test/e2e/bin/dune
Original file line number Diff line number Diff line change
Expand Up @@ -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))
4 changes: 4 additions & 0 deletions test/e2e/bin/eio_server_error.ml
Original file line number Diff line number Diff line change
@@ -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)
45 changes: 45 additions & 0 deletions test/e2e/bin/server_error.ml
Original file line number Diff line number Diff line change
@@ -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 <port> <file>"
in
let cwd = Yocaml.Path.rel [] in
let file = Yocaml.Path.rel [ file ] in
let resolver = new resolver ~source:cwd ~target:cwd in
(port, resolver, file)
4 changes: 4 additions & 0 deletions test/e2e/bin/unix_server_error.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
let () =
let port, resolver, file = Server_error.setup () in
Yocaml_unix.serve ~target:resolver#target ~port
(Server_error.program resolver file)
2 changes: 2 additions & 0 deletions test/e2e/dune
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
%{exe:bin/gen_liquid.exe}
%{exe:bin/gen_residuals.exe}
%{exe:bin/gen_pp_errors.exe}
%{exe:bin/unix_server_error.exe}
%{exe:bin/eio_server_error.exe}
(source_tree residuals)
(source_tree residuals_build)
(source_tree content)
Expand Down
22 changes: 22 additions & 0 deletions test/e2e/eio_server_metadata.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Eio server returns 500 and detailed required metadata error

$ pkill -f "eio_server_error.exe 8081" 2>/dev/null || true
$ fuser -k 8081/tcp >/dev/null 2>&1 || true

$ ./bin/eio_server_error.exe 8081 content_pp_errors/required_metadata.md >/dev/null 2>&1 &

$ sleep 1

$ curl -sS http://127.0.0.1:8081/
<h1>500 Internal server error</h1><hr /><p>The build failed while refreshing the site.</p><pre>--- 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`

---
</pre>

Cleanup
$ pkill -f "eio_server_error.exe 8081" 2>/dev/null || true
$ fuser -k 8081/tcp >/dev/null 2>&1 || true
26 changes: 26 additions & 0 deletions test/e2e/eio_server_parsing.t
Original file line number Diff line number Diff line change
@@ -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/
<h1>500 Internal server error</h1><hr /><p>The build failed while refreshing the site.</p><pre>--- 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`
---
</pre>

Cleanup
$ pkill -f "eio_server_error.exe 8080" 2>/dev/null || true
$ fuser -k 8080/tcp >/dev/null 2>&1 || true
32 changes: 32 additions & 0 deletions test/e2e/eio_server_validation.t
Original file line number Diff line number Diff line change
@@ -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/
<h1>500 Internal server error</h1><hr /><p>The build failed while refreshing the site.</p><pre>--- 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]`---
</pre>

Cleanup
$ pkill -f "eio_server_error.exe 8082" 2>/dev/null || true
$ fuser -k 8082/tcp >/dev/null 2>&1 || true
2 changes: 1 addition & 1 deletion test/e2e/parsing_error.t
Original file line number Diff line number Diff line change
Expand Up @@ -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]

2 changes: 1 addition & 1 deletion test/e2e/required_metadata.t
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ Required metadata diagnostic

---

Fatal error: exception Stdlib.Exit
Fatal error: exception Yocaml__Eff.Provider_error(_, _, _)
[2]
25 changes: 25 additions & 0 deletions test/e2e/unix_server_metadata.t
Original file line number Diff line number Diff line change
@@ -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/
<h1>500 Internal server error</h1><hr /><p>The build failed while refreshing the site.</p><pre>--- 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`

---
</pre>

Cleanup
$ pkill -f "unix_server_error.exe 8089" 2>/dev/null || true
$ fuser -k 8089/tcp >/dev/null 2>&1 || true



26 changes: 26 additions & 0 deletions test/e2e/unix_server_parsing.t
Original file line number Diff line number Diff line change
@@ -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/
<h1>500 Internal server error</h1><hr /><p>The build failed while refreshing the site.</p><pre>--- 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`
---
</pre>

Cleanup
$ pkill -f "unix_server_error.exe 8090" 2>/dev/null || true
$ fuser -k 8090/tcp >/dev/null 2>&1 || true
32 changes: 32 additions & 0 deletions test/e2e/unix_server_validation.t
Original file line number Diff line number Diff line change
@@ -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/
<h1>500 Internal server error</h1><hr /><p>The build failed while refreshing the site.</p><pre>--- 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]`---
</pre>

Cleanup
$ pkill -f "unix_server_error.exe 8091" 2>/dev/null || true
$ fuser -k 8091/tcp >/dev/null 2>&1 || true
2 changes: 1 addition & 1 deletion test/e2e/validation_error.t
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Loading