Introduce typed D&D spell artifacts
This commit is contained in:
@@ -23,6 +23,7 @@ type typedExtractorEntry struct {
|
||||
valueType reflect.Type
|
||||
validateOptions OptionValidator
|
||||
builder func(BuildRequest) (any, error)
|
||||
rawBuilder LegacyRawExtractorBuilder
|
||||
}
|
||||
|
||||
func NewExtractorRegistry() *ExtractorRegistry {
|
||||
@@ -92,6 +93,20 @@ func RegisterExtractor[T any](registry *ExtractorRegistry, spec ModuleSpec, cons
|
||||
}
|
||||
|
||||
func RegisterExtractorBuilder[T any](registry *ExtractorRegistry, spec ModuleSpec, validateOptions OptionValidator, builder func(BuildRequest) (contracts.Extractor[T], error)) error {
|
||||
return registerExtractorBuilder(registry, spec, validateOptions, builder, nil)
|
||||
}
|
||||
|
||||
// RegisterExtractorBuilderWithRawAdapter registers a typed extractor while a
|
||||
// raw downstream remains in use. Resolution selects the adapter until the
|
||||
// registration is replaced with the typed-only builder.
|
||||
func RegisterExtractorBuilderWithRawAdapter[T any](registry *ExtractorRegistry, spec ModuleSpec, validateOptions OptionValidator, builder func(BuildRequest) (contracts.Extractor[T], error), rawBuilder LegacyRawExtractorBuilder) error {
|
||||
if rawBuilder == nil {
|
||||
return fmt.Errorf("extractor raw adapter builder for %q must not be nil", strings.TrimSpace(spec.Key))
|
||||
}
|
||||
return registerExtractorBuilder(registry, spec, validateOptions, builder, rawBuilder)
|
||||
}
|
||||
|
||||
func registerExtractorBuilder[T any](registry *ExtractorRegistry, spec ModuleSpec, validateOptions OptionValidator, builder func(BuildRequest) (contracts.Extractor[T], error), rawBuilder LegacyRawExtractorBuilder) error {
|
||||
if registry == nil {
|
||||
return fmt.Errorf("extractor registry must not be nil")
|
||||
}
|
||||
@@ -118,6 +133,7 @@ func RegisterExtractorBuilder[T any](registry *ExtractorRegistry, spec ModuleSpe
|
||||
builder: func(request BuildRequest) (any, error) {
|
||||
return builder(cloneBuildRequest(request))
|
||||
},
|
||||
rawBuilder: rawBuilder,
|
||||
}
|
||||
if registry.typedEntries == nil {
|
||||
registry.typedEntries = make(map[string]typedExtractorEntry)
|
||||
@@ -143,6 +159,12 @@ func (r *ExtractorRegistry) BuildLegacyRawWithRequest(key string, request BuildR
|
||||
return nil, fmt.Errorf("extractor key must not be empty")
|
||||
}
|
||||
builder, ok := r.legacyBuilders[normalizedKey]
|
||||
if !ok {
|
||||
if entry, typedOK := r.typedEntries[normalizedKey]; typedOK && entry.rawBuilder != nil {
|
||||
builder = entry.rawBuilder
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("legacy raw extractor %q is not registered", normalizedKey)
|
||||
}
|
||||
@@ -193,6 +215,11 @@ func (r *ExtractorRegistry) typedEntry(key string) (typedExtractorEntry, bool) {
|
||||
return entry, ok
|
||||
}
|
||||
|
||||
func (r *ExtractorRegistry) usesRawAdapter(key string) bool {
|
||||
entry, ok := r.typedEntry(key)
|
||||
return ok && entry.rawBuilder != nil
|
||||
}
|
||||
|
||||
func (r *ExtractorRegistry) RegisteredKeys() []string {
|
||||
if r == nil {
|
||||
return nil
|
||||
|
||||
@@ -353,10 +353,48 @@ func TestExtractorRegistryBuildRejectsEmptyKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractorRegistryTypedRegistrationCanProvideRawAdapter(t *testing.T) {
|
||||
registry := NewExtractorRegistry()
|
||||
spec := ModuleSpec{Key: "typed-extractor", Stage: StageExtract, ArtifactKind: "test/value"}
|
||||
if err := RegisterExtractorBuilderWithRawAdapter(registry, spec, func(map[string]any) error { return nil },
|
||||
func(BuildRequest) (contracts.Extractor[registryTypedValue], error) {
|
||||
return registryTypedExtractor{key: spec.Key}, nil
|
||||
},
|
||||
func(BuildRequest) (contracts.LegacyRawExtractor, error) {
|
||||
return registryFakeExtractor{key: spec.Key}, nil
|
||||
},
|
||||
); err != nil {
|
||||
t.Fatalf("RegisterExtractorBuilderWithRawAdapter() error = %v", err)
|
||||
}
|
||||
if !registry.usesRawAdapter(spec.Key) {
|
||||
t.Fatal("usesRawAdapter() = false, want true")
|
||||
}
|
||||
if _, ok := registry.typedEntry(spec.Key); !ok {
|
||||
t.Fatal("typedEntry() ok = false, want true")
|
||||
}
|
||||
adapter, err := registry.BuildLegacyRaw(spec.Key)
|
||||
if err != nil || adapter.Key() != spec.Key {
|
||||
t.Fatalf("BuildLegacyRaw() = %#v, %v", adapter, err)
|
||||
}
|
||||
if err := RegisterExtractorBuilderWithRawAdapter[registryTypedValue](NewExtractorRegistry(), spec, func(map[string]any) error { return nil }, nil, nil); err == nil || !strings.Contains(err.Error(), "raw adapter") {
|
||||
t.Fatalf("nil raw builder error = %v, want raw adapter context", err)
|
||||
}
|
||||
}
|
||||
|
||||
type registryFakeExtractor struct {
|
||||
key string
|
||||
}
|
||||
|
||||
type registryTypedValue struct{ Value string }
|
||||
|
||||
type registryTypedExtractor struct{ key string }
|
||||
|
||||
func (extractor registryTypedExtractor) Key() string { return extractor.key }
|
||||
func (registryTypedExtractor) ReferenceSlots() []contracts.ReferenceSlot { return nil }
|
||||
func (registryTypedExtractor) Extract(context.Context, contracts.TypedExtractionRequest) (contracts.TypedExtractionResult[registryTypedValue], error) {
|
||||
return contracts.TypedExtractionResult[registryTypedValue]{}, nil
|
||||
}
|
||||
|
||||
func fakeExtractorConstructor(key string) LegacyRawExtractorConstructor {
|
||||
return func() (contracts.LegacyRawExtractor, error) {
|
||||
return registryFakeExtractor{key: key}, nil
|
||||
|
||||
@@ -393,6 +393,9 @@ func resolveArtifactIdentity(pipelineID, laneID string, lane *ResolvedArtifactLa
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("pipeline %q lane %q extract module %q declares artifact kind %q without a typed registration", pipelineID, laneID, lane.Extract.Module, extractSpec.ArtifactKind)
|
||||
}
|
||||
if catalog.Extractors.usesRawAdapter(lane.Extract.Module) {
|
||||
return nil, nil
|
||||
}
|
||||
if catalog.ArtifactCodecs == nil {
|
||||
return nil, fmt.Errorf("pipeline %q lane %q artifact codec registry must not be nil for kind %q", pipelineID, laneID, extractSpec.ArtifactKind)
|
||||
}
|
||||
@@ -639,12 +642,16 @@ func validatePipelineReferenceDefaults(
|
||||
|
||||
merge := resolveBinding(laneProfile.Merge, DefaultMergeModule)
|
||||
var artifactType reflect.Type
|
||||
if extractSpec.ArtifactKind != "" && catalog.Extractors != nil {
|
||||
artifactKind := extractSpec.ArtifactKind
|
||||
if catalog.Extractors != nil && catalog.Extractors.usesRawAdapter(extract.Module) {
|
||||
artifactKind = ""
|
||||
}
|
||||
if artifactKind != "" && catalog.Extractors != nil {
|
||||
if entry, ok := catalog.Extractors.typedEntry(extract.Module); ok {
|
||||
artifactType = entry.valueType
|
||||
}
|
||||
}
|
||||
mergeSpec, err := mergerSpecForArtifact(catalog, merge.Module, extractSpec.ArtifactKind, artifactType)
|
||||
mergeSpec, err := mergerSpecForArtifact(catalog, merge.Module, artifactKind, artifactType)
|
||||
if err != nil {
|
||||
return moduleLookupError(pipelineID, laneID, StageMerge, merge.Module, err)
|
||||
}
|
||||
@@ -653,7 +660,7 @@ func validatePipelineReferenceDefaults(
|
||||
}
|
||||
|
||||
normalize := resolveBinding(laneProfile.Normalize, DefaultNormalizeModule)
|
||||
normalizeSpec, err := normalizerSpecForArtifact(catalog, normalize.Module, extractSpec.ArtifactKind, artifactType)
|
||||
normalizeSpec, err := normalizerSpecForArtifact(catalog, normalize.Module, artifactKind, artifactType)
|
||||
if err != nil {
|
||||
return moduleLookupError(pipelineID, laneID, StageNormalize, normalize.Module, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user