Files
narratio/internal/config/effective_projection.go

140 lines
5.4 KiB
Go

package config
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strings"
)
// effectivePipelineProjection is the single safe, schema-specific view used
// for inspection, comparison, and identity. Runtime metadata and unresolved
// family declarations have no slot in this type.
type effectivePipelineProjection struct {
Workspace WorkspaceConfig `yaml:"workspace" json:"workspace"`
Campaigns CampaignsConfig `yaml:"campaigns" json:"campaigns"`
Storage StorageConfig `yaml:"storage" json:"storage"`
Spool SpoolConfig `yaml:"spool" json:"spool"`
Cache CacheConfig `yaml:"cache" json:"cache"`
Publish *PublishConfig `yaml:"publish" json:"publish"`
WhisperX WhisperXConfig `yaml:"whisperx" json:"whisperx"`
Seriatim SeriatimConfig `yaml:"seriatim" json:"seriatim"`
Audita AuditaConfig `yaml:"audita" json:"audita"`
Normalize *NormalizeConfig `yaml:"normalize" json:"normalize"`
Trim *TrimConfig `yaml:"trim" json:"trim"`
Render *RenderConfig `yaml:"render" json:"render"`
Scriptorium *effectiveScriptoriumConfig `yaml:"scriptorium" json:"scriptorium"`
Notarius *NotariusConfig `yaml:"notarius" json:"notarius"`
Notification NotificationConfig `yaml:"notification" json:"notification"`
}
type effectiveScriptoriumConfig struct {
Credentials []string `yaml:"credentials,omitempty" json:"credentials,omitempty"`
Binary string `yaml:"binary" json:"binary"`
ConfigPath string `yaml:"config_path" json:"config_path"`
Timeout string `yaml:"timeout" json:"timeout"`
RenderDebug bool `yaml:"render_debug" json:"render_debug"`
Artifacts map[string]ScriptoriumArtifactConfig `yaml:"artifacts" json:"artifacts"`
}
type projectedPipelineValue struct {
record EffectivePipelineValueRecord
segments []string
}
func projectEffectivePipeline(cfg *PipelineConfig, logicalNotarius bool) effectivePipelineProjection {
projection := effectivePipelineProjection{
Workspace: cfg.Workspace, Campaigns: cfg.Campaigns, Storage: cfg.Storage,
Spool: cfg.Spool, Cache: cfg.Cache, Publish: cfg.Publish,
WhisperX: cfg.WhisperX, Seriatim: cfg.Seriatim, Audita: cfg.Audita,
Normalize: cfg.Normalize, Trim: cfg.Trim, Render: cfg.Render,
Notarius: cfg.Notarius, Notification: cfg.Notification,
}
if cfg.Scriptorium != nil {
projection.Scriptorium = &effectiveScriptoriumConfig{
Credentials: secretReferenceNames(cfg.Scriptorium.Credentials),
Binary: cfg.Scriptorium.Binary, ConfigPath: cfg.Scriptorium.ConfigPath,
Timeout: cfg.Scriptorium.Timeout, RenderDebug: cfg.Scriptorium.RenderDebug,
Artifacts: cfg.Scriptorium.Artifacts,
}
}
if logicalNotarius && cfg.Notarius != nil && cfg.resolution != nil && cfg.resolution.logicalNotariusCaptured {
notarius := *cfg.Notarius
notarius.ConfigPath = cfg.resolution.logicalNotariusConfig
notarius.WorkingDirectory = cfg.resolution.logicalNotariusWorking
projection.Notarius = &notarius
}
return projection
}
func projectionJSON(projection effectivePipelineProjection) ([]byte, error) {
data, err := json.Marshal(projection)
if err != nil {
return nil, fmt.Errorf("encode effective pipeline projection: %w", err)
}
return data, nil
}
func projectionValueTree(value any) (any, error) {
data, err := json.Marshal(value)
if err != nil {
return nil, fmt.Errorf("encode typed pipeline projection: %w", err)
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.UseNumber()
var root any
if err := decoder.Decode(&root); err != nil {
return nil, fmt.Errorf("decode effective pipeline projection: %w", err)
}
return root, nil
}
func projectionValueRecords(projection effectivePipelineProjection) ([]EffectivePipelineValueRecord, error) {
detailed, err := projectionDetailedRecords(projection)
if err != nil {
return nil, err
}
records := make([]EffectivePipelineValueRecord, 0, len(detailed))
for _, value := range detailed {
records = append(records, value.record)
}
return records, nil
}
func projectionDetailedRecords(value any) ([]projectedPipelineValue, error) {
decoded, err := projectionValueTree(value)
if err != nil {
return nil, err
}
root, ok := decoded.(map[string]any)
if !ok {
return nil, fmt.Errorf("effective pipeline projection must be a mapping")
}
var records []projectedPipelineValue
appendProjectionRecords(&records, nil, root)
sort.Slice(records, func(i, j int) bool { return records[i].record.Path < records[j].record.Path })
return records, nil
}
func appendProjectionRecords(records *[]projectedPipelineValue, segments []string, value any) {
if mapping, ok := value.(map[string]any); ok && len(mapping) > 0 {
keys := make([]string, 0, len(mapping))
for key := range mapping {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
child := append(append([]string(nil), segments...), key)
appendProjectionRecords(records, child, mapping[key])
}
return
}
encoded, _ := json.Marshal(value)
path := strings.Join(segments, ".")
*records = append(*records, projectedPipelineValue{
record: EffectivePipelineValueRecord{Path: path, Value: string(encoded)},
segments: append([]string(nil), segments...),
})
}