From 8f11b1779b836da211121c40f720d5eb8b4ab57a Mon Sep 17 00:00:00 2001 From: Rizhen Zhang Date: Fri, 7 Jan 2022 11:47:13 -0800 Subject: [PATCH] PLAT-1067: fix wellknown type mapping --- generator/template.go | 63 ++++- .../google/protobuf/duration.proto | 116 +++++++++ .../google/protobuf/field_mask.proto | 245 ++++++++++++++++++ integration_tests/scripts/gen-protos.sh | 2 +- integration_tests/wellknown.proto | 29 +++ 5 files changed, 443 insertions(+), 12 deletions(-) create mode 100644 integration_tests/google/protobuf/duration.proto create mode 100644 integration_tests/google/protobuf/field_mask.proto create mode 100644 integration_tests/wellknown.proto diff --git a/generator/template.go b/generator/template.go index b92ab3f..6036e28 100644 --- a/generator/template.go +++ b/generator/template.go @@ -19,8 +19,12 @@ import ( const tmpl = ` {{define "dependencies"}} -{{range .}}import * as {{.ModuleIdentifier}} from "{{.SourceFile}}" -{{end}}{{end}} +{{range . -}} +{{- if isNotWellKnownDeps .SourceFile -}} +import * as {{.ModuleIdentifier}} from "{{.SourceFile}}" +{{- end }} +{{- end}} +{{end}} {{define "enums"}} {{range .}}export enum {{.Name}} { @@ -448,6 +452,9 @@ func GetTemplate(r *registry.Registry) *template.Template { "renderURL": renderURL(r), "buildInitReq": buildInitReq, "fieldName": fieldName(r), + "isNotWellKnownDeps": func(dep string) bool { + return !isWellKnownDeps(dep) + }, }) t = template.Must(t.Parse(tmpl)) @@ -555,13 +562,14 @@ func tsType(r *registry.Registry, fieldType data.Type) string { return fmt.Sprintf("{[key: %s]: %s}", keyType, valueType) } - typeStr := "" - if strings.Index(info.Type, ".") != 0 { - typeStr = mapScalaType(info.Type) - } else if !info.IsExternal { - typeStr = typeInfo.PackageIdentifier - } else { - typeStr = data.GetModuleName(typeInfo.Package, typeInfo.File) + "." + typeInfo.PackageIdentifier + typeStr := mapWellKnownType(info.Type) + + if typeStr == "" { + if !info.IsExternal { + typeStr = typeInfo.PackageIdentifier + } else { + typeStr = data.GetModuleName(typeInfo.Package, typeInfo.File) + "." + typeInfo.PackageIdentifier + } } if info.IsRepeated { @@ -570,7 +578,7 @@ func tsType(r *registry.Registry, fieldType data.Type) string { return typeStr } -func mapScalaType(protoType string) string { +func mapWellKnownType(protoType string) string { switch protoType { case "uint64", "sint64", "int64", "fixed64", "sfixed64", "string": return "string" @@ -580,8 +588,41 @@ func mapScalaType(protoType string) string { return "boolean" case "bytes": return "Uint8Array" + case ".google.protobuf.Timestamp": + return "string" + case ".google.protobuf.Duration": + return "string" + case ".google.protobuf.Struct": + return "unknown" + case ".google.protobuf.Value": + return "unknown" + case ".google.protobuf.ListValue": + return "unknown[]" + case ".google.protobuf.NullValue": + return "null" + case ".google.protobuf.FieldMask": + return "string[]" + case ".google.protobuf.Any": + return "unknown" } - return "" +} +var ( + wellKnownDeps = []string{ + "google/protobuf/duration", + "google/protobuf/timestamp", + "google/protobuf/struct", + "google/protobuf/field_mask", + "google/protobuf/any", + } +) + +func isWellKnownDeps(dep string) bool { + for _, wellKnownDep := range wellKnownDeps { + if strings.Contains(dep, wellKnownDep) { + return true + } + } + return false } diff --git a/integration_tests/google/protobuf/duration.proto b/integration_tests/google/protobuf/duration.proto new file mode 100644 index 0000000..cb7cf0e --- /dev/null +++ b/integration_tests/google/protobuf/duration.proto @@ -0,0 +1,116 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/durationpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DurationProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// A Duration represents a signed, fixed-length span of time represented +// as a count of seconds and fractions of seconds at nanosecond +// resolution. It is independent of any calendar and concepts like "day" +// or "month". It is related to Timestamp in that the difference between +// two Timestamp values is a Duration and it can be added or subtracted +// from a Timestamp. Range is approximately +-10,000 years. +// +// # Examples +// +// Example 1: Compute Duration from two Timestamps in pseudo code. +// +// Timestamp start = ...; +// Timestamp end = ...; +// Duration duration = ...; +// +// duration.seconds = end.seconds - start.seconds; +// duration.nanos = end.nanos - start.nanos; +// +// if (duration.seconds < 0 && duration.nanos > 0) { +// duration.seconds += 1; +// duration.nanos -= 1000000000; +// } else if (duration.seconds > 0 && duration.nanos < 0) { +// duration.seconds -= 1; +// duration.nanos += 1000000000; +// } +// +// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +// +// Timestamp start = ...; +// Duration duration = ...; +// Timestamp end = ...; +// +// end.seconds = start.seconds + duration.seconds; +// end.nanos = start.nanos + duration.nanos; +// +// if (end.nanos < 0) { +// end.seconds -= 1; +// end.nanos += 1000000000; +// } else if (end.nanos >= 1000000000) { +// end.seconds += 1; +// end.nanos -= 1000000000; +// } +// +// Example 3: Compute Duration from datetime.timedelta in Python. +// +// td = datetime.timedelta(days=3, minutes=10) +// duration = Duration() +// duration.FromTimedelta(td) +// +// # JSON Mapping +// +// 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". +// +// +message Duration { + // 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 + int64 seconds = 1; + + // 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. + int32 nanos = 2; +} \ No newline at end of file diff --git a/integration_tests/google/protobuf/field_mask.proto b/integration_tests/google/protobuf/field_mask.proto new file mode 100644 index 0000000..52c34c5 --- /dev/null +++ b/integration_tests/google/protobuf/field_mask.proto @@ -0,0 +1,245 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "FieldMaskProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb"; +option cc_enable_arenas = true; + +// `FieldMask` represents a set of symbolic field paths, for example: +// +// paths: "f.a" +// paths: "f.b.d" +// +// Here `f` represents a field in some root message, `a` and `b` +// fields in the message found in `f`, and `d` a field found in the +// message in `f.b`. +// +// Field masks are used to specify a subset of fields that should be +// returned by a get operation or modified by an update operation. +// Field masks also have a custom JSON encoding (see below). +// +// # Field Masks in Projections +// +// When used in the context of a projection, a response message or +// sub-message is filtered by the API to only contain those fields as +// specified in the mask. For example, if the mask in the previous +// example is applied to a response message as follows: +// +// f { +// a : 22 +// b { +// d : 1 +// x : 2 +// } +// y : 13 +// } +// z: 8 +// +// The result will not contain specific values for fields x,y and z +// (their value will be set to the default, and omitted in proto text +// output): +// +// +// f { +// a : 22 +// b { +// d : 1 +// } +// } +// +// A repeated field is not allowed except at the last position of a +// paths string. +// +// If a FieldMask object is not present in a get operation, the +// operation applies to all fields (as if a FieldMask of all fields +// had been specified). +// +// Note that a field mask does not necessarily apply to the +// top-level response message. In case of a REST get operation, the +// field mask applies directly to the response, but in case of a REST +// list operation, the mask instead applies to each individual message +// in the returned resource list. In case of a REST custom method, +// other definitions may be used. Where the mask applies will be +// clearly documented together with its declaration in the API. In +// any case, the effect on the returned resource/resources is required +// behavior for APIs. +// +// # Field Masks in Update Operations +// +// A field mask in update operations specifies which fields of the +// targeted resource are going to be updated. The API is required +// to only change the values of the fields as specified in the mask +// and leave the others untouched. If a resource is passed in to +// describe the updated values, the API ignores the values of all +// fields not covered by the mask. +// +// If a repeated field is specified for an update operation, new values will +// be appended to the existing repeated field in the target resource. Note that +// a repeated field is only allowed in the last position of a `paths` string. +// +// If a sub-message is specified in the last position of the field mask for an +// update operation, then new value will be merged into the existing sub-message +// in the target resource. +// +// For example, given the target message: +// +// f { +// b { +// d: 1 +// x: 2 +// } +// c: [1] +// } +// +// And an update message: +// +// f { +// b { +// d: 10 +// } +// c: [2] +// } +// +// then if the field mask is: +// +// paths: ["f.b", "f.c"] +// +// then the result will be: +// +// f { +// b { +// d: 10 +// x: 2 +// } +// c: [1, 2] +// } +// +// An implementation may provide options to override this default behavior for +// repeated and message fields. +// +// In order to reset a field's value to the default, the field must +// be in the mask and set to the default value in the provided resource. +// Hence, in order to reset all fields of a resource, provide a default +// instance of the resource and set all fields in the mask, or do +// not provide a mask as described below. +// +// If a field mask is not present on update, the operation applies to +// all fields (as if a field mask of all fields has been specified). +// Note that in the presence of schema evolution, this may mean that +// fields the client does not know and has therefore not filled into +// the request will be reset to their default. If this is unwanted +// behavior, a specific service may require a client to always specify +// a field mask, producing an error if not. +// +// As with get operations, the location of the resource which +// describes the updated values in the request message depends on the +// operation kind. In any case, the effect of the field mask is +// required to be honored by the API. +// +// ## Considerations for HTTP REST +// +// The HTTP kind of an update operation which uses a field mask must +// be set to PATCH instead of PUT in order to satisfy HTTP semantics +// (PUT must only be used for full updates). +// +// # JSON Encoding of Field Masks +// +// In JSON, a field mask is encoded as a single string where paths are +// separated by a comma. Fields name in each path are converted +// to/from lower-camel naming conventions. +// +// As an example, consider the following message declarations: +// +// message Profile { +// User user = 1; +// Photo photo = 2; +// } +// message User { +// string display_name = 1; +// string address = 2; +// } +// +// In proto a field mask for `Profile` may look as such: +// +// mask { +// paths: "user.display_name" +// paths: "photo" +// } +// +// In JSON, the same mask is represented as below: +// +// { +// mask: "user.displayName,photo" +// } +// +// # Field Masks and Oneof Fields +// +// Field masks treat fields in oneofs just as regular fields. Consider the +// following message: +// +// message SampleMessage { +// oneof test_oneof { +// string name = 4; +// SubMessage sub_message = 9; +// } +// } +// +// The field mask can be: +// +// mask { +// paths: "name" +// } +// +// Or: +// +// mask { +// paths: "sub_message" +// } +// +// Note that oneof type names ("test_oneof" in this case) cannot be used in +// paths. +// +// ## Field Mask Verification +// +// The implementation of any API method which has a FieldMask type field in the +// request should verify the included field paths, and return an +// `INVALID_ARGUMENT` error if any path is unmappable. +message FieldMask { + // The set of field mask paths. + repeated string paths = 1; +} \ No newline at end of file diff --git a/integration_tests/scripts/gen-protos.sh b/integration_tests/scripts/gen-protos.sh index c354c31..ba53b8d 100755 --- a/integration_tests/scripts/gen-protos.sh +++ b/integration_tests/scripts/gen-protos.sh @@ -4,4 +4,4 @@ ENABLE_STYLING_CHECK=${2:-"false"} cd .. && go install && cd integration_tests && \ protoc -I . -I ../.. \ --grpc-gateway-ts_out=logtostderr=true,use_proto_names=$USE_PROTO_NAMES,enable_styling_check=$ENABLE_STYLING_CHECK,loglevel=debug:./ \ - service.proto msg.proto empty.proto \ No newline at end of file + service.proto msg.proto empty.proto wellknown.proto \ No newline at end of file diff --git a/integration_tests/wellknown.proto b/integration_tests/wellknown.proto new file mode 100644 index 0000000..610445e --- /dev/null +++ b/integration_tests/wellknown.proto @@ -0,0 +1,29 @@ + +syntax = "proto3"; +package xxx; +option go_package = "github.com/xxx"; + +import "google/api/annotations.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/any.proto"; + +enum AEnum { + VALUE_0 = 0; + VALUE_1 = 1; +} + +message WellknownTypes { + google.protobuf.Timestamp timestamp = 1; + google.protobuf.Duration duration = 2; + AEnum enum_value = 3; + map map_value = 4; + google.protobuf.Struct struct = 5; + google.protobuf.ListValue list_value = 6; + google.protobuf.NullValue null_value = 7; + google.protobuf.FieldMask field_mask = 8; + google.protobuf.Any any = 9; +} \ No newline at end of file