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 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, decoded decodedConfiguration) (resolvedLoad[T], []Diagnostic) { var diagnostics []Diagnostic configuration := decoded.value.Interface().(T) metadata := metadataFromDecoded(decoded) if input.inheritance != nil { applyInheritanceMetadata(&metadata, input.composition.root, input.composition.origins, input.composition.overlays, input.inheritance) } else if input.composition != nil { applyOverlayMetadata(&metadata, input.composition.overlays) } final := decoded.final var resolutionSnapshots []resolutionSnapshot if definition.Resolve != nil { entry := newResolutionInput(metadata, decoded.final, input.sources, input.contexts) resolved := runResolution(&configuration, definition.Resolve, schema, decoded, input.document, entry) 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, 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 finalizeMetadataPaths(&metadata, final, applications, decoded.baseline, decoded.final) secrets := classifySecretPaths(schema, definition.Secret, metadata, final, applications, decoded.baseline, decoded.final) inspection := &Inspection{ metadata: metadata, sources: cloneSourceInfos(accumulator.sources), contexts: cloneResourceContexts(resolved.contexts), diagnostics: canonicalDiagnostics(accumulator.diagnostics), baseline: decoded.baseline, input: decoded.final, inputOrigins: cloneOriginIndex(decoded.origins), final: final, resolution: 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), } }