Skip to content

Commit

Permalink
fix: Stop lying how GRPC renders durations
Browse files Browse the repository at this point in the history
by reporting the data types and formats that are actually used

TODO:
 * [ ] Find out why it is different than expected when durations are used in
       a query parameter. What parts of gnostic work different here?

Fixes: google#351
  • Loading branch information
nightlyone committed Aug 6, 2023
1 parent 987797b commit 8ee62cb
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import "google/protobuf/struct.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";

option go_package = "github.com/google/gnostic/apps/protoc-gen-openapi/examples/tests/protobuftypes/message/v1;message";

Expand Down Expand Up @@ -113,4 +114,5 @@ message Message {
google.protobuf.FloatValue float_value_type = 22;
google.protobuf.DoubleValue double_value_type = 23;
google.protobuf.Timestamp timestamp_type = 24;
google.protobuf.Duration duration_type = 25;
}
19 changes: 19 additions & 0 deletions cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ paths:
schema:
type: string
format: date-time
- name: duration_type
in: query
description: |-
Signed seconds of the span of time. Must be from -315,576,000,000
to +315,576,000,000 inclusive. Note: these bounds are computed from:
60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
schema:
type: string
responses:
"200":
description: OK
Expand Down Expand Up @@ -303,6 +311,14 @@ paths:
schema:
type: string
format: date-time
- name: duration_type
in: query
description: |-
Signed seconds of the span of time. Must be from -315,576,000,000
to +315,576,000,000 inclusive. Note: these bounds are computed from:
60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
schema:
type: string
requestBody:
content:
application/json:
Expand Down Expand Up @@ -444,6 +460,9 @@ components:
timestamp_type:
type: string
format: date-time
duration_type:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
Message_EmbMessage:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
description: |-
Signed seconds of the span of time. Must be from -315,576,000,000
to +315,576,000,000 inclusive. Note: these bounds are computed from:
60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
schema:
type: string
responses:
"200":
description: OK
Expand Down Expand Up @@ -303,6 +311,14 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
description: |-
Signed seconds of the span of time. Must be from -315,576,000,000
to +315,576,000,000 inclusive. Note: these bounds are computed from:
60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
schema:
type: string
requestBody:
content:
application/json:
Expand Down Expand Up @@ -444,6 +460,9 @@ components:
timestampType:
type: string
format: date-time
durationType:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
Message_EmbMessage:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
description: |-
Signed seconds of the span of time. Must be from -315,576,000,000
to +315,576,000,000 inclusive. Note: these bounds are computed from:
60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
schema:
type: string
responses:
"200":
description: OK
Expand Down Expand Up @@ -303,6 +311,14 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
description: |-
Signed seconds of the span of time. Must be from -315,576,000,000
to +315,576,000,000 inclusive. Note: these bounds are computed from:
60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
schema:
type: string
requestBody:
content:
application/json:
Expand Down Expand Up @@ -460,6 +476,9 @@ components:
timestampType:
type: string
format: date-time
durationType:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
tests.protobuftypes.message.v1.Message_EmbMessage:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
description: |-
Signed seconds of the span of time. Must be from -315,576,000,000
to +315,576,000,000 inclusive. Note: these bounds are computed from:
60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
schema:
type: string
responses:
"200":
description: OK
Expand Down Expand Up @@ -303,6 +311,14 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
description: |-
Signed seconds of the span of time. Must be from -315,576,000,000
to +315,576,000,000 inclusive. Note: these bounds are computed from:
60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
schema:
type: string
requestBody:
content:
application/json:
Expand Down Expand Up @@ -444,6 +460,9 @@ components:
timestampType:
type: string
format: date-time
durationType:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
Message_EmbMessage:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
description: |-
Signed seconds of the span of time. Must be from -315,576,000,000
to +315,576,000,000 inclusive. Note: these bounds are computed from:
60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
schema:
type: string
responses:
"200":
description: OK
Expand Down Expand Up @@ -303,6 +311,14 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
description: |-
Signed seconds of the span of time. Must be from -315,576,000,000
to +315,576,000,000 inclusive. Note: these bounds are computed from:
60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
schema:
type: string
requestBody:
content:
application/json:
Expand Down Expand Up @@ -444,6 +460,9 @@ components:
timestampType:
type: string
format: date-time
durationType:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
Message_EmbMessage:
type: object
properties:
Expand Down
3 changes: 3 additions & 0 deletions cmd/protoc-gen-openapi/generator/reflector.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ func (r *OpenAPIv3Reflector) schemaOrReferenceForMessage(message protoreflect.Me
case ".google.protobuf.Timestamp":
return wk.NewGoogleProtobufTimestampSchema()

case ".google.protobuf.Duration":
return wk.NewGoogleProtobufDurationSchema()

case ".google.type.Date":
return wk.NewGoogleTypeDateSchema()

Expand Down
44 changes: 44 additions & 0 deletions cmd/protoc-gen-openapi/generator/wellknown/schemas.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,50 @@ func NewGoogleProtobufTimestampSchema() *v3.SchemaOrReference {
Schema: &v3.Schema{Type: "string", Format: "date-time"}}}
}

// google.protobuf.Duration is serialized as a string
func NewGoogleProtobufDurationSchema() *v3.SchemaOrReference {
return &v3.SchemaOrReference{
Oneof: &v3.SchemaOrReference_Schema{
// From: https://github.com/protocolbuffers/protobuf/blob/ece5ef6b9b6fa66ef4638335612284379ee4548f/src/google/protobuf/duration.proto
// In JSON format, the Duration type is encoded as a string rather than an
// object, where the string ends in the suffix "s" (indicating seconds) and
// is preceded by the number of seconds, with nanoseconds expressed as
// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
// microsecond should be expressed in JSON format as "3.000001s".
//
// The fields of message google.protobuf.Duration are further described as:
// "int64 seconds"
// Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive. Note: these bounds are computed from:
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
// `int32 nanos`
// Signed fractions of a second at nanosecond resolution of the span
// of time. Durations less than one second are represented with a 0
// `seconds` field and a positive or negative `nanos` field. For durations
// of one second or more, a non-zero value for the `nanos` field must be
// of the same sign as the `seconds` field. Must be from -999,999,999
// to +999,999,999 inclusive.
//
// This leads to the regex below limiting range from -315.576,000,000s to 315,576,000,000s
// allowing -0.999,999,999s to 0.999,999,999s in the floating precision range.
// That full range cannot be expressed precisly in float64 as demonstrated in
// the example at https://go.dev/play/p/XNtuhwdyu8Y for your reference.
// So the well known type google.protobuf.Duration needs a string.
//
// Please note that JSON schemas duration format is NOT the same, as that uses
// a different syntax starting with "P", supports daylight saving times and other
// different features, so it is NOT compatible.
Schema: &v3.Schema{
Type: "string",
Pattern: `^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$`,
Description: "Represents a a duration between -315,576,000,000s and 315,576,000,000s (around 10000 years). Precision is in nanoseconds. 1 nanosecond is represented as 0.000000001s",
},
},
}
}

// google.type.Date is serialized as a string
func NewGoogleTypeDateSchema() *v3.SchemaOrReference {
return &v3.SchemaOrReference{
Expand Down

0 comments on commit 8ee62cb

Please sign in to comment.