Skip to content

Commit 2d0f17f

Browse files
committed
feat: store keyword location on all errors
1 parent 5c9b4aa commit 2d0f17f

22 files changed

+88
-35
lines changed

Sources/Applicators/anyOf.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ func anyOf(context: Context, anyOf: Any, instance: Any, schema: [String: Any]) t
77
return AnySequence([
88
ValidationError(
99
"\(instance) does not meet anyOf validation rules.",
10-
instanceLocation: context.instanceLocation
10+
instanceLocation: context.instanceLocation,
11+
keywordLocation: context.keywordLocation
1112
),
1213
])
1314
}

Sources/Applicators/contains.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ func contains(context: Context, contains: Any, instance: Any, schema: [String: A
3131
return AnySequence([
3232
ValidationError(
3333
"\(instance) does not match contains + maxContains \(max)",
34-
instanceLocation: context.instanceLocation
34+
instanceLocation: context.instanceLocation,
35+
keywordLocation: context.keywordLocation
3536
)
3637
])
3738
}
@@ -43,7 +44,8 @@ func contains(context: Context, contains: Any, instance: Any, schema: [String: A
4344
return AnySequence([
4445
ValidationError(
4546
"'\(instance) does not match contains",
46-
instanceLocation: context.instanceLocation
47+
instanceLocation: context.instanceLocation,
48+
keywordLocation: context.keywordLocation
4749
)
4850
])
4951
}

Sources/Applicators/dependencies.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ func dependencies(context: Context, dependencies: Any, instance: Any, schema: [S
1616
results.append(AnySequence([
1717
ValidationError(
1818
"'\(key)' is a dependency for '\(property)'",
19-
instanceLocation: context.instanceLocation
19+
instanceLocation: context.instanceLocation,
20+
keywordLocation: context.keywordLocation
2021
),
2122
]))
2223
}

Sources/Applicators/not.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ func not(context: Context, not: Any, instance: Any, schema: [String: Any]) throw
66
return AnySequence([
77
ValidationError(
88
"'\(instance)' does not match 'not' validation.",
9-
instanceLocation: context.instanceLocation
9+
instanceLocation: context.instanceLocation,
10+
keywordLocation: context.keywordLocation
1011
)
1112
])
1213
}

Sources/Applicators/oneOf.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ func oneOf(context: Context, oneOf: Any, instance: Any, schema: [String: Any]) t
77
return AnySequence([
88
ValidationError(
99
"Only one value from `oneOf` should be met",
10-
instanceLocation: context.instanceLocation
10+
instanceLocation: context.instanceLocation,
11+
keywordLocation: context.keywordLocation
1112
),
1213
])
1314
}

Sources/Applicators/patternProperties.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ func patternProperties(context: Context, patternProperties: Any, instance: Any,
2828
return AnySequence([
2929
ValidationError(
3030
"[Schema] '\(pattern)' is not a valid regex pattern for patternProperties",
31-
instanceLocation: context.instanceLocation
31+
instanceLocation: context.instanceLocation,
32+
keywordLocation: context.keywordLocation
3233
),
3334
])
3435
}

Sources/Validation/const.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ func const(context: Context, const: Any, instance: Any, schema: [String: Any]) -
99
return AnySequence([
1010
ValidationError(
1111
"'\(instance)' is not equal to const '\(const)'",
12-
instanceLocation: context.instanceLocation
12+
instanceLocation: context.instanceLocation,
13+
keywordLocation: context.keywordLocation
1314
)
1415
])
1516
}

