daemon/rows.bend

cswap-bend-cli@0.1.0.0 · 135 lines · 23 definitions

1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/clock.bend as Clock
4import cswap-bend-cli@0.1.0.0/policy/types.bend as T
5import cswap-bend-cli@0.1.0.0/daemon/config.bend as Cfg
6
7# cache/usage.json rows (contract 1.5) into the policy's `Usage`. Percentages
8# become centi-percent Nat; epochs and ISO reset stamps become unix seconds.
9
10def nat_opt(+m: Maybe<&2, U32>) -> Maybe<&2, Nat>:
11  match m:
12    case None{}:
13      None{}
14    case Some{n}:
15      Some{U32.to_nat(n)}
16
17# "1792740000.1" -> 1792740000, "7" -> 7; None for anything else.
18def epoch_text(+t: String) -> Maybe<&2, U32>:
19  Cfg.read_u32(Maybe.default(&2, String, List.head(&2, String, String.split(t, '.')), ""))
20
21def num_text(+m: Maybe<&2, Json.Json>) -> Maybe<&2, String>:
22  match m:
23    case Some{Json.JNum{t}}:
24      Some{t}
25    case _:
26      None{}
27
28def str_text(+m: Maybe<&2, Json.Json>) -> Maybe<&2, String>:
29  match m:
30    case Some{Json.JStr{s}}:
31      Some{s}
32    case _:
33      None{}
34
35def bind_s(+m: Maybe<&2, String>, f: String -> Maybe<&2, U32>) -> Maybe<&2, U32>:
36  match m:
37    case None{}:
38      None{}
39    case Some{s}:
40      f(s)
41
42def epoch_of(+j: Json.Json, +key: String) -> Maybe<&2, Nat>:
43  nat_opt(bind_s(num_text(Json.get(key, j)), t => epoch_text(t)))
44
45def pct_of(+j: Json.Json) -> Maybe<&2, Nat>:
46  nat_opt(bind_s(num_text(Json.get("pct", j)), t => Cfg.centi(t)))
47
48def iso_of(+j: Json.Json) -> Maybe<&2, Nat>:
49  nat_opt(bind_s(str_text(Json.get("resets_at", j)), t => Clock.parse_iso8601(t)))
50
51def or_null(+m: Maybe<&2, Json.Json>) -> Json.Json:
52  match m:
53    case Some{w}:
54      w
55    case None{}:
56      Json.JNull{}
57
58def win(+j: Json.Json, +key: String) -> Json.Json:
59  or_null(Json.get(key, j))
60
61# ---- scoped windows -----------------------------------------------------------------------
62
63def scoped2(+name: Maybe<&2, String>, +pct: Maybe<&2, Nat>, +reset: Maybe<&2, Nat>) -> Maybe<&2, T.Scoped>:
64  match name pct:
65    case Some{n} Some{p}:
66      Some{T.Scoped{n, p, reset}}
67    case _ _:
68      None{}
69
70def scoped_one(+s: Json.Json) -> Maybe<&2, T.Scoped>:
71  scoped2(str_text(Json.get("name", s)), pct_of(s), iso_of(s))
72
73def scoped_push(+m: Maybe<&2, T.Scoped>, +rest: List<&2, T.Scoped>) -> List<&2, T.Scoped>:
74  match m:
75    case Some{s}:
76      s <> rest
77    case None{}:
78      rest
79
80def scoped_chain(+items: Json.Json) -> List<&2, T.Scoped>:
81  match items:
82    case Json.JCons{h, t}:
83      scoped_push(scoped_one(h), scoped_chain(t))
84    case _:
85      []
86
87def unwrap_arr(+j: Json.Json) -> Json.Json:
88  match j:
89    case Json.JArr{items}:
90      items
91    case _:
92      Json.JNil{}
93
94def scoped_of(+good: Json.Json) -> List<&2, T.Scoped>:
95  scoped_chain(unwrap_arr(win(good, "scoped")))
96
97# ---- error and plan fields ---------------------------------------------------------------------
98
99def err2(+is429: Bool) -> T.ErrKind:
100  match is429:
101    case True{}:
102      T.ErrHttp429{}
103    case False{}:
104      T.ErrOther{}
105
106def err_kind(+m: Maybe<&2, String>) -> T.ErrKind:
107  match m:
108    case None{}:
109      T.ErrNone{}
110    case Some{e}:
111      err2(String.eq(e, "http-429"))
112
113def zero_or(+m: Maybe<&2, Nat>) -> Nat:
114  match m:
115    case None{}:
116      0n
117    case Some{n}:
118      n
119
120def u32_field(+j: Json.Json, +key: String) -> Nat:
121  zero_or(nat_opt(bind_s(num_text(Json.get(key, j)), t => Cfg.read_u32(Maybe.default(&2, String, List.head(&2, String, String.split(t, '.')), "")))))
122
123def usage2(+slot: Nat, +row: Json.Json, +good: Json.Json, +sentinel: T.Sentinel) -> T.Usage:
124  T.Usage{slot, pct_of(win(good, "five_hour")), iso_of(win(good, "five_hour")), pct_of(win(good, "seven_day")), iso_of(win(good, "seven_day")),
125    scoped_of(good), epoch_of(row, "fetchedAt"), u32_field(row, "backoffUntil"), err_kind(str_text(Json.get("lastError", row))), u32_field(row, "consecutiveFailures"),
126    epoch_of(row, "nextPollAt"), u32_field(row, "pollIntervalS"), u32_field(row, "authDeadStrikes"), u32_field(row, "heldUntil"), u32_field(row, "claimUntil"), sentinel}
127
128# A row, as the policy reads it. `row` is JNull when the slot has none (or it
129# belongs to another identity): never fetched. `sentinel` is the overlay the
130# caller computed from the credential.
131def usage_of(+slot: Nat, +row: Json.Json, +sentinel: T.Sentinel) -> T.Usage:
132  usage2(slot, row, win(row, "lastGood"), sentinel)
133
134def empty_usage(+slot: Nat, +sentinel: T.Sentinel) -> T.Usage:
135  usage_of(slot, Json.JNull{}, sentinel)