138 lines
6.0 KiB
Swift
138 lines
6.0 KiB
Swift
/// The per-facet validation checks, split out so SimpleType's own body stays
|
|
/// within the type-body length budget.
|
|
extension PureXML.Schema.SimpleType {
|
|
private func boundsError(_ value: String, _: PureXML.Schema.Primitive) -> String? {
|
|
let (lower, upper) = base.bounds
|
|
guard lower != nil || upper != nil, let decimal = PureXML.Schema.DecimalValue(value, allowFraction: false) else {
|
|
return nil
|
|
}
|
|
if let lower, decimal < lower {
|
|
return "\(value) is below the minimum for \(base.rawValue)"
|
|
}
|
|
if let upper, decimal > upper {
|
|
return "\(value) is above the maximum for \(base.rawValue)"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private func lengthError(_ value: String, _ primitive: PureXML.Schema.Primitive) -> String? {
|
|
// length/minLength/maxLength measure characters, octets, or list
|
|
// items depending on the type; for QName the spec leaves the unit
|
|
// unspecified (XSD 1.0 Datatypes 4.3.1), so like Xerces and the XSTS
|
|
// NIST oracle we do not constrain QName values by length.
|
|
if case .qName = primitive {
|
|
return nil
|
|
}
|
|
if case .notation = primitive {
|
|
return nil
|
|
}
|
|
let length = primitive.measuredLength(value)
|
|
if let exact = facets.length, length != exact {
|
|
return "length \(length) is not \(exact)"
|
|
}
|
|
if let minimum = facets.minLength, length < minimum {
|
|
return "length \(length) is below \(minimum)"
|
|
}
|
|
if let maximum = facets.maxLength, length > maximum {
|
|
return "length \(length) is above \(maximum)"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private func digitsError(_ value: String) -> String? {
|
|
guard facets.totalDigits != nil || facets.fractionDigits != nil,
|
|
let decimal = PureXML.Schema.DecimalValue(value, allowFraction: true) else { return nil }
|
|
if let total = facets.totalDigits, decimal.totalDigits > total {
|
|
return "more than \(total) total digits"
|
|
}
|
|
if let fraction = facets.fractionDigits, decimal.fractionDigits.count > fraction {
|
|
return "more than \(fraction) fraction digits"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func patternError(_ value: String) -> String? {
|
|
for group in facets.patternGroups {
|
|
let matched = group.contains { pattern in
|
|
guard let regex = try? PureXML.Regex.Pattern(pattern) else { return false }
|
|
return regex.matches(value)
|
|
}
|
|
guard matched else {
|
|
let label = group.joined(separator: "|")
|
|
return "'\(value)' does not match pattern '\(label)'"
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private func enumerationError(_ value: String, _ primitive: PureXML.Schema.Primitive) -> String? {
|
|
guard let enumeration = facets.enumeration else { return nil }
|
|
let whiteSpace = facets.whiteSpace ?? base.whiteSpace
|
|
let matched = enumeration.contains { candidate in
|
|
let normalized = Self.process(candidate, whiteSpace: whiteSpace)
|
|
if let lhs = primitive.ordered(value), let rhs = primitive.ordered(normalized) {
|
|
return lhs == rhs
|
|
}
|
|
return normalized == value
|
|
}
|
|
return matched ? nil : "'\(value)' is not in the enumeration"
|
|
}
|
|
|
|
private func rangeError(_ value: String, _ primitive: PureXML.Schema.Primitive) -> String? {
|
|
if case .duration = primitive {
|
|
return durationRangeError(value)
|
|
}
|
|
guard let actual = primitive.ordered(value) else { return nil }
|
|
if let bound = facets.minInclusive, let limit = primitive.ordered(bound), actual < limit {
|
|
return "\(value) is below the inclusive minimum"
|
|
}
|
|
if let bound = facets.maxInclusive, let limit = primitive.ordered(bound), actual > limit {
|
|
return "\(value) is above the inclusive maximum"
|
|
}
|
|
if let bound = facets.minExclusive, let limit = primitive.ordered(bound), !(limit < actual) {
|
|
return "\(value) is not above the exclusive minimum"
|
|
}
|
|
if let bound = facets.maxExclusive, let limit = primitive.ordered(bound), !(actual < limit) {
|
|
return "\(value) is not below the exclusive maximum"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/// Ordering facets on `xs:duration`, which is a partial order: a value
|
|
/// that is incomparable to the bound does not satisfy it (it is not in
|
|
/// the ordered range), so an incomparable result is a violation.
|
|
private func durationRangeError(_ value: String) -> String? {
|
|
guard let actual = PureXML.Schema.DurationValue(value) else { return nil }
|
|
func order(_ bound: String?) -> PureXML.Schema.DurationOrder? {
|
|
bound.flatMap(PureXML.Schema.DurationValue.init).map(actual.compare(to:))
|
|
}
|
|
if let order = order(facets.minInclusive), order == .lessThan || order == .incomparable {
|
|
return "\(value) is below the inclusive minimum"
|
|
}
|
|
if let order = order(facets.maxInclusive), order == .greaterThan || order == .incomparable {
|
|
return "\(value) is above the inclusive maximum"
|
|
}
|
|
if let order = order(facets.minExclusive), order != .greaterThan {
|
|
return "\(value) is not above the exclusive minimum"
|
|
}
|
|
if let order = order(facets.maxExclusive), order != .lessThan {
|
|
return "\(value) is not below the exclusive maximum"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateAtomic(_ lexical: String) -> String? {
|
|
let value = Self.process(lexical, whiteSpace: facets.whiteSpace ?? base.whiteSpace)
|
|
let primitive = base.primitive
|
|
guard primitive.isValid(value) else {
|
|
return "'\(lexical)' is not a valid \(base.rawValue)"
|
|
}
|
|
return boundsError(value, primitive)
|
|
?? lengthError(value, primitive)
|
|
?? digitsError(value)
|
|
?? patternError(value)
|
|
?? enumerationError(value, primitive)
|
|
?? rangeError(value, primitive)
|
|
}
|
|
}
|