Add source-local version extraction

This commit is contained in:
2026-10-15 15:15:05 +00:00
parent 2fcf892324
commit 49218a836b
3 changed files with 740 additions and 0 deletions

View File

@@ -270,6 +270,23 @@ No environmental limitation affected validation; checks ran on Linux/amd64.
Validate: helper/version-path/directive/inheritance-control tests and everyday
check. Done: the processor is tested independently of public loading.
Completion evidence: added a private source-local processor that resolves the
configured path against the original document, separates imports, records
private path/ancestor/location evidence, and consumes accepted contribution
headers only from an owned payload copy. Missing and obstructed paths retain
their cause and nearest available location; required absence fails before the
checker, while optional main absence still invokes it. Structural directive
overlap is rejected before payload consumption. Focused coverage includes the
version vocabulary, malformed nodes, contextual numeric keys and indices,
empty ancestors, sequence deletion, and actual versus unrelated structural
overlap. These checks passed with Go `go1.26.8 linux/amd64`:
- `GOTOOLCHAIN=local GOPROXY=off go test ./internal/engine -run 'SourceVersion|Version|Import|Underlay|Overlay|Destination|Inheritance'`
- `GOTOOLCHAIN=local ./scripts/check.sh`
- `git diff --check`
No environmental limitation affected validation; checks ran on Linux/amd64.
## Stage 4 — Add private mode state and main-document integration
1. Add the engine mode to ordinary/layered options and loadRequest. Validate it at

View File

