diff --git a/contracts/go/clients/meshdeliverycheckerclient/client_test.go b/contracts/go/clients/meshdeliverycheckerclient/client_test.go index c9ef2850..1a8334c6 100644 --- a/contracts/go/clients/meshdeliverycheckerclient/client_test.go +++ b/contracts/go/clients/meshdeliverycheckerclient/client_test.go @@ -149,3 +149,46 @@ func TestVerdictKeyAsGoDeliveryWroteIt(t *testing.T) { t.Error("a short hash keys") } } + +// The codecs write a value at the kept values' version after its rules and read it back through them. +func TestTheCodecsWriteAtTheVersionAndReadBackThroughTheRules(t *testing.T) { + raw, err := EncodeVerdict(passing()) + if err != nil { + t.Fatal(err) + } + if got, err := DecodeVerdict(raw); err != nil || got.V == 0 || got.Group != "g" { + t.Errorf("verdict read back = %+v, %v", got, err) + } + bad := passing() + bad.Heads = "x" + if _, err := EncodeVerdict(bad); err == nil { + t.Error("a verdict whose heads are no hash is written") + } + if _, err := DecodeVerdict([]byte(`{"v":99,"group":"g","heads":"0123456789ab","state":"running"}`)); err == nil { + t.Error("a verdict of an unknown version is read") + } + k := checker.KeptByLevelDigestValue{State: VerdictPass, Repository: "novox/mesh-sdk", Package: "./go/keys", Level: LevelUnit, Toolchain: "1.26.0", At: "2026-11-05T10:00:00Z"} + raw, err = EncodeKept(k) + if err != nil { + t.Fatal(err) + } + if got, err := DecodeKept(raw); err != nil || got.Package != k.Package { + t.Errorf("kept read back = %+v, %v", got, err) + } + k.State = VerdictFail + if _, err := EncodeKept(k); err == nil { + t.Error("a failure is kept") + } + p := checker.PreviewsByRepositoryHeadTrunkValue{Repository: "novox/a", Head: "h1", Trunk: "t1", Conflicts: []string{"f.go"}} + raw, err = EncodePreview(p) + if err != nil { + t.Fatal(err) + } + if got, err := DecodePreview(raw); err != nil || got.Head != "h1" { + t.Errorf("preview read back = %+v, %v", got, err) + } + p.Conflicts = nil + if _, err := EncodePreview(p); err == nil { + t.Error("a preview with no merge commit and no conflict is written") + } +} diff --git a/contracts/go/clients/meshdeliverycheckerclient/codec.go b/contracts/go/clients/meshdeliverycheckerclient/codec.go new file mode 100644 index 00000000..2b6b2b0c --- /dev/null +++ b/contracts/go/clients/meshdeliverycheckerclient/codec.go @@ -0,0 +1,87 @@ +package meshdeliverycheckerclient + +import ( + "encoding/json" + "fmt" + + checker "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-sdk/go/keys" +) + +// The codecs of the three states the checker writes and others read (the view and the gate read verdicts, the indexer reads +// previews, the checker and the tools read kept verdicts): a value is written at the version of the kept values after its rules +// (CheckVerdict, CheckKept, CheckPreview) and read back through the same rules. The checker's owed state is read by no other +// part and keeps its own codec. + +// EncodeVerdict is v as JSON at the kept values' version, refused by CheckVerdict's rules. +func EncodeVerdict(v checker.VerdictsByGroupHeadsValue) ([]byte, error) { + if err := CheckVerdict(v); err != nil { + return nil, err + } + v.V = keys.Version + return json.Marshal(v) +} + +// DecodeVerdict reads a `verdicts` value; it refuses an unknown version (keys.ErrUnknownVersion) and what EncodeVerdict refuses. +func DecodeVerdict(data []byte) (checker.VerdictsByGroupHeadsValue, error) { + var v checker.VerdictsByGroupHeadsValue + if err := decode(data, &v); err != nil { + return v, err + } + if err := CheckVerdict(v); err != nil { + return checker.VerdictsByGroupHeadsValue{}, err + } + return v, nil +} + +// EncodeKept is k as JSON at the kept values' version, refused by CheckKept's rules. +func EncodeKept(k checker.KeptByLevelDigestValue) ([]byte, error) { + if err := CheckKept(k); err != nil { + return nil, err + } + k.V = keys.Version + return json.Marshal(k) +} + +// DecodeKept reads a `kept` value; it refuses an unknown version and what EncodeKept refuses. +func DecodeKept(data []byte) (checker.KeptByLevelDigestValue, error) { + var k checker.KeptByLevelDigestValue + if err := decode(data, &k); err != nil { + return k, err + } + if err := CheckKept(k); err != nil { + return checker.KeptByLevelDigestValue{}, err + } + return k, nil +} + +// EncodePreview is p as JSON at the kept values' version, refused by CheckPreview's rules. +func EncodePreview(p checker.PreviewsByRepositoryHeadTrunkValue) ([]byte, error) { + if err := CheckPreview(p); err != nil { + return nil, err + } + p.V = keys.Version + return json.Marshal(p) +} + +// DecodePreview reads a `previews` value; it refuses an unknown version and what EncodePreview refuses. +func DecodePreview(data []byte) (checker.PreviewsByRepositoryHeadTrunkValue, error) { + var p checker.PreviewsByRepositoryHeadTrunkValue + if err := decode(data, &p); err != nil { + return p, err + } + if err := CheckPreview(p); err != nil { + return checker.PreviewsByRepositoryHeadTrunkValue{}, err + } + return p, nil +} + +func decode(data []byte, into any) error { + if err := keys.CheckVersion(data); err != nil { + return err + } + if err := json.Unmarshal(data, into); err != nil { + return fmt.Errorf("meshdeliverycheckerclient: value does not fit its shape: %w", err) + } + return nil +}