Skip to content

Add support for date, time, and date-time validators#118

Closed
jnewc wants to merge 20 commits into
kylef:masterfrom
jnewc:master
Closed

Add support for date, time, and date-time validators#118
jnewc wants to merge 20 commits into
kylef:masterfrom
jnewc:master

Conversation

@jnewc

@jnewc jnewc commented Apr 14, 2021

Copy link
Copy Markdown
Contributor

Picks up on the work done in #90.

@jnewc

jnewc commented Apr 14, 2021

Copy link
Copy Markdown
Contributor Author

@kylef can you review please?

@jnewc

jnewc commented Apr 20, 2021

Copy link
Copy Markdown
Contributor Author

@kylef hey again, any updates on this?

@kylef kylef left a comment

Copy link
Copy Markdown
Owner

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

Comment thread Sources/Validation/date.swift Outdated
Comment thread .gitignore
@@ -0,0 +1,46 @@
import Foundation

func validateDateTime(_ context: Context, _ value: Any) -> AnySequence<ValidationError> {

Copy link
Copy Markdown
Owner

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)
  }
}

Copy link
Copy Markdown
Contributor Author

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.

Copy link
Copy Markdown
Owner

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

@kylef kylef Apr 22, 2021

Copy link
Copy Markdown
Owner

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.

Comment thread 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"

Copy link
Copy Markdown
Owner

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)
  }
}

Comment thread Sources/Validation/datetime.swift
@jnewc

jnewc commented Apr 20, 2021

Copy link
Copy Markdown
Contributor Author

Thanks @kylef - I'll ping you again when I've applied all the necessary updates 👍

@jnewc

jnewc commented Apr 21, 2021

Copy link
Copy Markdown
Contributor Author

@kylef added some commits + a comment re: leap seconds

@yspreen yspreen mentioned this pull request Apr 21, 2021
6 tasks
@yspreen

yspreen commented Apr 21, 2021

Copy link
Copy Markdown

This is actually not complete yet. While you added a date format, it fails during execution:

'format' validation of 'date' is not yet supported.

@yspreen

yspreen commented Apr 21, 2021

Copy link
Copy Markdown

See #121

@jnewc

jnewc commented Apr 21, 2021

Copy link
Copy Markdown
Contributor Author

This is actually not complete yet. While you added a date format, it fails during execution:

'format' validation of 'date' is not yet supported.

This appears to be working fine for our team's use case - example validation error:

  - 0 : "\'2021-01-01T00:00:0A\' is not a valid RFC 3339 formatted date-time." 

@yspreen

yspreen commented Apr 21, 2021

Copy link
Copy Markdown

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

@jnewc

jnewc commented Apr 22, 2021

Copy link
Copy Markdown
Contributor Author

@kylef in case you missed it, this is ready for another review - thanks again :-)

@kylef kylef left a comment

Copy link
Copy Markdown
Owner

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?

@kylef

kylef commented Apr 22, 2021

Copy link
Copy Markdown
Owner

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 isValidTime, isValidDate etc which internally the validators would use. Would possibly do the same for other validators such as the email format, because it depends on similar logic from the host/ipv4 and ipv6 formats.

@jnewc

jnewc commented Apr 23, 2021

Copy link
Copy Markdown
Contributor Author

If going down that route we could have internal functions such as isValidTime, isValidDate etc which internally the validators would use. Would possibly do the same for other validators such as the email format, because it depends on similar logic from the host/ipv4 and ipv6 formats.

@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 date-time? or something else?

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.

@kylef

kylef commented Apr 23, 2021

Copy link
Copy Markdown
Owner

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 time format on its own, and validating against json-schema-org/JSON-Schema-Test-Suite#481 (we can use the branch for those tests in Tests/Cases submodule to validate it works correctly) and iterate on date and then date-time. If I have a chance tonight I'll try prepare an equivalent to those time tests for date, looking at date.json, it doesn't cover many cases (and only one positive case).

@jnewc

jnewc commented Apr 23, 2021

Copy link
Copy Markdown
Contributor Author

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.

This is a very good point that I hadn't considered.

If your keen, we can try tackle the time format on its own, and validating against json-schema-org/JSON-Schema-Test-Suite#481 (we can use the branch for those tests in Tests/Cases submodule to validate it works correctly) and iterate on date and then date-time. If I have a chance tonight I'll try prepare an equivalent to those time tests for date, looking at date.json, it doesn't cover many cases (and only one positive case).

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!