@@ -0,0 +1,339 @@
package engine
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
type sourceVersionMode uint8
const (
sourceVersionModeDefault sourceVersionMode = iota
sourceVersionModeIfPresent
sourceVersionModeRequired
)
type sourceVersionRole uint8
const (
sourceVersionMain sourceVersionRole = iota
sourceVersionContribution
)
type sourceVersionAncestor struct {
path Path
kind document.Kind
}
type sourceVersionEvidence struct {
path Path
resolvedPath Path
destination Path
version Version
location *Location
ancestors []sourceVersionAncestor
absence sourceVersionPathAbsence
checked bool
redacted bool
}
type sourceVersionProcessingResult struct {
payload *document.Node
imports []importDeclaration
evidence *sourceVersionEvidence
diagnostics []Diagnostic
}
type sourceVersionPathAbsence uint8
const (
sourceVersionPathPresent sourceVersionPathAbsence = iota
sourceVersionPathInvalid
sourceVersionMemberMissing
sourceVersionIndexUnavailable
sourceVersionPathObstructed
)
type sourceVersionPath struct {
node *document.Node
present bool
absence sourceVersionPathAbsence
nearest document.Location
resolvedPath Path
ancestors []sourceVersionAncestor
}
func processSourceVersion(root *document.Node, rule *VersionRule, role sourceVersionRole, destination Path, mode sourceVersionMode) sourceVersionProcessingResult {
if role != sourceVersionMain && role != sourceVersionContribution {
return sourceVersionProcessingResult{diagnostics: []Diagnostic{sourceVersionOptionsDiagnostic("source role is invalid")}}
}
if mode != sourceVersionModeDefault && mode != sourceVersionModeIfPresent && mode != sourceVersionModeRequired {
return sourceVersionProcessingResult{diagnostics: []Diagnostic{sourceVersionOptionsDiagnostic("source-version mode is invalid")}}
}
if mode != sourceVersionModeDefault && (rule == nil || rule.Check == nil || !rule.Path.valid()) {
return sourceVersionProcessingResult{diagnostics: []Diagnostic{sourceVersionOptionsDiagnostic("source-version checking requires a valid version rule and checker")}}
}
if root == nil {
if role == sourceVersionMain && mode != sourceVersionModeDefault {
return sourceVersionProcessingResult{diagnostics: canonicalDiagnostics(reportVersion(rule, Version{}, nil))}
}
return sourceVersionProcessingResult{}
}
payload, imports, diagnostics := splitImportDirectives(root)
if len(diagnostics) != 0 {
return sourceVersionProcessingResult{diagnostics: diagnostics}
}
result := sourceVersionProcessingResult{payload: payload, imports: imports}
if mode == sourceVersionModeDefault {
return result
}
resolved := resolveSourceVersionPath(root, rule.Path)
evidence := &sourceVersionEvidence{
path: rule.Path.clone(), resolvedPath: resolved.resolvedPath,
destination: destination.clone(), ancestors: resolved.ancestors,
absence: resolved.absence,
}
location := resolved.nearest
if resolved.present {
location = resolved.node.Location
}
if locationKnown(location) {
public := publicLocation(location)
evidence.location = &public
}
result.evidence = evidence
interpreted := interpretVersion(rule, root)
evidence.version = interpreted.version
if sourceVersionControlOverlap(root, evidence.resolvedPath) {
evidence.redactText()
result.payload = nil
result.diagnostics = []Diagnostic{sourceVersionOverlapDiagnostic(rule, evidence)}
return result
}
if hasErrors(interpreted.diagnostics) {
evidence.redactText()
result.payload = nil
result.diagnostics = canonicalDiagnostics(interpreted.diagnostics)
return result
}
if !interpreted.version.Present {
if mode == sourceVersionModeRequired {
evidence.redactText()
result.payload = nil
result.diagnostics = []Diagnostic{sourceVersionMissingDiagnostic(rule, evidence)}
return result
}
if role == sourceVersionContribution {
return result
}
}
evidence.checked = true
result.diagnostics = canonicalDiagnostics(reportVersion(rule, interpreted.version, root))
if hasErrors(result.diagnostics) {
evidence.redactText()
result.payload = nil
return result
}
if role == sourceVersionContribution && interpreted.version.Present {
if !removeSourceVersionNode(result.payload, evidence.resolvedPath) {
evidence.redactText()
result.payload = nil
result.diagnostics = []Diagnostic{sourceVersionOptionsDiagnostic("selected source version could not be removed from its owned payload")}
return result
}
}
return result
}
func sourceVersionOptionsDiagnostic(message string) Diagnostic {
return Diagnostic{Code: CodeOptionsInvalid, Severity: SeverityError, Message: message}
}
func sourceVersionMissingDiagnostic(rule *VersionRule, evidence *sourceVersionEvidence) Diagnostic {
return Diagnostic{
Code: CodeVersionError, Severity: SeverityError, Path: rule.Path.clone(),
Message: "required schema version is missing", Primary: cloneLocation(evidence.location),
}
}
func sourceVersionOverlapDiagnostic(rule *VersionRule, evidence *sourceVersionEvidence) Diagnostic {
return Diagnostic{
Code: CodeVersionError, Severity: SeverityError, Path: rule.Path.clone(),
Message: "schema version path overlaps a structural directive", Primary: cloneLocation(evidence.location),
}
}
func (evidence *sourceVersionEvidence) redactText() {
if evidence == nil {
return
}
evidence.version.Text = ""
evidence.redacted = true
}
func resolveSourceVersionPath(root *document.Node, path Path) sourceVersionPath {
resolved := sourceVersionPath{absence: sourceVersionPathInvalid}
if root == nil || !path.valid() {
return resolved
}
current := root
resolved.nearest = current.Location
resolved.ancestors = append(resolved.ancestors, sourceVersionAncestor{kind: current.Kind})
for _, segment := range path.segments {
switch current.Kind {
case document.Mapping:
if segment.kind == pathSegmentIndex {
resolved.absence = sourceVersionPathObstructed
return resolved
}
resolved.resolvedPath = resolved.resolvedPath.Key(segment.key)
found := false
for _, entry := range current.Mapping {
if entry.Key == segment.key {
current = entry.Value
found = true
break
}
}
if !found {
resolved.absence = sourceVersionMemberMissing
return resolved
}
case document.Sequence:
index, ok := schemaIndex(segment)
if !ok {
resolved.absence = sourceVersionIndexUnavailable
return resolved
}
resolved.resolvedPath = resolved.resolvedPath.Index(index)
if index >= len(current.Sequence) {
resolved.absence = sourceVersionIndexUnavailable
return resolved
}
current = current.Sequence[index]
default:
resolved.absence = sourceVersionPathObstructed
return resolved
}
resolved.nearest = current.Location
resolved.ancestors = append(resolved.ancestors, sourceVersionAncestor{
path: resolved.resolvedPath.clone(), kind: current.Kind,
})
}
resolved.node = current
resolved.present = true
resolved.absence = sourceVersionPathPresent
return resolved
}
func sourceVersionControlOverlap(root *document.Node, path Path) bool {
if root == nil || !path.valid() {
return false
}
if root.Kind == document.Mapping {
for _, entry := range root.Mapping {
if entry.Key == directiveKey && pathHasPrefix(path, Keys(directiveKey)) {
return true
}
}
}
return sourceVersionInheritanceOverlap(root, Path{}, path)
}
func sourceVersionInheritanceOverlap(node *document.Node, current, path Path) bool {
if node == nil {
return false
}
if node.Kind == document.Mapping {
if _, found := inheritanceDirective(node); found {
controlPath := current.Key(inheritanceKey)
if pathHasPrefix(path, controlPath) {
return true
}
}
for _, entry := range node.Mapping {
if entry.Key == inheritanceKey {
continue
}
if sourceVersionInheritanceOverlap(entry.Value, current.Key(entry.Key), path) {
return true
}
}
} else if node.Kind == document.Sequence {
for index, child := range node.Sequence {
if sourceVersionInheritanceOverlap(child, current.Index(index), path) {
return true
}
}
}
return false
}
func pathHasPrefix(path, prefix Path) bool {
if !path.valid() || !prefix.valid() || len(prefix.segments) > len(path.segments) {
return false
}
for index, segment := range prefix.segments {
candidate := path.segments[index]
if candidate.kind != segment.kind || candidate.key != segment.key || candidate.index != segment.index {
return false
}
}
return true
}
func removeSourceVersionNode(root *document.Node, path Path) bool {
if root == nil || !path.valid() || len(path.segments) == 0 {
return false
}
current := root
for _, segment := range path.segments[:len(path.segments)-1] {
switch current.Kind {
case document.Mapping:
if segment.kind == pathSegmentIndex {
return false
}
found := false
for _, entry := range current.Mapping {
if entry.Key == segment.key {
current = entry.Value
found = true
break
}
}
if !found {
return false
}
case document.Sequence:
index, ok := schemaIndex(segment)
if !ok || index >= len(current.Sequence) {
return false
}
current = current.Sequence[index]
default:
return false
}
}
last := path.segments[len(path.segments)-1]
switch current.Kind {
case document.Mapping:
if last.kind == pathSegmentIndex {
return false
}
for index, entry := range current.Mapping {
if entry.Key == last.key {
current.Mapping = append(current.Mapping[:index], current.Mapping[index+1:]...)
return true
}
}
case document.Sequence:
index, ok := schemaIndex(last)
if !ok || index >= len(current.Sequence) {
return false
}
current.Sequence = append(current.Sequence[:index], current.Sequence[index+1:]...)
return true
}
return false
}

