Require candidate decoders for artifact codecs
This commit is contained in:
@@ -400,6 +400,9 @@ func (referenceContractCodecA) Encode(stateTestArtifact) ([]byte, error) {
|
|||||||
func (referenceContractCodecA) Decode([]byte) (stateTestArtifact, error) {
|
func (referenceContractCodecA) Decode([]byte) (stateTestArtifact, error) {
|
||||||
return stateTestArtifact{Value: "ok"}, nil
|
return stateTestArtifact{Value: "ok"}, nil
|
||||||
}
|
}
|
||||||
|
func (codec referenceContractCodecA) DecodeCandidate(content []byte) (stateTestArtifact, error) {
|
||||||
|
return codec.Decode(content)
|
||||||
|
}
|
||||||
|
|
||||||
func (referenceContractCodecB) Kind() contracts.ArtifactKind { return referenceContractKindBeta }
|
func (referenceContractCodecB) Kind() contracts.ArtifactKind { return referenceContractKindBeta }
|
||||||
func (referenceContractCodecB) Schema() contracts.ArtifactSchema {
|
func (referenceContractCodecB) Schema() contracts.ArtifactSchema {
|
||||||
@@ -415,6 +418,9 @@ func (referenceContractCodecB) Encode(stateTestArtifact) ([]byte, error) {
|
|||||||
func (referenceContractCodecB) Decode([]byte) (stateTestArtifact, error) {
|
func (referenceContractCodecB) Decode([]byte) (stateTestArtifact, error) {
|
||||||
return stateTestArtifact{Value: "ok"}, nil
|
return stateTestArtifact{Value: "ok"}, nil
|
||||||
}
|
}
|
||||||
|
func (codec referenceContractCodecB) DecodeCandidate(content []byte) (stateTestArtifact, error) {
|
||||||
|
return codec.Decode(content)
|
||||||
|
}
|
||||||
|
|
||||||
func referenceContractLane(t *testing.T, resolved pipeline.ResolvedPipeline, id string) pipeline.ResolvedArtifactLane {
|
func referenceContractLane(t *testing.T, resolved pipeline.ResolvedPipeline, id string) pipeline.ResolvedArtifactLane {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|||||||
@@ -955,6 +955,9 @@ func (stateTestCodec) Encode(v stateTestArtifact) ([]byte, error) {
|
|||||||
func (stateTestCodec) Decode([]byte) (stateTestArtifact, error) {
|
func (stateTestCodec) Decode([]byte) (stateTestArtifact, error) {
|
||||||
return stateTestArtifact{Value: "ok"}, nil
|
return stateTestArtifact{Value: "ok"}, nil
|
||||||
}
|
}
|
||||||
|
func (codec stateTestCodec) DecodeCandidate(content []byte) (stateTestArtifact, error) {
|
||||||
|
return codec.Decode(content)
|
||||||
|
}
|
||||||
|
|
||||||
type stateTestExtractor struct{ harness *stateTestHarness }
|
type stateTestExtractor struct{ harness *stateTestHarness }
|
||||||
|
|
||||||
|
|||||||
@@ -427,6 +427,10 @@ func (effectiveCodec) Decode(content []byte) (effectiveArtifact, error) {
|
|||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (codec effectiveCodec) DecodeCandidate(content []byte) (effectiveArtifact, error) {
|
||||||
|
return codec.Decode(content)
|
||||||
|
}
|
||||||
|
|
||||||
type effectiveInput struct{ key string }
|
type effectiveInput struct{ key string }
|
||||||
|
|
||||||
func (m effectiveInput) Key() string { return m.key }
|
func (m effectiveInput) Key() string { return m.key }
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ type artifactCodecEntry struct {
|
|||||||
encodeCandidate func(any) ([]byte, error)
|
encodeCandidate func(any) ([]byte, error)
|
||||||
metadata func(any) (map[string]any, error)
|
metadata func(any) (map[string]any, error)
|
||||||
decode func([]byte) (any, error)
|
decode func([]byte) (any, error)
|
||||||
|
decodeCandidate func([]byte) (any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewArtifactCodecRegistry() *ArtifactCodecRegistry {
|
func NewArtifactCodecRegistry() *ArtifactCodecRegistry {
|
||||||
@@ -77,7 +78,7 @@ func NewArtifactCodecRegistry() *ArtifactCodecRegistry {
|
|||||||
|
|
||||||
// RegisterArtifactCodec registers one codec for T. The concrete type is kept
|
// RegisterArtifactCodec registers one codec for T. The concrete type is kept
|
||||||
// private and checked at every erased encode boundary.
|
// private and checked at every erased encode boundary.
|
||||||
func RegisterArtifactCodec[T any](registry *ArtifactCodecRegistry, codec contracts.ArtifactCodec[T]) error {
|
func RegisterArtifactCodec[T any](registry *ArtifactCodecRegistry, codec contracts.CandidateArtifactCodec[T]) error {
|
||||||
if registry == nil {
|
if registry == nil {
|
||||||
return fmt.Errorf("artifact codec registry must not be nil")
|
return fmt.Errorf("artifact codec registry must not be nil")
|
||||||
}
|
}
|
||||||
@@ -119,6 +120,13 @@ func RegisterArtifactCodec[T any](registry *ArtifactCodecRegistry, codec contrac
|
|||||||
}
|
}
|
||||||
return decoded, nil
|
return decoded, nil
|
||||||
},
|
},
|
||||||
|
decodeCandidate: func(content []byte) (any, error) {
|
||||||
|
decoded, err := codec.DecodeCandidate(append([]byte(nil), content...))
|
||||||
|
if err != nil {
|
||||||
|
return nil, &ArtifactCodecOperationError{Operation: "decode candidate", Kind: spec.Kind, Err: err}
|
||||||
|
}
|
||||||
|
return decoded, nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
entry.encodeCandidate = func(value any) ([]byte, error) {
|
entry.encodeCandidate = func(value any) ([]byte, error) {
|
||||||
typed, err := exactTypedValue[T]("encode candidate artifact", value)
|
typed, err := exactTypedValue[T]("encode candidate artifact", value)
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ type codecScore struct {
|
|||||||
type codecNotesAlias codecNotes
|
type codecNotesAlias codecNotes
|
||||||
|
|
||||||
type testArtifactCodec[T any] struct {
|
type testArtifactCodec[T any] struct {
|
||||||
kind contracts.ArtifactKind
|
kind contracts.ArtifactKind
|
||||||
schema contracts.ArtifactSchema
|
schema contracts.ArtifactSchema
|
||||||
mediaType string
|
mediaType string
|
||||||
encodeFunc func(T) ([]byte, error)
|
encodeFunc func(T) ([]byte, error)
|
||||||
candidateFunc func(T) ([]byte, error)
|
candidateFunc func(T) ([]byte, error)
|
||||||
decodeFunc func([]byte) (T, error)
|
decodeFunc func([]byte) (T, error)
|
||||||
|
candidateDecodeFunc func([]byte) (T, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c testArtifactCodec[T]) Kind() contracts.ArtifactKind { return c.kind }
|
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]) 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]) 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.ArtifactCodec[codecNotes] = testArtifactCodec[codecNotes]{}
|
||||||
|
var _ contracts.CandidateArtifactCodec[codecNotes] = testArtifactCodec[codecNotes]{}
|
||||||
|
|
||||||
func TestArtifactCodecRegistryStoresHeterogeneousExactTypes(t *testing.T) {
|
func TestArtifactCodecRegistryStoresHeterogeneousExactTypes(t *testing.T) {
|
||||||
registry := NewArtifactCodecRegistry()
|
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) {
|
func TestArtifactCodecRegistryStoresValidatedSchemaMetadata(t *testing.T) {
|
||||||
registry := NewArtifactCodecRegistry()
|
registry := NewArtifactCodecRegistry()
|
||||||
codec := notesCodec()
|
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) {
|
func TestArtifactCodecRegistryClonesCodecBytes(t *testing.T) {
|
||||||
shared := []byte(`{"items":["one"]}`)
|
shared := []byte(`{"items":["one"]}`)
|
||||||
codec := notesCodec()
|
codec := notesCodec()
|
||||||
@@ -275,6 +343,10 @@ func TestArtifactCodecRegistryClonesCodecBytes(t *testing.T) {
|
|||||||
content[0] = '['
|
content[0] = '['
|
||||||
return codecNotes{Items: []string{"one"}}, nil
|
return codecNotes{Items: []string{"one"}}, nil
|
||||||
}
|
}
|
||||||
|
codec.candidateDecodeFunc = func(content []byte) (codecNotes, error) {
|
||||||
|
content[0] = '['
|
||||||
|
return codecNotes{Items: []string{"candidate"}}, nil
|
||||||
|
}
|
||||||
registry := NewArtifactCodecRegistry()
|
registry := NewArtifactCodecRegistry()
|
||||||
if err := RegisterArtifactCodec(registry, codec); err != nil {
|
if err := RegisterArtifactCodec(registry, codec); err != nil {
|
||||||
t.Fatalf("RegisterArtifactCodec() error = %v, want nil", err)
|
t.Fatalf("RegisterArtifactCodec() error = %v, want nil", err)
|
||||||
@@ -295,6 +367,18 @@ func TestArtifactCodecRegistryClonesCodecBytes(t *testing.T) {
|
|||||||
if !bytes.Equal(artifact.Content, before) {
|
if !bytes.Equal(artifact.Content, before) {
|
||||||
t.Fatalf("serialized content changed during decode: %q", artifact.Content)
|
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] {
|
func notesCodec() testArtifactCodec[codecNotes] {
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ func (*observedNotesCodec) Decode(content []byte) (codecNotes, error) {
|
|||||||
return value, json.Unmarshal(content, &value)
|
return value, json.Unmarshal(content, &value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *observedNotesCodec) DecodeCandidate(content []byte) (codecNotes, error) {
|
||||||
|
return c.Decode(content)
|
||||||
|
}
|
||||||
|
|
||||||
func firstNote(value codecNotes) string {
|
func firstNote(value codecNotes) string {
|
||||||
if len(value.Items) == 0 {
|
if len(value.Items) == 0 {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -500,7 +500,7 @@ func registriesFromModuleCatalog(catalog ModuleCatalog) Registries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustRegisterArtifactCodec[T any](t *testing.T, registry *ArtifactCodecRegistry, codec contracts.ArtifactCodec[T]) {
|
func mustRegisterArtifactCodec[T any](t *testing.T, registry *ArtifactCodecRegistry, codec contracts.CandidateArtifactCodec[T]) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if err := RegisterArtifactCodec(registry, codec); err != nil {
|
if err := RegisterArtifactCodec(registry, codec); err != nil {
|
||||||
t.Fatalf("RegisterArtifactCodec() error = %v", err)
|
t.Fatalf("RegisterArtifactCodec() error = %v", err)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const (
|
|||||||
var schemaAssets embed.FS
|
var schemaAssets embed.FS
|
||||||
|
|
||||||
var _ contracts.ArtifactCodec[dnd.CombatTurnList] = (*Codec)(nil)
|
var _ contracts.ArtifactCodec[dnd.CombatTurnList] = (*Codec)(nil)
|
||||||
|
var _ contracts.CandidateArtifactCodec[dnd.CombatTurnList] = (*Codec)(nil)
|
||||||
|
|
||||||
type Codec struct{}
|
type Codec struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const (
|
|||||||
var schemaAssets embed.FS
|
var schemaAssets embed.FS
|
||||||
|
|
||||||
var _ contracts.ArtifactCodec[dnd.EnemyEventList] = (*Codec)(nil)
|
var _ contracts.ArtifactCodec[dnd.EnemyEventList] = (*Codec)(nil)
|
||||||
|
var _ contracts.CandidateArtifactCodec[dnd.EnemyEventList] = (*Codec)(nil)
|
||||||
|
|
||||||
type Codec struct{}
|
type Codec struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -82,13 +82,16 @@ func TestCodecDefensivelyOwnsValuesAndDefersSemanticValidation(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
decoded, err := codec.Decode(content)
|
decoded, err := codec.DecodeCandidate(content)
|
||||||
if err != nil || !reflect.DeepEqual(decoded, candidate) {
|
if err != nil || !reflect.DeepEqual(decoded, candidate) {
|
||||||
t.Fatalf("Decode() = %#v, %v; want semantic candidate preservation", decoded, err)
|
t.Fatalf("DecodeCandidate() = %#v, %v; want semantic candidate preservation", decoded, err)
|
||||||
}
|
}
|
||||||
decoded.Events[0].SourceRefs[0].SourceID = "changed"
|
decoded.Events[0].SourceRefs[0].SourceID = "changed"
|
||||||
if candidate.Events[0].SourceRefs[0].SourceID != "" {
|
if candidate.Events[0].SourceRefs[0].SourceID != "" {
|
||||||
t.Fatal("Decode() retained caller-owned source references")
|
t.Fatal("DecodeCandidate() retained caller-owned source references")
|
||||||
|
}
|
||||||
|
if decoded, err := codec.Decode(content); err != nil || !reflect.DeepEqual(decoded, candidate) {
|
||||||
|
t.Fatalf("Decode() = %#v, %v; want durable decode behavior", decoded, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
first := codec.Schema()
|
first := codec.Schema()
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const (
|
|||||||
var schemaAssets embed.FS
|
var schemaAssets embed.FS
|
||||||
|
|
||||||
var _ contracts.ArtifactCodec[dnd.SceneDescriptionList] = (*Codec)(nil)
|
var _ contracts.ArtifactCodec[dnd.SceneDescriptionList] = (*Codec)(nil)
|
||||||
|
var _ contracts.CandidateArtifactCodec[dnd.SceneDescriptionList] = (*Codec)(nil)
|
||||||
|
|
||||||
type Codec struct{}
|
type Codec struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const (
|
|||||||
var schemaAssets embed.FS
|
var schemaAssets embed.FS
|
||||||
|
|
||||||
var _ contracts.ArtifactCodec[dnd.SpellList] = (*Codec)(nil)
|
var _ contracts.ArtifactCodec[dnd.SpellList] = (*Codec)(nil)
|
||||||
|
var _ contracts.CandidateArtifactCodec[dnd.SpellList] = (*Codec)(nil)
|
||||||
|
|
||||||
type Codec struct{}
|
type Codec struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,10 @@ func TestCodecEncodesIncompleteCandidateWithoutWeakeningFinalEncoding(t *testing
|
|||||||
if !json.Valid(content) {
|
if !json.Valid(content) {
|
||||||
t.Fatalf("EncodeCandidate() = %q, want JSON", content)
|
t.Fatalf("EncodeCandidate() = %q, want JSON", content)
|
||||||
}
|
}
|
||||||
|
decoded, err := codec.DecodeCandidate(content)
|
||||||
|
if err != nil || !reflect.DeepEqual(decoded, candidate) {
|
||||||
|
t.Fatalf("DecodeCandidate() = %#v, %v; want %#v", decoded, err, candidate)
|
||||||
|
}
|
||||||
result, err := spellshape.New(spellshape.Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.SpellList]{Value: candidate})
|
result, err := spellshape.New(spellshape.Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.SpellList]{Value: candidate})
|
||||||
if err != nil || result.Approved || result.ReasonCode != spellshape.ReasonCode {
|
if err != nil || result.Approved || result.ReasonCode != spellshape.ReasonCode {
|
||||||
t.Fatalf("shape validation = %#v, %v; want candidate rejection", result, err)
|
t.Fatalf("shape validation = %#v, %v; want candidate rejection", result, err)
|
||||||
@@ -121,6 +125,9 @@ func TestCodecEncodesIncompleteCandidateWithoutWeakeningFinalEncoding(t *testing
|
|||||||
if _, err := codec.Encode(candidate); err == nil || !strings.Contains(err.Error(), "spell_casts must be present") {
|
if _, err := codec.Encode(candidate); err == nil || !strings.Contains(err.Error(), "spell_casts must be present") {
|
||||||
t.Fatalf("Encode() error = %v, want strict final shape error", err)
|
t.Fatalf("Encode() error = %v, want strict final shape error", err)
|
||||||
}
|
}
|
||||||
|
if _, err := codec.Decode(content); err == nil || !strings.Contains(err.Error(), "spell_casts must be present") {
|
||||||
|
t.Fatalf("Decode() error = %v, want strict final shape error", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCodecSchemaIsMutationSafe(t *testing.T) {
|
func TestCodecSchemaIsMutationSafe(t *testing.T) {
|
||||||
|
|||||||
@@ -164,3 +164,7 @@ func (seriatimArtifactCodec) Decode(content []byte) (seriatimArtifact, error) {
|
|||||||
err := json.Unmarshal(content, &value)
|
err := json.Unmarshal(content, &value)
|
||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (codec seriatimArtifactCodec) DecodeCandidate(content []byte) (seriatimArtifact, error) {
|
||||||
|
return codec.Decode(content)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user