oficina / suite Public

Clone
git clone https://amadan.net/oficina/suite

Plain git — no account needed to clone.

Download

Download this file

1package suite
2
3import "encoding/json"
4
5// Authorization is what S2 answers for one item, for one named asker.
6//
7// Doctrine §4.3: four values, none collapsible into another. Only
8// NOT_VISIBLE may be silent; everything else that is not VISIBLE withholds
9// loudly and names the app.
10type Authorization string
11
12const (
13 // VISIBLE: this person may read this revision.
14 VISIBLE Authorization = "VISIBLE"
15 // NOT_VISIBLE: this person may not read it. The only value a consumer
16 // may act on silently.
17 NOT_VISIBLE Authorization = "NOT_VISIBLE"
18 // STALE: the item is readable, but the app of record has moved past the
19 // revision asked about. A synchronisation condition, never a denial.
20 STALE Authorization = "STALE"
21 // UNKNOWN: the app of record did not answer usefully, or cannot vouch
22 // for the revision asked about. Withheld loudly.
23 UNKNOWN Authorization = "UNKNOWN"
24)
25
26// ItemRevision is one pair in an S2 question: which item, at which revision.
27type ItemRevision struct {
28 ItemID string `json:"item_id"`
29 Revision int64 `json:"revision"`
30}
31
32type s2Result struct {
33 ItemID *string `json:"item_id"`
34 Revision *int64 `json:"revision"`
35 Result *Authorization `json:"result"`
36}
37
38type s2Body struct {
39 Results []s2Result `json:"results"`
40}
41
42// DecodeS2 turns an S2 answer into exactly one Authorization per pair asked,
43// in the order they were asked.
44func DecodeS2(asked []ItemRevision, status int, body []byte) []Authorization {
45 // Whole-call failures. A body that arrives with a non-2xx status is not
46 // an answer however well it parses, and a body that does not parse is
47 // not an answer however it arrived.
48 if status < 200 || status > 299 {
49 return allUnknown(len(asked))
50 }
51 // Before decoding, not after: a duplicate key is resolved by encoding/json
52 // and gone by the time the duplicate-row rule below could see it.
53 if err := rejectAmbiguousJSON(body); err != nil {
54 return allUnknown(len(asked))
55 }
56 var parsed s2Body
57 if err := json.Unmarshal(body, &parsed); err != nil {
58 return allUnknown(len(asked))
59 }
60
61 byPair := make(map[ItemRevision]Authorization, len(parsed.Results))
62 for _, r := range parsed.Results {
63 if r.ItemID == nil || r.Revision == nil || r.Result == nil {
64 return allUnknown(len(asked))
65 }
66 pair := ItemRevision{ItemID: *r.ItemID, Revision: *r.Revision}
67 if _, already := byPair[pair]; already {
68 // Answered twice. Two answers are not an answer, and choosing
69 // between them would be this client making a decision the app of
70 // record did not make.
71 byPair[pair] = UNKNOWN
72 continue
73 }
74 byPair[pair] = known(*r.Result)
75 }
76
77 out := make([]Authorization, len(asked))
78 for i, pair := range asked {
79 answer, answered := byPair[pair]
80 if !answered {
81 answer = UNKNOWN
82 }
83 out[i] = answer
84 }
85 return out
86}
87
88// known maps anything outside the closed four-value vocabulary to UNKNOWN.
89// A fifth word is a sibling this client does not understand; passing it
90// through would hand a caller a value it cannot switch on.
91func known(a Authorization) Authorization {
92 switch a {
93 case VISIBLE, NOT_VISIBLE, STALE, UNKNOWN:
94 return a
95 }
96 return UNKNOWN
97}
98
99func allUnknown(n int) []Authorization {
100 out := make([]Authorization, n)
101 for i := range out {
102 out[i] = UNKNOWN
103 }
104 return out
105}
106