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).
154 lines
5.4 KiB
Swift
154 lines
5.4 KiB
Swift
@testable import PureYAML
|
|
|
|
enum UnsupportedYAMLGapsFixtures {
|
|
struct ParseErrorCase: CustomStringConvertible {
|
|
var name: String
|
|
var yaml: String
|
|
var expected: PureYAML.Parsing.ParseError
|
|
var expectedDescription: String
|
|
|
|
var description: String {
|
|
name
|
|
}
|
|
}
|
|
|
|
struct FallbackValueCase: CustomStringConvertible {
|
|
var name: String
|
|
var yaml: String
|
|
var expected: PureYAML.Model.Value
|
|
var absentRootKeys: [String]
|
|
|
|
var description: String {
|
|
name
|
|
}
|
|
|
|
init(
|
|
name: String,
|
|
yaml: String,
|
|
expected: PureYAML.Model.Value,
|
|
absentRootKeys: [String] = ["missing"],
|
|
) {
|
|
self.name = name
|
|
self.yaml = yaml
|
|
self.expected = expected
|
|
self.absentRootKeys = absentRootKeys
|
|
}
|
|
}
|
|
|
|
static let parseErrors: [ParseErrorCase] = [
|
|
ParseErrorCase(
|
|
name: "second document start marker",
|
|
yaml: """
|
|
---
|
|
- Mark McGwire
|
|
---
|
|
- Sammy Sosa
|
|
""",
|
|
expected: .unsupportedMultiDocumentStream(line: 3),
|
|
expectedDescription: "multi-document streams are not supported at line 3",
|
|
),
|
|
ParseErrorCase(
|
|
name: "content after explicit document end",
|
|
yaml: """
|
|
name: First
|
|
...
|
|
name: Second
|
|
""",
|
|
expected: .unsupportedMultiDocumentStream(line: 3),
|
|
expectedDescription: "multi-document streams are not supported at line 3",
|
|
),
|
|
ParseErrorCase(
|
|
// A directive must be followed by a `---` document start. A reserved
|
|
// or unknown directive is otherwise ignored, but here it is trailed
|
|
// by bare content rather than a document start, which is invalid.
|
|
name: "directive without a document start",
|
|
yaml: """
|
|
%FOO bar
|
|
value: one
|
|
""",
|
|
expected: .malformed(reason: "did not find expected <document start>", line: 2, column: 1),
|
|
expectedDescription: "did not find expected <document start> at line 2, column 1",
|
|
),
|
|
ParseErrorCase(
|
|
// After document content has begun, a line starting with `%` is a
|
|
// misplaced directive (a plain scalar may not start with `%`). The
|
|
// first document ended implicitly, so the directive is rejected: a
|
|
// directive applies to the next document and requires a preceding `...`.
|
|
name: "percent after content is plain text not a directive",
|
|
yaml: """
|
|
value: one
|
|
%TAG !e! tag:example.com,2026:
|
|
""",
|
|
expected: .malformed(reason: "directives require a preceding document end marker", line: 2, column: 1),
|
|
expectedDescription: "directives require a preceding document end marker at line 2, column 1",
|
|
),
|
|
ParseErrorCase(
|
|
// A directive after `---` ends that document implicitly, so it applies
|
|
// to the next document and requires a preceding `...` end marker.
|
|
name: "percent after document start is plain text not a directive",
|
|
yaml: """
|
|
---
|
|
%TAG !e! tag:example.com,2026:
|
|
value: !e!thing one
|
|
""",
|
|
expected: .malformed(reason: "directives require a preceding document end marker", line: 2, column: 1),
|
|
expectedDescription: "directives require a preceding document end marker at line 2, column 1",
|
|
),
|
|
]
|
|
|
|
static let fallbackValues: [FallbackValueCase] = [
|
|
FallbackValueCase(
|
|
name: "unsupported built-in tags remain value trees",
|
|
yaml: """
|
|
date: !!timestamp 2001-01-23
|
|
payload: !!binary |
|
|
YWJj
|
|
set: !!set {a: null}
|
|
omap: !!omap [{a: 1}, {b: 2}]
|
|
""",
|
|
expected: .mapping(.init([
|
|
.init(key: "date", value: .string("2001-01-23")),
|
|
.init(key: "payload", value: .string("YWJj\n")),
|
|
.init(key: "set", value: .mapping(.init([
|
|
.init(key: "a", value: .null),
|
|
]))),
|
|
.init(key: "omap", value: .sequence([
|
|
.mapping(.init([
|
|
.init(key: "a", value: .int(1)),
|
|
])),
|
|
.mapping(.init([
|
|
.init(key: "b", value: .int(2)),
|
|
])),
|
|
])),
|
|
])),
|
|
),
|
|
FallbackValueCase(
|
|
name: "comments after explicit document end stay non-content",
|
|
yaml: """
|
|
name: First
|
|
...
|
|
# trailing comment
|
|
""",
|
|
expected: .mapping(.init([
|
|
.init(key: "name", value: .string("First")),
|
|
])),
|
|
),
|
|
]
|
|
|
|
static let directPreservedValue = PureYAML.Model.Value.mapping(.init([
|
|
.init(key: "<<", value: .mapping(.init([
|
|
.init(key: "enabled", value: .bool(true)),
|
|
]))),
|
|
.init(key: "title", value: .string("First")),
|
|
.init(key: "title", value: .string("Second")),
|
|
]))
|
|
|
|
static let directPreservedIssues: [PureYAML.Validation.ValidationError] = [
|
|
.init(
|
|
severity: .error,
|
|
reason: "Duplicate mapping key 'title'",
|
|
path: .init([.key("title")]),
|
|
),
|
|
]
|
|
}
|