Skip to content

Commit 1b4ec48

Browse files
authored
Merge pull request #114 from Linda-Njau/pp_validation_error
Include source and target paths in provider error pretty-printing
2 parents d2ce139 + 6464394 commit 1b4ec48

File tree

15 files changed

+394
-21
lines changed

15 files changed

+394
-21
lines changed

lib/core/action.ml

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,38 @@ let perform_update now target eff cache =
107107
in
108108
perform_writing now target cache fc hc dynamic_deps
109109

110+
let propagate_target target program cache =
111+
let handler =
112+
Effect.Deep.
113+
{
114+
exnc = (fun exn -> raise exn)
115+
; retc = Eff.return
116+
; effc =
117+
(fun (type a) (eff : a Effect.t) ->
118+
match eff with
119+
| Eff.Yocaml_failwith (Eff.Provider_error e) ->
120+
Some
121+
(fun (k : (a, _) continuation) ->
122+
let open Eff in
123+
let new_exn =
124+
Eff.Provider_error { e with target = Some target }
125+
in
126+
let* x = raise new_exn in
127+
continue k x)
128+
| _ -> None)
129+
}
130+
in
131+
Eff.run handler (fun cache -> program cache) cache
132+
110133
let write_dynamic_file target task =
111-
perform target task
112-
~when_creation:(fun now target eff cache ->
113-
let open Eff.Syntax in
114-
let* fc, dynamic_deps = eff () in
115-
let* hc = Eff.hash fc in
116-
perform_writing now target cache fc hc dynamic_deps)
117-
~when_update:perform_update
134+
propagate_target target
135+
(perform target task
136+
~when_creation:(fun now target eff cache ->
137+
let open Eff.Syntax in
138+
let* fc, dynamic_deps = eff () in
139+
let* hc = Eff.hash fc in
140+
perform_writing now target cache fc hc dynamic_deps)
141+
~when_update:perform_update)
118142

119143
let write_static_file target task cache =
120144
write_dynamic_file target Task.(task ||> no_dynamic_deps) cache

lib/core/diagnostic.ml

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,31 @@ and pp_record_error custom_error ppf = function
8484
(pp_validation_error custom_error)
8585
error
8686

87-
let pp_provider_error custom_error ppf = function
87+
let pp_source_label ppf label = function
88+
| None -> Format.fprintf ppf "%s:@." label
89+
| Some src -> Format.fprintf ppf "%s in: %a@." label Path.pp src
90+
91+
let pp_entity ppf entity = Format.fprintf ppf "Entity: `%s`@,@." entity
92+
93+
let pp_target ppf = function
94+
| None -> ()
95+
| Some tgt ->
96+
Format.fprintf ppf "Unable to write to target %a:@,@," Path.pp tgt
97+
98+
let pp_provider_error custom_error ~source ~target ppf = function
8899
| Required.Parsing_error { given; message } ->
89-
Format.fprintf ppf "Parsing error:@,Given: `%s`@,Message: `%s`" given
90-
message
100+
pp_target ppf target;
101+
pp_source_label ppf "Parsing error" source;
102+
Format.fprintf ppf "@.Given:@.%s@.Message: `%s`" given message
91103
| Required.Required_metadata { entity } ->
92-
Format.fprintf ppf "Required metadata: `%s`" entity
104+
pp_target ppf target;
105+
pp_source_label ppf "Required metadata" source;
106+
pp_entity ppf entity
93107
| Required.Validation_error { entity; error } ->
94-
Format.fprintf ppf "Validation error: `%s`@,@[%a@]" entity
95-
(pp_validation_error custom_error)
96-
error
108+
pp_target ppf target;
109+
pp_source_label ppf "Validation error" source;
110+
pp_entity ppf entity;
111+
Format.fprintf ppf "@,@[%a@]" (pp_validation_error custom_error) error
97112

98113
let glob_pp p v backtrace ppf =
99114
Format.fprintf ppf "--- %a ---@,%a@,---@,%s" Lexicon.there_is_an_error () p v
@@ -118,7 +133,8 @@ let exception_to_diagnostic
118133
glob_pp (Lexicon.directory_not_exists source path) ()
119134
| Eff.Directory_is_a_file (source, path) ->
120135
glob_pp (Lexicon.directory_is_a_file source path) ()
121-
| Eff.Provider_error error -> glob_pp (pp_provider_error custom_error) error
136+
| Eff.Provider_error { source; target; error } ->
137+
glob_pp (pp_provider_error custom_error ~source ~target) error
122138
| exn -> glob_pp Lexicon.unknown_error exn
123139
124140
let runtime_error_to_diagnostic ppf message =

lib/core/eff.ml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,13 @@ exception Invalid_path of filesystem * Path.t
128128
exception File_is_a_directory of filesystem * Path.t
129129
exception Directory_is_a_file of filesystem * Path.t
130130
exception Directory_not_exists of filesystem * Path.t
131-
exception Provider_error of Required.provider_error
131+
132+
exception
133+
Provider_error of {
134+
source : Path.t option
135+
; target : Path.t option
136+
; error : Required.provider_error
137+
}
132138

133139
let yocaml_log_src = Logs.Src.create ~doc:"Log emitted by YOCaml" "yocaml"
134140

