package engine import ( "errors" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) var ( errInvalidDestination = errors.New("configkit: destination must contain only non-reserved mapping keys") errDestinationLimit = errors.New("configkit: destination exceeds document limits") ) type placementMode uint8 const ( placementModeMapping placementMode = iota placementModeSequenceItem ) func normalizeDestination(path Path) (Path, error) { if !path.valid() { return Path{}, errInvalidDestination } segments := make([]pathSegment, 0, len(path.segments)) for _, segment := range path.segments { if segment.kind != pathSegmentKey && segment.kind != pathSegmentParsed { return Path{}, errInvalidDestination } if segment.key == "configkit" || segment.key == "extends" { return Path{}, errInvalidDestination } segments = append(segments, pathSegment{kind: pathSegmentKey, key: segment.key}) } return Path{segments: segments}, nil } // appendDestination combines validated mapping-key paths with owned segments. func appendDestination(parent, relative Path) Path { segments := clonePathSegments(parent.segments, len(relative.segments)) segments = append(segments, relative.segments...) return Path{segments: segments} } func invalidDestinationDiagnostic(destination Path) Diagnostic { return Diagnostic{ Code: CodeOptionsInvalid, Severity: SeverityError, Path: destination.clone(), Message: "input destination must contain only non-reserved mapping keys", } } // placeDocument checks all per-document bounds before allocating destination // wrappers. Aggregate accounting remains the caller's responsibility. func placeDocument(body *document.Node, destination Path, sourceRoot document.Location, maxDepth, maxNodes int) (Path, *document.Node, error) { return placeDocumentWithMode(body, destination, placementModeMapping, sourceRoot, maxDepth, maxNodes) } // placeDocumentWithMode places a source mapping beneath its literal destination. // Sequence-item placement adds one sequence containing the complete mapping. func placeDocumentWithMode(body *document.Node, destination Path, mode placementMode, sourceRoot document.Location, maxDepth, maxNodes int) (Path, *document.Node, error) { normalized, err := normalizeDestination(destination) if err != nil { return Path{}, nil, err } if mode != placementModeMapping && mode != placementModeSequenceItem { return Path{}, nil, errInvalidDestination } if mode == placementModeSequenceItem && len(normalized.segments) == 0 { return Path{}, nil, errInvalidDestination } if body == nil { return normalized, nil, nil } if mode == placementModeSequenceItem && body.Kind != document.Mapping { return normalized, nil, errInvalidDestination } if !destinationWithinLimitsWithMode(body, normalized, mode, maxDepth, maxNodes) { return normalized, nil, errDestinationLimit } placed := body if mode == placementModeSequenceItem { placed = &document.Node{ Kind: document.Sequence, Location: sourceRoot, Sequence: []*document.Node{placed}, } } for index := len(normalized.segments) - 1; index >= 0; index-- { key := normalized.segments[index].key placed = &document.Node{ Kind: document.Mapping, Location: sourceRoot, Mapping: []document.Entry{{ Key: key, KeyLocation: sourceRoot, ValueLocation: sourceRoot, Value: placed, }}, } } return normalized, placed, nil } // destinationWithinLimits expects a normalized mapping-key destination. func destinationWithinLimits(body *document.Node, destination Path, maxDepth, maxNodes int) bool { return destinationWithinLimitsWithMode(body, destination, placementModeMapping, maxDepth, maxNodes) } func destinationWithinLimitsWithMode(body *document.Node, destination Path, mode placementMode, maxDepth, maxNodes int) bool { if body == nil { return true } if maxDepth < 0 || maxNodes < 0 || !destination.valid() || (mode != placementModeMapping && mode != placementModeSequenceItem) { return false } segments := len(destination.segments) sequenceNodes := 0 sequenceDepth := 0 if mode == placementModeSequenceItem { if segments == 0 { return false } sequenceNodes = 1 sequenceDepth = 1 } if segments > maxDepth-sequenceDepth || segments > (maxNodes-sequenceNodes)/2 { return false } wrapperNodes := destinationWrapperNodeCount(destination) + sequenceNodes for _, segment := range destination.segments { if segment.kind != pathSegmentKey { return false } } if documentDepth(body) > maxDepth-segments-sequenceDepth { return false } return countInputNodes(body) <= maxNodes-wrapperNodes } func destinationWrapperNodeCountForMode(destination Path, mode placementMode) int { count := destinationWrapperNodeCount(destination) if mode == placementModeSequenceItem { count++ } return count } func destinationWrapperNodeCount(destination Path) int { return 2 * len(destination.segments) } func documentDepth(node *document.Node) int { if node == nil { return -1 } depth := 0 var visit func(*document.Node, int) visit = func(current *document.Node, currentDepth int) { if current == nil { return } if currentDepth > depth { depth = currentDepth } for _, entry := range current.Mapping { visit(entry.Value, currentDepth+1) } for _, child := range current.Sequence { visit(child, currentDepth+1) } } visit(node, 0) return depth }