package check import ( "encoding/json" "fmt" "sort" "strings" "git.novox.be/novox/mesh-sdk/go/delivery" ) // The read tools (novox/hq ADR 0484 clauses 8 and 13, issue 1686): what the checker stored, as stored, with the line of the // status it owes in full, so a check is read live without a shell. They write nothing. // The read tools, named as the module is: checker_ and the noun. const ( VerdictsReadTool = "checker_verdicts" PullReadTool = "checker_pull" KeptReadTool = "checker_kept" ) // StateRead reads one state: its keys and a key's stored value (nil for a key with none). type StateRead struct { Keys func() ([]string, error) Get func(key string) (json.RawMessage, error) } // Reader answers the read tools from the checker's verdicts and kept states and the indexer's candidates. type Reader struct{ VerdictState, KeptState, CandidateState StateRead } // VerdictRead is a verdict as stored with the full report of the status of each pull request it judged (Report), when the // candidate still holds those heads; without it the report is that of the verdict alone. type VerdictRead struct { Key string `json:"key"` Verdict json.RawMessage `json:"verdict"` Reports map[string]string `json:"reports,omitempty"` Note string `json:"note,omitempty"` } func (r Reader) candidate(group string) (delivery.Candidate, bool) { key, err := delivery.CandidatesKey(group) if err != nil || r.CandidateState.Get == nil { return delivery.Candidate{}, false } raw, err := r.CandidateState.Get(key) if err != nil || len(raw) == 0 { return delivery.Candidate{}, false } c, err := delivery.DecodeCandidate(raw) return c, err == nil } func (r Reader) read(key string, raw json.RawMessage) (VerdictRead, error) { out := VerdictRead{Key: key, Verdict: raw} v, err := delivery.DecodeVerdict(raw) if err != nil { return out, fmt.Errorf("%s holds what is not a verdict: %w", key, err) } c, ok := r.candidate(v.Group) if !ok || c.HeadsHash != v.Heads { out.Note = "the candidate no longer holds these heads" return out, nil } cs := CaseOf(c, v.Trunk) out.Reports = map[string]string{} if cs == nil { return out, nil } for _, p := range cs.Pulls { out.Reports[fmt.Sprintf("%s#%d", p.Repository, p.Number)] = Report(v, cs, p, nil, nil) } for _, l := range cs.Left { out.Reports[strings.SplitN(l, ": ", 2)[0]] = strings.SplitN(l, ": ", 2)[1] } return out, nil } // Verdicts are the verdicts the checker holds, by key; with a group only that group's. func (r Reader) Verdicts(group string) ([]VerdictRead, error) { if r.VerdictState.Keys == nil || r.VerdictState.Get == nil { return nil, fmt.Errorf("the verdicts state cannot be read here") } keys, err := r.VerdictState.Keys() if err != nil { return nil, err } sort.Strings(keys) var out []VerdictRead for _, k := range keys { if group != "" { if g, _, err := delivery.ParseCheckerVerdictKey(k); err != nil || g != group { continue } } raw, err := r.VerdictState.Get(k) if err != nil { return nil, err } if len(raw) == 0 { continue } vr, err := r.read(k, raw) if err != nil { return nil, err } out = append(out, vr) } return out, nil } // Pull answers for one pull request: the verdict of the candidate that holds it, with its report; for an hq pull request, // that hq is judged by merge-check.sh until issue 1296 (HQNote); for one the indexer holds in no candidate, that none does. func (r Reader) Pull(repository string, number int) (VerdictRead, error) { if r.CandidateState.Keys == nil || r.CandidateState.Get == nil { return VerdictRead{}, fmt.Errorf("the candidates state cannot be read here") } keys, err := r.CandidateState.Keys() if err != nil { return VerdictRead{}, err } sort.Strings(keys) for _, k := range keys { raw, err := r.CandidateState.Get(k) if err != nil || len(raw) == 0 { continue } c, err := delivery.DecodeCandidate(raw) if err != nil { continue } for _, p := range c.Pulls { if p.Repository != repository || p.Number != number { continue } if repository == HQRepository { return VerdictRead{Key: k, Note: HQNote}, nil } vkey, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash) if err != nil { return VerdictRead{}, err } vraw, err := r.VerdictState.Get(vkey) if err != nil { return VerdictRead{}, err } if len(vraw) == 0 { return VerdictRead{Key: vkey, Note: "no verdict yet for the heads of its candidate"}, nil } return r.read(vkey, vraw) } } return VerdictRead{}, fmt.Errorf("no candidate holds %s#%d", repository, number) } // Kept is the kept verdicts: one key's value as stored, or with an empty key the keys of a level (or every level) and their values. func (r Reader) Kept(level, digest string) (map[string]json.RawMessage, error) { if r.KeptState.Keys == nil || r.KeptState.Get == nil { return nil, fmt.Errorf("the kept state cannot be read here") } if digest != "" { key, err := delivery.KeptKey(level, digest) if err != nil { return nil, err } raw, err := r.KeptState.Get(key) if err != nil { return nil, err } if len(raw) == 0 { return nil, fmt.Errorf("no kept verdict %s", key) } return map[string]json.RawMessage{key: raw}, nil } keys, err := r.KeptState.Keys() if err != nil { return nil, err } sort.Strings(keys) out := map[string]json.RawMessage{} for _, k := range keys { if l, _, err := delivery.ParseKeptKey(k); err != nil || (level != "" && l != level) { continue } raw, err := r.KeptState.Get(k) if err != nil { return nil, err } if len(raw) > 0 { out[k] = raw } } return out, nil }