package suite import "encoding/json" // Authorization is what S2 answers for one item, for one named asker. // // Doctrine ยง4.3: four values, none collapsible into another. Only // NOT_VISIBLE may be silent; everything else that is not VISIBLE withholds // loudly and names the app. type Authorization string const ( // VISIBLE: this person may read this revision. VISIBLE Authorization = "VISIBLE" // NOT_VISIBLE: this person may not read it. The only value a consumer // may act on silently. NOT_VISIBLE Authorization = "NOT_VISIBLE" // STALE: the item is readable, but the app of record has moved past the // revision asked about. A synchronisation condition, never a denial. STALE Authorization = "STALE" // UNKNOWN: the app of record did not answer usefully, or cannot vouch // for the revision asked about. Withheld loudly. UNKNOWN Authorization = "UNKNOWN" ) // ItemRevision is one pair in an S2 question: which item, at which revision. type ItemRevision struct { ItemID string `json:"item_id"` Revision int64 `json:"revision"` } type s2Result struct { ItemID *string `json:"item_id"` Revision *int64 `json:"revision"` Result *Authorization `json:"result"` } type s2Body struct { Results []s2Result `json:"results"` } // DecodeS2 turns an S2 answer into exactly one Authorization per pair asked, // in the order they were asked. func DecodeS2(asked []ItemRevision, status int, body []byte) []Authorization { // Whole-call failures. A body that arrives with a non-2xx status is not // an answer however well it parses, and a body that does not parse is // not an answer however it arrived. if status < 200 || status > 299 { return allUnknown(len(asked)) } // Before decoding, not after: a duplicate key is resolved by encoding/json // and gone by the time the duplicate-row rule below could see it. if err := rejectAmbiguousJSON(body); err != nil { return allUnknown(len(asked)) } var parsed s2Body if err := json.Unmarshal(body, &parsed); err != nil { return allUnknown(len(asked)) } byPair := make(map[ItemRevision]Authorization, len(parsed.Results)) for _, r := range parsed.Results { if r.ItemID == nil || r.Revision == nil || r.Result == nil { return allUnknown(len(asked)) } pair := ItemRevision{ItemID: *r.ItemID, Revision: *r.Revision} if _, already := byPair[pair]; already { // Answered twice. Two answers are not an answer, and choosing // between them would be this client making a decision the app of // record did not make. byPair[pair] = UNKNOWN continue } byPair[pair] = known(*r.Result) } out := make([]Authorization, len(asked)) for i, pair := range asked { answer, answered := byPair[pair] if !answered { answer = UNKNOWN } out[i] = answer } return out } // known maps anything outside the closed four-value vocabulary to UNKNOWN. // A fifth word is a sibling this client does not understand; passing it // through would hand a caller a value it cannot switch on. func known(a Authorization) Authorization { switch a { case VISIBLE, NOT_VISIBLE, STALE, UNKNOWN: return a } return UNKNOWN } func allUnknown(n int) []Authorization { out := make([]Authorization, n) for i := range out { out[i] = UNKNOWN } return out }