Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion internal/resp/decoder/decode_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ func decodeArray(reader *bytes.Reader) (resp_value.Value, error) {
}
}

if length < 0 {
if length == -1 {
return resp_value.NewNilValue(), nil
}

if length < -1 {
// Ensure error points to the correct byte
reader.Seek(int64(offsetBeforeLength), io.SeekStart)

Expand Down
16 changes: 16 additions & 0 deletions internal/resp/value/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,19 @@ func (v *Value) FormattedString() string {

return ""
}

func (v Value) ToSerializable() interface{} {
switch v.Type {
case BULK_STRING:
return v.String()
case ARRAY:
arr := v.Array()
result := make([]interface{}, len(arr))
for i, elem := range arr {
result[i] = elem.ToSerializable()
}
return result
default:
return v.String()
}
}
82 changes: 82 additions & 0 deletions internal/resp_assertions/xread_response_assertion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package resp_assertions

import (
"encoding/json"
"fmt"

resp_value "github.com/codecrafters-io/redis-tester/internal/resp/value"
)

type StreamEntry struct {
Id string
FieldValuePairs [][]string
}

type StreamResponse struct {
Key string
Entries []StreamEntry
}

type XReadResponseAssertion struct {
ExpectedStreamResponses []StreamResponse
}

func NewXReadResponseAssertion(expectedValue []StreamResponse) RESPAssertion {
return XReadResponseAssertion{ExpectedStreamResponses: expectedValue}
}

func (a XReadResponseAssertion) Run(value resp_value.Value) error {
if value.Type != resp_value.ARRAY {
return fmt.Errorf("Expected array, got %s", value.Type)
}

expected := a.buildExpected().ToSerializable()
actual := value.ToSerializable()

expectedJSON, err := json.MarshalIndent(expected, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal expected value: %w", err)
}
actualJSON, err := json.MarshalIndent(actual, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal actual value: %w", err)
}

if string(expectedJSON) != string(actualJSON) {
return fmt.Errorf("XREAD response mismatch:\nExpected:\n%s\nGot:\n%s", expectedJSON, actualJSON)
}

return nil
}

func (a XReadResponseAssertion) buildExpected() resp_value.Value {
streams := make([]resp_value.Value, len(a.ExpectedStreamResponses))
for i, stream := range a.ExpectedStreamResponses {
streams[i] = stream.toRESPValue()
}
return resp_value.NewArrayValue(streams)
}

func (s StreamResponse) toRESPValue() resp_value.Value {
entries := make([]resp_value.Value, len(s.Entries))
for i, entry := range s.Entries {
entries[i] = entry.toRESPValue()
}
return resp_value.NewArrayValue([]resp_value.Value{
resp_value.NewBulkStringValue(s.Key),
resp_value.NewArrayValue(entries),
})
}

func (e StreamEntry) toRESPValue() resp_value.Value {
var fieldValues []resp_value.Value
for _, pair := range e.FieldValuePairs {
for _, v := range pair {
fieldValues = append(fieldValues, resp_value.NewBulkStringValue(v))
}
}
return resp_value.NewArrayValue([]resp_value.Value{
resp_value.NewBulkStringValue(e.Id),
resp_value.NewArrayValue(fieldValues),
})
}
Loading