149 lines
4.7 KiB
Go
149 lines
4.7 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
|
)
|
|
|
|
// ArtifactEvidenceProjector returns the direct source references represented
|
|
// by one normalized artifact value.
|
|
type ArtifactEvidenceProjector[T any] func(T) []source.SourceRef
|
|
|
|
// ArtifactEvidenceRegistry keeps the typed projection boundary private while
|
|
// allowing preparation and execution to discover registered capabilities.
|
|
type ArtifactEvidenceRegistry struct {
|
|
entries map[contracts.ArtifactKind]artifactEvidenceEntry
|
|
}
|
|
|
|
type artifactEvidenceEntry struct {
|
|
valueType reflect.Type
|
|
project func(any) ([]source.SourceRef, error)
|
|
}
|
|
|
|
func NewArtifactEvidenceRegistry() *ArtifactEvidenceRegistry {
|
|
return &ArtifactEvidenceRegistry{entries: make(map[contracts.ArtifactKind]artifactEvidenceEntry)}
|
|
}
|
|
|
|
// RegisterArtifactEvidence registers one evidence projector for an artifact
|
|
// kind. Projectors are invoked only after an exact Go-type check.
|
|
func RegisterArtifactEvidence[T any](registry *ArtifactEvidenceRegistry, kind contracts.ArtifactKind, projector ArtifactEvidenceProjector[T]) error {
|
|
if registry == nil {
|
|
return fmt.Errorf("artifact evidence registry must not be nil")
|
|
}
|
|
if projector == nil {
|
|
return fmt.Errorf("artifact evidence projector must not be nil")
|
|
}
|
|
kind = normalizeArtifactKind(kind)
|
|
if kind == "" {
|
|
return fmt.Errorf("artifact evidence kind must not be empty")
|
|
}
|
|
if _, ok := registry.entries[kind]; ok {
|
|
return fmt.Errorf("artifact evidence %q is already registered", kind)
|
|
}
|
|
valueType := reflect.TypeFor[T]()
|
|
if registry.entries == nil {
|
|
registry.entries = make(map[contracts.ArtifactKind]artifactEvidenceEntry)
|
|
}
|
|
registry.entries[kind] = artifactEvidenceEntry{
|
|
valueType: valueType,
|
|
project: func(value any) ([]source.SourceRef, error) {
|
|
actualType := reflect.TypeOf(value)
|
|
if actualType != valueType {
|
|
return nil, newArtifactEvidenceTypeError(kind, valueType, actualType)
|
|
}
|
|
typed, ok := value.(T)
|
|
if !ok {
|
|
return nil, newArtifactEvidenceTypeError(kind, valueType, actualType)
|
|
}
|
|
return append([]source.SourceRef(nil), projector(typed)...), nil
|
|
},
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *ArtifactEvidenceRegistry) RegisteredKinds() []contracts.ArtifactKind {
|
|
if r == nil || len(r.entries) == 0 {
|
|
return nil
|
|
}
|
|
kinds := make([]contracts.ArtifactKind, 0, len(r.entries))
|
|
for kind := range r.entries {
|
|
kinds = append(kinds, kind)
|
|
}
|
|
sort.Slice(kinds, func(i, j int) bool { return kinds[i] < kinds[j] })
|
|
return kinds
|
|
}
|
|
|
|
// Project returns independently owned direct references for a registered
|
|
// artifact value.
|
|
func (r *ArtifactEvidenceRegistry) Project(kind contracts.ArtifactKind, value any) ([]source.SourceRef, error) {
|
|
entry, normalizedKind, err := r.entry(kind)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
refs, err := entry.project(value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("project artifact evidence %q: %w", normalizedKind, err)
|
|
}
|
|
return append([]source.SourceRef(nil), refs...), nil
|
|
}
|
|
|
|
func (r *ArtifactEvidenceRegistry) entry(kind contracts.ArtifactKind) (artifactEvidenceEntry, contracts.ArtifactKind, error) {
|
|
if r == nil {
|
|
return artifactEvidenceEntry{}, "", fmt.Errorf("artifact evidence registry must not be nil")
|
|
}
|
|
kind = normalizeArtifactKind(kind)
|
|
if kind == "" {
|
|
return artifactEvidenceEntry{}, "", fmt.Errorf("artifact evidence kind must not be empty")
|
|
}
|
|
entry, ok := r.entries[kind]
|
|
if !ok {
|
|
return artifactEvidenceEntry{}, kind, fmt.Errorf("artifact evidence %q is not registered", kind)
|
|
}
|
|
return entry, kind, nil
|
|
}
|
|
|
|
func (r *ArtifactEvidenceRegistry) valueType(kind contracts.ArtifactKind) (reflect.Type, bool) {
|
|
if r == nil {
|
|
return nil, false
|
|
}
|
|
entry, ok := r.entries[normalizeArtifactKind(kind)]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return entry.valueType, true
|
|
}
|
|
|
|
func newArtifactEvidenceTypeError(kind contracts.ArtifactKind, expected, actual reflect.Type) error {
|
|
actualName := "<nil>"
|
|
if actual != nil {
|
|
actualName = actual.String()
|
|
}
|
|
return fmt.Errorf("project artifact evidence %q: expected exact Go type %s, got %s", kind, expected, actualName)
|
|
}
|
|
|
|
func normalizeEvidenceLaneIDs(values []string) ([]string, error) {
|
|
if len(values) == 0 {
|
|
return nil, fmt.Errorf("evidence lane ids must not be empty")
|
|
}
|
|
seen := make(map[string]struct{}, len(values))
|
|
lanes := make([]string, 0, len(values))
|
|
for _, raw := range values {
|
|
lane := strings.TrimSpace(raw)
|
|
if lane == "" {
|
|
return nil, fmt.Errorf("evidence lane id must not be empty")
|
|
}
|
|
if _, ok := seen[lane]; ok {
|
|
return nil, fmt.Errorf("evidence lane id %q is duplicated", lane)
|
|
}
|
|
seen[lane] = struct{}{}
|
|
lanes = append(lanes, lane)
|
|
}
|
|
sort.Strings(lanes)
|
|
return lanes, nil
|
|
}
|