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).
142 lines
5.2 KiB
Swift
142 lines
5.2 KiB
Swift
public extension PureYAML {
|
|
/// Parses and validates YAML after collecting full-text diagnostics.
|
|
static func diagnosticValidationReport(
|
|
_ yaml: String,
|
|
file: String? = nil,
|
|
using validator: Validation.Validator<Model.Value, Model.Value> = .init(),
|
|
preflightScanner: Validation.PreflightScanner = .init(),
|
|
) -> Validation.Report {
|
|
diagnosticValidationResult(
|
|
yaml,
|
|
file: file,
|
|
using: validator,
|
|
preflightScanner: preflightScanner,
|
|
).report
|
|
}
|
|
|
|
/// Validates many YAML inputs with preflight diagnostics for damaged input.
|
|
static func diagnosticValidationReports(
|
|
_ sources: [Validation.Source],
|
|
using validator: Validation.Validator<Model.Value, Model.Value> = .init(),
|
|
failOnWarnings: Bool = false,
|
|
preflightScanner: Validation.PreflightScanner = .init(),
|
|
) -> Validation.BatchReport {
|
|
let sourceReports = sources.map { source in
|
|
let report = diagnosticValidationReport(
|
|
source.yaml,
|
|
file: source.name,
|
|
using: validator,
|
|
preflightScanner: preflightScanner,
|
|
)
|
|
return Validation.SourceReport(
|
|
name: source.name,
|
|
report: report,
|
|
invalid: report.containsFailures(failOnWarnings: failOnWarnings),
|
|
)
|
|
}
|
|
return Validation.BatchReport(sourceReports: sourceReports, failOnWarnings: failOnWarnings)
|
|
}
|
|
|
|
/// Parses a YAML document and throws ``Validation/ReportError`` on failure.
|
|
static func parseValidated(
|
|
_ yaml: String,
|
|
file: String? = nil,
|
|
using validator: Validation.Validator<Model.Value, Model.Value> = .init(),
|
|
preflightScanner: Validation.PreflightScanner = .init(),
|
|
) throws -> Model.Value {
|
|
let documents = try parseValidatedStream(
|
|
yaml,
|
|
file: file,
|
|
using: validator,
|
|
preflightScanner: preflightScanner,
|
|
)
|
|
return documents.first?.value ?? .null
|
|
}
|
|
|
|
/// Parses a YAML stream and throws ``Validation/ReportError`` on failure.
|
|
static func parseValidatedStream(
|
|
_ yaml: String,
|
|
file: String? = nil,
|
|
using validator: Validation.Validator<Model.Value, Model.Value> = .init(),
|
|
preflightScanner: Validation.PreflightScanner = .init(),
|
|
) throws -> [Stream.Document] {
|
|
let result = diagnosticValidationResult(
|
|
yaml,
|
|
file: file,
|
|
using: validator,
|
|
preflightScanner: preflightScanner,
|
|
)
|
|
guard result.report.isValid, let documents = result.documents else {
|
|
throw Validation.ReportError(report: result.report)
|
|
}
|
|
return documents
|
|
}
|
|
}
|
|
|
|
private struct DiagnosticValidationResult {
|
|
var documents: [PureYAML.Stream.Document]?
|
|
var report: PureYAML.Validation.Report
|
|
}
|
|
|
|
private extension PureYAML {
|
|
static func diagnosticValidationResult(
|
|
_ yaml: String,
|
|
file: String?,
|
|
using validator: Validation.Validator<Model.Value, Model.Value>,
|
|
preflightScanner: Validation.PreflightScanner,
|
|
) -> DiagnosticValidationResult {
|
|
var diagnostics = preflightScanner.diagnostics(in: yaml, file: file)
|
|
|
|
do {
|
|
let documents = try parseStreamForDiagnostics(yaml)
|
|
let result = validator.collect(documents)
|
|
diagnostics.append(contentsOf: result.issues.map { streamIssue in
|
|
Validation.Diagnostic(
|
|
kind: .validation,
|
|
severity: streamIssue.issue.severity,
|
|
file: file,
|
|
documentIndex: streamIssue.documentIndex,
|
|
path: streamIssue.issue.path,
|
|
reason: streamIssue.issue.reason,
|
|
)
|
|
})
|
|
return DiagnosticValidationResult(
|
|
documents: documents,
|
|
report: Validation.Report(diagnostics),
|
|
)
|
|
} catch let error as Parsing.ParseError {
|
|
diagnostics.append(Validation.Diagnostic(
|
|
kind: .parse,
|
|
code: error.diagnosticCode,
|
|
severity: .error,
|
|
file: file,
|
|
line: error.sourceLine,
|
|
column: error.sourceColumn,
|
|
reason: error.description,
|
|
))
|
|
return DiagnosticValidationResult(
|
|
documents: nil,
|
|
report: Validation.Report(diagnostics),
|
|
)
|
|
} catch {
|
|
diagnostics.append(Validation.Diagnostic(
|
|
kind: .parse,
|
|
severity: .error,
|
|
file: file,
|
|
reason: String(describing: error),
|
|
))
|
|
return DiagnosticValidationResult(
|
|
documents: nil,
|
|
report: Validation.Report(diagnostics),
|
|
)
|
|
}
|
|
}
|
|
|
|
static func parseStreamForDiagnostics(_ yaml: String) throws -> [Stream.Document] {
|
|
let parser = Parsing.Parser()
|
|
let events = try parser.parseEvents(yaml)
|
|
var composer = Parsing.EventComposer(events: events, scalarParser: parser)
|
|
return try composer.composeStream()
|
|
}
|
|
}
|