A Rule was exactly a Check<Model.Value, Model.Value>: it fired on every node, switched on the value itself, and carried the root. It was therefore a second copy of the one check machinery, the duplication the validation idiom exists to prevent (principle 10), now also resolved for the tagged layer. Remove Rule and its Context. The model validator holds only checks; its default is the type-directed BuiltinCheck.mappingKeysAreUnique; validating takes a Check. Custom whole-tree rules become Check<Model.Value, Model.Value> with the former context.root now context.document. Behaviour is byte-identical: the mapping-key builtin reports the same issues at the same paths in the same DFS order, so the public validate/collect/blank surface is unchanged. All 399 tests pass; the coverage gate reconciles to 60 public types.
87 lines
3.2 KiB
Swift
87 lines
3.2 KiB
Swift
@testable import PureYAML
|
|
import Testing
|
|
|
|
@Suite("Validation Built-in Rules")
|
|
struct ValidationBuiltinRuleTests {
|
|
@Test("Validates preserved value trees that ordinary YAML loaders may collapse")
|
|
func test_validatesPreservedValueTreesThatOrdinaryYAMLLoadersMayCollapse() {
|
|
let result = PureYAML.Validation.Validator().collect(preservedDuplicateKeyValue)
|
|
|
|
#expect(result.issues == expectedIssues)
|
|
#expect(result.issues.map(\.description) == [
|
|
"error: Duplicate mapping key 'title' at $.title",
|
|
"error: Duplicate mapping key 'items' at $.items",
|
|
"error: Duplicate mapping key '/users' at $.paths[\"/users\"]",
|
|
"error: Duplicate mapping key 'name.with.dot' at $.items[0][\"name.with.dot\"]",
|
|
])
|
|
#expect(!result.issues.contains(.init(
|
|
severity: .error,
|
|
reason: "Duplicate mapping key 'paths'",
|
|
path: .init([.key("paths")]),
|
|
)))
|
|
expectValidationError(preservedDuplicateKeyValue) { collection in
|
|
#expect(collection.issues == expectedIssues)
|
|
#expect(collection.description == expectedIssues.map(\.description).joined(separator: "\n"))
|
|
}
|
|
}
|
|
|
|
@Test("Mapping key uniqueness is scoped to mappings by type-directed dispatch")
|
|
func test_mappingKeyUniquenessIsScopedToMappings() {
|
|
let validator = PureYAML.Validation.Validator()
|
|
|
|
// A scalar value carries no mapping, so the mapping-keyed check never fires on it.
|
|
#expect(validator.collect(.string("subject")).issues.isEmpty)
|
|
|
|
// A mapping with a duplicate key reports exactly one issue, at the key's path.
|
|
#expect(validator.collect(.mapping(.init([
|
|
.init(key: "title", value: .string("one")),
|
|
.init(key: "title", value: .string("two")),
|
|
]))).issues == [
|
|
.init(
|
|
severity: .error,
|
|
reason: "Duplicate mapping key 'title'",
|
|
path: .init([.key("title")]),
|
|
),
|
|
])
|
|
}
|
|
}
|
|
|
|
private let preservedDuplicateKeyValue = PureYAML.Model.Value.mapping(.init([
|
|
.init(key: "title", value: .string("First")),
|
|
.init(key: "title", value: .string("Second")),
|
|
.init(key: "paths", value: .mapping(.init([
|
|
.init(key: "/users", value: .string("first")),
|
|
.init(key: "/users", value: .string("second")),
|
|
]))),
|
|
.init(key: "items", value: .sequence([
|
|
.mapping(.init([
|
|
.init(key: "name.with.dot", value: .string("one")),
|
|
.init(key: "name.with.dot", value: .string("two")),
|
|
])),
|
|
])),
|
|
.init(key: "items", value: .sequence([])),
|
|
]))
|
|
|
|
private let expectedIssues: [PureYAML.Validation.Issue] = [
|
|
.init(
|
|
severity: .error,
|
|
reason: "Duplicate mapping key 'title'",
|
|
path: .init([.key("title")]),
|
|
),
|
|
.init(
|
|
severity: .error,
|
|
reason: "Duplicate mapping key 'items'",
|
|
path: .init([.key("items")]),
|
|
),
|
|
.init(
|
|
severity: .error,
|
|
reason: "Duplicate mapping key '/users'",
|
|
path: .init([.key("paths"), .key("/users")]),
|
|
),
|
|
.init(
|
|
severity: .error,
|
|
reason: "Duplicate mapping key 'name.with.dot'",
|
|
path: .init([.key("items"), .index(0), .key("name.with.dot")]),
|
|
),
|
|
]
|