@@ -170,7 +176,9 @@ let read_file_as_metadata (type a) (module P : Required.DATA_PROVIDER)
170176
|> Option.some
171177
|> Metadata.validate (module P) (module R)
172178
|> Result.fold
173-
~error:(fun err -> raise @@ Provider_error err)
179+
~error:(fun err ->
180+
raise
181+
@@ Provider_error { source = Some path; target = None; error = err })
174182
~ok:(fun metadata -> return metadata)
175183

176184
let read_file_with_metadata (type a) (module P : Required.DATA_PROVIDER)
@@ -183,7 +191,9 @@ let read_file_with_metadata (type a) (module P : Required.DATA_PROVIDER)
183191
raw_metadata
184192
|> Metadata.validate (module P) (module R)
185193
|> Result.fold
186-
~error:(fun err -> raise @@ Provider_error err)
194+
~error:(fun err ->
195+
raise
196+
@@ Provider_error { source = Some path; target = None; error = err })
187197
~ok:(fun metadata -> return (metadata, content))
188198

189199
let get_mtime ~on path =

lib/core/eff.mli

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,15 @@ exception Directory_is_a_file of filesystem * Path.t
303303
exception Directory_not_exists of filesystem * Path.t
304304
(** Exception raised when we try to use a directory as a regular file. *)
305305

306-
exception Provider_error of Required.provider_error
307-
(** Exception raised when we try to validate an invalid source of metadata. *)
306+
exception
307+
Provider_error of {
308+
source : Path.t option
309+
; target : Path.t option
310+
; error : Required.provider_error
311+
}
312+
(** Exception raised when reading, validating, or writing metadata fails.
313+
[source] indicates the file from which the metadata was read, when known.
314+
[target] indicates the destination path we tried to write to, when known. *)
308315

309316
(** {2 Helpers for performing effects}
310317

test/e2e/bin/dune

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,14 @@
1818
(name gen_residuals)
1919
(modules gen_residuals runtime)
2020
(libraries yocaml yocaml_unix))
21+
22+
(executable
23+
(name gen_pp_errors)
24+
(modules gen_pp_errors runtime test_article)
25+
(libraries
26+
yocaml
27+
yocaml_unix
28+
yocaml_yaml
29+
yocaml_jingoo
30+
yocaml_cmarkit
31+
yocaml_markdown))

test/e2e/bin/gen_pp_errors.ml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
module Test_Article = Test_article
2+
3+
class resolver ~source ~target =
4+
object (self)
5+
val get_source : Yocaml.Path.t = source
6+
val get_target : Yocaml.Path.t = target
7+
method source = get_source
8+
method target = Yocaml.Path.(get_target / "_www")
9+
method cache = Yocaml.Path.(self#target / ".cache")
10+
method templates = Yocaml.Path.(self#source / "content" / "templates")
11+
method articles = Yocaml.Path.(self#target / "articles")
12+
13+
method as_article path =
14+
path
15+
|> Yocaml.Path.move ~into:self#articles
16+
|> Yocaml.Path.change_extension "html"
17+
18+
method as_template path = Yocaml.Path.(self#templates / path)
19+
end
20+
21+
let article (resolver : resolver) file =
22+
Yocaml.Action.Static.write_file_with_metadata (resolver#as_article file)
23+
Yocaml.Task.(
24+
Yocaml_yaml.Pipeline.read_file_with_metadata (module Test_Article) file
25+
>>> Yocaml_cmarkit.content_to_html ()
26+
>>> Yocaml_jingoo.Pipeline.as_template
27+
(module Test_Article)
28+
(resolver#as_template "article.html")
29+
>>> Yocaml_jingoo.Pipeline.as_template
30+
(module Test_Article)
31+
(resolver#as_template "layout.html"))
32+
33+
let program (resolver : resolver) file () =
34+
let open Yocaml.Eff in
35+
let* () = logf ~level:`Debug "Trigger in %a" Yocaml.Path.pp resolver#source in
36+
Yocaml.Action.with_cache ~on:`Target resolver#cache (article resolver file)
37+
38+
module R = Yocaml.Runtime.Make (Runtime)
39+
40+
let () =
41+
let () = Array.iter print_endline Sys.argv in
42+
let cwd = Yocaml.Path.rel [] in
43+
let file =
44+
match Array.to_list Sys.argv with
45+
| _ :: file :: _ -> Yocaml.Path.rel [ file ]
46+
| _ -> failwith "Usage: gen_pp_errors.exe <path-to-markdown-file>"
47+
in
48+
let resolver = new resolver ~source:cwd ~target:cwd in
49+
let () = Yocaml_runtime.Log.setup ~level:`Debug () in
50+
R.run (program resolver file)

test/e2e/bin/test_article.ml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
open Yocaml
2+
3+
let entity_name = "Test_article"
4+
5+
type t = { title : string; date : string }
6+
7+
let neutral : (t, Required.provider_error) result =
8+
Error (Required.Required_metadata { entity = entity_name })
9+
10+
let validate =
11+
let open Data.Validation in
12+
record (fun fields ->
13+
let+ title = required fields "title" string
14+
and+ date = required fields "date" string in
15+
{ title; date })
16+
17+
let normalize { title; date } =
18+
let open Data in
19+
[ ("title", string title); ("date", string date) ]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: A broken article
3+
date 2025-06-08
4+
---
5+
6+
text for parsing error
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
text for required metadata error
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: Valid title
3+
date: [1, 2, 3]
4+
---
5+
6+
text for validation error

0 commit comments

Comments
 (0)