package engine import ( "reflect" "strings" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) // decodedOverrideResult keeps the typed pre-CLI projection and raw CLI // contributions alongside the effective typed value and its inspection data. type decodedOverrideResult struct { value reflect.Value preCLI *document.Node final *document.Node presence map[string]inputPresence origins map[string]Origin overrides []preparedOverride } // applyDecodedOverrides assigns prepared CLI values to a detached, decoded // configuration. Each sparse mapping is decoded against the current typed // value, preserving omitted mapping fields while replacing supplied sequences. func applyDecodedOverrides( schema *schemaDescriptor, decoded decodedConfiguration, batch preparedOverrideBatch, context decodeContext, ) (decodedOverrideResult, []decodeIssue) { if schema == nil || schema.root == nil || !decoded.value.IsValid() || decoded.value.Type() != schema.root.typeOf || decoded.final == nil { return decodedOverrideResult{}, []decodeIssue{{code: CodeSchemaUnsupportedType, message: "decoded configuration does not match the schema"}} } preCLI := document.Clone(decoded.final) working, cloneIssue := cloneConfiguration(schema, decoded.value, defaultProjectionLimits()) if cloneIssue != nil { return decodedOverrideResult{}, []decodeIssue{decodeIssueFromProjection(cloneIssue)} } presence := cloneInputPresence(decoded.presence) origins := cloneOrigins(decoded.origins) currentProjection := document.Clone(decoded.final) for _, override := range batch.overrides { if len(override.path.segments) == 0 { return decodedOverrideResult{}, []decodeIssue{{code: CodeOverrideInvalidPath, message: "configuration override path is invalid", location: document.Location{SourceID: override.source.ID}}} } input, issue := decodedOverrideInput(override) if issue != nil { return decodedOverrideResult{}, []decodeIssue{*issue} } updated, issues := decodeValue(input, working, schema.root, Path{}, schema, context) if len(issues) != 0 { return decodedOverrideResult{}, issues } working = updated location := document.Location{SourceID: override.source.ID} recordOverrideAncestors(presence, origins, override.path, location) updateOverridePresence(presence, currentProjection, override.value, override.path, location) updateOverrideOrigins(origins, currentProjection, override.value, override.path, location) } final, projectionIssue := projectConfiguration(schema, working, defaultProjectionLimits()) if projectionIssue != nil { return decodedOverrideResult{}, []decodeIssue{decodeIssueFromProjection(projectionIssue)} } // New fields created by decoding a partial mapping into a previously nil // pointer have no input origin. Fill those gaps as defaults while retaining // all surviving source and CLI contributions already in the origin map. fillDefaultOrigins(final, Path{}, origins) return decodedOverrideResult{ value: working, preCLI: preCLI, final: final, presence: presence, origins: origins, overrides: clonePreparedOverrides(batch.overrides), }, nil } func recordOverrideAncestors(presence map[string]inputPresence, origins map[string]Origin, path Path, location document.Location) { mergeOverlayOrigin(origins, Path{}, location, LayerCLI) prefix := Path{} for index, segment := range path.segments { prefix = prefix.Key(segment.key) if index == len(path.segments)-1 { break } pointer := prefix.String() if _, exists := presence[pointer]; !exists { presence[pointer] = inputPresence{location: location} } mergeOverlayOrigin(origins, prefix, location, LayerCLI) } } func decodedOverrideInput(override preparedOverride) (*document.Node, *decodeIssue) { location := document.Location{SourceID: override.source.ID} value := document.Clone(override.value) if value == nil { return nil, &decodeIssue{code: CodeOverrideInvalidPath, path: override.path.clone(), message: "configuration override value is invalid", location: location} } for index := len(override.path.segments) - 1; index >= 0; index-- { segment := override.path.segments[index] if segment.kind == pathSegmentIndex { return nil, &decodeIssue{code: CodeOverrideInvalidPath, path: override.path.clone(), message: "configuration override path is invalid", location: location} } value = &document.Node{ Kind: document.Mapping, Location: location, Mapping: []document.Entry{{ Key: segment.key, KeyLocation: location, ValueLocation: location, Value: value, }}, } } return value, nil } func cloneInputPresence(input map[string]inputPresence) map[string]inputPresence { cloned := make(map[string]inputPresence, len(input)) for path, value := range input { cloned[path] = value } return cloned } func cloneOrigins(input map[string]Origin) map[string]Origin { cloned := make(map[string]Origin, len(input)) for path, value := range input { cloned[path] = cloneOrigin(value) } return cloned } func clonePreparedOverrides(input []preparedOverride) []preparedOverride { cloned := make([]preparedOverride, len(input)) for index, value := range input { cloned[index] = preparedOverride{ path: value.path.clone(), value: document.Clone(value.value), source: value.source, bytes: value.bytes, nodes: value.nodes, } } return cloned } func updateOverridePresence(presence map[string]inputPresence, existing, incoming *document.Node, path Path, location document.Location) { if existing != nil && existing.Kind == document.Mapping && incoming.Kind == document.Mapping { if _, exists := presence[path.String()]; !exists { presence[path.String()] = inputPresence{location: location} } var index mappingIndex for _, entry := range incoming.Mapping { childPath := path.Key(entry.Key) position := index.find(existing, entry.Key) var previous *document.Node if position >= 0 { previous = existing.Mapping[position].Value } updateOverridePresence(presence, previous, entry.Value, childPath, location) } return } deletePresencePathSubtree(presence, path) collectPresence(incoming, path, presence) } func deletePresencePathSubtree(presence map[string]inputPresence, path Path) { pointer := path.String() for candidate := range presence { if candidate == pointer || strings.HasPrefix(candidate, pointer+"/") { delete(presence, candidate) } } } func updateOverrideOrigins(origins map[string]Origin, existing, incoming *document.Node, path Path, location document.Location) { if existing != nil && existing.Kind == document.Mapping && incoming.Kind == document.Mapping { mergeOverlayOrigin(origins, path, location, LayerCLI) var index mappingIndex for _, entry := range incoming.Mapping { childPath := path.Key(entry.Key) position := index.find(existing, entry.Key) var previous *document.Node if position >= 0 { previous = existing.Mapping[position].Value } updateOverrideOrigins(origins, previous, entry.Value, childPath, location) } return } pointer := path.String() for candidate := range origins { if candidate == pointer || strings.HasPrefix(candidate, pointer+"/") { delete(origins, candidate) } } collectOverlayOrigins(origins, incoming, path, LayerCLI) } func fillDefaultOrigins(node *document.Node, path Path, origins map[string]Origin) { if _, exists := origins[path.String()]; !exists { origins[path.String()] = Origin{Layer: LayerDefault} } switch node.Kind { case document.Mapping: for _, entry := range node.Mapping { fillDefaultOrigins(entry.Value, path.Key(entry.Key), origins) } case document.Sequence: for index, child := range node.Sequence { fillDefaultOrigins(child, path.Index(index), origins) } } }