157 lines
6.9 KiB
Go
157 lines
6.9 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 = cloneHistoryApplications(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: cloneHistoryApplications(prepared.postDecodeApps), structuralInput: document.Clone(prepared.structuralInput),
|
|
preCLI: document.Clone(prepared.preCLI), preCLIOrigins: cloneOrigins(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 publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T], resolved resolvedLoad[T], 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, metadata := resolved.decoded, resolved.metadata
|
|
final, applications, resolutionSnapshots := resolved.final, resolved.applications, resolved.snapshots
|
|
structuralInput := document.Clone(resolved.structuralInput)
|
|
preCLI := document.Clone(resolved.preCLI)
|
|
allApplications := append(cloneHistoryApplications(applications), cloneHistoryApplications(resolved.postDecodeApps)...)
|
|
finalizeMetadataPaths(&metadata, final, allApplications, decoded.baseline, resolved.structuralInput, resolved.preCLI, decoded.final)
|
|
secrets := classifySecretPathsWithEvidence(schema, definition.Secret, metadata, final, allApplications, resolved.versionEvidence, decoded.baseline, resolved.structuralInput, resolved.preCLI, decoded.final)
|
|
sources := sourceInfosWithVersionEvidence(accumulator.sources, resolved.versionEvidence, secrets, false)
|
|
retainedTrees := make([]*document.Node, 0, len(allApplications)+5)
|
|
retainedTrees = append(retainedTrees, decoded.final, final, structuralInput, preCLI)
|
|
for _, application := range allApplications {
|
|
retainedTrees = append(retainedTrees, application.value)
|
|
}
|
|
inspection := &Inspection{
|
|
metadata: metadata,
|
|
sources: sources,
|
|
contexts: cloneResourceContexts(resolved.contexts),
|
|
diagnostics: canonicalDiagnostics(accumulator.diagnostics),
|
|
baseline: decoded.baseline,
|
|
input: decoded.final,
|
|
inputOrigins: cloneOriginIndex(decoded.origins),
|
|
structuralInput: structuralInput,
|
|
preCLI: preCLI,
|
|
preCLIOrigins: cloneOrigins(resolved.preCLIOrigins),
|
|
postDecodeApps: cloneHistoryApplications(resolved.postDecodeApps),
|
|
final: final,
|
|
immutableLookup: buildDocumentLookup(retainedTrees...),
|
|
resolution: resolutionSnapshots,
|
|
resolutionReplay: coalescedResolutionSnapshots(resolutionSnapshots),
|
|
applications: applications,
|
|
inheritance: inheritanceOperations(resolved.inheritance),
|
|
secrets: secrets,
|
|
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),
|
|
}
|
|
}
|