diff --git a/Package.swift b/Package.swift index cf0befa..0a777c4 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "JSONSchema", platforms: [ - .macOS(.v10_13), + .macOS(.v10_13), .iOS(.v11) ], products: [ .library(name: "JSONSchema", targets: ["JSONSchema"]), diff --git a/Sources/Applicators/anyOf.swift b/Sources/Applicators/anyOf.swift index 4ebd045..4ed5fd9 100644 --- a/Sources/Applicators/anyOf.swift +++ b/Sources/Applicators/anyOf.swift @@ -1,12 +1,16 @@ +import Foundation + + func anyOf(context: Context, anyOf: Any, instance: Any, schema: [String: Any]) throws -> AnySequence { guard let anyOf = anyOf as? [Any] else { return AnySequence(EmptyCollection()) } if try !anyOf.contains(where: { try context.descend(instance: instance, subschema: $0).isValid }) { + let message = String(format: NSLocalizedString("%@ does not meet anyOf validation rules.", comment: ""), "\(instance)") return AnySequence([ ValidationError( - "\(instance) does not meet anyOf validation rules.", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ), diff --git a/Sources/Applicators/contains.swift b/Sources/Applicators/contains.swift index eae9dfc..94b7ae6 100644 --- a/Sources/Applicators/contains.swift +++ b/Sources/Applicators/contains.swift @@ -1,3 +1,6 @@ +import Foundation + + func contains(context: Context, contains: Any, instance: Any, schema: [String: Any]) throws -> AnySequence { guard let instance = instance as? [Any] else { return AnySequence(EmptyCollection()) @@ -28,9 +31,10 @@ func contains(context: Context, contains: Any, instance: Any, schema: [String: A return try context.descend(instance: subinstance, subschema: contains).isValid }).count if let max = max, containsCount > max { + let message = String(format: NSLocalizedString("%@ does not match contains + maxContains %@.", comment: ""), "\(instance)", "\(max)") return AnySequence([ ValidationError( - "\(instance) does not match contains + maxContains \(max)", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) @@ -41,9 +45,10 @@ func contains(context: Context, contains: Any, instance: Any, schema: [String: A return AnySequence(EmptyCollection()) } + let message = String(format: NSLocalizedString("%@ does not match contains.", comment: ""), "\(instance)") return AnySequence([ ValidationError( - "'\(instance) does not match contains", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) diff --git a/Sources/Applicators/dependencies.swift b/Sources/Applicators/dependencies.swift index 65957e6..5e88c52 100644 --- a/Sources/Applicators/dependencies.swift +++ b/Sources/Applicators/dependencies.swift @@ -1,3 +1,6 @@ +import Foundation + + func dependencies(context: Context, dependencies: Any, instance: Any, schema: [String: Any]) throws -> AnySequence { guard let dependencies = dependencies as? [String: Any] else { return AnySequence(EmptyCollection()) @@ -13,9 +16,10 @@ func dependencies(context: Context, dependencies: Any, instance: Any, schema: [S if let dependency = dependency as? [String] { for key in dependency { if !instance.keys.contains(key) { + let message = String(format: NSLocalizedString("'%@' is a dependency for '%@'.", comment: ""), key, property) results.append(AnySequence([ ValidationError( - "'\(key)' is a dependency for '\(property)'", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ), diff --git a/Sources/Applicators/not.swift b/Sources/Applicators/not.swift index ba0e768..b9e9087 100644 --- a/Sources/Applicators/not.swift +++ b/Sources/Applicators/not.swift @@ -1,11 +1,15 @@ +import Foundation + + func not(context: Context, not: Any, instance: Any, schema: [String: Any]) throws -> AnySequence { guard try context.descend(instance: instance, subschema: not).isValid else { return AnySequence(EmptyCollection()) } + let message = String(format: NSLocalizedString("'%@' does not match 'not' validation.", comment: ""), "\(instance)") return AnySequence([ ValidationError( - "'\(instance)' does not match 'not' validation.", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) diff --git a/Sources/Applicators/oneOf.swift b/Sources/Applicators/oneOf.swift index 14f81b0..10e69aa 100644 --- a/Sources/Applicators/oneOf.swift +++ b/Sources/Applicators/oneOf.swift @@ -1,12 +1,16 @@ +import Foundation + + func oneOf(context: Context, oneOf: Any, instance: Any, schema: [String: Any]) throws -> AnySequence { guard let oneOf = oneOf as? [Any] else { return AnySequence(EmptyCollection()) } if try oneOf.filter({ try context.descend(instance: instance, subschema: $0).isValid }).count != 1 { + let message = NSLocalizedString("Only one value from `oneOf` should be met.", comment: "") return AnySequence([ ValidationError( - "Only one value from `oneOf` should be met", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ), diff --git a/Sources/Applicators/patternProperties.swift b/Sources/Applicators/patternProperties.swift index 1b8dd6c..9a074d8 100644 --- a/Sources/Applicators/patternProperties.swift +++ b/Sources/Applicators/patternProperties.swift @@ -25,9 +25,10 @@ func patternProperties(context: Context, patternProperties: Any, instance: Any, results.append(try context.descend(instance: instance[key]!, subschema: schema)) } } catch { + let message = String(format: NSLocalizedString("'%@' is not a valid regex pattern for patternProperties.", comment: ""), pattern) return AnySequence([ ValidationError( - "[Schema] '\(pattern)' is not a valid regex pattern for patternProperties", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ), diff --git a/Sources/Format/date.swift b/Sources/Format/date.swift index 6390404..05f5ab3 100644 --- a/Sources/Format/date.swift +++ b/Sources/Format/date.swift @@ -5,10 +5,11 @@ func validateDate(_ context: Context, _ value: String) -> AnySequence AnySequence Bool { func validateDuration(_ context: Context, _ value: String) -> AnySequence { guard isValidDuration(value) else { + let message = String(format: NSLocalizedString("'%@' is not a valid duration.", comment: ""), value) return AnySequence([ ValidationError( - "'\(value)' is not a valid duration.", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) diff --git a/Sources/Format/time.swift b/Sources/Format/time.swift index 18e7b56..c65967b 100644 --- a/Sources/Format/time.swift +++ b/Sources/Format/time.swift @@ -136,9 +136,10 @@ func validateTime(_ context: Context, _ value: String) -> AnySequence (String, String) { - guard let hashIndex = url.index(of: "#") else { + guard let hashIndex = url.firstIndex(of: "#") else { return (url, "") } diff --git a/Sources/Validation/const.swift b/Sources/Validation/const.swift index 9916675..bc0dcd6 100644 --- a/Sources/Validation/const.swift +++ b/Sources/Validation/const.swift @@ -5,10 +5,11 @@ func const(context: Context, const: Any, instance: Any, schema: [String: Any]) - if isEqual(instance as! NSObject, const as! NSObject) { return AnySequence(EmptyCollection()) } - + + let message = String(format: NSLocalizedString("'%@' is not equal to const '%@'", comment: ""), "\(instance)", "\(const)") return AnySequence([ ValidationError( - "'\(instance)' is not equal to const '\(const)'", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) diff --git a/Sources/Validation/enum.swift b/Sources/Validation/enum.swift index 879f99c..0bc4d65 100644 --- a/Sources/Validation/enum.swift +++ b/Sources/Validation/enum.swift @@ -11,9 +11,10 @@ func `enum`(context: Context, enum: Any, instance: Any, schema: [String: Any]) - return AnySequence(EmptyCollection()) } + let message = String(format: NSLocalizedString("'%@' is not a valid enumeration value of '%@'", comment: ""), "\(instance)", "\(`enum`)") return AnySequence([ ValidationError( - "'\(instance)' is not a valid enumeration value of '\(`enum`)'", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) diff --git a/Sources/Validation/minMaxItems.swift b/Sources/Validation/minMaxItems.swift index b5a6eda..448a00b 100644 --- a/Sources/Validation/minMaxItems.swift +++ b/Sources/Validation/minMaxItems.swift @@ -1,3 +1,6 @@ +import Foundation + + func validateArrayLength(_ context: Context, _ rhs: Int, comparitor: @escaping ((Int, Int) -> Bool), error: String) -> (_ value: Any) -> AnySequence { return { value in if let value = value as? [Any] { @@ -22,7 +25,8 @@ func minItems(context: Context, minItems: Any, instance: Any, schema: [String: A return AnySequence(EmptyCollection()) } - return validateArrayLength(context, minItems, comparitor: >=, error: "Length of array is smaller than the minimum \(minItems)")(instance) + let message = String(format: NSLocalizedString("Length of array is smaller than the minimum %@", comment: ""), "\(minItems)") + return validateArrayLength(context, minItems, comparitor: >=, error: message)(instance) } @@ -30,6 +34,7 @@ func maxItems(context: Context, maxItems: Any, instance: Any, schema: [String: A guard let maxItems = maxItems as? Int else { return AnySequence(EmptyCollection()) } - - return validateArrayLength(context, maxItems, comparitor: <=, error: "Length of array is greater than maximum \(maxItems)")(instance) + + let message = String(format: NSLocalizedString("Length of array is greater than maximum %@", comment: ""), "\(maxItems)") + return validateArrayLength(context, maxItems, comparitor: <=, error: message)(instance) } diff --git a/Sources/Validation/minMaxLength.swift b/Sources/Validation/minMaxLength.swift index 0a1419f..fabd631 100644 --- a/Sources/Validation/minMaxLength.swift +++ b/Sources/Validation/minMaxLength.swift @@ -1,3 +1,6 @@ +import Foundation + + func validateLength(_ context: Context, _ comparitor: @escaping ((Int, Int) -> (Bool)), length: Int, error: String) -> (_ value: Any) -> AnySequence { return { value in if let value = value as? String { @@ -22,7 +25,8 @@ func minLength(context: Context, minLength: Any, instance: Any, schema: [String: return AnySequence(EmptyCollection()) } - return validateLength(context, >=, length: minLength, error: "Length of string is smaller than minimum length \(minLength)")(instance) + let message = String(format: NSLocalizedString("Length of string is smaller than minimum length %@", comment: ""), "\(minLength)") + return validateLength(context, >=, length: minLength, error: message)(instance) } @@ -31,5 +35,6 @@ func maxLength(context: Context, maxLength: Any, instance: Any, schema: [String: return AnySequence(EmptyCollection()) } - return validateLength(context, <=, length: maxLength, error: "Length of string is larger than max length \(maxLength)")(instance) + let message = String(format: NSLocalizedString("Length of string is larger than max length %@", comment: ""), "\(maxLength)") + return validateLength(context, <=, length: maxLength, error: message)(instance) } diff --git a/Sources/Validation/minMaxNumber.swift b/Sources/Validation/minMaxNumber.swift index 0a66dc2..a8b7fad 100644 --- a/Sources/Validation/minMaxNumber.swift +++ b/Sources/Validation/minMaxNumber.swift @@ -1,3 +1,6 @@ +import Foundation + + func validateNumericLength(_ context: Context, _ length: Double, comparitor: @escaping ((Double, Double) -> (Bool)), exclusiveComparitor: @escaping ((Double, Double) -> (Bool)), exclusive: Bool?, error: String) -> (_ value: Any) -> AnySequence { return { value in if let value = value as? Double { @@ -34,7 +37,8 @@ func minimumDraft4(context: Context, minimum: Any, instance: Any, schema: [Strin return AnySequence(EmptyCollection()) } - return validateNumericLength(context, minimum, comparitor: >=, exclusiveComparitor: >, exclusive: schema["exclusiveMinimum"] as? Bool, error: "Value is lower than minimum value of \(minimum)")(instance) + let message = String(format: NSLocalizedString("Value is lower than minimum value of %@", comment: ""), "\(minimum)") + return validateNumericLength(context, minimum, comparitor: >=, exclusiveComparitor: >, exclusive: schema["exclusiveMinimum"] as? Bool, error: message)(instance) } @@ -43,7 +47,8 @@ func maximumDraft4(context: Context, maximum: Any, instance: Any, schema: [Strin return AnySequence(EmptyCollection()) } - return validateNumericLength(context, maximum, comparitor: <=, exclusiveComparitor: <, exclusive: schema["exclusiveMaximum"] as? Bool, error: "Value exceeds maximum value of \(maximum)")(instance) + let message = String(format: NSLocalizedString("Value exceeds maximum value of %@", comment: ""), "\(maximum)") + return validateNumericLength(context, maximum, comparitor: <=, exclusiveComparitor: <, exclusive: schema["exclusiveMaximum"] as? Bool, error: message)(instance) } @@ -52,7 +57,8 @@ func minimum(context: Context, minimum: Any, instance: Any, schema: [String: Any return AnySequence(EmptyCollection()) } - return validateNumericLength(context, minimum, comparitor: >=, exclusiveComparitor: >, exclusive: false, error: "Value is lower than minimum value of \(minimum)")(instance) + let message = String(format: NSLocalizedString("Value is lower than minimum value of %@", comment: ""), "\(minimum)") + return validateNumericLength(context, minimum, comparitor: >=, exclusiveComparitor: >, exclusive: false, error: message)(instance) } @@ -61,7 +67,8 @@ func maximum(context: Context, maximum: Any, instance: Any, schema: [String: Any return AnySequence(EmptyCollection()) } - return validateNumericLength(context, maximum, comparitor: <=, exclusiveComparitor: <, exclusive: false, error: "Value exceeds maximum value of \(maximum)")(instance) + let message = String(format: NSLocalizedString("Value exceeds maximum value of %@", comment: ""), "\(maximum)") + return validateNumericLength(context, maximum, comparitor: <=, exclusiveComparitor: <, exclusive: false, error: message)(instance) } @@ -70,7 +77,8 @@ func exclusiveMinimum(context: Context, minimum: Any, instance: Any, schema: [St return AnySequence(EmptyCollection()) } - return validateNumericLength(context, minimum, comparitor: >=, exclusiveComparitor: >, exclusive: true, error: "Value is lower than exclusive minimum value of \(minimum)")(instance) + let message = String(format: NSLocalizedString("Value is lower than exclusive minimum value of %@", comment: ""), "\(minimum)") + return validateNumericLength(context, minimum, comparitor: >=, exclusiveComparitor: >, exclusive: true, error: message)(instance) } @@ -79,5 +87,6 @@ func exclusiveMaximum(context: Context, maximum: Any, instance: Any, schema: [St return AnySequence(EmptyCollection()) } - return validateNumericLength(context, maximum, comparitor: <=, exclusiveComparitor: <, exclusive: true, error: "Value exceeds exclusive maximum value of \(maximum)")(instance) + let message = String(format: NSLocalizedString("Value exceeds exclusive maximum value of %@", comment: ""), "\(maximum)") + return validateNumericLength(context, maximum, comparitor: <=, exclusiveComparitor: <, exclusive: true, error: message)(instance) } diff --git a/Sources/Validation/minMaxProperties.swift b/Sources/Validation/minMaxProperties.swift index ec7a0e2..9fcf890 100644 --- a/Sources/Validation/minMaxProperties.swift +++ b/Sources/Validation/minMaxProperties.swift @@ -1,3 +1,6 @@ +import Foundation + + func validatePropertiesLength(_ context: Context, _ length: Int, comparitor: @escaping ((Int, Int) -> (Bool)), error: String) -> (_ value: Any) -> AnySequence { return { value in if let value = value as? [String: Any] { @@ -21,8 +24,9 @@ func minProperties(context: Context, minProperties: Any, instance: Any, schema: guard let minProperties = minProperties as? Int else { return AnySequence(EmptyCollection()) } - - return validatePropertiesLength(context, minProperties, comparitor: <=, error: "Amount of properties is less than the required amount")(instance) + + let message = NSLocalizedString("Amount of properties is less than the required amount", comment: "") + return validatePropertiesLength(context, minProperties, comparitor: <=, error: message)(instance) } @@ -31,5 +35,6 @@ func maxProperties(context: Context, maxProperties: Any, instance: Any, schema: return AnySequence(EmptyCollection()) } - return validatePropertiesLength(context, maxProperties, comparitor: >=, error: "Amount of properties is greater than maximum permitted")(instance) + let message = NSLocalizedString("Amount of properties is greater than maximum permitted", comment: "") + return validatePropertiesLength(context, maxProperties, comparitor: >=, error: message)(instance) } diff --git a/Sources/Validation/multipleOf.swift b/Sources/Validation/multipleOf.swift index 4fc99b4..d3c2ceb 100644 --- a/Sources/Validation/multipleOf.swift +++ b/Sources/Validation/multipleOf.swift @@ -12,9 +12,10 @@ func multipleOf(context: Context, multipleOf: Any, instance: Any, schema: [Strin let result = instance / multipleOf if result != floor(result) { + let message = String(format: NSLocalizedString("%@ is not a multiple of %@", comment: ""), "\(instance)", "\(multipleOf)") return AnySequence([ ValidationError( - "\(instance) is not a multiple of \(multipleOf)", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) diff --git a/Sources/Validation/pattern.swift b/Sources/Validation/pattern.swift index 646994d..200df17 100644 --- a/Sources/Validation/pattern.swift +++ b/Sources/Validation/pattern.swift @@ -11,9 +11,10 @@ func pattern(context: Context, pattern: Any, instance: Any, schema: [String: Any } guard let expression = try? NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options(rawValue: 0)) else { + let message = String(format: NSLocalizedString("Regex pattern '%@' is not valid", comment: ""), pattern) return AnySequence([ ValidationError( - "[Schema] Regex pattern '\(pattern)' is not valid", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) @@ -22,9 +23,10 @@ func pattern(context: Context, pattern: Any, instance: Any, schema: [String: Any let range = NSMakeRange(0, instance.count) if expression.matches(in: instance, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: range).count == 0 { + let message = String(format: NSLocalizedString("'%@' does not match pattern: '%@'", comment: ""), instance, pattern) return AnySequence([ ValidationError( - "'\(instance)' does not match pattern: '\(pattern)'", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) diff --git a/Sources/Validation/required.swift b/Sources/Validation/required.swift index b75153a..6eb2795 100644 --- a/Sources/Validation/required.swift +++ b/Sources/Validation/required.swift @@ -1,3 +1,6 @@ +import Foundation + + func required(context: Context, required: Any, instance: Any, schema: [String: Any]) throws -> AnySequence { guard let instance = instance as? [String: Any] else { return AnySequence(EmptyCollection()) @@ -10,8 +13,9 @@ func required(context: Context, required: Any, instance: Any, schema: [String: A return AnySequence(required.compactMap { key -> ValidationError? in guard !instance.keys.contains(key) else { return nil } + let message = String(format: NSLocalizedString("Required property '%@' is missing", comment: ""), key) return ValidationError( - "Required property '\(key)' is missing", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) diff --git a/Sources/Validation/uniqueItems.swift b/Sources/Validation/uniqueItems.swift index ce95a84..6c56034 100644 --- a/Sources/Validation/uniqueItems.swift +++ b/Sources/Validation/uniqueItems.swift @@ -13,9 +13,10 @@ func uniqueItems(context: Context, uniqueItems: Any, instance: Any, schema: [Str var items: [Any] = [] for item in instance { if items.contains(where: { isEqual(item as! NSObject, $0 as! NSObject) }) { + let message = String(format: NSLocalizedString("%@ does not have unique items", comment: ""), "\(instance)") return AnySequence([ ValidationError( - "\(instance) does not have unique items", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) diff --git a/Sources/Validator.swift b/Sources/Validator.swift index 13016c5..cacd4b2 100644 --- a/Sources/Validator.swift +++ b/Sources/Validator.swift @@ -18,9 +18,10 @@ class Context { return AnySequence(EmptyCollection()) } + let message = NSLocalizedString("Falsy schema", comment: "") return AnySequence([ ValidationError( - "Falsy schema", + message, instanceLocation: instanceLocation, keywordLocation: keywordLocation ) diff --git a/Sources/Validators.swift b/Sources/Validators.swift index b289ce6..faa9655 100644 --- a/Sources/Validators.swift +++ b/Sources/Validators.swift @@ -43,9 +43,10 @@ func type(context: Context, type: Any, instance: Any, schema: [String: Any]) -> } let types = type.map { "'\($0)'" }.joined(separator: ", ") + let message = String(format: NSLocalizedString("'%@' is not of type %@", comment: ""), "\(instance)", "\(types)") return AnySequence([ ValidationError( - "'\(instance)' is not of type \(types)", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) @@ -164,9 +165,10 @@ extension Sequence where Iterator.Element == ValidationError { func unsupported(_ keyword: String) -> (_ context: Context, _ value: Any, _ instance: Any, _ schema: [String: Any]) -> AnySequence { return { (context, _, _, _) in + let message = String(format: NSLocalizedString("'%@' is not supported.", comment: ""), keyword) return AnySequence([ ValidationError( - "'\(keyword)' is not supported.", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ), diff --git a/Sources/format.swift b/Sources/format.swift index 136c990..c12ff7c 100644 --- a/Sources/format.swift +++ b/Sources/format.swift @@ -11,9 +11,10 @@ func format(context: Context, format: Any, instance: Any, schema: [String: Any]) } guard let validator = context.validator.formats[format] else { + let message = String(format: NSLocalizedString("'format' validation of '%@' is not yet supported.", comment: ""), format) return AnySequence([ ValidationError( - "'format' validation of '\(format)' is not yet supported.", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) @@ -31,9 +32,10 @@ func validateIPv4(_ context: Context, _ value: String) -> AnySequence AnySequence AnySequence AnySequence AnySequence { if UUID(uuidString: value) == nil { + let message = String(format: NSLocalizedString("'%@' is not a valid uuid.", comment: ""), value) return AnySequence([ ValidationError( - "'\(value)' is not a valid uuid.", + message, instanceLocation: context.instanceLocation, keywordLocation: context.keywordLocation ) @@ -101,9 +106,10 @@ func validateRegex(_ context: Context, _ value: String) -> AnySequence AnySequence AnySequence