Assemble imported sequence-item scopes
This commit is contained in:
@@ -51,11 +51,13 @@ type importTraversal struct {
|
||||
rule *VersionRule
|
||||
versionState *sourceVersionState
|
||||
|
||||
scope acquisitionScope
|
||||
sources []SourceInfo
|
||||
active map[resourceIdentity]int
|
||||
visited map[resourceIdentity]importRoute
|
||||
stack []importRoute
|
||||
scope acquisitionScope
|
||||
itemScope bool
|
||||
itemPrefix Path
|
||||
sources []SourceInfo
|
||||
active map[resourceIdentity]int
|
||||
visited map[resourceIdentity]importRoute
|
||||
stack []importRoute
|
||||
}
|
||||
|
||||
func loadImportedDocuments(source Source, context *FilesystemContext, main parsedSource, rule *VersionRule, limits importLimits) (*composedDocument, []SourceInfo, []Diagnostic) {
|
||||
@@ -128,6 +130,10 @@ func loadImportedWithAccessTrackedAndProcessing(access *filesystemAccess, basePa
|
||||
}
|
||||
mainInfo := cloneSourceInfo(*main.source)
|
||||
mainInfo.Destination = destination.clone()
|
||||
mainIsItem := main.source.placementMode == placementModeSequenceItem
|
||||
if mainIsItem {
|
||||
mainInfo.destinationUnresolved = true
|
||||
}
|
||||
if mainPath != "" {
|
||||
mainInfo.Path = mainPath
|
||||
}
|
||||
@@ -148,6 +154,12 @@ func loadImportedWithAccessTrackedAndProcessing(access *filesystemAccess, basePa
|
||||
sources: []SourceInfo{mainInfo},
|
||||
active: make(map[resourceIdentity]int), visited: visited,
|
||||
}
|
||||
if mainIsItem {
|
||||
state.itemScope = true
|
||||
state.itemPrefix = destination.clone()
|
||||
state.scope.destination = Path{}
|
||||
state.sources[0] = cloneSourceInfo(mainInfo)
|
||||
}
|
||||
if state.visited == nil {
|
||||
state.visited = make(map[resourceIdentity]importRoute)
|
||||
}
|
||||
@@ -174,19 +186,26 @@ func loadImportedWithAccessTrackedAndProcessing(access *filesystemAccess, basePa
|
||||
body, declarations, diagnostics = splitImportDirectives(main.document)
|
||||
}
|
||||
if len(diagnostics) == 0 {
|
||||
_, placed, err := placeDocument(body, destination, body.Location, maxDocumentDepth, maxDocumentNodes)
|
||||
placementNodes := destinationWrapperNodeCount(destination)
|
||||
if err != nil {
|
||||
diagnostics = []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, main.source.ID, "configuration exceeds the source depth or node limit after destination placement")}
|
||||
} else if accounting.nodes+placementNodes > limits.nodes {
|
||||
diagnostics = []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", nil)}
|
||||
} else {
|
||||
accounting.nodes += placementNodes
|
||||
if mainIsItem {
|
||||
state.scope.contributions = append(state.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(mainInfo), payload: body, document: placed,
|
||||
destination: destination.clone(), processing: processing,
|
||||
source: cloneSourceInfo(mainInfo), payload: body, document: body, processing: processing,
|
||||
})
|
||||
diagnostics = state.walkDeclarations(basePath, declarations, 0, destination)
|
||||
diagnostics = state.walkDeclarations(basePath, declarations, 0, Path{})
|
||||
} else {
|
||||
_, placed, err := placeDocument(body, destination, body.Location, maxDocumentDepth, maxDocumentNodes)
|
||||
placementNodes := destinationWrapperNodeCount(destination)
|
||||
if err != nil {
|
||||
diagnostics = []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, main.source.ID, "configuration exceeds the source depth or node limit after destination placement")}
|
||||
} else if accounting.nodes+placementNodes > limits.nodes {
|
||||
diagnostics = []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", nil)}
|
||||
} else {
|
||||
accounting.nodes += placementNodes
|
||||
state.scope.contributions = append(state.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(mainInfo), payload: body, document: placed,
|
||||
destination: destination.clone(), processing: processing,
|
||||
})
|
||||
diagnostics = state.walkDeclarations(basePath, declarations, 0, destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,12 +216,49 @@ func loadImportedWithAccessTrackedAndProcessing(access *filesystemAccess, basePa
|
||||
if closeErr != nil {
|
||||
return nil, cloneSourceInfos(state.sources), []Diagnostic{importDiagnostic(CodeImportRead, "configuration root could not be closed", nil)}
|
||||
}
|
||||
if mainIsItem {
|
||||
localScope := state.scope
|
||||
documents := make([]*document.Node, 0, len(localScope.contributions))
|
||||
for _, contribution := range localScope.contributions {
|
||||
documents = append(documents, contribution.document)
|
||||
}
|
||||
completed, failures := composeDocuments(documents...)
|
||||
if len(failures) != 0 {
|
||||
return nil, cloneSourceInfos(state.sources), prependDiagnosticPaths(failures, destination.Index(0))
|
||||
}
|
||||
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(localScope)}
|
||||
projectNestedEvidence(completed, localScope)
|
||||
_, placed, err := placeDocumentWithMode(completed.root, destination, placementModeSequenceItem, body.Location, maxDocumentDepth, maxDocumentNodes)
|
||||
if err != nil {
|
||||
return nil, cloneSourceInfos(state.sources), []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, main.source.ID, "configuration exceeds the source depth or node limit after destination placement")}
|
||||
}
|
||||
placementNodes := destinationWrapperNodeCountForMode(destination, placementModeSequenceItem)
|
||||
if accounting.nodes+placementNodes > limits.nodes {
|
||||
return nil, cloneSourceInfos(state.sources), []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", nil)}
|
||||
}
|
||||
accounting.nodes += placementNodes
|
||||
composed, failures := composeDocuments(placed)
|
||||
if composed != nil {
|
||||
synthetic := acquisitionScope{contributions: []acquiredContribution{{
|
||||
source: cloneSourceInfo(mainInfo), payload: completed.root, document: placed,
|
||||
destination: destination.clone(), processing: processing, nested: completed,
|
||||
}}}
|
||||
projectNestedEvidence(composed, synthetic)
|
||||
projectAcquiredDestinations(composed, synthetic, state.sources)
|
||||
composed.acquisition = []acquisitionScope{cloneAcquisitionScope(synthetic)}
|
||||
composed.inputBytes = accounting.bytes
|
||||
composed.inputNodes = accounting.nodes
|
||||
}
|
||||
return composed, cloneSourceInfos(state.sources), failures
|
||||
}
|
||||
documents := make([]*document.Node, 0, len(state.scope.contributions))
|
||||
for _, contribution := range state.scope.contributions {
|
||||
documents = append(documents, contribution.document)
|
||||
}
|
||||
composed, diagnostics := composeDocuments(documents...)
|
||||
if composed != nil {
|
||||
projectNestedEvidence(composed, state.scope)
|
||||
projectAcquiredDestinations(composed, state.scope, state.sources)
|
||||
composed.acquisition = []acquisitionScope{cloneAcquisitionScope(state.scope)}
|
||||
composed.inputBytes = accounting.bytes
|
||||
composed.inputNodes = accounting.nodes
|
||||
@@ -260,6 +316,14 @@ func (s *importTraversal) walkDeclarations(basePath string, declarations []impor
|
||||
|
||||
func (s *importTraversal) visitPath(basePath string, declaration importDeclaration, destination, relativeDestination Path, depth int) []Diagnostic {
|
||||
location := declaration.location
|
||||
if declaration.placement == placementModeSequenceItem && len(destination.segments) == 0 {
|
||||
return []Diagnostic{{
|
||||
Code: CodeImportInvalid, Severity: SeverityError,
|
||||
Path: declaration.placementPath.clone(),
|
||||
Message: "sequence-item placement requires a nonempty destination",
|
||||
Primary: cloneLocation(declaration.placementLocation),
|
||||
}}
|
||||
}
|
||||
if depth > s.limits.depth {
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the active import-depth limit", &location)}
|
||||
}
|
||||
@@ -312,7 +376,11 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
|
||||
kind = SourceKindFilesystem
|
||||
label = s.access.label + "/" + canonical
|
||||
}
|
||||
info := SourceInfo{ID: sourceID, Label: label, Path: canonical, Destination: destination.clone(), Layer: LayerBase, Kind: kind, ImportedFrom: &location}
|
||||
info := SourceInfo{ID: sourceID, Label: label, Path: canonical, Destination: s.sourceDestination(destination), Layer: LayerBase, Kind: kind, ImportedFrom: &location}
|
||||
if declaration.placement == placementModeSequenceItem {
|
||||
info.placementMode = placementModeSequenceItem
|
||||
}
|
||||
info.destinationUnresolved = s.itemScope || declaration.placement == placementModeSequenceItem
|
||||
if kind == SourceKindFilesystem {
|
||||
info.namespace = s.access.token
|
||||
}
|
||||
@@ -356,11 +424,26 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
|
||||
return diagnostics
|
||||
}
|
||||
}
|
||||
_, placed, err := placeDocument(body, destination, body.Location, maxDocumentDepth, maxDocumentNodes)
|
||||
placementDestination := destination
|
||||
mode := placementModeMapping
|
||||
if declaration.placement == placementModeSequenceItem {
|
||||
mode = placementModeSequenceItem
|
||||
}
|
||||
if mode == placementModeSequenceItem {
|
||||
route := importRoute{canonical: canonical, locations: currentRoute.locations}
|
||||
s.active[identity] = len(s.stack)
|
||||
s.visited[identity] = route
|
||||
s.stack = append(s.stack, route)
|
||||
diagnostics := s.visitItemSource(canonical, declaration, destination, relativeDestination, depth, info, body, declarations, processing)
|
||||
delete(s.active, identity)
|
||||
s.stack = s.stack[:len(s.stack)-1]
|
||||
return diagnostics
|
||||
}
|
||||
_, placed, err := placeDocument(body, placementDestination, body.Location, maxDocumentDepth, maxDocumentNodes)
|
||||
if err != nil {
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", &location)}
|
||||
}
|
||||
placementNodes := destinationWrapperNodeCount(destination)
|
||||
placementNodes := destinationWrapperNodeCount(placementDestination)
|
||||
if s.accounting.nodes+placementNodes > s.limits.nodes {
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &location)}
|
||||
}
|
||||
@@ -380,7 +463,7 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
|
||||
route := importRoute{canonical: canonical, locations: currentRoute.locations}
|
||||
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(info), payload: body, document: placed,
|
||||
destination: destination.clone(), relativeDestination: relativeDestination.clone(), processing: processing,
|
||||
destination: placementDestination.clone(), relativeDestination: relativeDestination.clone(), processing: processing,
|
||||
})
|
||||
s.active[identity] = len(s.stack)
|
||||
s.visited[identity] = route
|
||||
@@ -391,6 +474,195 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
func (s *importTraversal) sourceDestination(destination Path) Path {
|
||||
if !s.itemScope {
|
||||
return destination.clone()
|
||||
}
|
||||
return appendDestination(s.itemPrefix, destination)
|
||||
}
|
||||
|
||||
func (s *importTraversal) visitItemSource(canonical string, declaration importDeclaration, destination, relativeDestination Path, depth int, info SourceInfo, body *document.Node, declarations []importDeclaration, processing *sourceVersionProcessingResult) []Diagnostic {
|
||||
parentScope := s.scope
|
||||
parentItemScope, parentPrefix := s.itemScope, s.itemPrefix
|
||||
s.scope = acquisitionScope{}
|
||||
s.itemScope = true
|
||||
if parentItemScope {
|
||||
s.itemPrefix = appendDestination(parentPrefix, destination)
|
||||
} else {
|
||||
s.itemPrefix = destination.clone()
|
||||
}
|
||||
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(info), payload: body, document: body,
|
||||
processing: processing,
|
||||
})
|
||||
diagnostics := s.walkDeclarations(s.access.dir(canonical), declarations, depth, Path{})
|
||||
itemScope := s.scope
|
||||
s.scope, s.itemScope, s.itemPrefix = parentScope, parentItemScope, parentPrefix
|
||||
if len(diagnostics) != 0 {
|
||||
return diagnostics
|
||||
}
|
||||
documents := make([]*document.Node, 0, len(itemScope.contributions))
|
||||
for _, contribution := range itemScope.contributions {
|
||||
documents = append(documents, contribution.document)
|
||||
}
|
||||
completed, diagnostics := composeDocuments(documents...)
|
||||
if len(diagnostics) != 0 {
|
||||
prefix := destination.Index(0)
|
||||
if parentItemScope {
|
||||
prefix = appendDestination(parentPrefix, prefix)
|
||||
}
|
||||
return prependDiagnosticPaths(diagnostics, prefix)
|
||||
}
|
||||
itemScope.destination = Path{}
|
||||
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(itemScope)}
|
||||
projectNestedEvidence(completed, itemScope)
|
||||
pl := destination
|
||||
_, placed, err := placeDocumentWithMode(completed.root, pl, placementModeSequenceItem, body.Location, maxDocumentDepth, maxDocumentNodes)
|
||||
if err != nil {
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", &declaration.location)}
|
||||
}
|
||||
placementNodes := destinationWrapperNodeCountForMode(pl, placementModeSequenceItem)
|
||||
if s.accounting.nodes+placementNodes > s.limits.nodes {
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &declaration.location)}
|
||||
}
|
||||
s.accounting.nodes += placementNodes
|
||||
protectionRule := s.rule
|
||||
if s.versionState != nil && s.versionState.effectiveRule != nil {
|
||||
protectionRule = s.versionState.effectiveRule
|
||||
}
|
||||
if versionNode := fragmentVersionClaim(placed, protectionRule); versionNode != nil {
|
||||
versionLocation := publicLocation(versionNode.Location)
|
||||
return []Diagnostic{{
|
||||
Code: CodeImportVersion, Severity: SeverityError, Path: protectionRule.Path.clone(),
|
||||
Message: "imported fragments cannot contribute to the schema-version path", Primary: &versionLocation,
|
||||
}}
|
||||
}
|
||||
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(info), payload: completed.root, document: placed,
|
||||
destination: pl.clone(), relativeDestination: relativeDestination.clone(), processing: processing,
|
||||
nested: completed,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func prependDiagnosticPaths(diagnostics []Diagnostic, prefix Path) []Diagnostic {
|
||||
cloned := append([]Diagnostic(nil), diagnostics...)
|
||||
for index := range cloned {
|
||||
cloned[index].Path = appendDestination(prefix, cloned[index].Path)
|
||||
cloned[index].Primary = cloneLocation(cloned[index].Primary)
|
||||
cloned[index].Related = cloneLocations(cloned[index].Related)
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
func projectNestedEvidence(composed *composedDocument, scope acquisitionScope) {
|
||||
if composed == nil {
|
||||
return
|
||||
}
|
||||
for sourceIndex, contribution := range scope.contributions {
|
||||
if contribution.nested == nil {
|
||||
continue
|
||||
}
|
||||
start := 0
|
||||
for _, assigned := range composed.sequenceRanges {
|
||||
if assigned.sourceIndex == sourceIndex && assigned.path.String() == contribution.destination.String() {
|
||||
start = assigned.start
|
||||
break
|
||||
}
|
||||
}
|
||||
prefix := contribution.destination.Index(start)
|
||||
if sourceIndex < len(composed.applications) {
|
||||
outer := composed.applications[sourceIndex]
|
||||
structural := document.Clone(outer.value)
|
||||
if node, found := documentAtPath(structural, contribution.destination); found && node.Kind == document.Sequence && len(node.Sequence) > start {
|
||||
node.Sequence[start] = &document.Node{Kind: document.Mapping, Location: node.Sequence[start].Location}
|
||||
}
|
||||
outer.value = structural
|
||||
composed.applications[sourceIndex] = outer
|
||||
for _, nested := range contribution.nested.applications {
|
||||
projected := nested
|
||||
projected.path = appendDestination(prefix, nested.path)
|
||||
projected.sequences = cloneSequenceHistoryContributions(nested.sequences)
|
||||
for index := range projected.sequences {
|
||||
projected.sequences[index].path = appendDestination(prefix, projected.sequences[index].path)
|
||||
}
|
||||
composed.applications = append(composed.applications, projected)
|
||||
}
|
||||
}
|
||||
projectNestedOrigins(composed.origins, contribution.nested.root, contribution.nested.origins, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
func projectNestedOrigins(target map[string]Origin, root *document.Node, origins map[string]Origin, prefix Path) {
|
||||
var visit func(*document.Node, Path)
|
||||
visit = func(node *document.Node, path Path) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
if origin, ok := origins[path.String()]; ok {
|
||||
target[appendDestination(prefix, path).String()] = cloneOrigin(origin)
|
||||
}
|
||||
for _, entry := range node.Mapping {
|
||||
visit(entry.Value, path.Key(entry.Key))
|
||||
}
|
||||
for index, child := range node.Sequence {
|
||||
visit(child, path.Index(index))
|
||||
}
|
||||
}
|
||||
visit(root, Path{})
|
||||
}
|
||||
|
||||
func projectAcquiredDestinations(composed *composedDocument, scope acquisitionScope, sources []SourceInfo) {
|
||||
byID := make(map[string]int, len(sources))
|
||||
for index := range sources {
|
||||
byID[sources[index].ID] = index
|
||||
}
|
||||
var projectScope func(*composedDocument, acquisitionScope, Path, bool, string)
|
||||
projectScope = func(currentComposition *composedDocument, current acquisitionScope, prefix Path, insideItem bool, parentSourceID string) {
|
||||
for sourceIndex, contribution := range current.contributions {
|
||||
if contribution.source.ID == parentSourceID {
|
||||
continue
|
||||
}
|
||||
contributionPrefix := prefix
|
||||
contributionInsideItem := insideItem
|
||||
start := 0
|
||||
for _, assigned := range currentComposition.sequenceRanges {
|
||||
if assigned.sourceIndex == sourceIndex && assigned.path.String() == contribution.destination.String() {
|
||||
start = assigned.start
|
||||
break
|
||||
}
|
||||
}
|
||||
if contribution.source.placementMode == placementModeSequenceItem {
|
||||
container := appendDestination(contributionPrefix, contribution.destination)
|
||||
contribution.source.Destination = container.clone()
|
||||
contribution.source.destinationUnresolved = false
|
||||
current.contributions[sourceIndex].source = cloneSourceInfo(contribution.source)
|
||||
if position, ok := byID[contribution.source.ID]; ok {
|
||||
sources[position].Destination = container
|
||||
sources[position].destinationUnresolved = false
|
||||
}
|
||||
contributionPrefix = appendDestination(container, Path{}.Index(start))
|
||||
contributionInsideItem = true
|
||||
} else if contributionInsideItem {
|
||||
exactDestination := appendDestination(contributionPrefix, contribution.destination)
|
||||
contribution.source.Destination = exactDestination.clone()
|
||||
contribution.source.destinationUnresolved = false
|
||||
current.contributions[sourceIndex].source = cloneSourceInfo(contribution.source)
|
||||
if position, ok := byID[contribution.source.ID]; ok {
|
||||
sources[position].Destination = exactDestination
|
||||
sources[position].destinationUnresolved = false
|
||||
}
|
||||
}
|
||||
if contribution.nested != nil && contribution.nested.acquisition != nil {
|
||||
for _, nestedScope := range contribution.nested.acquisition {
|
||||
projectScope(contribution.nested, nestedScope, contributionPrefix, contributionInsideItem, contribution.source.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
projectScope(composed, scope, Path{}, false, "")
|
||||
}
|
||||
|
||||
func (s *importTraversal) readImport(name, sourceID string, optional bool) ([]byte, bool, *Diagnostic) {
|
||||
remaining := s.limits.bytes - s.accounting.bytes
|
||||
limit := min(int64(maxSourceBytes), remaining)
|
||||
|
||||
Reference in New Issue
Block a user