218 lines
5.7 KiB
Go
218 lines
5.7 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
|
)
|
|
|
|
type ModuleStage string
|
|
|
|
const (
|
|
StageInput ModuleStage = "input"
|
|
StageChunk ModuleStage = "chunk"
|
|
StageExtract ModuleStage = "extract"
|
|
StageMerge ModuleStage = "merge"
|
|
StageNormalize ModuleStage = "normalize"
|
|
StageValidate ModuleStage = "validate"
|
|
StageOutput ModuleStage = "output"
|
|
)
|
|
|
|
type ModuleSpec struct {
|
|
Key string
|
|
Stage ModuleStage
|
|
ArtifactKind contracts.ArtifactKind
|
|
Provides []string
|
|
Requires []string
|
|
ReferenceSlots []contracts.ReferenceSlot
|
|
}
|
|
|
|
func defaultModuleSpec(key string, stage ModuleStage) ModuleSpec {
|
|
return ModuleSpec{
|
|
Key: key,
|
|
Stage: stage,
|
|
}
|
|
}
|
|
|
|
func normalizeModuleSpec(spec ModuleSpec) ModuleSpec {
|
|
return ModuleSpec{
|
|
Key: strings.TrimSpace(spec.Key),
|
|
Stage: spec.Stage,
|
|
ArtifactKind: normalizeArtifactKind(spec.ArtifactKind),
|
|
Provides: normalizeCapabilities(spec.Provides),
|
|
Requires: normalizeCapabilities(spec.Requires),
|
|
ReferenceSlots: normalizeReferenceSlots(spec.ReferenceSlots),
|
|
}
|
|
}
|
|
|
|
func normalizeCapabilities(values []string) []string {
|
|
if len(values) == 0 {
|
|
return nil
|
|
}
|
|
|
|
seen := make(map[string]struct{}, len(values))
|
|
for _, value := range values {
|
|
normalized := strings.TrimSpace(value)
|
|
if normalized == "" {
|
|
continue
|
|
}
|
|
seen[normalized] = struct{}{}
|
|
}
|
|
if len(seen) == 0 {
|
|
return nil
|
|
}
|
|
|
|
capabilities := make([]string, 0, len(seen))
|
|
for value := range seen {
|
|
capabilities = append(capabilities, value)
|
|
}
|
|
sort.Strings(capabilities)
|
|
return capabilities
|
|
}
|
|
|
|
func cloneModuleSpec(spec ModuleSpec) ModuleSpec {
|
|
return ModuleSpec{
|
|
Key: spec.Key,
|
|
Stage: spec.Stage,
|
|
ArtifactKind: spec.ArtifactKind,
|
|
Provides: append([]string(nil), spec.Provides...),
|
|
Requires: append([]string(nil), spec.Requires...),
|
|
ReferenceSlots: contracts.CloneReferenceSlots(spec.ReferenceSlots),
|
|
}
|
|
}
|
|
|
|
func validateModuleSpec(kind string, expectedStage ModuleStage, spec ModuleSpec) error {
|
|
if spec.Key == "" {
|
|
return fmt.Errorf("%s key must not be empty", kind)
|
|
}
|
|
if spec.Stage != expectedStage {
|
|
return fmt.Errorf("%s %q must use %q stage, got %q", kind, spec.Key, expectedStage, spec.Stage)
|
|
}
|
|
if spec.ArtifactKind != "" && spec.Stage != StageExtract && spec.Stage != StageMerge && spec.Stage != StageNormalize {
|
|
return fmt.Errorf("%s %q must not declare an artifact kind", kind, spec.Key)
|
|
}
|
|
if !referenceSlotStage(spec.Stage) && len(spec.ReferenceSlots) > 0 {
|
|
return fmt.Errorf("%s %q must not declare reference slots", kind, spec.Key)
|
|
}
|
|
if err := validateReferenceSlots(spec.ReferenceSlots); err != nil {
|
|
return fmt.Errorf("%s %q reference slots: %w", kind, spec.Key, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func referenceSlotStage(stage ModuleStage) bool {
|
|
return stage == StageChunk || stage == StageExtract || stage == StageMerge || stage == StageNormalize
|
|
}
|
|
|
|
func sortedRegistryKeys[C any](constructors map[string]C) []string {
|
|
if len(constructors) == 0 {
|
|
return nil
|
|
}
|
|
|
|
keys := make([]string, 0, len(constructors))
|
|
for key := range constructors {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
return keys
|
|
}
|
|
|
|
func sortArtifactKinds(kinds []contracts.ArtifactKind) {
|
|
sort.Slice(kinds, func(i, j int) bool { return kinds[i] < kinds[j] })
|
|
}
|
|
|
|
func normalizeReferenceSlots(slots []contracts.ReferenceSlot) []contracts.ReferenceSlot {
|
|
if len(slots) == 0 {
|
|
return nil
|
|
}
|
|
|
|
normalized := make([]contracts.ReferenceSlot, 0, len(slots))
|
|
for _, slot := range slots {
|
|
slot.Name = strings.TrimSpace(slot.Name)
|
|
slot.Description = strings.TrimSpace(slot.Description)
|
|
slot.AcceptedMediaTypes = normalizeStringSet(slot.AcceptedMediaTypes)
|
|
slot.AcceptedArtifactKinds = normalizeArtifactKinds(slot.AcceptedArtifactKinds)
|
|
normalized = append(normalized, slot)
|
|
}
|
|
sort.SliceStable(normalized, func(i, j int) bool {
|
|
return normalized[i].Name < normalized[j].Name
|
|
})
|
|
return normalized
|
|
}
|
|
|
|
func normalizeArtifactKinds(values []contracts.ArtifactKind) []contracts.ArtifactKind {
|
|
if len(values) == 0 {
|
|
return nil
|
|
}
|
|
seen := make(map[contracts.ArtifactKind]struct{}, len(values))
|
|
containsEmpty := false
|
|
for _, value := range values {
|
|
value = normalizeArtifactKind(value)
|
|
if value != "" {
|
|
seen[value] = struct{}{}
|
|
} else {
|
|
containsEmpty = true
|
|
}
|
|
}
|
|
if len(seen) == 0 && !containsEmpty {
|
|
return nil
|
|
}
|
|
result := make([]contracts.ArtifactKind, 0, len(seen))
|
|
for value := range seen {
|
|
result = append(result, value)
|
|
}
|
|
if containsEmpty {
|
|
result = append(result, "")
|
|
}
|
|
sort.Slice(result, func(i, j int) bool { return result[i] < result[j] })
|
|
return result
|
|
}
|
|
|
|
func normalizeStringSet(values []string) []string {
|
|
if len(values) == 0 {
|
|
return nil
|
|
}
|
|
seen := make(map[string]struct{}, len(values))
|
|
for _, value := range values {
|
|
normalized := strings.TrimSpace(value)
|
|
if normalized == "" {
|
|
continue
|
|
}
|
|
seen[normalized] = struct{}{}
|
|
}
|
|
if len(seen) == 0 {
|
|
return nil
|
|
}
|
|
|
|
out := make([]string, 0, len(seen))
|
|
for value := range seen {
|
|
out = append(out, value)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
func validateReferenceSlots(slots []contracts.ReferenceSlot) error {
|
|
seen := make(map[string]struct{}, len(slots))
|
|
for i, slot := range slots {
|
|
if slot.Name == "" {
|
|
return fmt.Errorf("slot[%d].name must not be empty", i)
|
|
}
|
|
if _, ok := seen[slot.Name]; ok {
|
|
return fmt.Errorf("slot name %q is duplicated", slot.Name)
|
|
}
|
|
seen[slot.Name] = struct{}{}
|
|
if slot.MaxBytes < 0 {
|
|
return fmt.Errorf("slot %q max_bytes must not be negative", slot.Name)
|
|
}
|
|
for _, kind := range slot.AcceptedArtifactKinds {
|
|
if normalizeArtifactKind(kind) == "" {
|
|
return fmt.Errorf("slot %q accepted artifact kind must not be empty", slot.Name)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|