Require candidate decoders for artifact codecs
This commit is contained in:
@@ -24,12 +24,13 @@ type codecScore struct {
|
||||
type codecNotesAlias codecNotes
|
||||
|
||||
type testArtifactCodec[T any] struct {
|
||||
kind contracts.ArtifactKind
|
||||
schema contracts.ArtifactSchema
|
||||
mediaType string
|
||||
encodeFunc func(T) ([]byte, error)
|
||||
candidateFunc func(T) ([]byte, error)
|
||||
decodeFunc func([]byte) (T, error)
|
||||
kind contracts.ArtifactKind
|
||||
schema contracts.ArtifactSchema
|
||||
mediaType string
|
||||
encodeFunc func(T) ([]byte, error)
|
||||
candidateFunc func(T) ([]byte, error)
|
||||
decodeFunc func([]byte) (T, error)
|
||||
candidateDecodeFunc func([]byte) (T, error)
|
||||
}
|
||||
|
||||
func (c testArtifactCodec[T]) Kind() contracts.ArtifactKind { return c.kind }
|
||||
@@ -43,8 +44,15 @@ func (c testArtifactCodec[T]) EncodeCandidate(value T) ([]byte, error) {
|
||||
}
|
||||
func (c testArtifactCodec[T]) Encode(value T) ([]byte, error) { return c.encodeFunc(value) }
|
||||
func (c testArtifactCodec[T]) Decode(content []byte) (T, error) { return c.decodeFunc(content) }
|
||||
func (c testArtifactCodec[T]) DecodeCandidate(content []byte) (T, error) {
|
||||
if c.candidateDecodeFunc != nil {
|
||||
return c.candidateDecodeFunc(content)
|
||||
}
|
||||
return c.decodeFunc(content)
|
||||
}
|
||||
|
||||
var _ contracts.ArtifactCodec[codecNotes] = testArtifactCodec[codecNotes]{}
|
||||
var _ contracts.CandidateArtifactCodec[codecNotes] = testArtifactCodec[codecNotes]{}
|
||||
|
||||
func TestArtifactCodecRegistryStoresHeterogeneousExactTypes(t *testing.T) {
|
||||
registry := NewArtifactCodecRegistry()
|
||||
@@ -138,6 +146,44 @@ func TestArtifactCodecRegistryKeepsCandidateAndFinalEncodingDistinct(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactCodecRegistryKeepsCandidateAndFinalDecodingDistinct(t *testing.T) {
|
||||
candidateCalls, finalCalls := 0, 0
|
||||
codec := notesCodec()
|
||||
codec.candidateDecodeFunc = func([]byte) (codecNotes, error) {
|
||||
candidateCalls++
|
||||
return codecNotes{Items: []string{"candidate"}}, nil
|
||||
}
|
||||
codec.decodeFunc = func([]byte) (codecNotes, error) {
|
||||
finalCalls++
|
||||
return codecNotes{Items: []string{"final"}}, nil
|
||||
}
|
||||
registry := NewArtifactCodecRegistry()
|
||||
if err := RegisterArtifactCodec(registry, codec); err != nil {
|
||||
t.Fatalf("RegisterArtifactCodec() error = %v, want nil", err)
|
||||
}
|
||||
entry, _, err := registry.entry(codec.kind)
|
||||
if err != nil {
|
||||
t.Fatalf("entry() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
candidate, err := entry.decodeCandidate([]byte(`{"items":["one"]}`))
|
||||
if err != nil {
|
||||
t.Fatalf("candidate decode error = %v", err)
|
||||
}
|
||||
candidateWant := codecNotes{Items: []string{"candidate"}}
|
||||
if !reflect.DeepEqual(candidate, candidateWant) || candidateCalls != 1 || finalCalls != 0 {
|
||||
t.Fatalf("candidate decode = %#v, calls = candidate %d, final %d", candidate, candidateCalls, finalCalls)
|
||||
}
|
||||
decoded, err := entry.decode([]byte(`{"items":["one"]}`))
|
||||
if err != nil {
|
||||
t.Fatalf("final decode error = %v", err)
|
||||
}
|
||||
finalWant := codecNotes{Items: []string{"final"}}
|
||||
if !reflect.DeepEqual(decoded, finalWant) || candidateCalls != 1 || finalCalls != 1 {
|
||||
t.Fatalf("final decode = %#v, calls = candidate %d, final %d", decoded, candidateCalls, finalCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactCodecRegistryStoresValidatedSchemaMetadata(t *testing.T) {
|
||||
registry := NewArtifactCodecRegistry()
|
||||
codec := notesCodec()
|
||||
@@ -267,6 +313,28 @@ func TestArtifactCodecRegistryWrapsEncodeFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactCodecRegistryWrapsCandidateDecodeFailure(t *testing.T) {
|
||||
cause := errors.New("cannot decode candidate notes")
|
||||
codec := notesCodec()
|
||||
codec.candidateDecodeFunc = func([]byte) (codecNotes, error) {
|
||||
return codecNotes{}, cause
|
||||
}
|
||||
registry := NewArtifactCodecRegistry()
|
||||
if err := RegisterArtifactCodec(registry, codec); err != nil {
|
||||
t.Fatalf("RegisterArtifactCodec() error = %v, want nil", err)
|
||||
}
|
||||
entry, _, err := registry.entry(codec.kind)
|
||||
if err != nil {
|
||||
t.Fatalf("entry() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
_, err = entry.decodeCandidate([]byte(`{"items":["one"]}`))
|
||||
var operationErr *ArtifactCodecOperationError
|
||||
if !errors.As(err, &operationErr) || operationErr.Operation != "decode candidate" || !errors.Is(err, cause) {
|
||||
t.Fatalf("candidate decode error = %T %v, want typed wrapping error", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactCodecRegistryClonesCodecBytes(t *testing.T) {
|
||||
shared := []byte(`{"items":["one"]}`)
|
||||
codec := notesCodec()
|
||||
@@ -275,6 +343,10 @@ func TestArtifactCodecRegistryClonesCodecBytes(t *testing.T) {
|
||||
content[0] = '['
|
||||
return codecNotes{Items: []string{"one"}}, nil
|
||||
}
|
||||
codec.candidateDecodeFunc = func(content []byte) (codecNotes, error) {
|
||||
content[0] = '['
|
||||
return codecNotes{Items: []string{"candidate"}}, nil
|
||||
}
|
||||
registry := NewArtifactCodecRegistry()
|
||||
if err := RegisterArtifactCodec(registry, codec); err != nil {
|
||||
t.Fatalf("RegisterArtifactCodec() error = %v, want nil", err)
|
||||
@@ -295,6 +367,18 @@ func TestArtifactCodecRegistryClonesCodecBytes(t *testing.T) {
|
||||
if !bytes.Equal(artifact.Content, before) {
|
||||
t.Fatalf("serialized content changed during decode: %q", artifact.Content)
|
||||
}
|
||||
candidateContent := []byte(`{"items":["candidate"]}`)
|
||||
candidateBefore := append([]byte(nil), candidateContent...)
|
||||
entry, _, err := registry.entry("test/notes")
|
||||
if err != nil {
|
||||
t.Fatalf("entry() error = %v, want nil", err)
|
||||
}
|
||||
if _, err := entry.decodeCandidate(candidateContent); err != nil {
|
||||
t.Fatalf("candidate decode error = %v, want nil", err)
|
||||
}
|
||||
if !bytes.Equal(candidateContent, candidateBefore) {
|
||||
t.Fatalf("candidate content changed during decode: %q", candidateContent)
|
||||
}
|
||||
}
|
||||
|
||||
func notesCodec() testArtifactCodec[codecNotes] {
|
||||
|
||||
Reference in New Issue
Block a user