Files
notarius/internal/framework/pipeline/extractor_registry.go

118 lines
4.3 KiB
Go
Raw Permalink Normal View History

package pipeline
2026-07-03 12:50:09 +00:00
import (
"context"
2026-07-03 12:50:09 +00:00
"fmt"
2026-07-17 05:57:31 +00:00
"reflect"
2026-07-03 12:50:09 +00:00
"strings"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
)
type ExtractorRegistry struct {
2026-07-17 08:13:08 +00:00
typedEntries map[string]typedExtractorEntry
specs map[string]ModuleSpec
2026-07-17 05:57:31 +00:00
}
type typedExtractorEntry struct {
spec ModuleSpec
valueType reflect.Type
validateOptions OptionValidator
builder func(BuildRequest) (any, error)
extract typedExtractOperation
2026-07-03 12:50:09 +00:00
}
func NewExtractorRegistry() *ExtractorRegistry {
2026-07-17 08:13:08 +00:00
return &ExtractorRegistry{typedEntries: map[string]typedExtractorEntry{}, specs: map[string]ModuleSpec{}}
2026-07-03 12:50:09 +00:00
}
2026-07-17 05:57:31 +00:00
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))
}
2026-07-17 08:13:08 +00:00
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 {
2026-07-17 05:57:31 +00:00
if registry == nil {
return fmt.Errorf("extractor registry must not be nil")
}
2026-07-17 08:13:08 +00:00
normalized := normalizeModuleSpec(spec)
if err := validateModuleSpec("extractor", StageExtract, normalized); err != nil {
2026-07-17 05:57:31 +00:00
return err
}
2026-07-17 08:13:08 +00:00
if normalized.ArtifactKind == "" {
return fmt.Errorf("typed extractor %q artifact kind must not be empty", normalized.Key)
2026-07-17 05:57:31 +00:00
}
if validateOptions == nil {
2026-07-17 08:13:08 +00:00
return fmt.Errorf("extractor option validator for %q must not be nil", normalized.Key)
}
if builder == nil {
2026-07-17 08:13:08 +00:00
return fmt.Errorf("extractor builder for %q must not be nil", normalized.Key)
2026-07-17 05:57:31 +00:00
}
2026-07-17 08:13:08 +00:00
if _, ok := registry.specs[normalized.Key]; ok {
return fmt.Errorf("extractor %q is already registered", normalized.Key)
2026-07-17 05:57:31 +00:00
}
2026-07-17 08:13:08 +00:00
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)
}
correction, err := contracts.CloneSemanticCorrection(request.Correction)
if err != nil {
return erasedTypedResult{}, fmt.Errorf("clone extraction correction: %w", err)
}
request.Correction = correction
2026-07-17 08:13:08 +00:00
result, err := extractor.Extract(ctx, request)
if err != nil {
return erasedTypedResult{}, err
}
candidate, err := contracts.CloneModelCandidate(result.ModelCandidate)
if err != nil {
return erasedTypedResult{}, fmt.Errorf("clone extraction model candidate: %w", err)
}
return erasedTypedResult{Value: result.Value, Diagnostics: contracts.CloneProducerDiagnostics(result.Diagnostics), ModelCandidate: candidate}, nil
2026-07-17 08:13:08 +00:00
}}
2026-07-17 05:57:31 +00:00
if registry.typedEntries == nil {
2026-07-17 08:13:08 +00:00
registry.typedEntries = map[string]typedExtractorEntry{}
2026-07-17 05:57:31 +00:00
}
if registry.specs == nil {
2026-07-17 08:13:08 +00:00
registry.specs = map[string]ModuleSpec{}
2026-07-17 05:57:31 +00:00
}
2026-07-17 08:13:08 +00:00
registry.typedEntries[normalized.Key] = entry
registry.specs[normalized.Key] = cloneModuleSpec(normalized)
2026-07-17 05:57:31 +00:00
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")
}
2026-07-17 08:13:08 +00:00
normalized := strings.TrimSpace(key)
entry, ok := r.typedEntries[normalized]
if !ok {
2026-07-17 08:13:08 +00:00
return fmt.Errorf("extractor %q is not registered", normalized)
}
2026-07-17 08:13:08 +00:00
return validateRegisteredOptions(entry.validateOptions, options)
}
2026-07-03 15:30:44 +00:00
func (r *ExtractorRegistry) Spec(key string) (ModuleSpec, bool) {
if r == nil {
return ModuleSpec{}, false
}
spec, ok := r.specs[strings.TrimSpace(key)]
2026-07-17 08:13:08 +00:00
return cloneModuleSpec(spec), ok
2026-07-03 15:30:44 +00:00
}
2026-07-17 05:57:31 +00:00
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 {
2026-07-03 12:50:09 +00:00
if r == nil {
return nil
}
2026-07-17 05:57:31 +00:00
return sortedRegistryKeys(r.specs)
2026-07-03 12:50:09 +00:00
}