Add explicit path import traversal

This commit is contained in:
2026-09-06 20:41:40 +00:00
parent 3257b2124c
commit 9f04c09be3
15 changed files with 1233 additions and 118 deletions

49
load.go
View File

@@ -17,7 +17,8 @@ type Definition[T any] struct {
}
// Options contains the application definition and per-load input selections.
// The current single-source boundary rejects features scheduled for later work.
// Imports enables explicit path imports; glob imports and later precedence layers
// remain unavailable until their owning features are implemented.
type Options[T any] struct {
Definition Definition[T]
Imports bool
@@ -93,8 +94,9 @@ func (r *Result[T]) Diagnostics() []Diagnostic {
return cloneDiagnostics(r.diagnostics)
}
// Load parses and strictly decodes zero or one source. It accepts at most one
// Options value and returns no result when any error is reported.
// Load parses and strictly decodes zero or one selected source plus explicitly
// enabled imports. It accepts at most one Options value and returns no result when
// any error is reported.
func Load[T any](source Source, supplied ...Options[T]) (*Result[T], error) {
accumulator := diagnosticAccumulator{}
options, diagnostics := selectOptions(source, supplied)
@@ -104,15 +106,30 @@ func Load[T any](source Source, supplied ...Options[T]) (*Result[T], error) {
}
parsed, diagnostics := parseSource(source, "source-1", defaultParseLimits())
if parsed.source != nil {
accumulator.sources = append(accumulator.sources, *parsed.source)
}
if len(diagnostics) != 0 {
if parsed.source != nil {
accumulator.sources = append(accumulator.sources, *parsed.source)
}
accumulator.add(diagnostics...)
return nil, accumulator.loadError()
}
if parsed.document != nil {
directiveDiagnostics := disabledDirectiveDiagnostics(parsed.document, Path{})
inputDocument := parsed.document
var composition *composedDocument
if options.Imports {
composition, accumulator.sources, diagnostics = loadImportedDocuments(source, options.Filesystem, parsed, options.Definition.Version, defaultImportLimits())
if len(diagnostics) != 0 {
accumulator.add(diagnostics...)
return nil, accumulator.loadError()
}
inputDocument = composition.root
} else {
if parsed.source != nil {
accumulator.sources = append(accumulator.sources, *parsed.source)
}
}
if !options.Imports && inputDocument != nil {
directiveDiagnostics := disabledDirectiveDiagnostics(inputDocument, Path{})
if len(directiveDiagnostics) != 0 {
accumulator.add(directiveDiagnostics...)
return nil, accumulator.loadError()
@@ -125,7 +142,7 @@ func Load[T any](source Source, supplied ...Options[T]) (*Result[T], error) {
return nil, accumulator.loadError()
}
versionDiagnostics := checkVersion(options.Definition.Version, parsed.document)
versionDiagnostics := checkVersion(options.Definition.Version, inputDocument)
accumulator.add(versionDiagnostics...)
if hasErrors(versionDiagnostics) {
return nil, accumulator.loadError()
@@ -135,20 +152,23 @@ func Load[T any](source Source, supplied ...Options[T]) (*Result[T], error) {
if options.Definition.Defaults != nil {
defaults = reflect.ValueOf(options.Definition.Defaults())
}
decoded, decodeIssues := decodeConfiguration(schema, defaults, parsed.document)
decoded, decodeIssues := decodeConfiguration(schema, defaults, inputDocument)
if len(decodeIssues) != 0 {
for _, issue := range decodeIssues {
accumulator.add(diagnosticFromDecodeIssue(issue))
}
return nil, accumulator.loadError()
}
if composition != nil {
applyCompositionOrigins(&decoded, composition.origins)
}
configuration := decoded.value.Interface().(T)
metadata := metadataFromDecoded(decoded)
final := decoded.final
var resolutionSnapshots []resolutionSnapshot
if options.Definition.Resolve != nil {
resolved := runResolution(&configuration, options.Definition.Resolve, schema, decoded, parsed.document)
resolved := runResolution(&configuration, options.Definition.Resolve, schema, decoded, inputDocument)
accumulator.add(resolved.diagnostics...)
if hasErrors(resolved.diagnostics) {
return nil, accumulator.loadError()
@@ -158,7 +178,7 @@ func Load[T any](source Source, supplied ...Options[T]) (*Result[T], error) {
applyResolutionMetadata(&metadata, resolved)
}
if options.Definition.Validate != nil {
validationDiagnostics := runValidation(&configuration, options.Definition.Validate, schema, final, parsed.document)
validationDiagnostics := runValidation(&configuration, options.Definition.Validate, schema, final, inputDocument)
accumulator.add(validationDiagnostics...)
if hasErrors(validationDiagnostics) {
return nil, accumulator.loadError()
@@ -187,13 +207,10 @@ func selectOptions[T any](source Source, supplied []Options[T]) (Options[T], []D
if definition.Secret != nil {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsUnavailable, "dynamic secret classification is not available in this release boundary"))
}
if options.Imports {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsUnavailable, "configuration imports are not available in this release boundary"))
}
if options.Profile != "" && options.Profile != "default" {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsUnavailable, "configuration profiles are not available in this release boundary"))
}
if options.Filesystem != nil {
if options.Filesystem != nil && !options.Imports {
code := CodeOptionsUnavailable
message := "filesystem context is not available in this release boundary"
if source.kind == sourceNone {