116 lines
4.6 KiB
Go
116 lines
4.6 KiB
Go
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),
|
|
}
|
|
}
|