Sources/Validation/minMaxItems.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ func validateArrayLength(_ context: Context, _ rhs: Int, comparitor: @escaping (
55
return AnySequence([
66
ValidationError(
77
error,
8-
instanceLocation: context.instanceLocation
8+
instanceLocation: context.instanceLocation,
9+
keywordLocation: context.keywordLocation
910
)
1011
])
1112
}

Sources/Validation/minMaxLength.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ func validateLength(_ context: Context, _ comparitor: @escaping ((Int, Int) -> (
33
if let value = value as? String {
44
if !comparitor(value.count, length) {
55
return AnySequence([
6-
ValidationError(error, instanceLocation: context.instanceLocation),
6+
ValidationError(
7+
error,
8+
instanceLocation: context.instanceLocation,
9+
keywordLocation: context.keywordLocation
10+
),
711
])
812
}
913
}

Sources/Validation/minMaxNumber.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@ func validateNumericLength(_ context: Context, _ length: Double, comparitor: @es
44
if exclusive ?? false {
55
if !exclusiveComparitor(value, length) {
66
return AnySequence([
7-
ValidationError(error, instanceLocation: context.instanceLocation),
7+
ValidationError(
8+
error,
9+
instanceLocation: context.instanceLocation,
10+
keywordLocation: context.keywordLocation
11+
)
812
])
913
}
1014
}
1115

1216
if !comparitor(value, length) {
1317
return AnySequence([
14-
ValidationError(error, instanceLocation: context.instanceLocation),
18+
ValidationError(
19+
error,
20+
instanceLocation: context.instanceLocation,
21+
keywordLocation: context.keywordLocation
22+
)
1523
])
1624
}
1725
}

Sources/Validation/minMaxProperties.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ func validatePropertiesLength(_ context: Context, _ length: Int, comparitor: @es
33
if let value = value as? [String: Any] {
44
if !comparitor(length, value.count) {
55
return AnySequence([
6-
ValidationError(error, instanceLocation: context.instanceLocation),
6+
ValidationError(
7+
error,
8+
instanceLocation: context.instanceLocation,
9+
keywordLocation: context.keywordLocation
10+
),
711
])
812
}
913
}

Sources/Validation/multipleOf.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ func multipleOf(context: Context, multipleOf: Any, instance: Any, schema: [Strin
1515
return AnySequence([
1616
ValidationError(
1717
"\(instance) is not a multiple of \(multipleOf)",
18-
instanceLocation: context.instanceLocation
18+
instanceLocation: context.instanceLocation,
19+
keywordLocation: context.keywordLocation
1920
)
2021
])
2122
}

Sources/Validation/pattern.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ func pattern(context: Context, pattern: Any, instance: Any, schema: [String: Any
1414
return AnySequence([
1515
ValidationError(
1616
"[Schema] Regex pattern '\(pattern)' is not valid",
17-
instanceLocation: context.instanceLocation
17+
instanceLocation: context.instanceLocation,
18+
keywordLocation: context.keywordLocation
1819
)
1920
])
2021
}
@@ -24,7 +25,8 @@ func pattern(context: Context, pattern: Any, instance: Any, schema: [String: Any
2425
return AnySequence([
2526
ValidationError(
2627
"'\(instance)' does not match pattern: '\(pattern)'",
27-
instanceLocation: context.instanceLocation
28+
instanceLocation: context.instanceLocation,
29+
keywordLocation: context.keywordLocation
2830
)
2931
])
3032
}

Sources/Validation/uniqueItems.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ func uniqueItems(context: Context, uniqueItems: Any, instance: Any, schema: [Str
1616
return AnySequence([
1717
ValidationError(
1818
"\(instance) does not have unique items",
19-
instanceLocation: context.instanceLocation
19+
instanceLocation: context.instanceLocation,
20+
keywordLocation: context.keywordLocation
2021
)
2122
])
2223
}

Sources/ValidationResult.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
public class ValidationError: Encodable {
22
public let description: String
33

4-
init(_ value: String, instanceLocation: JSONPointer, keywordLocation: JSONPointer? = nil) {
4+
init(_ value: String, instanceLocation: JSONPointer, keywordLocation: JSONPointer) {
55
self.description = value
66
self.instanceLocation = instanceLocation
77
self.keywordLocation = keywordLocation
@@ -10,16 +10,18 @@ public class ValidationError: Encodable {
1010
enum CodingKeys: String, CodingKey {
1111
case error
1212
case instanceLocation
13+
case keywordLocation
1314
}
1415

1516
public func encode(to encoder: Encoder) throws {
1617
var container = encoder.container(keyedBy: CodingKeys.self)
1718
try container.encode(description, forKey: .error)
1819
try container.encode(instanceLocation.path, forKey: .instanceLocation)
20+
try container.encode(keywordLocation.path, forKey: .keywordLocation)
1921
}
2022

2123
public let instanceLocation: JSONPointer
22-
public let keywordLocation: JSONPointer?
24+
public let keywordLocation: JSONPointer
2325
}
2426

2527

Sources/Validator.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ class Context {
1919
}
2020

2121
return AnySequence([
22-
ValidationError("Falsy schema", instanceLocation: instanceLocation),
22+
ValidationError(
23+
"Falsy schema",
24+
instanceLocation: instanceLocation,
25+
keywordLocation: keywordLocation
26+
)
2327
])
2428
}
2529

Sources/Validators.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ func validValidation(_ value: Any) -> AnySequence<ValidationError> {
1010
func invalidValidation(_ context: Context, _ error: String) -> (_ value: Any) -> AnySequence<ValidationError> {
1111
return { value in
1212
return AnySequence([
13-
ValidationError(error, instanceLocation: context.instanceLocation),
13+
ValidationError(
14+
error,
15+
instanceLocation: context.instanceLocation,
16+
keywordLocation: context.keywordLocation
17+
)
1418
])
1519
}
1620
}
@@ -42,7 +46,8 @@ func type(context: Context, type: Any, instance: Any, schema: [String: Any]) ->
4246
return AnySequence([
4347
ValidationError(
4448
"'\(instance)' is not of type \(types)",
45-
instanceLocation: context.instanceLocation
49+
instanceLocation: context.instanceLocation,
50+
keywordLocation: context.keywordLocation
4651
)
4752
])
4853
}
@@ -162,7 +167,8 @@ func unsupported(_ keyword: String) -> (_ context: Context, _ value: Any, _ inst
162167
return AnySequence([
163168
ValidationError(
164169
"'\(keyword)' is not supported.",
165-
instanceLocation: context.instanceLocation
170+
instanceLocation: context.instanceLocation,
171+
keywordLocation: context.keywordLocation
166172
),
167173
])
168174
}

Sources/format.swift

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ func format(context: Context, format: Any, instance: Any, schema: [String: Any])
1414
return AnySequence([
1515
ValidationError(
1616
"'format' validation of '\(format)' is not yet supported.",
17-
instanceLocation: context.instanceLocation
17+
instanceLocation: context.instanceLocation,
18+
keywordLocation: context.keywordLocation
1819
)
1920
])
2021
}
@@ -34,7 +35,8 @@ func validateIPv4(_ context: Context, _ value: Any) -> AnySequence<ValidationErr
3435
return AnySequence([
3536
ValidationError(
3637
"'\(ipv4)' is not a IPv4 address.",
37-
instanceLocation: context.instanceLocation
38+
instanceLocation: context.instanceLocation,
39+
keywordLocation: context.keywordLocation
3840
)
3941
])
4042
}
@@ -56,7 +58,8 @@ func validateIPv6(_ context: Context, _ value: Any) -> AnySequence<ValidationErr
5658
return AnySequence([
5759
ValidationError(
5860
"'\(ipv6)' is not a IPv6 address.",
59-
instanceLocation: context.instanceLocation
61+
instanceLocation: context.instanceLocation,
62+
keywordLocation: context.keywordLocation
6063
)
6164
])
6265
}
@@ -82,7 +85,8 @@ func validateURI(_ context: Context, _ value: Any) -> AnySequence<ValidationErro
8285
return AnySequence([
8386
ValidationError(
8487
"'\(uri)' is not a valid uri.",
85-
instanceLocation: context.instanceLocation
88+
instanceLocation: context.instanceLocation,
89+
keywordLocation: context.keywordLocation
8690
)
8791
])
8892
}
@@ -97,7 +101,8 @@ func validateUUID(_ context: Context, _ value: Any) -> AnySequence<ValidationErr
97101
return AnySequence([
98102
ValidationError(
99103
"'\(value)' is not a valid uuid.",
100-
instanceLocation: context.instanceLocation
104+
instanceLocation: context.instanceLocation,
105+
keywordLocation: context.keywordLocation
101106
)
102107
])
103108
}
@@ -115,7 +120,8 @@ func validateRegex(_ context: Context, _ value: Any) -> AnySequence<ValidationEr
115120
return AnySequence([
116121
ValidationError(
117122
"'\(value)' is not a valid regex.",
118-
instanceLocation: context.instanceLocation
123+
instanceLocation: context.instanceLocation,
124+
keywordLocation: context.keywordLocation
119125
)
120126
])
121127
}
@@ -131,7 +137,8 @@ func validateJSONPointer(_ context: Context, _ value: Any) -> AnySequence<Valida
131137
return AnySequence([
132138
ValidationError(
133139
"'\(value)' is not a valid json-pointer.",
134-
instanceLocation: context.instanceLocation
140+
instanceLocation: context.instanceLocation,
141+
keywordLocation: context.keywordLocation
135142
)
136143
])
137144
}
@@ -145,7 +152,8 @@ func validateJSONPointer(_ context: Context, _ value: Any) -> AnySequence<Valida
145152
return AnySequence([
146153
ValidationError(
147154
"'\(value)' is not a valid json-pointer.",
148-
instanceLocation: context.instanceLocation
155+
instanceLocation: context.instanceLocation,
156+
keywordLocation: context.keywordLocation
149157
)
150158
])
151159
}

Tests/JSONSchemaTests/Validation/TestEnum.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class EnumTests: XCTestCase {
2020

2121
//XCTAssertEqual(error.description, "is not a valid enumeration value of '[\"one\"]'")
2222
XCTAssertEqual(error.instanceLocation.path, "")
23-
XCTAssertEqual(error.keywordLocation?.path, "#/enum")
23+
XCTAssertEqual(error.keywordLocation.path, "#/enum")
2424
}
2525
}
2626
}

Tests/JSONSchemaTests/Validation/TestRequired.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class RequiredTests: XCTestCase {
2222

2323
XCTAssertEqual(error.description, "Required property 'test' is missing")
2424
XCTAssertEqual(error.instanceLocation.path, "/0")
25-
XCTAssertEqual(error.keywordLocation?.path, "#/items/required")
25+
XCTAssertEqual(error.keywordLocation.path, "#/items/required")
2626
}
2727
}
2828
}

