diff --git a/protoc-gen-grpc-gateway/descriptor/services.go b/protoc-gen-grpc-gateway/descriptor/services.go index c9dfec8e040..a62bd06fe16 100644 --- a/protoc-gen-grpc-gateway/descriptor/services.go +++ b/protoc-gen-grpc-gateway/descriptor/services.go @@ -194,7 +194,12 @@ func (r *Registry) newParam(meth *Method, path string) (Parameter, error) { target := fields[l-1].Target switch target.GetType() { case descriptor.FieldDescriptorProto_TYPE_MESSAGE, descriptor.FieldDescriptorProto_TYPE_GROUP: - return Parameter{}, fmt.Errorf("aggregate type %s in parameter of %s.%s: %s", target.Type, meth.Service.GetName(), meth.GetName(), path) + glog.V(2).Infoln("found aggregate type:", target, target.TypeName) + if IsWellKnownType(*target.TypeName) { + glog.V(2).Infoln("found well known aggregate type:", target) + } else { + return Parameter{}, fmt.Errorf("aggregate type %s in parameter of %s.%s: %s", target.Type, meth.Service.GetName(), meth.GetName(), path) + } } return Parameter{ FieldPath: FieldPath(fields), diff --git a/protoc-gen-grpc-gateway/descriptor/types.go b/protoc-gen-grpc-gateway/descriptor/types.go index 248538e7bad..c24bd61c890 100644 --- a/protoc-gen-grpc-gateway/descriptor/types.go +++ b/protoc-gen-grpc-gateway/descriptor/types.go @@ -9,6 +9,12 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/httprule" ) +// IsWellKnownType returns true if the provided fully qualified type name is considered 'well-known'. +func IsWellKnownType(typeName string) bool { + _, ok := wellKnownTypeConv[typeName] + return ok +} + // GoPackage represents a golang package type GoPackage struct { // Path is the package path to the package. @@ -194,6 +200,9 @@ func (p Parameter) ConvertFuncExpr() (string, error) { } typ := p.Target.GetType() conv, ok := tbl[typ] + if !ok { + conv, ok = wellKnownTypeConv[p.Target.GetTypeName()] + } if !ok { return "", fmt.Errorf("unsupported field type %s of parameter %s in %s.%s", typ, p.FieldPath, p.Method.Service.GetName(), p.Method.GetName()) } @@ -319,4 +328,9 @@ var ( descriptor.FieldDescriptorProto_TYPE_SINT32: "runtime.Int32P", descriptor.FieldDescriptorProto_TYPE_SINT64: "runtime.Int64P", } + + wellKnownTypeConv = map[string]string{ + ".google.protobuf.Timestamp": "runtime.Timestamp", + ".google.protobuf.Duration": "runtime.Duration", + } ) diff --git a/protoc-gen-swagger/genswagger/template.go b/protoc-gen-swagger/genswagger/template.go index 170e0e4befd..80f60b5bc3d 100644 --- a/protoc-gen-swagger/genswagger/template.go +++ b/protoc-gen-swagger/genswagger/template.go @@ -13,6 +13,16 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor" ) +var wktSchemas = map[string]schemaCore{ + ".google.protobuf.Timestamp": schemaCore{ + Type: "string", + Format: "date-time", + }, + ".google.protobuf.Duration": schemaCore{ + Type: "string", + }, +} + func listEnumNames(enum *descriptor.Enum) (names []string) { for _, value := range enum.GetValue() { names = append(names, value.GetName()) @@ -161,6 +171,8 @@ func renderMessagesAsDefinition(messages messageMap, d swaggerDefinitionsObject, switch name { case ".google.protobuf.Timestamp": continue + case ".google.protobuf.Duration": + continue } if opt := msg.GetOptions(); opt != nil && opt.MapEntry != nil && *opt.MapEntry { continue @@ -213,11 +225,8 @@ func schemaOfField(f *descriptor.Field, reg *descriptor.Registry) swaggerSchemaO switch ft := fd.GetType(); ft { case pbdescriptor.FieldDescriptorProto_TYPE_ENUM, pbdescriptor.FieldDescriptorProto_TYPE_MESSAGE, pbdescriptor.FieldDescriptorProto_TYPE_GROUP: - if fd.GetTypeName() == ".google.protobuf.Timestamp" && pbdescriptor.FieldDescriptorProto_TYPE_MESSAGE == ft { - core = schemaCore{ - Type: "string", - Format: "date-time", - } + if wktSchema, ok := wktSchemas[fd.GetTypeName()]; ok { + core = wktSchema } else { core = schemaCore{ Ref: "#/definitions/" + fullyQualifiedNameToSwaggerName(fd.GetTypeName(), reg), @@ -446,7 +455,13 @@ func renderServices(services []*descriptor.Service, paths swaggerPathsObject, re var paramType, paramFormat string switch pt := parameter.Target.GetType(); pt { case pbdescriptor.FieldDescriptorProto_TYPE_GROUP, pbdescriptor.FieldDescriptorProto_TYPE_MESSAGE: - return fmt.Errorf("only primitive types are allowed in path parameters") + if descriptor.IsWellKnownType(parameter.Target.GetTypeName()) { + schema := schemaOfField(parameter.Target, reg) + paramType = schema.Type + paramFormat = schema.Format + } else { + return fmt.Errorf("only primitive and well-known types are allowed in path parameters") + } case pbdescriptor.FieldDescriptorProto_TYPE_ENUM: paramType = fullyQualifiedNameToSwaggerName(parameter.Target.GetTypeName(), reg) paramFormat = "" diff --git a/runtime/convert.go b/runtime/convert.go index 1af5cc4ebdd..eb6893b58bc 100644 --- a/runtime/convert.go +++ b/runtime/convert.go @@ -2,6 +2,10 @@ package runtime import ( "strconv" + + "github.com/gogo/protobuf/jsonpb" + "github.com/golang/protobuf/ptypes/duration" + "github.com/golang/protobuf/ptypes/timestamp" ) // String just returns the given string. @@ -56,3 +60,17 @@ func Uint32(val string) (uint32, error) { } return uint32(i), nil } + +// Timestamp converts the given RFC3339 formatted string into a timestamp.Timestamp. +func Timestamp(val string) (*timestamp.Timestamp, error) { + var r *timestamp.Timestamp + err := jsonpb.UnmarshalString(val, r) + return r, err +} + +// Duration converts the given string into a timestamp.Duration. +func Duration(val string) (*duration.Duration, error) { + var r *duration.Duration + err := jsonpb.UnmarshalString(val, r) + return r, err +}