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).
107 lines
4.8 KiB
Swift
107 lines
4.8 KiB
Swift
public extension PureYAML.Tagged {
|
|
/// The caseless namespace of built-in type-directed checks for the tagged-node tree, the tagged
|
|
/// layer's counterpart of `Validation.BuiltinValidation`. Each is a `Validation<_, Node>` on the one
|
|
/// shared validation framework, not a copy of the machinery (validation-rules.md, principle 10).
|
|
enum BuiltinValidation {
|
|
/// Reports each duplicate key within a single tagged mapping. The tagged-layer counterpart of
|
|
/// `Validation.BuiltinValidation.mappingKeysAreUnique`; duplicate keys are as invalid in a
|
|
/// tag-preserving tree as in the plain model, and `Tagged.Mapping` keeps every parsed pair.
|
|
public static var mappingKeysAreUnique: PureYAML.Validation.Validation<Mapping, Node> {
|
|
PureYAML.Validation.Validation(description: "Mapping keys are unique") { context in
|
|
var seen = Set<String>()
|
|
var errors: [PureYAML.Validation.ValidationError] = []
|
|
for pair in context.subject.pairs where !seen.insert(pair.key).inserted {
|
|
errors.append(PureYAML.Validation.ValidationError(
|
|
severity: .error,
|
|
reason: "Duplicate mapping key '\(pair.key)'",
|
|
path: context.path.appending(.key(pair.key)),
|
|
))
|
|
}
|
|
return errors
|
|
}
|
|
}
|
|
|
|
/// Reports unsupported built-in tags and built-in tags applied to the wrong node kind, on the
|
|
/// node itself and on each mapping key.
|
|
public static var builtInTagsAreSupportedAndKindConsistent: PureYAML.Validation.Validation<Node, Node> {
|
|
PureYAML.Validation.Validation(description: "Built-in tags are supported and kind-consistent") { context in
|
|
var errors: [PureYAML.Validation.ValidationError] = []
|
|
if case let .mapping(mapping) = context.subject {
|
|
errors.append(contentsOf: mapping.pairs.compactMap { pair in
|
|
keyTagIssue(for: pair, at: context.path.appending(.key(pair.key)))
|
|
})
|
|
}
|
|
|
|
guard let tag = context.subject.tag, tag.isBuiltIn else {
|
|
return errors
|
|
}
|
|
|
|
if let expectedKind = tag.expectedKind, expectedKind != context.subject.kind {
|
|
errors.append(
|
|
PureYAML.Validation.ValidationError(
|
|
severity: .error,
|
|
reason: "Tag '\(tag.rawValue)' expects a \(expectedKind) node, found \(context.subject.kind)",
|
|
path: context.path,
|
|
),
|
|
)
|
|
return errors
|
|
}
|
|
|
|
if !tag.isSupportedBuiltInTag {
|
|
errors.append(
|
|
PureYAML.Validation.ValidationError(
|
|
severity: .error,
|
|
reason: "Unsupported built-in tag '\(tag.rawValue)'",
|
|
path: context.path,
|
|
),
|
|
)
|
|
}
|
|
|
|
return errors
|
|
} when: { context in
|
|
if context.subject.tag?.isBuiltIn == true {
|
|
return true
|
|
}
|
|
if case let .mapping(mapping) = context.subject {
|
|
return mapping.pairs.contains { $0.keyTag?.isBuiltIn == true }
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
static func keyTagIssue(
|
|
for pair: PureYAML.Tagged.Pair,
|
|
at path: PureYAML.Validation.Path,
|
|
) -> PureYAML.Validation.ValidationError? {
|
|
guard let tag = pair.keyTag, tag.isBuiltIn else {
|
|
return nil
|
|
}
|
|
if tag == .merge, pair.key == "<<" {
|
|
return nil
|
|
}
|
|
if tag == .merge {
|
|
return PureYAML.Validation.ValidationError(
|
|
severity: .error,
|
|
reason: "Tag '\(tag.rawValue)' is only supported on '<<' mapping keys",
|
|
path: path,
|
|
)
|
|
}
|
|
if let expectedKind = tag.expectedKind, expectedKind != .scalar {
|
|
return PureYAML.Validation.ValidationError(
|
|
severity: .error,
|
|
reason: "Tag '\(tag.rawValue)' expects a scalar mapping key",
|
|
path: path,
|
|
)
|
|
}
|
|
guard tag.isSupportedBuiltInTag else {
|
|
return PureYAML.Validation.ValidationError(
|
|
severity: .error,
|
|
reason: "Unsupported built-in tag '\(tag.rawValue)' on mapping key",
|
|
path: path,
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
}
|