Files
ConfigKit/internal/engine/load_result.go

157 lines
6.8 KiB
Go

package engine
import (
"reflect"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
// resolvedLoad carries typed phase outputs needed for validation and publication;
// it owns no source access, options, or long-lived schema state.
type resolvedLoad[T any] struct {
configuration T
decoded decodedConfiguration
metadata Metadata
final *document.Node
snapshots []resolutionSnapshot
applications []historyApplication
postDecodeApps []historyApplication
structuralInput *document.Node
preCLI *document.Node
preCLIOrigins map[string]Origin
referenceInput *document.Node
versionEvidence []*sourceVersionEvidence
inheritance *inheritanceScope
contexts map[string]ResourceContext
}
func decodeLoadInput[T any](schema *schemaDescriptor, definition Definition[T], input assembledInput) (decodedConfiguration, []Diagnostic) {
defaults := reflect.Zero(reflect.TypeFor[T]())
if definition.Defaults != nil {
defaults = reflect.ValueOf(definition.Defaults())
}
context := decodeContext{}
if definition.Version != nil {
context.versionPath = &definition.Version.Path
}
decoded, decodeIssues := decodeConfigurationWithContext(schema, defaults, input.document, context)
if len(decodeIssues) != 0 {
diagnostics := make([]Diagnostic, len(decodeIssues))
for i, issue := range decodeIssues {
diagnostics[i] = diagnosticFromDecodeIssue(issue)
}
return decodedConfiguration{}, diagnostics
}
if input.inheritance != nil {
applyCompositionOrigins(&decoded, input.inheritance.origins)
} else if input.composition != nil {
applyCompositionOrigins(&decoded, input.composition.origins)
}
return decoded, nil
}
func resolveLoadInput[T any](schema *schemaDescriptor, definition Definition[T], input assembledInput, prepared postDecodeOverrideContext) (resolvedLoad[T], []Diagnostic) {
var diagnostics []Diagnostic
decoded := prepared.decoded
configuration := decoded.value.Interface().(T)
metadata := prepared.metadata
final := decoded.final
referenceInput := prepared.referenceInput
var resolutionSnapshots []resolutionSnapshot
if definition.Resolve != nil {
resolved := runResolution(&configuration, definition.Resolve, schema, decoded, referenceInput, prepared.resolution)
diagnostics = append(diagnostics, resolved.diagnostics...)
if hasErrors(resolved.diagnostics) {
return resolvedLoad[T]{}, diagnostics
}
final = resolved.final
resolutionSnapshots = resolved.snapshots
applyResolutionMetadata(&metadata, resolved)
referenceInput = documentWithMetadataLocations(final, metadata, input.document)
}
applications := []historyApplication(nil)
if input.composition != nil {
applications = input.composition.applications
} else if input.document != nil {
applications = []historyApplication{{layer: LayerBase, value: input.document}}
}
var versionEvidence []*sourceVersionEvidence
if input.versionState != nil {
versionEvidence = append(versionEvidence, input.versionState.evidence...)
}
return resolvedLoad[T]{
configuration: configuration, decoded: decoded, metadata: metadata, final: final,
snapshots: resolutionSnapshots, applications: applications,
postDecodeApps: prepared.postDecodeApps, structuralInput: document.Clone(prepared.structuralInput),
preCLI: prepared.preCLI, preCLIOrigins: prepared.preCLIOrigins, referenceInput: referenceInput,
versionEvidence: versionEvidence, inheritance: input.inheritance, contexts: input.contexts,
}, diagnostics
}
func detachLoadConfiguration[T any](schema *schemaDescriptor, configuration T, mode secretCloneMode) (T, *Diagnostic) {
detached, issue := cloneConfigurationWithMode(schema, reflect.ValueOf(configuration), defaultProjectionLimits(), mode)
if issue != nil {
diagnostic := diagnosticFromDecodeIssue(decodeIssueFromProjection(issue))
var zero T
return zero, &diagnostic
}
return detached.Interface().(T), nil
}
func prepareInspectionHistory[T any](resolved resolvedLoad[T]) *Inspection {
return &Inspection{
metadata: resolved.metadata,
baseline: resolved.decoded.baseline,
input: resolved.decoded.final,
inputOrigins: cloneOriginIndex(resolved.decoded.origins),
preCLI: resolved.preCLI,
preCLIOrigins: cloneOrigins(resolved.preCLIOrigins),
postDecodeApps: cloneHistoryApplications(resolved.postDecodeApps),
final: resolved.final,
resolution: resolved.snapshots,
resolutionReplay: coalescedResolutionSnapshots(resolved.snapshots),
applications: cloneHistoryApplications(resolved.applications),
inheritance: inheritanceOperations(resolved.inheritance),
}
}
func publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T], resolved resolvedLoad[T], inspection *Inspection, applications []historyApplication, references map[string]string, accumulator *diagnosticAccumulator) (*Loaded[T], error) {
// A read-only validator can replace a value with an equal borrowed object.
// Detach without re-reading the environment or discarding validated state.
configuration, issue := detachLoadConfiguration(schema, resolved.configuration, cloneValidatedSecrets)
if issue != nil {
accumulator.add(*issue)
return nil, accumulator.loadError()
}
decoded := resolved.decoded
metadata, final, preCLI := inspection.metadata, inspection.final, inspection.preCLI
finalizeMetadataPaths(&metadata, final, applications, decoded.baseline, resolved.structuralInput, resolved.preCLI, decoded.final)
secrets := classifySecretPathsWithEvidence(schema, definition.Secret, metadata, final, applications, resolved.versionEvidence, decoded.baseline, resolved.structuralInput, resolved.preCLI, decoded.final)
sources := sourceInfosWithVersionEvidence(accumulator.sources, resolved.versionEvidence, secrets, false)
retainedTrees := make([]*document.Node, 0, len(applications)+4)
retainedTrees = append(retainedTrees, decoded.final, final, preCLI)
for _, application := range applications {
retainedTrees = append(retainedTrees, application.value)
}
inspection.metadata = metadata
inspection.sources = sources
inspection.contexts = cloneResourceContexts(resolved.contexts)
inspection.diagnostics = canonicalDiagnostics(accumulator.diagnostics)
inspection.immutableLookup = buildDocumentLookup(retainedTrees...)
inspection.secrets = secrets
inspection.secretReferences = cloneSecretReferences(references)
return &Loaded[T]{Config: configuration, Inspection: inspection}, nil
}
func diagnosticFromSchemaIssue(issue *schemaIssue) Diagnostic {
return Diagnostic{Code: issue.code, Severity: SeverityError, Path: issue.path.clone(), Message: issue.message}
}
func diagnosticFromDecodeIssue(issue decodeIssue) Diagnostic {
return Diagnostic{
Code: issue.code, Severity: SeverityError, Path: issue.path.clone(), Message: issue.message,
Primary: documentLocationPointer(issue.location),
}
}