diff --git a/go.mod b/go.mod index 69e39b1e..cd3e8467 100644 --- a/go.mod +++ b/go.mod @@ -211,6 +211,7 @@ require ( k8s.io/klog/v2 v2.110.1 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect lukechampine.com/blake3 v1.3.0 // indirect + pgregory.net/rapid v1.1.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 1daacfe6..ebd0f19c 100644 --- a/go.sum +++ b/go.sum @@ -1355,6 +1355,8 @@ modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6 modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= +pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/pkg/data/types.go b/pkg/data/data.go similarity index 87% rename from pkg/data/types.go rename to pkg/data/data.go index 048bcdef..5af6efba 100644 --- a/pkg/data/types.go +++ b/pkg/data/data.go @@ -1,6 +1,8 @@ package data import ( + "encoding/json" + "github.com/lilypad-tech/lilypad/pkg/data/bacalhau" ) @@ -76,11 +78,62 @@ type Result struct { ID string `json:"id"` DealID string `json:"deal_id"` // the CID of the actual results - DataID string `json:"results_id"` + DataID string `json:"data_id"` Error string `json:"error"` InstructionCount uint64 `json:"instruction_count"` } +// Provides compatibility for older clients that expect the results_id field +func (r Result) MarshalJSON() ([]byte, error) { + // TODO(bgins) Remove when older clients have been deprecated + + // Create an auxiliary type to avoid recursively calling json.Marshal + // https://stackoverflow.com/a/23046869 + type ResultAux Result + + // Add results_id field to the existing Result fields and marshal + return json.Marshal(struct { + ResultAux + ResultsID string `json:"results_id"` + }{ + ResultAux: ResultAux(r), + ResultsID: r.DataID, + }) +} + +// Provides compatibility for newer clients that expect the data_id field +func (r *Result) UnmarshalJSON(data []byte) error { + // TODO(bgins) Remove when older clients have been deprecated + + // Create an auxiliary type to avoid recursively calling json.Unmarshal + // https://stackoverflow.com/a/52433660 + type ResultAux Result + + // Unmarshal into auxiliary type + var aux ResultAux + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + // Cast the auxilliary type to Result + *r = Result(aux) + + // Create a raw map to capture the results_id field + var rawMap map[string]interface{} + if err := json.Unmarshal(data, &rawMap); err != nil { + return err + } + + // Check if results_id exists and assign it to DataID if so + if resultsID, ok := rawMap["results_id"]; ok { + if strID, ok := resultsID.(string); ok { + r.DataID = strID + } + } + + return nil +} + // MarketPrice means - get me the best deal // job creators will do this by default i.e. "just buy me the cheapest" // FixedPrice means - take it or leave it diff --git a/pkg/data/data_test.go b/pkg/data/data_test.go new file mode 100644 index 00000000..9449f705 --- /dev/null +++ b/pkg/data/data_test.go @@ -0,0 +1,106 @@ +//go:build unit + +package data + +import ( + "encoding/json" + "testing" + + "github.com/mr-tron/base58" + "pgregory.net/rapid" +) + +func TestResultJSONRoundtrip(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + // Generate a random Result + original := Result{ + ID: generateCID(t), + DealID: generateCID(t), + DataID: generateCID(t), + } + + // Test marshaling to JSON + data, err := json.Marshal(original) + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + + // Test unmarshaling back + var decoded Result + err = json.Unmarshal(data, &decoded) + if err != nil { + t.Fatalf("Unmarshal failed: %v", err) + } + + // Verify the roundtrip preserved values + if original != decoded { + t.Errorf("Roundtrip failed: got %v, want %v", decoded, original) + } + + // Verify both fields exist in JSON + var rawData map[string]interface{} + err = json.Unmarshal(data, &rawData) + if err != nil { + t.Fatalf("Raw unmarshal failed: %v", err) + } + + // Check both data_id and results_id exist and match + dataID, hasDataID := rawData["data_id"] + resultsID, hasResultsID := rawData["results_id"] + + if !hasDataID { + t.Error("data_id field missing") + } + if !hasResultsID { + t.Error("results_id field missing") + } + if dataID != resultsID { + t.Errorf("data_id and results_id mismatch: %v != %v", dataID, resultsID) + } + }) +} + +func TestResultJSONBackwardsCompatibility(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + expectedDataID := generateCID(t) + + // Test old client format (results_id) + oldClientJSON := map[string]interface{}{ + "id": generateCID(t), + "results_id": expectedDataID, + } + oldClientData, _ := json.Marshal(oldClientJSON) + + var resultFromOld Result + err := json.Unmarshal(oldClientData, &resultFromOld) + if err != nil { + t.Fatalf("Unmarshal of old format failed: %v", err) + } + if resultFromOld.DataID != expectedDataID { + t.Errorf("Old format: got DataID %v, want %v", resultFromOld.DataID, expectedDataID) + } + + // Test new client format (data_id) + newClientJSON := map[string]interface{}{ + "id": generateCID(t), + "data_id": expectedDataID, + } + newClientData, _ := json.Marshal(newClientJSON) + + var resultFromNew Result + err = json.Unmarshal(newClientData, &resultFromNew) + if err != nil { + t.Fatalf("Unmarshal of new format failed: %v", err) + } + if resultFromNew.DataID != expectedDataID { + t.Errorf("New format: got DataID %v, want %v", resultFromNew.DataID, expectedDataID) + } + }) +} + +// Generators + +func generateCID(t *rapid.T) string { + bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "bytes") + return "Qm" + base58.Encode(bytes) +}