142 lines
5.4 KiB
Go
142 lines
5.4 KiB
Go
package engine
|
|
|
|
import (
|
|
"reflect"
|
|
|
|
"go.maximumdirect.net/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
|
|
metadata Metadata
|
|
final *document.Node
|
|
referenceInput *document.Node
|
|
versionEvidence []*sourceVersionEvidence
|
|
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
|
|
builder := metadata.historyBuilder
|
|
final := decoded.final
|
|
referenceInput := prepared.referenceInput
|
|
var resolutionSnapshots []resolutionSnapshot
|
|
refreshReferenceInput := false
|
|
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
|
|
if builder != nil && len(resolutionSnapshots) != 0 {
|
|
builder.observeResolutionEvidence(resolutionSnapshots)
|
|
}
|
|
applyResolutionMetadata(&metadata, resolved, prepared.resolution)
|
|
finalizeEffectiveMetadataPaths(&metadata, final)
|
|
refreshReferenceInput = true
|
|
}
|
|
if builder != nil {
|
|
builder.model.final = final
|
|
}
|
|
if refreshReferenceInput {
|
|
referenceInput = documentWithMetadataLocations(final, metadata, input.document)
|
|
}
|
|
var versionEvidence []*sourceVersionEvidence
|
|
if input.versionState != nil {
|
|
versionEvidence = append(versionEvidence, input.versionState.evidence...)
|
|
}
|
|
return resolvedLoad[T]{
|
|
configuration: configuration, metadata: metadata, final: final, referenceInput: referenceInput,
|
|
versionEvidence: versionEvidence, 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], counter *retainedNodeCounter) (*Inspection, bool) {
|
|
metadata := resolved.metadata
|
|
freezeMetadataHistory(&metadata, resolved.final)
|
|
model := metadata.historyModel
|
|
inspection := &Inspection{
|
|
metadata: metadata,
|
|
history: model,
|
|
final: resolved.final,
|
|
}
|
|
if !addInspectionHistory(counter, inspection) {
|
|
return nil, false
|
|
}
|
|
return inspection, true
|
|
}
|
|
|
|
func publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T], resolved resolvedLoad[T], inspection *Inspection, 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()
|
|
}
|
|
metadata := inspection.metadata
|
|
secrets := classifySecretPathsWithEvidence(schema, definition.Secret, metadata, resolved.versionEvidence)
|
|
sources := sourceInfosWithVersionEvidence(accumulator.sources, resolved.versionEvidence, secrets, false)
|
|
inspection.metadata = metadata
|
|
inspection.sources = sources
|
|
inspection.contexts = cloneResourceContexts(resolved.contexts)
|
|
inspection.diagnostics = canonicalDiagnostics(accumulator.diagnostics)
|
|
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),
|
|
}
|
|
}
|