PureYAML/Tests/ValidationCorpusSupport.swift
Mihaela Mihaljevic bf2dc4ece7 refactor(validation): rename framework to match the validation idiom
The validation module already implemented the composable-rule idiom under
its own names (Check/Issue/SubjectContext) and a value-type Validator with
duplicated traversal machinery per layer. Renamed the core types to the
canonical names (Validation/ValidationError/ValidationErrorCollection/
ValidationContext/BuiltinValidation/AnyValidation), converted Validator to
a reference-type class with the two-tier default-rule split, and genericized
it over both Subject and Document via a new Traversable protocol so the
model and tagged layers share one walk engine instead of two copies. Wired
the JSON-representability rule set into the same shape and closed the two
missing pieces (unwrap combinator, JSON coverage/config-pin tests).
2026-07-12 18:10:00 +02:00

162 lines
5.0 KiB
Swift

@testable import PureYAML
enum ValidationCorpusSource {
case yaml(String)
case value(PureYAML.Model.Value)
func load() throws -> PureYAML.Model.Value {
switch self {
case let .yaml(yaml):
try PureYAML.parse(yaml)
case let .value(value):
value
}
}
}
struct ValidationCorpusIssueFixture: CustomStringConvertible {
var name: String
var source: ValidationCorpusSource
var validator: PureYAML.Validation.Validator<PureYAML.Model.Value, PureYAML.Model.Value>
var strict: Bool
var expectedIssues: [PureYAML.Validation.ValidationError]
var forbiddenIssues: [PureYAML.Validation.ValidationError]
init(
name: String,
source: ValidationCorpusSource,
validator: PureYAML.Validation.Validator<PureYAML.Model.Value, PureYAML.Model.Value>,
strict: Bool = true,
expectedIssues: [PureYAML.Validation.ValidationError],
forbiddenIssues: [PureYAML.Validation.ValidationError] = [],
) {
self.name = name
self.source = source
self.validator = validator
self.strict = strict
self.expectedIssues = expectedIssues
self.forbiddenIssues = forbiddenIssues
}
var description: String {
name
}
}
struct ValidationCorpusSuccessFixture: CustomStringConvertible {
var name: String
var source: ValidationCorpusSource
var validator: PureYAML.Validation.Validator<PureYAML.Model.Value, PureYAML.Model.Value>
var forbiddenIssues: [PureYAML.Validation.ValidationError]
init(
name: String,
source: ValidationCorpusSource,
validator: PureYAML.Validation.Validator<PureYAML.Model.Value, PureYAML.Model.Value>,
forbiddenIssues: [PureYAML.Validation.ValidationError] = [],
) {
self.name = name
self.source = source
self.validator = validator
self.forbiddenIssues = forbiddenIssues
}
var description: String {
name
}
}
func requiredRootStringKeyRule(_ key: String) -> PureYAML.Validation.Validation<PureYAML.Model.Value, PureYAML.Model.Value> {
PureYAML.Validation.Validation<PureYAML.Model.Value, PureYAML.Model.Value>(description: "Root requires string key '\(key)'") { context in
guard case let .mapping(mapping) = context.subject else {
return []
}
guard let value = mapping[key] else {
return [
PureYAML.Validation.ValidationError(
severity: .error,
reason: "Required string key '\(key)' is missing",
path: context.path.appending(.key(key)),
),
]
}
guard case .string = value else {
return [
PureYAML.Validation.ValidationError(
severity: .error,
reason: "Required string key '\(key)' must be a string",
path: context.path.appending(.key(key)),
),
]
}
return []
} when: { context in
context.path.isRoot
}
}
func forbiddenMappingKeyRule(
_ key: String,
severity: PureYAML.Validation.Severity,
) -> PureYAML.Validation.Validation<PureYAML.Model.Value, PureYAML.Model.Value> {
PureYAML.Validation.Validation<PureYAML.Model.Value, PureYAML.Model.Value>(description: "Mapping forbids key '\(key)'") { context in
guard case let .mapping(mapping) = context.subject,
mapping[key] != nil
else {
return []
}
return [
PureYAML.Validation.ValidationError(
severity: severity,
reason: "Forbidden key '\(key)' is present",
path: context.path.appending(.key(key)),
),
]
} when: { context in
if case .mapping = context.subject {
return true
}
return false
}
}
func uniqueRouteNamesRule() -> PureYAML.Validation.Validation<PureYAML.Model.Value, PureYAML.Model.Value> {
PureYAML.Validation.Validation<PureYAML.Model.Value, PureYAML.Model.Value>(description: "Route names are unique") { context in
guard case let .mapping(root) = context.subject,
case let .sequence(routes)? = root["routes"]
else {
return []
}
var seen = Set<String>()
var issues: [PureYAML.Validation.ValidationError] = []
for index in routes.indices {
guard case let .mapping(route) = routes[index],
case let .string(name)? = route["name"]
else {
continue
}
if seen.insert(name).inserted {
continue
}
issues.append(.init(
severity: .error,
reason: "Duplicate route name '\(name)'",
path: context.path
.appending(.key("routes"))
.appending(.index(index))
.appending(.key("name")),
))
}
return issues
} when: { context in
context.path.isRoot
}
}