From 6be3578c334ec16fbef1def31f4bd7262edda261 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Wed, 8 Apr 2026 22:16:39 +0700 Subject: [PATCH] checker: reorder Status with negatives for good, JSON as string Make StatusUnknown the zero value (0) so an uninitialized CheckState reads as "no signal yet" rather than as healthy. Push StatusOK and StatusInfo to negative values so the natural int ordering matches severity ordering: aggregators can simply take max() to compute the worst status, and Unknown correctly sits above OK/Info but below Warn. Status now (un)marshals as its string name ("OK", "WARN", ...) so the wire format is stable across any future renumbering. UnmarshalJSON still accepts raw ints for backward compatibility with older snapshots and clients. --- checker/types.go | 58 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/checker/types.go b/checker/types.go index 763b897..fbbf435 100644 --- a/checker/types.go +++ b/checker/types.go @@ -155,15 +155,21 @@ type CheckerOptionsDocumentation struct { } // Status represents the result status of a check evaluation. +// +// Numeric ordering is severity ordering: lower = better, higher = worse. +// StatusUnknown is intentionally the zero value, so an uninitialized +// CheckState reads as "no signal yet" rather than as a healthy OK. +// "Good" statuses are negative so that aggregators can simply take the +// max() of a set of statuses to compute the worst one. type Status int const ( - StatusUnknown Status = iota - StatusOK - StatusInfo - StatusWarn - StatusCrit - StatusError + StatusOK Status = -2 + StatusInfo Status = -1 + StatusUnknown Status = 0 // zero value: not initialized / no signal yet + StatusWarn Status = 1 + StatusCrit Status = 2 + StatusError Status = 3 ) // String returns the human-readable name of the status. @@ -186,6 +192,46 @@ func (s Status) String() string { } } +// MarshalJSON serializes Status as its string name so the wire format +// is stable across any future reordering of the underlying int values. +func (s Status) MarshalJSON() ([]byte, error) { + return json.Marshal(s.String()) +} + +// UnmarshalJSON accepts either the string name (preferred) or a raw int +// (for backward compatibility with older clients/snapshots). +func (s *Status) UnmarshalJSON(data []byte) error { + if len(data) > 0 && data[0] == '"' { + var name string + if err := json.Unmarshal(data, &name); err != nil { + return err + } + switch name { + case "OK": + *s = StatusOK + case "INFO": + *s = StatusInfo + case "UNKNOWN", "": + *s = StatusUnknown + case "WARN": + *s = StatusWarn + case "CRIT": + *s = StatusCrit + case "ERROR": + *s = StatusError + default: + return fmt.Errorf("unknown status %q", name) + } + return nil + } + var n int + if err := json.Unmarshal(data, &n); err != nil { + return err + } + *s = Status(n) + return nil +} + // CheckState is the result of evaluating a single rule. type CheckState struct { Status Status `json:"status"`