From 716f3041fa8cddaacb8c311eb71b43c016acf45c Mon Sep 17 00:00:00 2001 From: Matthias Preu <5973515+mpreu@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:11:33 +0200 Subject: [PATCH] Fix RFC3339 validation OpenAPI specifies the format `date-time` as RFC3339 compliant. In a RFC3339 `date-time` the `time-offset` is a required component. The current RFC3339 validation regex defines the `time-offset` as an optional component. This leads to errors where a timestamp is considered valid by the library but the value still cannot be parsed into a `time.Time`. This change makes the `time-offset` a required component. Relevant tests are changed accordingly. Links: - https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 Fixes: #1031 --- .github/docs/openapi3.txt | 2 +- openapi3/schema_formats.go | 2 +- openapi3/schema_test.go | 2 +- openapi3filter/validation_test.go | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/docs/openapi3.txt b/.github/docs/openapi3.txt index 7d0fa4496..7458a1b1d 100644 --- a/.github/docs/openapi3.txt +++ b/.github/docs/openapi3.txt @@ -38,7 +38,7 @@ const ( FormatOfStringDate = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])$` // FormatOfStringDateTime is a RFC3339 date-time format regexp, for example "2017-07-21T17:32:28Z". - FormatOfStringDateTime = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$` + FormatOfStringDateTime = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$` ) const ( SerializationSimple = "simple" diff --git a/openapi3/schema_formats.go b/openapi3/schema_formats.go index 489105b48..da954dc58 100644 --- a/openapi3/schema_formats.go +++ b/openapi3/schema_formats.go @@ -44,7 +44,7 @@ const ( FormatOfStringDate = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])$` // FormatOfStringDateTime is a RFC3339 date-time format regexp, for example "2017-07-21T17:32:28Z". - FormatOfStringDateTime = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$` + FormatOfStringDateTime = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$` ) func init() { diff --git a/openapi3/schema_test.go b/openapi3/schema_test.go index a56148e56..ef35dc2fa 100644 --- a/openapi3/schema_test.go +++ b/openapi3/schema_test.go @@ -489,7 +489,6 @@ var schemaExamples = []schemaExample{ "format": "date-time", }, AllValid: []any{ - "2017-12-31T11:59:59", "2017-12-31T11:59:59Z", "2017-12-31T11:59:59-11:30", "2017-12-31T11:59:59+11:30", @@ -503,6 +502,7 @@ var schemaExamples = []schemaExample{ "2017-12-31T11:59:59\n", "2017-12-31T11:59:59.+11:30", "2017-12-31T11:59:59.Z", + "2017-12-31T11:59:59", }, }, diff --git a/openapi3filter/validation_test.go b/openapi3filter/validation_test.go index 7f78b117b..865ffa254 100644 --- a/openapi3filter/validation_test.go +++ b/openapi3filter/validation_test.go @@ -248,14 +248,14 @@ func TestFilter(t *testing.T) { // Test query parameter openapi3filter req = ExampleRequest{ Method: "POST", - URL: "http://example.com/api/prefix/v/suffix?queryArgAnyOf=ae&queryArgOneOf=ac&queryArgAllOf=2017-12-31T11:59:59", + URL: "http://example.com/api/prefix/v/suffix?queryArgAnyOf=ae&queryArgOneOf=ac&queryArgAllOf=2017-12-31T11:59:59Z", } err = expect(req, resp) require.NoError(t, err) req = ExampleRequest{ Method: "POST", - URL: "http://example.com/api/prefix/v/suffix?queryArgAnyOf=2017-12-31T11:59:59", + URL: "http://example.com/api/prefix/v/suffix?queryArgAnyOf=2017-12-31T11:59:59Z", } err = expect(req, resp) require.NoError(t, err) @@ -269,7 +269,7 @@ func TestFilter(t *testing.T) { req = ExampleRequest{ Method: "POST", - URL: "http://example.com/api/prefix/v/suffix?queryArgOneOf=2017-12-31T11:59:59", + URL: "http://example.com/api/prefix/v/suffix?queryArgOneOf=2017-12-31T11:59:59Z", } err = expect(req, resp) require.IsType(t, &RequestError{}, err)