@kylef

kylef commented Apr 23, 2021

Copy link
Copy Markdown
Owner

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.

@jnewc

jnewc commented Apr 26, 2021

Copy link
Copy Markdown
Contributor Author

@kylef LGTM, as a first pass.

In the interim, you could add 23.59.60+00:00 as a literal check.

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.

@yspreen

yspreen commented Apr 30, 2021

Copy link
Copy Markdown

Okay guys, I haven't followed the discussion too closely, so sorry for jumping in. But I want to add two quick thoughts:

  1. We are using jnewc's branch for an EU-wide project at the moment, and it works perfectly. Merging this PR would fix the current issue with master not allowing date formats: 'format' validation of 'date' is not yet supported. It would be good to get rid of a fork in our dependency list.
  2. To get back to the schema discussion in Add support for date, time, and date-time validators, and support a greater range of schema ids. #121: That PR is completely disfunct, and I agree with closing it. However, I still think making schema validation more robust would be a valuable asset to this project. If you agree, @kylef​, I'll make a new one with a) more robustness, and b) avoiding the mistakes I did in Add support for date, time, and date-time validators, and support a greater range of schema ids. #121. Let me know. No need in opening a PR when I know it will not get merged :)

@kylef

kylef commented Apr 30, 2021

Copy link
Copy Markdown
Owner
  1. We are using jnewc's branch for an EU-wide project at the moment, and it works perfectly. Merging this PR would fix the current issue with master not allowing date formats: 'format' validation of 'date' is not yet supported. It would be good to get rid of a fork in our dependency list.

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.

  1. To get back to the schema discussion in Add support for date, time, and date-time validators, and support a greater range of schema ids. #121: That PR is completely disfunct, and I agree with closing it. However, I still think making schema validation more robust would be a valuable asset to this project. If you agree, @kylef​, I'll make a new one with a) more robustness, and b) avoiding the mistakes I did in Add support for date, time, and date-time validators, and support a greater range of schema ids. #121. Let me know. No need in opening a PR when I know it will not get merged :)

I'll reiterate what my thoughts for clarity:

  • I'm completely for allowing https when the spec states http for the scheme. Forcing the use of http when the spec is defined as https is not going to be accepted, but I will happily accept the https when spec states http.
  • There's two places in which meta schema id/$id are matched from (both from $schema, and from $ref). The initial detection of a schema type from $schema I think you alredy found what does that. The other place for $ref is within the RefResolver when a schema uses $ref against a metaschema. The schemas are registered with the resolver upon load. Perhaps we should register metaschemas twice in that case for https upgrades) in the respective validator (for example Draft7Validator). I would like to ensure the behaviour is consistent with matching core specs whether it be via $id or $ref.
  • I'm hesistant to allow http when the spec states https and json-schema.org explicitly calls out that https should be used. When it comes to interopability with other libraries, that can incur an insecure HTTP request to fetch an untrusted metaschema. Disallowing that entirely reduce risk as it forces consumers to ensure that a trusted URL is used which uses encryption and certificate verification or embedded in libraries.

@kylef

kylef commented Apr 30, 2021

Copy link
Copy Markdown
Owner

@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. 🎉

@cwagdev

cwagdev commented May 3, 2021

Copy link
Copy Markdown
Contributor

Looking forward to the date-time addition!

@jnewc

jnewc commented May 4, 2021

Copy link
Copy Markdown
Contributor Author

@kylef great news!

CI is failing because there are now two files named time.swift, which the Swift compiler is unhappy with - do you have a preference of which filename you'd like to change, and to what name?

@kylef

kylef commented May 4, 2021

Copy link
Copy Markdown
Owner

Based on #118 (comment) and #118 (comment), I'd suggest that the file should exist in the Sources/Format directory.

@jnewc

jnewc commented May 5, 2021

Copy link
Copy Markdown
Contributor Author

Sorry @kylef got my wires crossed - offending file has been removed.

@yspreen

yspreen commented May 6, 2021

Copy link
Copy Markdown

I think we're good to merge? @jnewc

@yspreen

yspreen commented May 10, 2021

Copy link
Copy Markdown

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?

@jnewc jnewc closed this Sep 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants