diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-11 16:21:46 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-11 16:21:46 +0200 |
| commit | 745ec8f8edb32ee7ef0a8c12ab84833216296f73 (patch) | |
| tree | 3ca4dc144b2448553923716fdf54d91526354446 /lib/kernel | |
| parent | 1203380c4644277a2949d42f4619360c1c0ef1e5 (diff) | |
| download | colitur-745ec8f8edb32ee7ef0a8c12ab84833216296f73.tar.gz colitur-745ec8f8edb32ee7ef0a8c12ab84833216296f73.zip | |
kernel: loaders never escape as an exception, date-spec validates on load
Layer.load narrowed its catch to Sexplib0.Sexp_conv_error.Of_sexp_error,
but rank_of_sexp is caller-supplied and may raise anything -- e.g. a
hand-written rank parser that calls invalid_arg. Overlay.load already
catches every exception from the equivalent call; mirror that here so
layer.mli's "never as an exception" promise actually holds.
Date_spec.t derived its sexp converters with plain ppx_sexp_conv, unlike
Slug and Lang, which hand-write validating parsers specifically so
malformed data is rejected at load. (Fixed (month 13) (day 40)) used to
deserialise cleanly into a spec that simply never resolves -- a saint
quietly vanishing with no diagnostic. t_of_sexp now re-runs the value
through the existing fixed validator, the same shape Slug and Lang
already use.
Covering tests: a Layer.load case where rank_of_sexp raises
Invalid_argument instead of Of_sexp_error (would have escaped
uncaught before this fix); two Date_spec.t_of_sexp cases (month 13,
31 April) that must raise Of_sexp_error rather than silently
constructing an unresolvable spec.
Diffstat (limited to 'lib/kernel')
| -rw-r--r-- | lib/kernel/date_spec.ml | 20 | ||||
| -rw-r--r-- | lib/kernel/layer.ml | 6 |
2 files changed, 23 insertions, 3 deletions
diff --git a/lib/kernel/date_spec.ml b/lib/kernel/date_spec.ml index 76e21e3..732f6b1 100644 --- a/lib/kernel/date_spec.ml +++ b/lib/kernel/date_spec.ml @@ -4,7 +4,11 @@ open Sexplib0.Sexp_conv sanctoral needs -- a fixed calendar date -- because EF movable feasts come from the rite's temporal code, not from data. Sunday-relative and Easter-relative forms arrive with the OF sanctoral. *) -type t = Fixed of { month : int; day : int } [@@deriving sexp] +module Repr = struct + type t = Fixed of { month : int; day : int } [@@deriving sexp] +end + +type t = Repr.t = Fixed of { month : int; day : int } (* Leap-year maximum, so 29 February is constructible; it simply does not resolve in a common year. *) @@ -23,3 +27,17 @@ let fixed ~month ~day = let resolve t ~year = match t with | Fixed { month; day } -> Result.to_option (Date.make ~year ~month ~day) + +let sexp_of_t = Repr.sexp_of_t + +(* Validating parser, matching Slug and Lang: [ppx_sexp_conv]'s derived + [t_of_sexp] (relocated to [Repr] above) accepts any in-range int pair, so + [(Fixed (month 13) (day 40))] would otherwise deserialise into a spec that + silently never resolves -- a saint quietly vanishing with no diagnostic. + Re-running the value through [fixed] closes that gap the same way loaders + already close it for slugs and language codes. *) +let t_of_sexp sexp = + let (Fixed { month; day }) = Repr.t_of_sexp sexp in + match fixed ~month ~day with + | Ok t -> t + | Error msg -> Sexplib0.Sexp_conv.of_sexp_error msg sexp diff --git a/lib/kernel/layer.ml b/lib/kernel/layer.ml index bf71206..515b052 100644 --- a/lib/kernel/layer.ml +++ b/lib/kernel/layer.ml @@ -52,5 +52,7 @@ let load rank_of_sexp path = | sexp -> ( match t_of_sexp rank_of_sexp sexp with | t -> Ok { t with entries = canonical t.entries } - | exception Sexplib0.Sexp_conv_error.Of_sexp_error (exn, _) -> - Error (Printf.sprintf "%s: %s" path (Printexc.to_string exn))) + (* [rank_of_sexp] is caller-supplied and may raise anything, not only + [Of_sexp_error] -- mirrors [Overlay.load]'s catch-all, so "never as + an exception" (layer.mli) actually holds. *) + | exception exn -> Error (Printf.sprintf "%s: %s" path (Printexc.to_string exn))) |
