-
Notifications
You must be signed in to change notification settings - Fork 76
Add support for date, time, and date-time validators #118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@kylef can you review please? |
@kylef hey again, any updates on this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking a go at this @jnewc.
It seems like there's still a few gaps in the rfc3339 support which I've commented below. Could you please add this patch to enable the JSON Schema tests suite (which is not very extensive but at-least covers some positive and negative cases):
diff --git a/Tests/JSONSchemaTests/JSONSchemaCases.swift b/Tests/JSONSchemaTests/JSONSchemaCases.swift
index e4ac246..67e1dc8 100644
--- a/Tests/JSONSchemaTests/JSONSchemaCases.swift
+++ b/Tests/JSONSchemaTests/JSONSchemaCases.swift
@@ -117,7 +117,6 @@ class JSONSchemaCases: XCTestCase {
"infinite-loop-detection.json",
// optional formats
- "date-time.json",
"email.json",
"hostname.json",
"uri-reference.json",
@@ -137,8 +136,6 @@ class JSONSchemaCases: XCTestCase {
"infinite-loop-detection.json",
// optional, format
- "date-time.json",
- "date.json",
"email.json",
"hostname.json",
"idn-email.json",
@@ -146,7 +143,6 @@ class JSONSchemaCases: XCTestCase {
"iri-reference.json",
"iri.json",
"relative-json-pointer.json",
- "time.json",
"uri-reference.json",
"uri-template.json",
] + additionalExclusions)
@@ -173,8 +169,6 @@ class JSONSchemaCases: XCTestCase {
// optional, format
"format.json",
- "date-time.json",
- "date.json",
"duration.json",
"email.json",
"hostname.json",
@@ -183,7 +177,6 @@ class JSONSchemaCases: XCTestCase {
"iri-reference.json",
"iri.json",
"relative-json-pointer.json",
- "time.json",
"uri-reference.json",
"uri-template.json",
] + additionalExclusions)
@@ -212,8 +205,6 @@ class JSONSchemaCases: XCTestCase {
// optional, format
"format.json",
- "date-time.json",
- "date.json",
"duration.json",
"email.json",
"hostname.json",
@@ -222,7 +213,6 @@ class JSONSchemaCases: XCTestCase {
"iri-reference.json",
"iri.json",
"relative-json-pointer.json",
- "time.json",
"uri-reference.json",
"uri-template.json",
] + additionalExclusions)
@@ -245,7 +235,7 @@ class JSONSchemaCases: XCTestCase {
return cases.filter {
if let schema = $0.schema as? [String: Any] {
let format = schema["format"] as! String
- return !["date-time", "email", "hostname"].contains(format)
+ return !["email", "hostname"].contains(format)
}
return true
@@ -0,0 +1,46 @@ | |||
import Foundation | |||
|
|||
func validateDateTime(_ context: Context, _ value: Any) -> AnySequence<ValidationError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While testing some of the examples from rfc3339 (https://tools.ietf.org/html/rfc3339#section-5.8), appears this isn't behaving as expected with leap seconds, the test below seems to fail.
import XCTest
import JSONSchema
class DateTimeFormatTests: XCTestCase {
func testDateTimeWithLeapSecond() throws {
let schema: [String: Any] = [
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "date-time",
]
let result = try validate("1990-12-31T23:59:60Z", schema: schema)
XCTAssertTrue(result.valid)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kylef I'm a bit confused about this one as it's called out as invalid in the jsonschema.org tests repo - see https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/6bc53e60c3de5d2e2ab15a84bcd3a79ca12c66a9/tests/draft2020-12/optional/format/date-time.json#L28
This seems to be at odds with the spec though:
1990-12-31T15:59:60-08:00
This represents the same leap second in Pacific Standard Time, 8
hours behind UTC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, unsure if that's test is trying to disallow a leap second or instead there being 31 days in February. We can try to clarify that in the tests suite.
Started with json-schema-org/JSON-Schema-Test-Suite#481
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1990-02-31T15:59:60.123-08:00
this particular date would be invalid even for a leap second, because leap seconds wouldn't be allowed in that date or time, but it should be possible in other times. There's further explanation at https://tools.ietf.org/html/rfc3339#appendix-D.
The leap second would need to be at the end of the day.
Sources/Validation/time.swift
Outdated
func validateTime(_ context: Context, _ value: Any) -> AnySequence<ValidationError> { | ||
if let date = value as? String { | ||
let rfc3339DateTimeFormatter = DateFormatter() | ||
rfc3339DateTimeFormatter.dateFormat = "HH:mm:ss.SSSZZZZZ" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can tell from rfc3339, the time-secfrag component of partial time is optional:
partial-time = time-hour ":" time-minute ":" time-second
[time-secfrac]
The following test can be used to assert this is implemented:
class TimeFormatTests: XCTestCase {
func testTimeWithoutSecondFraction() throws {
let schema: [String: Any] = [
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "time",
]
let result = try validate("23:59:50Z", schema: schema)
XCTAssertTrue(result.valid)
}
}
Thanks @kylef - I'll ping you again when I've applied all the necessary updates 👍 |
@kylef added some commits + a comment re: leap seconds |
This is actually not complete yet. While you added a date format, it fails during execution:
|
See #121 |
This appears to be working fine for our team's use case - example validation error:
|
Yeah, the error was something unrelated! Our id string in the given spec used HTTP instead of HTTPS. Still, some changes were necessary, so I based my PR on yours |
@kylef in case you missed it, this is ready for another review - thanks again :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jnewc Overall I'm +1 on these changes, thanks for taking the time to prepare them and looking forward to merging this. I'd like to spend a bit of time verifying each of the RFC3339 behaviours, and hoping to do that in JSON-Schema-Test-Suite itself. So to start, I've prepared json-schema-org/JSON-Schema-Test-Suite#481.
I'm debating if we should make the date-time
format use the underlying date
and time
validators internally so that we can be sure that they are absolutely identical in regards to behaviour (and would possibly simplify having multiple date formatters to validate each part). What do you think?
If going down that route we could have internal functions such as |
@kylef This seems like a good idea in principle - for clarity, do you mean that the validators would i.e. split the date and time on 'T' and then validate separately, in all cases for EDIT: Thinking about this, it could be taken further by splitting dates/times down into their component parts, validating the correct number of components and such along the way, which would in turn avoid using things like the regex I added to ensure two-digit padding is honoured. The other benefit here would be avoiding using Swift's DateFormatter entirely, which is clearly too flexible for + lacks the configurability needed to meet rfc3339 requirements. |
After giving more thought to this, in date-time we will need to parse both the date and time, not just validate the date and time separately, but also enforce further restrictions that the time is valid for the given date (respecting leap seconds etc). That's one constraint I didn't consider yesterday. If your keen, we can try tackle the |
This is a very good point that I hadn't considered.
Well I am certainly keen to scratch this itch! However, I'm not sure I have the time free right now to get too immersed in the leap-seconds problem due to other commitments. That will change in a few weeks though and I'd definitely be keen to pick it up then! |
Ran out of time for now, but made an attempt at regex time validation, missing the leap second checks but there as comments in 12bbf6d in the time branch. Not entirely sure this is the best approach so just exploring options. |
@kylef LGTM, as a first pass. In the interim, you could add For longer term solutions that verify the time and offset in pair (as mentioned in your code comment), I'd lean towards this not being possible and/or viable with regex ... maybe with some clever use of backrefs, but even then it will be a maintenance nightmare and so a procedural solution might be more desirable. |
Okay guys, I haven't followed the discussion too closely, so sorry for jumping in. But I want to add two quick thoughts:
|
One thing to mention so your aware, there are dates which I think this branch may errenously validate as incorrect when they are indeed valid dates. I think this will mostly be around the second fraction decimal places and leap seconds. There may be risk in the opposite direction too, I don't recall the details at the moment. So bear that in mind when using this in any production environment. Working on getting those things fully tested and implemented.
I'll reiterate what my thoughts for clarity:
|
@jnewc I've merged in the date parser (pretty must as-is) via 9b9dc51, I've extended the test suite in json-schema-org/JSON-Schema-Test-Suite#483 to contain all the month-day limit validations which should test regex vs date formatter (i.e, they would fail if date formatter check is removed). I've merged in a slightly different time validation to count for leap seconds in bd34ab5. Next step is to bridge them together and do the date-time parsing. 🎉 |
Looking forward to the date-time addition! |
@kylef great news! CI is failing because there are now two files named |
Based on #118 (comment) and #118 (comment), I'd suggest that the file should exist in the |
Sorry @kylef got my wires crossed - offending file has been removed. |
I think we're good to merge? @jnewc |
This is literally the only thing holding us from releasing our library, spm doesn't let you release a package that relies on non-merged dependencies 😄 What can I do to help get this PR to the finish line? |
Picks up on the work done in #90.