Extract explicit configuration loading phases
This commit is contained in:
115
load_result.go
Normal file
115
load_result.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package configkit
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
decoded, decodeIssues := decodeConfiguration(schema, defaults, input.document)
|
||||
if len(decodeIssues) != 0 {
|
||||
diagnostics := make([]Diagnostic, len(decodeIssues))
|
||||
for i, issue := range decodeIssues {
|
||||
diagnostics[i] = diagnosticFromDecodeIssue(issue)
|
||||
}
|
||||
return decodedConfiguration{}, diagnostics
|
||||
}
|
||||
if input.composition != nil {
|
||||
applyCompositionOrigins(&decoded, input.composition.origins)
|
||||
}
|
||||
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
func resolveLoadInput[T any](schema *schemaDescriptor, definition Definition[T], input assembledInput, decoded decodedConfiguration) (resolvedLoad[T], []Diagnostic) {
|
||||
var diagnostics []Diagnostic
|
||||
configuration := decoded.value.Interface().(T)
|
||||
metadata := metadataFromDecoded(decoded)
|
||||
if input.composition != nil {
|
||||
applyOverlayMetadata(&metadata, input.composition.overlays)
|
||||
}
|
||||
final := decoded.final
|
||||
var resolutionSnapshots []resolutionSnapshot
|
||||
if definition.Resolve != nil {
|
||||
resolved := runResolution(&configuration, definition.Resolve, schema, decoded, input.document)
|
||||
diagnostics = append(diagnostics, resolved.diagnostics...)
|
||||
if hasErrors(resolved.diagnostics) {
|
||||
return resolvedLoad[T]{}, diagnostics
|
||||
}
|
||||
final = resolved.final
|
||||
resolutionSnapshots = resolved.snapshots
|
||||
applyResolutionMetadata(&metadata, resolved)
|
||||
}
|
||||
applications := []historyApplication(nil)
|
||||
if input.composition != nil {
|
||||
applications = cloneHistoryApplications(input.composition.applications)
|
||||
} else if input.document != nil {
|
||||
applications = []historyApplication{{layer: LayerBase, value: input.document}}
|
||||
}
|
||||
return resolvedLoad[T]{configuration: configuration, decoded: decoded, metadata: metadata, final: final, snapshots: resolutionSnapshots, applications: applications}, 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) (*Result[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
|
||||
finalizeMetadataPaths(&metadata, final, applications, decoded.baseline, decoded.final)
|
||||
secrets := classifySecretPaths(schema, definition.Secret, metadata, final, applications, decoded.baseline, decoded.final)
|
||||
return &Result[T]{
|
||||
Config: configuration,
|
||||
metadata: metadata,
|
||||
sources: cloneSourceInfos(accumulator.sources),
|
||||
diagnostics: canonicalDiagnostics(accumulator.diagnostics),
|
||||
baseline: decoded.baseline,
|
||||
input: decoded.final,
|
||||
inputOrigins: cloneOriginIndex(decoded.origins),
|
||||
final: final,
|
||||
resolution: resolutionSnapshots,
|
||||
applications: applications,
|
||||
secrets: secrets,
|
||||
secretReferences: cloneSecretReferences(references),
|
||||
}, 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),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user