109 lines
3.8 KiB
Go
109 lines
3.8 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
|
)
|
|
|
|
type ExtractorRegistry struct {
|
|
typedEntries map[string]typedExtractorEntry
|
|
specs map[string]ModuleSpec
|
|
}
|
|
|
|
type typedExtractorEntry struct {
|
|
spec ModuleSpec
|
|
valueType reflect.Type
|
|
validateOptions OptionValidator
|
|
builder func(BuildRequest) (any, error)
|
|
extract typedExtractOperation
|
|
}
|
|
|
|
func NewExtractorRegistry() *ExtractorRegistry {
|
|
return &ExtractorRegistry{typedEntries: map[string]typedExtractorEntry{}, specs: map[string]ModuleSpec{}}
|
|
}
|
|
|
|
func RegisterExtractor[T any](registry *ExtractorRegistry, spec ModuleSpec, constructor func() (contracts.Extractor[T], error)) error {
|
|
if constructor == nil {
|
|
return fmt.Errorf("extractor constructor for %q must not be nil", strings.TrimSpace(spec.Key))
|
|
}
|
|
return RegisterExtractorBuilder(registry, spec, rejectUnconfiguredOptions, func(BuildRequest) (contracts.Extractor[T], error) { return constructor() })
|
|
}
|
|
|
|
func RegisterExtractorBuilder[T any](registry *ExtractorRegistry, spec ModuleSpec, validateOptions OptionValidator, builder func(BuildRequest) (contracts.Extractor[T], error)) error {
|
|
if registry == nil {
|
|
return fmt.Errorf("extractor registry must not be nil")
|
|
}
|
|
normalized := normalizeModuleSpec(spec)
|
|
if err := validateModuleSpec("extractor", StageExtract, normalized); err != nil {
|
|
return err
|
|
}
|
|
if normalized.ArtifactKind == "" {
|
|
return fmt.Errorf("typed extractor %q artifact kind must not be empty", normalized.Key)
|
|
}
|
|
if validateOptions == nil {
|
|
return fmt.Errorf("extractor option validator for %q must not be nil", normalized.Key)
|
|
}
|
|
if builder == nil {
|
|
return fmt.Errorf("extractor builder for %q must not be nil", normalized.Key)
|
|
}
|
|
if _, ok := registry.specs[normalized.Key]; ok {
|
|
return fmt.Errorf("extractor %q is already registered", normalized.Key)
|
|
}
|
|
entry := typedExtractorEntry{spec: cloneModuleSpec(normalized), valueType: reflect.TypeFor[T](), validateOptions: validateOptions, builder: func(request BuildRequest) (any, error) { return builder(cloneBuildRequest(request)) }, extract: func(ctx context.Context, implementation any, request contracts.TypedExtractionRequest) (erasedTypedResult, error) {
|
|
extractor, ok := implementation.(contracts.Extractor[T])
|
|
if !ok {
|
|
return erasedTypedResult{}, fmt.Errorf("extractor %q has incompatible implementation %T", normalized.Key, implementation)
|
|
}
|
|
result, err := extractor.Extract(ctx, request)
|
|
if err != nil {
|
|
return erasedTypedResult{}, err
|
|
}
|
|
return erasedTypedResult{Value: result.Value, Warnings: result.Warnings}, nil
|
|
}}
|
|
if registry.typedEntries == nil {
|
|
registry.typedEntries = map[string]typedExtractorEntry{}
|
|
}
|
|
if registry.specs == nil {
|
|
registry.specs = map[string]ModuleSpec{}
|
|
}
|
|
registry.typedEntries[normalized.Key] = entry
|
|
registry.specs[normalized.Key] = cloneModuleSpec(normalized)
|
|
return nil
|
|
}
|
|
|
|
func (r *ExtractorRegistry) validateOptions(key string, options map[string]any) error {
|
|
if r == nil {
|
|
return fmt.Errorf("extractor registry must not be nil")
|
|
}
|
|
normalized := strings.TrimSpace(key)
|
|
entry, ok := r.typedEntries[normalized]
|
|
if !ok {
|
|
return fmt.Errorf("extractor %q is not registered", normalized)
|
|
}
|
|
return validateRegisteredOptions(entry.validateOptions, options)
|
|
}
|
|
func (r *ExtractorRegistry) Spec(key string) (ModuleSpec, bool) {
|
|
if r == nil {
|
|
return ModuleSpec{}, false
|
|
}
|
|
spec, ok := r.specs[strings.TrimSpace(key)]
|
|
return cloneModuleSpec(spec), ok
|
|
}
|
|
func (r *ExtractorRegistry) typedEntry(key string) (typedExtractorEntry, bool) {
|
|
if r == nil {
|
|
return typedExtractorEntry{}, false
|
|
}
|
|
entry, ok := r.typedEntries[strings.TrimSpace(key)]
|
|
return entry, ok
|
|
}
|
|
func (r *ExtractorRegistry) RegisteredKeys() []string {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
return sortedRegistryKeys(r.specs)
|
|
}
|