View File

@@ -0,0 +1,384 @@
package engine
import (
"testing"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
func sourceVersionTestDocument(t *testing.T, input string) *document.Node {
t.Helper()
parsed, diagnostics := parseSource(Bytes("source-version.yml", []byte(input)), "source-version", defaultParseLimits())
if len(diagnostics) != 0 {
t.Fatalf("parse diagnostics = %#v", diagnostics)
}
return parsed.document
}
func requireSourceVersionDiagnostic(t *testing.T, diagnostics []Diagnostic, code Code, path string) Diagnostic {
t.Helper()
for _, diagnostic := range diagnostics {
if diagnostic.Code == code && diagnostic.Path.String() == path {
return diagnostic
}
}
t.Fatalf("diagnostics = %#v, want %s at %q", diagnostics, code, path)
return Diagnostic{}
}
func TestSourceVersionExtractionPreservesVersionVocabulary(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
want Version
}{
{name: "string", input: "version: \"app.v7\"\n", want: Version{Present: true, Text: "app.v7"}},
{name: "integer", input: "version: 123456789012345678901234567890\n", want: Version{Present: true, Text: "123456789012345678901234567890"}},
{name: "null", input: "version: null\n", want: Version{Present: true, Null: true}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
root := sourceVersionTestDocument(t, test.input)
var checked Version
rule := &VersionRule{Path: Keys("version"), Check: func(version Version, _ *Validation) error {
checked = version
return nil
}}
result := processSourceVersion(root, rule, sourceVersionContribution, Keys("collections", "weather"), sourceVersionModeIfPresent)
if len(result.diagnostics) != 0 || !result.evidence.checked || result.evidence.version != test.want || checked != test.want {
t.Fatalf("result = %#v, checked = %#v", result, checked)
}
if _, found := documentAtPath(result.payload, rule.Path); found {
t.Fatal("accepted non-main version remained in the payload")
}
if node, found := documentAtPath(root, rule.Path); !found || node == nil {
t.Fatal("source document was mutated while consuming its version")
}
if result.evidence.destination.String() != "/collections/weather" {
t.Fatalf("evidence destination = %q", result.evidence.destination.String())
}
})
}
}
func TestSourceVersionExtractionHandlesMissingAndMalformedValues(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
path Path
mode sourceVersionMode
role sourceVersionRole
wantCode Code
wantPresent bool
wantCallback bool
wantAbsence sourceVersionPathAbsence
}{
{
name: "missing mapping member is optional absence", input: "settings: {}\n",
path: Keys("settings", "version"), mode: sourceVersionModeIfPresent,
role: sourceVersionContribution, wantPresent: false, wantAbsence: sourceVersionMemberMissing,
},
{
name: "missing below scalar is optional absence", input: "settings: scalar\n",
path: Keys("settings", "version"), mode: sourceVersionModeIfPresent,
role: sourceVersionContribution, wantPresent: false, wantAbsence: sourceVersionPathObstructed,
},
{
name: "required absence reports nearest location", input: "settings: scalar\n",
path: Keys("settings", "version"), mode: sourceVersionModeRequired,
role: sourceVersionContribution, wantCode: CodeVersionError, wantAbsence: sourceVersionPathObstructed,
},
{
name: "unavailable sequence index is absence", input: "versions: [1]\n",
path: Keys("versions").Index(3), mode: sourceVersionModeRequired,
role: sourceVersionContribution, wantCode: CodeVersionError, wantAbsence: sourceVersionIndexUnavailable,
},
{
name: "required actual main absence", input: "name: demo\n", path: Keys("version"),
mode: sourceVersionModeRequired, role: sourceVersionMain, wantCode: CodeVersionError, wantAbsence: sourceVersionMemberMissing,
},
{
name: "main optional absence invokes checker", input: "name: demo\n", path: Keys("version"),
mode: sourceVersionModeIfPresent, role: sourceVersionMain, wantCallback: true, wantAbsence: sourceVersionMemberMissing,
},
{name: "boolean", input: "version: true\n", path: Keys("version"), mode: sourceVersionModeIfPresent, role: sourceVersionContribution, wantCode: CodeVersionType, wantPresent: true},
{name: "fraction", input: "version: 4.5\n", path: Keys("version"), mode: sourceVersionModeIfPresent, role: sourceVersionContribution, wantCode: CodeVersionType, wantPresent: true},
{name: "mapping", input: "version: {}\n", path: Keys("version"), mode: sourceVersionModeIfPresent, role: sourceVersionContribution, wantCode: CodeVersionType, wantPresent: true},
{name: "sequence", input: "version: []\n", path: Keys("version"), mode: sourceVersionModeIfPresent, role: sourceVersionContribution, wantCode: CodeVersionType, wantPresent: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
root := sourceVersionTestDocument(t, test.input)
called := false
rule := &VersionRule{Path: test.path, Check: func(Version, *Validation) error {
called = true
return nil
}}
result := processSourceVersion(root, rule, test.role, Path{}, test.mode)
if called != test.wantCallback {
t.Fatalf("checker called = %v, want %v", called, test.wantCallback)
}
if test.wantCode != "" {
diagnostic := requireSourceVersionDiagnostic(t, result.diagnostics, test.wantCode, test.path.String())
if result.payload != nil {
t.Fatalf("failed processing returned payload %#v", result.payload)
}
if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-version" || diagnostic.Primary.Line != 1 {
t.Fatalf("diagnostic location = %#v", diagnostic.Primary)
}
} else if len(result.diagnostics) != 0 {
t.Fatalf("diagnostics = %#v", result.diagnostics)
}
if result.evidence == nil || result.evidence.version.Present != test.wantPresent {
t.Fatalf("evidence = %#v", result.evidence)
}
if result.evidence.absence != test.wantAbsence {
t.Fatalf("absence cause = %v, want %v", result.evidence.absence, test.wantAbsence)
}
if test.wantCode == CodeVersionType && (!result.evidence.redacted || result.evidence.version.Text != "") {
t.Fatalf("malformed value was retained: %#v", result.evidence)
}
if !test.wantCallback && result.evidence.checked {
t.Fatalf("evidence incorrectly records a callback: %#v", result.evidence)
}
})
}
}
func TestSourceVersionExtractionConsumesOnlySelectedNodes(t *testing.T) {
t.Parallel()
destination := Keys("collections", "weather")
root := sourceVersionTestDocument(t, "settings:\n version: 8\n")
originalSettings, _ := documentAtPath(root, Keys("settings"))
result := processSourceVersion(root, &VersionRule{
Path: Keys("settings", "version"), Check: func(Version, *Validation) error { return nil },
}, sourceVersionContribution, destination, sourceVersionModeIfPresent)
settings, found := documentAtPath(result.payload, Keys("settings"))
if len(result.diagnostics) != 0 || !found || settings.Kind != document.Mapping || len(settings.Mapping) != 0 {
t.Fatalf("payload = %#v, diagnostics = %#v", result.payload, result.diagnostics)
}
if settings.Location != originalSettings.Location {
t.Fatalf("authored ancestor location changed: got %#v, want %#v", settings.Location, originalSettings.Location)
}
if original, found := documentAtPath(root, Keys("settings", "version")); !found || original.Text != "8" {
t.Fatalf("original source changed: %#v, found %v", original, found)
}
if len(result.evidence.ancestors) != 3 || result.evidence.ancestors[1].kind != document.Mapping {
t.Fatalf("ancestor shape = %#v", result.evidence.ancestors)
}
sequenceRoot := sourceVersionTestDocument(t, "versions: [5, 7, 9]\n")
originalVersions, _ := documentAtPath(sequenceRoot, Keys("versions"))
originalLastLocation := originalVersions.Sequence[2].Location
sequencePath, err := ParsePath("/versions/1")
if err != nil {
t.Fatal(err)
}
sequenceResult := processSourceVersion(sequenceRoot, &VersionRule{
Path: sequencePath, Check: func(version Version, _ *Validation) error {
if version.Text != "7" {
t.Fatalf("checked version = %#v", version)
}
return nil
},
}, sourceVersionContribution, Path{}, sourceVersionModeRequired)
versions, found := documentAtPath(sequenceResult.payload, Keys("versions"))
if len(sequenceResult.diagnostics) != 0 || !found || versions.Kind != document.Sequence || len(versions.Sequence) != 2 || versions.Sequence[0].Text != "5" || versions.Sequence[1].Text != "9" {
t.Fatalf("sequence payload = %#v, diagnostics = %#v", sequenceResult.payload, sequenceResult.diagnostics)
}
if versions.Sequence[1].Location != originalLastLocation {
t.Fatalf("remaining sequence location changed: got %#v, want %#v", versions.Sequence[1].Location, originalLastLocation)
}
if len(sequenceResult.evidence.resolvedPath.segments) != 2 || sequenceResult.evidence.resolvedPath.segments[1].kind != pathSegmentIndex || sequenceResult.evidence.resolvedPath.segments[1].index != 1 {
t.Fatalf("original sequence path = %#v", sequenceResult.evidence.resolvedPath)
}
itemRoot := sourceVersionTestDocument(t, "items:\n - name: first\n - version: 12\n")
itemResult := processSourceVersion(itemRoot, &VersionRule{
Path: Keys("items").Index(1).Key("version"), Check: func(Version, *Validation) error { return nil },
}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
items, found := documentAtPath(itemResult.payload, Keys("items"))
if len(itemResult.diagnostics) != 0 || !found || len(items.Sequence) != 2 || items.Sequence[1].Kind != document.Mapping || len(items.Sequence[1].Mapping) != 0 {
t.Fatalf("sequence mapping payload = %#v, diagnostics = %#v", itemResult.payload, itemResult.diagnostics)
}
emptySequenceRoot := sourceVersionTestDocument(t, "versions: [7]\n")
emptySequenceResult := processSourceVersion(emptySequenceRoot, &VersionRule{
Path: Keys("versions").Index(0), Check: func(Version, *Validation) error { return nil },
}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
emptySequence, found := documentAtPath(emptySequenceResult.payload, Keys("versions"))
if len(emptySequenceResult.diagnostics) != 0 || !found || emptySequence.Kind != document.Sequence || len(emptySequence.Sequence) != 0 {
t.Fatalf("empty sequence payload = %#v, diagnostics = %#v", emptySequenceResult.payload, emptySequenceResult.diagnostics)
}
}
func TestSourceVersionExtractionResolvesContextualPathSegments(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
path Path
wantRemainingPath bool
wantRemainingText string
}{
{name: "literal numeric mapping key", input: "versions:\n \"0\": 3\n other: 4\n", path: Keys("versions", "0")},
{name: "parsed numeric mapping key", input: "versions:\n \"0\": 3\n other: 4\n", path: mustParseSourceVersionPath(t, "/versions/0")},
{name: "parsed sequence index", input: "versions: [3, 4]\n", path: mustParseSourceVersionPath(t, "/versions/0"), wantRemainingPath: true, wantRemainingText: "4"},
{name: "explicit sequence index", input: "versions: [3, 4]\n", path: Keys("versions").Index(0), wantRemainingPath: true, wantRemainingText: "4"},
{name: "escaped mapping key", input: "a/b:\n version: 3\n", path: mustParseSourceVersionPath(t, "/a~1b/version")},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
root := sourceVersionTestDocument(t, test.input)
result := processSourceVersion(root, &VersionRule{
Path: test.path, Check: func(version Version, _ *Validation) error {
if version.Text != "3" {
t.Errorf("checked version = %#v", version)
}
return nil
},
}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
if len(result.diagnostics) != 0 {
t.Fatalf("diagnostics = %#v", result.diagnostics)
}
remaining, found := documentAtPath(result.payload, test.path)
if found != test.wantRemainingPath || found && remaining.Text != test.wantRemainingText {
t.Fatalf("remaining selected path = %#v, found = %v", remaining, found)
}
if !result.evidence.version.Present || result.evidence.version.Text != "3" {
t.Fatalf("evidence = %#v", result.evidence)
}
})
}
}
func TestSourceVersionExtractionRejectsStructuralOverlap(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
path Path
wantCode Code
}{
{
name: "inside import declaration", input: "configkit:\n imports:\n - path: child.yml\n",
path: Keys("configkit", "imports").Index(0).Key("path"), wantCode: CodeVersionError,
},
{
name: "extends control", input: "settings:\n extends: parent\n",
path: Keys("settings", "extends"), wantCode: CodeVersionError,
},
{
name: "invalid extends subtree", input: "settings:\n extends:\n version: 1\n",
path: Keys("settings", "extends", "version"), wantCode: CodeVersionError,
},
{
name: "malformed directive cannot be consumed", input: "configkit:\n imports: true\n",
path: Keys("configkit", "imports"), wantCode: CodeImportInvalid,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
root := sourceVersionTestDocument(t, test.input)
called := false
result := processSourceVersion(root, &VersionRule{Path: test.path, Check: func(Version, *Validation) error {
called = true
return nil
}}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
diagnostic := requireSourceVersionDiagnostic(t, result.diagnostics, test.wantCode, test.path.String())
if called || result.payload != nil {
t.Fatalf("invalid overlap produced payload %#v or called checker %v", result.payload, called)
}
if test.wantCode == CodeVersionError && (result.evidence == nil || !result.evidence.redacted || result.evidence.version.Text != "") {
t.Fatalf("overlap evidence was not conservatively redacted: %#v", result.evidence)
}
if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-version" {
t.Fatalf("diagnostic location = %#v", diagnostic.Primary)
}
})
}
unrelatedRoot := sourceVersionTestDocument(t, "configkit:\n imports:\n - path: child.yml\nmetadata:\n version: 4\n")
unrelatedResult := processSourceVersion(unrelatedRoot, &VersionRule{
Path: Keys("metadata", "version"), Check: func(Version, *Validation) error { return nil },
}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
metadata, found := documentAtPath(unrelatedResult.payload, Keys("metadata"))
if len(unrelatedResult.diagnostics) != 0 || !found || len(metadata.Mapping) != 0 || len(unrelatedResult.imports) != 1 {
t.Fatalf("unrelated directive result = %#v", unrelatedResult)
}
ordinaryRoot := sourceVersionTestDocument(t, "extends_version: 4\n")
ordinaryResult := processSourceVersion(ordinaryRoot, &VersionRule{
Path: Keys("extends_version"), Check: func(Version, *Validation) error { return nil },
}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
if len(ordinaryResult.diagnostics) != 0 || ordinaryResult.payload == nil {
t.Fatalf("ordinary extends_version key was rejected: %#v", ordinaryResult)
}
}
func TestSourceVersionDefaultModeDoesNotCheckOrConsume(t *testing.T) {
t.Parallel()
root := sourceVersionTestDocument(t, "version: 2\n")
called := false
result := processSourceVersion(root, &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error {
called = true
return nil
}}, sourceVersionContribution, Path{}, sourceVersionModeDefault)
if len(result.diagnostics) != 0 || called || result.evidence != nil {
t.Fatalf("default result = %#v, checker called = %v", result, called)
}
if node, found := documentAtPath(result.payload, Keys("version")); !found || node.Text != "2" {
t.Fatalf("default payload = %#v", result.payload)
}
}
func TestSourceVersionNoSourceMainRetainsAbsentCallback(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
mode sourceVersionMode
}{
{name: "if present", mode: sourceVersionModeIfPresent},
{name: "required", mode: sourceVersionModeRequired},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
called := false
result := processSourceVersion(nil, &VersionRule{
Path: Keys("version"),
Check: func(version Version, _ *Validation) error {
called = true
if version != (Version{}) {
t.Errorf("NoSource version = %#v", version)
}
return nil
},
}, sourceVersionMain, Path{}, test.mode)
if !called || len(result.diagnostics) != 0 || result.evidence != nil || result.payload != nil {
t.Fatalf("NoSource result = %#v, callback called = %v", result, called)
}
})
}
}
func mustParseSourceVersionPath(t *testing.T, input string) Path {
t.Helper()
path, err := ParsePath(input)
if err != nil {
t.Fatal(err)
}
return path
}