Tests/JSONSchemaTests/ValidationErrorTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ class ValidationErrorTests: XCTestCase {
77
func testEncodableAsJSON() throws {
88
let error = ValidationError(
99
"example description",
10-
instanceLocation: JSONPointer(path: "/test/1")
10+
instanceLocation: JSONPointer(path: "/test/1"),
11+
keywordLocation: JSONPointer(path: "#/example")
1112
)
1213

1314
let jsonData = try JSONEncoder().encode(error)
@@ -16,6 +17,7 @@ class ValidationErrorTests: XCTestCase {
1617
XCTAssertEqual(json as! NSDictionary, [
1718
"error": "example description",
1819
"instanceLocation": "/test/1",
20+
"keywordLocation": "#/example",
1921
])
2022
}
2123
}

Tests/JSONSchemaTests/ValidationResultTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ class ValidationResultsTests: XCTestCase {
1818
func testInvalidEncodableAsJSON() throws {
1919
let error = ValidationError(
2020
"example description",
21-
instanceLocation: JSONPointer(path: "/test/1")
21+
instanceLocation: JSONPointer(path: "/test/1"),
22+
keywordLocation: JSONPointer(path: "#/example")
2223
)
2324
let result = ValidationResult.invalid([error])
2425

@@ -31,6 +32,7 @@ class ValidationResultsTests: XCTestCase {
3132
[
3233
"error": "example description",
3334
"instanceLocation": "/test/1",
35+
"keywordLocation": "#/example",
3436
],
3537
],
3638
])

0 commit comments

Comments
 (0)