Files
ConfigKit/internal/engine/destination.go

123 lines
3.3 KiB
Go

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")
)
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
}
func invalidDestinationDiagnostic(destination Path) Diagnostic {
return Diagnostic{
Code: CodeOptionsInvalid, Severity: SeverityError,
Path: destination.clone(),
Message: "input destination must contain only non-reserved mapping keys",
}
}
func setSourceDestinations(sources []SourceInfo, destination Path) {
for index := range sources {
sources[index].Destination = destination.clone()
}
}
// 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) {
normalized, err := normalizeDestination(destination)
if err != nil {
return Path{}, nil, err
}
if body == nil {
return normalized, nil, nil
}
if !destinationWithinLimits(body, normalized, maxDepth, maxNodes) {
return normalized, nil, errDestinationLimit
}
placed := body
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 {
if body == nil {
return true
}
if maxDepth < 0 || maxNodes < 0 || !destination.valid() {
return false
}
segments := len(destination.segments)
if segments > maxDepth || segments > maxNodes/2 {
return false
}
for _, segment := range destination.segments {
if segment.kind != pathSegmentKey {
return false
}
}
if documentDepth(body) > maxDepth-segments {
return false
}
return countInputNodes(body) <= maxNodes-2*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
}