Skip to content

Commit

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

Fixes: #351
  • Loading branch information
nightlyone authored Aug 25, 2023
1 parent 65eab73 commit ee84fd2
Show file tree
Hide file tree
Showing 9 changed files with 139 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;
}
15 changes: 15 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,12 @@ paths:
schema:
type: string
format: date-time
- name: duration_type
in: query
schema:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
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
responses:
"200":
description: OK
Expand Down Expand Up @@ -303,6 +309,12 @@ paths:
schema:
type: string
format: date-time
- name: duration_type
in: query
schema:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
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
requestBody:
content:
application/json:
Expand Down Expand Up @@ -444,6 +456,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,12 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
schema:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
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
responses:
"200":
description: OK
Expand Down Expand Up @@ -303,6 +309,12 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
schema:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
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
requestBody:
content:
application/json:
Expand Down Expand Up @@ -444,6 +456,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,12 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
schema:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
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
responses:
"200":
description: OK
Expand Down Expand Up @@ -303,6 +309,12 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
schema:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
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
requestBody:
content:
application/json:
Expand Down Expand Up @@ -460,6 +472,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,12 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
schema:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
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
responses:
"200":
description: OK
Expand Down Expand Up @@ -303,6 +309,12 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
schema:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
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
requestBody:
content:
application/json:
Expand Down Expand Up @@ -444,6 +456,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,12 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
schema:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
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
responses:
"200":
description: OK
Expand Down Expand Up @@ -303,6 +309,12 @@ paths:
schema:
type: string
format: date-time
- name: durationType
in: query
schema:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
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
requestBody:
content:
application/json:
Expand Down Expand Up @@ -444,6 +456,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
15 changes: 15 additions & 0 deletions cmd/protoc-gen-openapi/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,21 @@ func (g *OpenAPIv3Generator) _buildQueryParamsV3(field *protogen.Field, depths m
},
})
return parameters
case ".google.protobuf.Duration":
fieldSchema := g.reflect.schemaOrReferenceForMessage(field.Message.Desc)
parameters = append(parameters,
&v3.ParameterOrReference{
Oneof: &v3.ParameterOrReference_Parameter{
Parameter: &v3.Parameter{
Name: queryFieldName,
In: "query",
Description: fieldDescription,
Required: false,
Schema: fieldSchema,
},
},
})
return parameters
}

if field.Desc.IsList() {
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 ee84fd2

Please sign in to comment.