Files
notarius/internal/framework/pipeline/artifact_evidence_registry.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
}