Add typed spell validation strategies
This commit is contained in:
@@ -174,6 +174,30 @@ func TestMergeRejectsInvalidJSONAndNonJSONMediaTypes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypedMergeUsesRequestOrderForReusableValueType(t *testing.T) {
|
||||
type notes struct{ Values []string }
|
||||
merger, err := NewTyped(func(values []notes) (notes, error) {
|
||||
var combined notes
|
||||
for _, value := range values {
|
||||
combined.Values = append(combined.Values, value.Values...)
|
||||
}
|
||||
return combined, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewTyped() error = %v", err)
|
||||
}
|
||||
result, err := merger.Merge(context.Background(), contracts.TypedMergeRequest[notes]{ExtractOutputs: []contracts.ExtractArtifact[notes]{
|
||||
{ChunkIndex: 4, Value: notes{Values: []string{"first"}}},
|
||||
{ChunkIndex: 1, Value: notes{Values: []string{"second"}}},
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatalf("Merge() error = %v", err)
|
||||
}
|
||||
if got := result.Value.Values; !reflect.DeepEqual(got, []string{"first", "second"}) {
|
||||
t.Fatalf("Values = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func extractOutput(chunkID string, chunkIndex int, content string) contracts.ExtractOutput {
|
||||
return contracts.ExtractOutput{
|
||||
LaneID: "events",
|
||||
|
||||
62
internal/modules/generic/merge/appendorder/typed.go
Normal file
62
internal/modules/generic/merge/appendorder/typed.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package appendorder
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||
)
|
||||
|
||||
// CombineFunc combines values in the source-chunk order supplied by the
|
||||
// framework. Implementations must not reorder the slice.
|
||||
type CombineFunc[T any] func([]T) (T, error)
|
||||
|
||||
type TypedMerger[T any] struct {
|
||||
combine CombineFunc[T]
|
||||
}
|
||||
|
||||
func NewTyped[T any](combine CombineFunc[T]) (*TypedMerger[T], error) {
|
||||
if combine == nil {
|
||||
return nil, mergerErrorf("combine function must not be nil")
|
||||
}
|
||||
return &TypedMerger[T]{combine: combine}, nil
|
||||
}
|
||||
|
||||
func (m *TypedMerger[T]) Key() string { return Key }
|
||||
|
||||
func (m *TypedMerger[T]) Merge(ctx context.Context, req contracts.TypedMergeRequest[T]) (contracts.TypedMergeResult[T], error) {
|
||||
if m == nil || m.combine == nil {
|
||||
return contracts.TypedMergeResult[T]{}, mergerErrorf("merger must not be nil")
|
||||
}
|
||||
if ctx == nil {
|
||||
return contracts.TypedMergeResult[T]{}, mergerErrorf("context must not be nil")
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return contracts.TypedMergeResult[T]{}, mergerErrorf("context error before merge: %w", err)
|
||||
}
|
||||
values := make([]T, len(req.ExtractOutputs))
|
||||
for i, output := range req.ExtractOutputs {
|
||||
values[i] = output.Value
|
||||
}
|
||||
value, err := m.combine(values)
|
||||
if err != nil {
|
||||
return contracts.TypedMergeResult[T]{}, mergerErrorf("combine values: %w", err)
|
||||
}
|
||||
return contracts.TypedMergeResult[T]{Value: value}, nil
|
||||
}
|
||||
|
||||
func TypedModuleSpec(kind contracts.ArtifactKind) pipeline.ModuleSpec {
|
||||
spec := ModuleSpec()
|
||||
spec.ArtifactKind = kind
|
||||
return spec
|
||||
}
|
||||
|
||||
func RegisterTyped[T any](registry *pipeline.MergerRegistry, kind contracts.ArtifactKind, combine CombineFunc[T]) error {
|
||||
validateOptions := func(options map[string]any) error { return pipeline.RejectUnknownOptions(options) }
|
||||
return pipeline.RegisterMergerBuilder(registry, TypedModuleSpec(kind), validateOptions, func(request pipeline.BuildRequest) (contracts.Merger[T], error) {
|
||||
if err := pipeline.RejectUnknownOptions(request.Options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewTyped(combine)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user