warn.bend

cswap-bend-cli@0.1.0.0 · 182 lines · 33 definitions

1import Base
2import cswap-bend-cli@0.1.0.0/pollrow.bend as Pollrow
3import cswap-bend-cli@0.1.0.0/usage/gate.bend as UGate
4import cswap-bend-cli@0.1.0.0/store/seq.bend as SSeq
5import cswap-bend-cli@0.1.0.0/json.bend as Json
6import cswap-bend-cli@0.1.0.0/view.bend as View
7import cswap-bend-cli@0.1.0.0/collect.bend as Collect
8
9# The list payload's additive warnings (switcher.py _duplicate_account_warnings
10# and _lockstep_usage_warnings). Credentials are compared by lineage identity
11# as text (UGate.fp), so no hash is spawned.
12
13def dash() -> String:
14  " \u{2014} "
15
16type Seen is Data:
17  Seen{key: String, slot: String}
18
19def seen.pick(hit: Bool, s: String, rest: Maybe<&2, String>) -> Maybe<&2, String>:
20  match hit:
21    case True{}:
22      Some{s}
23    case False{}:
24      rest
25
26def seen_find(+key: String, xs: List<&2, Seen>) -> Maybe<&2, String>:
27  match xs:
28    case Nil{}:
29      None{}
30    case Con{Seen{k, s}, t}:
31      seen.pick(String.eq(k, key), s, seen_find(key, t))
32
33def append_str(xs: List<&2, String>, +x: String) -> List<&2, String>:
34  List.append(&2, String, xs, [x])
35
36def win_key2(p: Maybe<&2, Json.Json>, r: Maybe<&2, Json.Json>) -> Maybe<&2, String>:
37  match p r:
38    case Some{Json.JNum{t}} Some{Json.JStr{s}}:
39      Some{t ++ "@" ++ s}
40    case _ _:
41      None{}
42
43def win_key(+w: Json.Json) -> Maybe<&2, String>:
44  win_key2(Json.get("pct", w), Json.get("resets_at", w))
45
46def key3(a: Maybe<&2, String>, b: Maybe<&2, String>) -> Maybe<&2, String>:
47  match a b:
48    case Some{x} Some{y}:
49      Some{x ++ "|" ++ y}
50    case _ _:
51      None{}
52
53def key2(a: Maybe<&2, Json.Json>, b: Maybe<&2, Json.Json>) -> Maybe<&2, String>:
54  match a b:
55    case Some{x} Some{y}:
56      key3(win_key(x), win_key(y))
57    case _ _:
58      None{}
59
60def usage_key(+u: Json.Json) -> Maybe<&2, String>:
61  key2(Json.get("five_hour", u), Json.get("seven_day", u))
62
63type LS is Data:
64  LS{seen: List<&2, Seen>, msgs: List<&2, String>}
65
66def ls_seen(ls: LS) -> List<&2, Seen>:
67  match ls:
68    case LS{s, m}:
69      s
70
71def lock.seen(other: Maybe<&2, String>, +k: String, +slot: String, ls: LS) -> LS:
72  match other ls:
73    case Some{o} LS{s, msgs}:
74      LS{s, append_str(msgs, "Account-" ++ o ++ " and Account-" ++ slot ++ " report identical usage and reset times" ++ dash() ++ "they may be the same account (issue #117). If it persists, log in with the missing account and re-add it: cswap add --slot N")}
75    case None{} LS{s, msgs}:
76      LS{Seen{k, slot} <> s, msgs}
77
78def lock.step(m: Maybe<&2, String>, +slot: String, +ls: LS) -> LS:
79  match m:
80    case None{}:
81      ls
82    case Some{+k}:
83      lock.seen(seen_find(k, ls_seen(ls)), k, slot, ls)
84
85def lock.live(sentinel: Maybe<&2, String>, +row: Pollrow.Row, +slot: String, models: List<&2, String>, +now: U32, ls: LS) -> LS:
86  match sentinel:
87    case Some{s}:
88      ls
89    case None{}:
90      lock.step(usage_key(Pollrow.decision_usage(row, models, now)), slot, ls)
91
92def lock.acct(+a: View.Acct, models: List<&2, String>, +now: U32, ls: LS) -> LS:
93  match a:
94    case View.Acct{num, email, org_name, org_uuid, active, alias, disabled, login, sentinel, row}:
95      lock.live(sentinel, row, U32.show(num), models, now, ls)
96
97def lock.go(as: List<&2, View.Acct>, +models: List<&2, String>, +now: U32, ls: LS) -> LS:
98  match as:
99    case Nil{}:
100      ls
101    case Con{h, t}:
102      lock.go(t, models, now, lock.acct(h, models, now, ls))
103
104def msgs_of(ls: LS) -> List<&2, String>:
105  match ls:
106    case LS{s, m}:
107      m
108
109# Slots whose 5h and 7d usage and resets match to the digit.
110def lockstep(as: List<&2, View.Acct>, models: List<&2, String>, +now: U32) -> List<&2, String>:
111  msgs_of(lock.go(as, models, now, LS{[], []}))
112
113type DS is Data:
114  DS{fps: List<&2, Seen>, ids: List<&2, Seen>, msgs: List<&2, String>}
115
116def uuid_of(+seq: Json.Json, +slot: String) -> String:
117  String.trim(Maybe.default(&2, String, Json.as_string(Maybe.default(&2, Json.Json, Maybe.bind(&2, Json.Json, Json.Json, SSeq.slot_record(seq, slot), r => Json.get("uuid", r)), Json.JNull{})), ""))
118
119def ds_fps(ds: DS) -> List<&2, Seen>:
120  match ds:
121    case DS{f, i, m}:
122      f
123
124def dup.fp3(other: Maybe<&2, String>, +fp: String, +slot: String, +email: String, ds: DS) -> DS:
125  match other ds:
126    case Some{o} DS{f, i, m}:
127      DS{f, i, append_str(m, "Account-" ++ o ++ " and Account-" ++ slot ++ " hold the same credential (" ++ email ++ ")" ++ dash() ++ "one slot's backup was overwritten. Log in with the missing account and re-add it: cswap add --slot N")}
128    case None{} DS{f, i, m}:
129      DS{Seen{fp, slot} <> f, i, m}
130
131def dup.fp2(has: Bool, +fp: String, +slot: String, +email: String, +ds: DS) -> DS:
132  match has:
133    case False{}:
134      ds
135    case True{}:
136      dup.fp3(seen_find(fp, ds_fps(ds)), fp, slot, email, ds)
137
138def dup.fp(+creds: String, +slot: String, +email: String, ds: DS) -> DS:
139  dup.fp2(Bool.not(String.is_empty(creds)), UGate.fp(creds), slot, email, ds)
140
141def ds_ids(ds: DS) -> List<&2, Seen>:
142  match ds:
143    case DS{f, i, m}:
144      i
145
146def dup.id3(other: Maybe<&2, String>, +key: String, +slot: String, +email: String, ds: DS) -> DS:
147  match other ds:
148    case Some{o} DS{f, i, m}:
149      DS{f, i, append_str(m, "Account-" ++ o ++ " and Account-" ++ slot ++ " both authenticate as " ++ email ++ dash() ++ "remove or re-login one of them.")}
150    case None{} DS{f, i, m}:
151      DS{f, Seen{key, slot} <> i, m}
152
153def dup.id2(has: Bool, +key: String, +slot: String, +email: String, +ds: DS) -> DS:
154  match has:
155    case False{}:
156      ds
157    case True{}:
158      dup.id3(seen_find(key, ds_ids(ds)), key, slot, email, ds)
159
160def dup.id(+uuid: String, +org: String, +slot: String, +email: String, ds: DS) -> DS:
161  dup.id2(Bool.not(String.is_empty(uuid)), uuid ++ "@" ++ org, slot, email, ds)
162
163def dup.info(+i: Collect.Info, +seq: Json.Json, ds: DS) -> DS:
164  match i:
165    case Collect.Info{num, email, org_name, org_uuid, alias, disabled, active, creds}:
166      dup.id(uuid_of(seq, U32.show(num)), org_uuid, U32.show(num), email, dup.fp(creds, U32.show(num), email, ds))
167
168def dup.go(xs: List<&2, Collect.Info>, +seq: Json.Json, ds: DS) -> DS:
169  match xs:
170    case Nil{}:
171      ds
172    case Con{h, t}:
173      dup.go(t, seq, dup.info(h, seq, ds))
174
175def ds_msgs(ds: DS) -> List<&2, String>:
176  match ds:
177    case DS{f, i, m}:
178      m
179
180# Slots that provably authenticate as the same account.
181def duplicates(infos: List<&2, Collect.Info>, +seq: Json.Json) -> List<&2, String>:
182  ds_msgs(dup.go(infos, seq, DS{[], [], []}))