Native OCaml Rego/OPA policy engine
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

ocaml-rego: drop unix in favour of ptime

Calendar math goes through Ptime.to_date_time / Ptime.weekday; the
time builtins keep their named-zone offset table and feed tz_offset_s
to Ptime instead of shifting seconds through Unix.gmtime.

The clock used by time.now_ns is also pluggable: pass a
?now_ns: unit -> float when creating the engine (for example
Eio-backed fun () -> Eio.Time.now clock *. 1e9). The default returns
0., so policies that don't consult the clock still work without
linking unix or eio.

+84 -42
+1
dune-project
··· 30 30 (menhir (>= 20230608)) 31 31 (menhirLib (>= 20230608)) 32 32 (uutf (>= 1.0)) 33 + (ptime (>= 1.1)) 33 34 (alcotest :with-test) 34 35 (odoc :with-doc) 35 36 (mdx :with-test)
+1 -1
lib/dune
··· 1 1 (library 2 2 (name rego) 3 3 (public_name rego) 4 - (libraries fmt nox-json astring re sedlex menhirLib nox-loc unix uutf) 4 + (libraries fmt nox-json astring re sedlex menhirLib nox-loc uutf ptime) 5 5 (preprocess 6 6 (pps sedlex.ppx))) 7 7
+73 -37
lib/eval.ml
··· 800 800 801 801 (* ── Time ──────────────────────────────────────────────────────────────── *) 802 802 803 - (** OPA's time builtins use nanoseconds since the Unix epoch. We rely on 804 - {!Unix.gmtime} for calendar math and a small named-zone table for the 805 - explicit-timezone overloads (the full IANA database isn't bundled). *) 803 + (** OPA's time builtins use nanoseconds since the Unix epoch. We use {!Ptime} 804 + for calendar math so the library doesn't depend on [unix]; a small 805 + named-zone table maps the explicit-timezone overloads to the integer offset 806 + that [Ptime] expects. *) 807 + 808 + type tm = { 809 + tm_year : int; (** Calendar year, e.g. 2026 *) 810 + tm_mon : int; (** 1..12 *) 811 + tm_mday : int; (** 1..31 *) 812 + tm_hour : int; (** 0..23 *) 813 + tm_min : int; (** 0..59 *) 814 + tm_sec : int; (** 0..60 *) 815 + tm_wday : int; (** 0=Sunday .. 6=Saturday *) 816 + } 817 + 818 + (** Convert seconds-since-epoch to a [tm] in the requested fixed-offset timezone 819 + (seconds east of UTC). *) 820 + let tm_of_secs ?(tz_offset_s = 0) secs_f = 821 + match Ptime.of_float_s secs_f with 822 + | None -> raise (Eval_error "time: timestamp out of Ptime range") 823 + | Some t -> 824 + let (y, m, d), ((hh, mm, ss), _) = Ptime.to_date_time ~tz_offset_s t in 825 + let wday = 826 + match Ptime.weekday ~tz_offset_s t with 827 + | `Sun -> 0 828 + | `Mon -> 1 829 + | `Tue -> 2 830 + | `Wed -> 3 831 + | `Thu -> 4 832 + | `Fri -> 5 833 + | `Sat -> 6 834 + in 835 + { 836 + tm_year = y; 837 + tm_mon = m; 838 + tm_mday = d; 839 + tm_hour = hh; 840 + tm_min = mm; 841 + tm_sec = ss; 842 + tm_wday = wday; 843 + } 806 844 807 845 (** Min / max nanosecond timestamps OPA accepts. int64 [±2^63] both round to the 808 846 same float64 value (≈ ±9.223372036854776e18), so the closest float strictly ··· 836 874 837 875 let unix_tm_of_ns ?(tz = "UTC") ns = 838 876 let secs = ns /. 1e9 in 839 - if tz = "" || tz = "UTC" then Unix.gmtime secs 840 - else 841 - let offset_minutes = 842 - match tz with 843 - | "America/Los_Angeles" -> -480 844 - | "America/Denver" -> -420 845 - | "America/Chicago" -> -360 846 - | "America/New_York" -> -300 847 - | "Europe/London" -> 0 848 - | "Europe/Paris" | "Europe/Berlin" -> 60 849 - | "Asia/Tokyo" -> 540 850 - | "Asia/Shanghai" | "Asia/Hong_Kong" -> 480 851 - | "Australia/Sydney" -> 660 852 - | _ -> 0 853 - in 854 - Unix.gmtime (secs +. (float_of_int offset_minutes *. 60.)) 877 + let tz_offset_s = 878 + match tz with 879 + | "" | "UTC" -> 0 880 + | "America/Los_Angeles" -> -480 * 60 881 + | "America/Denver" -> -420 * 60 882 + | "America/Chicago" -> -360 * 60 883 + | "America/New_York" -> -300 * 60 884 + | "Europe/London" -> 0 885 + | "Europe/Paris" | "Europe/Berlin" -> 60 * 60 886 + | "Asia/Tokyo" -> 540 * 60 887 + | "Asia/Shanghai" | "Asia/Hong_Kong" -> 480 * 60 888 + | "Australia/Sydney" -> 660 * 60 889 + | _ -> 0 890 + in 891 + tm_of_secs ~tz_offset_s secs 855 892 856 893 let parse_duration s = 857 894 let n = String.length s in ··· 2673 2710 let tm = unix_tm_of_ns ~tz ns in 2674 2711 Value.Array 2675 2712 [ 2676 - Value.Number (float_of_int (tm.Unix.tm_year + 1900)); 2677 - Value.Number (float_of_int (tm.Unix.tm_mon + 1)); 2678 - Value.Number (float_of_int tm.Unix.tm_mday); 2713 + Value.Number (float_of_int tm.tm_year); 2714 + Value.Number (float_of_int tm.tm_mon); 2715 + Value.Number (float_of_int tm.tm_mday); 2679 2716 ] 2680 2717 | "time.clock", [ arg ] -> 2681 2718 let ns, tz = parse_time_arg arg in 2682 2719 let tm = unix_tm_of_ns ~tz ns in 2683 2720 Value.Array 2684 2721 [ 2685 - Value.Number (float_of_int tm.Unix.tm_hour); 2686 - Value.Number (float_of_int tm.Unix.tm_min); 2687 - Value.Number (float_of_int tm.Unix.tm_sec); 2722 + Value.Number (float_of_int tm.tm_hour); 2723 + Value.Number (float_of_int tm.tm_min); 2724 + Value.Number (float_of_int tm.tm_sec); 2688 2725 ] 2689 2726 | "time.weekday", [ arg ] -> 2690 2727 let ns, tz = parse_time_arg arg in 2691 2728 let tm = unix_tm_of_ns ~tz ns in 2692 - Value.String (weekday_name tm.Unix.tm_wday) 2729 + Value.String (weekday_name tm.tm_wday) 2693 2730 | ( "time.add_date", 2694 2731 [ Value.Number ns; Value.Number y; Value.Number m; Value.Number d ] ) -> 2695 2732 if ns < int64_min_ns || ns > int64_max_ns then 2696 2733 raise (Eval_error "time outside of valid range"); 2697 2734 let tm = unix_tm_of_ns ns in 2698 - let new_year = tm.Unix.tm_year + 1900 + int_of_float y in 2699 - let new_month = tm.Unix.tm_mon + 1 + int_of_float m in 2700 - let new_day = tm.Unix.tm_mday + int_of_float d in 2735 + let new_year = tm.tm_year + int_of_float y in 2736 + let new_month = tm.tm_mon + int_of_float m in 2737 + let new_day = tm.tm_mday + int_of_float d in 2701 2738 let days = days_from_civil new_year new_month new_day in 2702 2739 let frac_secs = 2703 - float_of_int 2704 - ((tm.Unix.tm_hour * 3600) + (tm.Unix.tm_min * 60) + tm.Unix.tm_sec) 2740 + float_of_int ((tm.tm_hour * 3600) + (tm.tm_min * 60) + tm.tm_sec) 2705 2741 in 2706 2742 let utc_secs = (float_of_int days *. 86400.) +. frac_secs in 2707 2743 let frac_ns = ns -. (Float.floor (ns /. 1e9) *. 1e9) in ··· 2715 2751 let lo, hi = if ns_a <= ns_b then (ns_a, ns_b) else (ns_b, ns_a) in 2716 2752 let tm_lo = unix_tm_of_ns lo in 2717 2753 let tm_hi = unix_tm_of_ns hi in 2718 - let yrs = ref (tm_hi.Unix.tm_year - tm_lo.Unix.tm_year) in 2719 - let mos = ref (tm_hi.Unix.tm_mon - tm_lo.Unix.tm_mon) in 2720 - let days = ref (tm_hi.Unix.tm_mday - tm_lo.Unix.tm_mday) in 2721 - let hrs = ref (tm_hi.Unix.tm_hour - tm_lo.Unix.tm_hour) in 2722 - let mns = ref (tm_hi.Unix.tm_min - tm_lo.Unix.tm_min) in 2723 - let scs = ref (tm_hi.Unix.tm_sec - tm_lo.Unix.tm_sec) in 2754 + let yrs = ref (tm_hi.tm_year - tm_lo.tm_year) in 2755 + let mos = ref (tm_hi.tm_mon - tm_lo.tm_mon) in 2756 + let days = ref (tm_hi.tm_mday - tm_lo.tm_mday) in 2757 + let hrs = ref (tm_hi.tm_hour - tm_lo.tm_hour) in 2758 + let mns = ref (tm_hi.tm_min - tm_lo.tm_min) in 2759 + let scs = ref (tm_hi.tm_sec - tm_lo.tm_sec) in 2724 2760 if !scs < 0 then begin 2725 2761 scs := !scs + 60; 2726 2762 decr mns
+9 -4
lib/rego.ml
··· 109 109 (** When [true], builtin errors propagate instead of producing [Undefined] 110 110 — matches OPA's [strict_error] mode. *) 111 111 mutable now_ns : unit -> float; 112 - (** Clock used by [time.now_ns]. Defaults to a Unix gettimeofday-based 113 - source; pass [Eio.Time.Clock.now] (scaled to ns) at engine creation or 114 - via [set_now_ns] to use an Eio clock instead. *) 112 + (** Clock used by [time.now_ns]. The library deliberately doesn't depend 113 + on [unix] or [eio]; pass an Eio-backed 114 + [fun () -> Eio.Time.now clock *. 1e9] (or any other source) at engine 115 + creation or via {!set_now_ns}. *) 115 116 } 116 117 117 - let default_now_ns () = Unix.gettimeofday () *. 1e9 118 + (** No default clock: ocaml-rego doesn't link against [unix] or [eio], so 119 + callers that use [time.now_ns] in their policies must pass a clock callback 120 + explicitly. The fallback returns [0.] (1970-01-01) so a policy that doesn't 121 + actually consult the clock still works. *) 122 + let default_now_ns () = 0. 118 123 119 124 let engine ?(now_ns = default_now_ns) () = 120 125 { modules = []; data = Value.Object []; strict = false; now_ns }