This commit is contained in:
2025-04-12 12:38:00 +08:00
parent 5341dfcd1a
commit 52bfd05b80
47 changed files with 4730 additions and 2 deletions

View File

@@ -0,0 +1,59 @@
package plugin
import (
"strings"
"git.apinb.com/bsm-tools/protoc-gen-ts/internal/codegen"
"google.golang.org/genproto/googleapis/api/annotations"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
type commentGenerator struct {
descriptor protoreflect.Descriptor
}
func (c commentGenerator) generateLeading(f *codegen.File, indent int) {
loc := c.descriptor.ParentFile().SourceLocations().ByDescriptor(c.descriptor)
var comments string
if loc.TrailingComments != "" {
comments = comments + loc.TrailingComments
}
if loc.LeadingComments != "" {
comments = comments + loc.LeadingComments
}
lines := strings.Split(comments, "\n")
for _, line := range lines {
if line == "" {
continue
}
f.P(t(indent), "/** "+strings.TrimSpace(line)+" */ ")
}
if field, ok := c.descriptor.(protoreflect.FieldDescriptor); ok {
if behaviorComment := fieldBehaviorComment(field); len(behaviorComment) > 0 {
f.P(t(indent), "/** "+behaviorComment+" */")
}
}
}
func fieldBehaviorComment(field protoreflect.FieldDescriptor) string {
behaviors := getFieldBehaviors(field)
if len(behaviors) == 0 {
return ""
}
behaviorStrings := make([]string, 0, len(behaviors))
for _, b := range behaviors {
behaviorStrings = append(behaviorStrings, b.String())
}
return "Behaviors: " + strings.Join(behaviorStrings, ", ")
}
func getFieldBehaviors(field protoreflect.FieldDescriptor) []annotations.FieldBehavior {
if behaviors, ok := proto.GetExtension(
field.Options(), annotations.E_FieldBehavior,
).([]annotations.FieldBehavior); ok {
return behaviors
}
return nil
}

View File

@@ -0,0 +1,31 @@
package plugin
import (
"strconv"
"git.apinb.com/bsm-tools/protoc-gen-ts/internal/codegen"
"google.golang.org/protobuf/reflect/protoreflect"
)
type enumGenerator struct {
pkg protoreflect.FullName
enum protoreflect.EnumDescriptor
}
func (e enumGenerator) Generate(f *codegen.File) {
commentGenerator{descriptor: e.enum}.generateLeading(f, 0)
f.P("export type ", scopedDescriptorTypeName(e.pkg, e.enum), " =")
if e.enum.Values().Len() == 1 {
commentGenerator{descriptor: e.enum.Values().Get(0)}.generateLeading(f, 1)
f.P(t(1), strconv.Quote(string(e.enum.Values().Get(0).Name())), ";")
return
}
rangeEnumValues(e.enum, func(value protoreflect.EnumValueDescriptor, last bool) {
commentGenerator{descriptor: value}.generateLeading(f, 1)
if last {
f.P(t(1), "| ", strconv.Quote(string(value.Name())), ";")
} else {
f.P(t(1), "| ", strconv.Quote(string(value.Name())))
}
})
}

View File

@@ -0,0 +1,52 @@
package plugin
import (
"fmt"
"path"
"strings"
"git.apinb.com/bsm-tools/protoc-gen-ts/internal/codegen"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/pluginpb"
)
func Generate(request *pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorResponse, error) {
generate := make(map[string]struct{})
registry, err := protodesc.NewFiles(&descriptorpb.FileDescriptorSet{
File: request.GetProtoFile(),
})
if err != nil {
return nil, fmt.Errorf("create proto registry: %w", err)
}
for _, f := range request.GetFileToGenerate() {
generate[f] = struct{}{}
}
packaged := make(map[protoreflect.FullName][]protoreflect.FileDescriptor)
for _, f := range request.GetFileToGenerate() {
file, err := registry.FindFileByPath(f)
if err != nil {
return nil, fmt.Errorf("find file %s: %w", f, err)
}
packaged[file.Package()] = append(packaged[file.Package()], file)
}
var res pluginpb.CodeGeneratorResponse
for pkg, files := range packaged {
var index codegen.File
indexPathElems := append(strings.Split(string(pkg), "."), "index.ts")
if err := (packageGenerator{pkg: pkg, files: files}).Generate(&index); err != nil {
return nil, fmt.Errorf("generate package '%s': %w", pkg, err)
}
index.P()
index.P("// @@protoc_insertion_point(typescript-http-eof)")
res.File = append(res.File, &pluginpb.CodeGeneratorResponse_File{
Name: proto.String(path.Join(indexPathElems...)),
Content: proto.String(string(index.Content())),
})
}
res.SupportedFeatures = proto.Uint64(uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL))
return &res, nil
}

View File

@@ -0,0 +1,58 @@
package plugin
import (
"strings"
"google.golang.org/protobuf/reflect/protoreflect"
)
func scopedDescriptorTypeName(pkg protoreflect.FullName, desc protoreflect.Descriptor) string {
name := string(desc.Name())
var prefix string
if desc.Parent() != desc.ParentFile() {
prefix = descriptorTypeName(desc.Parent()) + "_"
}
if desc.ParentFile().Package() != pkg {
prefix = packagePrefix(desc.ParentFile().Package()) + prefix
}
return prefix + name
}
func descriptorTypeName(desc protoreflect.Descriptor) string {
name := string(desc.Name())
var prefix string
if desc.Parent() != desc.ParentFile() {
prefix = descriptorTypeName(desc.Parent()) + "_"
}
return prefix + name
}
func packagePrefix(pkg protoreflect.FullName) string {
return strings.Join(strings.Split(string(pkg), "."), "") + "_"
}
func rangeFields(message protoreflect.MessageDescriptor, f func(field protoreflect.FieldDescriptor)) {
for i := 0; i < message.Fields().Len(); i++ {
f(message.Fields().Get(i))
}
}
func rangeMethods(methods protoreflect.MethodDescriptors, f func(method protoreflect.MethodDescriptor)) {
for i := 0; i < methods.Len(); i++ {
f(methods.Get(i))
}
}
func rangeEnumValues(enum protoreflect.EnumDescriptor, f func(value protoreflect.EnumValueDescriptor, last bool)) {
for i := 0; i < enum.Values().Len(); i++ {
if i == enum.Values().Len()-1 {
f(enum.Values().Get(i), true)
} else {
f(enum.Values().Get(i), false)
}
}
}
func t(n int) string {
return strings.Repeat(" ", n)
}

View File

@@ -0,0 +1,48 @@
package plugin
import (
"git.apinb.com/bsm-tools/protoc-gen-ts/internal/httprule"
"google.golang.org/protobuf/reflect/protoreflect"
)
type jsonLeafWalkFunc func(path httprule.FieldPath, field protoreflect.FieldDescriptor)
func walkJSONLeafFields(message protoreflect.MessageDescriptor, f jsonLeafWalkFunc) {
var w jsonWalker
w.walkMessage(nil, message, f)
}
type jsonWalker struct {
seen map[protoreflect.FullName]struct{}
}
func (w *jsonWalker) enter(name protoreflect.FullName) bool {
if _, ok := w.seen[name]; ok {
return false
}
if w.seen == nil {
w.seen = make(map[protoreflect.FullName]struct{})
}
w.seen[name] = struct{}{}
return true
}
func (w *jsonWalker) walkMessage(path httprule.FieldPath, message protoreflect.MessageDescriptor, f jsonLeafWalkFunc) {
if w.enter(message.FullName()) {
for i := 0; i < message.Fields().Len(); i++ {
field := message.Fields().Get(i)
p := append(httprule.FieldPath{}, path...)
p = append(p, string(field.Name()))
switch {
case !field.IsMap() && field.Kind() == protoreflect.MessageKind:
if IsWellKnownType(field.Message()) {
f(p, field)
} else {
w.walkMessage(p, field.Message(), f)
}
default:
f(p, field)
}
}
}
}

View File

@@ -0,0 +1,24 @@
package plugin
import (
"git.apinb.com/bsm-tools/protoc-gen-ts/internal/codegen"
"google.golang.org/protobuf/reflect/protoreflect"
)
type messageGenerator struct {
pkg protoreflect.FullName
message protoreflect.MessageDescriptor
}
func (m messageGenerator) Generate(f *codegen.File) {
commentGenerator{descriptor: m.message}.generateLeading(f, 0)
f.P("export type ", scopedDescriptorTypeName(m.pkg, m.message), " = {")
rangeFields(m.message, func(field protoreflect.FieldDescriptor) {
commentGenerator{descriptor: field}.generateLeading(f, 1)
fieldType := typeFromField(m.pkg, field)
f.P(t(1), field.JSONName(), "?: ", fieldType.Reference(), ";")
})
f.P("};")
f.P()
}

View File

@@ -0,0 +1,51 @@
package plugin
import (
"git.apinb.com/bsm-tools/protoc-gen-ts/internal/codegen"
"git.apinb.com/bsm-tools/protoc-gen-ts/internal/protowalk"
"google.golang.org/protobuf/reflect/protoreflect"
)
type packageGenerator struct {
pkg protoreflect.FullName
files []protoreflect.FileDescriptor
}
func (p packageGenerator) Generate(f *codegen.File) error {
p.generateHeader(f)
var seenService bool
var walkErr error
protowalk.WalkFiles(p.files, func(desc protoreflect.Descriptor) bool {
if wkt, ok := WellKnownType(desc); ok {
f.P(wkt.TypeDeclaration())
return false
}
switch v := desc.(type) {
case protoreflect.MessageDescriptor:
if v.IsMapEntry() {
return false
}
messageGenerator{pkg: p.pkg, message: v}.Generate(f)
case protoreflect.EnumDescriptor:
enumGenerator{pkg: p.pkg, enum: v}.Generate(f)
case protoreflect.ServiceDescriptor:
if err := (serviceGenerator{pkg: p.pkg, service: v, genHandler: !seenService}).Generate(f); err != nil {
walkErr = err
return false
}
seenService = true
}
return true
})
if walkErr != nil {
return walkErr
}
return nil
}
func (p packageGenerator) generateHeader(f *codegen.File) {
f.P("// Code generated by protoc-gen-typescript-http. DO NOT EDIT.")
f.P("/* eslint-disable camelcase */")
f.P("// @ts-nocheck")
f.P()
}

View File

@@ -0,0 +1,237 @@
package plugin
import (
"fmt"
"strconv"
"strings"
"git.apinb.com/bsm-tools/protoc-gen-ts/internal/codegen"
"git.apinb.com/bsm-tools/protoc-gen-ts/internal/httprule"
"google.golang.org/protobuf/reflect/protoreflect"
)
type serviceGenerator struct {
pkg protoreflect.FullName
genHandler bool
service protoreflect.ServiceDescriptor
}
func (s serviceGenerator) Generate(f *codegen.File) error {
s.generateInterface(f)
if s.genHandler {
s.generateHandler(f)
}
return s.generateClient(f)
}
func (s serviceGenerator) generateInterface(f *codegen.File) {
commentGenerator{descriptor: s.service}.generateLeading(f, 0)
f.P("export interface ", descriptorTypeName(s.service), " {")
rangeMethods(s.service.Methods(), func(method protoreflect.MethodDescriptor) {
if !supportedMethod(method) {
return
}
commentGenerator{descriptor: method}.generateLeading(f, 1)
input := typeFromMessage(s.pkg, method.Input())
output := typeFromMessage(s.pkg, method.Output())
f.P(t(1), method.Name(), "(request: ", input.Reference(), "): Promise<", output.Reference(), ">;")
})
f.P("}")
f.P()
}
func (s serviceGenerator) generateHandler(f *codegen.File) {
f.P("type RequestType = {")
f.P(t(1), "path: string;")
f.P(t(1), "method: string;")
f.P(t(1), "body: string | null;")
f.P("};")
f.P()
f.P("type RequestHandler = (request: RequestType, meta: { service: string, method: string }) => Promise<unknown>;")
f.P()
}
func (s serviceGenerator) generateClient(f *codegen.File) error {
f.P(
"export function create",
descriptorTypeName(s.service),
"Client(",
"\n",
t(1),
"handler: RequestHandler",
"\n",
"): ",
descriptorTypeName(s.service),
" {",
)
f.P(t(1), "return {")
var methodErr error
rangeMethods(s.service.Methods(), func(method protoreflect.MethodDescriptor) {
if err := s.generateMethod(f, method); err != nil {
methodErr = fmt.Errorf("generate method %s: %w", method.Name(), err)
}
})
if methodErr != nil {
return methodErr
}
f.P(t(1), "};")
f.P("}")
return nil
}
func (s serviceGenerator) generateMethod(f *codegen.File, method protoreflect.MethodDescriptor) error {
outputType := typeFromMessage(s.pkg, method.Output())
r, ok := httprule.Get(method)
if !ok {
return nil
}
rule, err := httprule.ParseRule(r)
if err != nil {
return fmt.Errorf("parse http rule: %w", err)
}
f.P(t(2), method.Name(), "(request) { // eslint-disable-line @typescript-eslint/no-unused-vars")
s.generateMethodPathValidation(f, method.Input(), rule)
s.generateMethodPath(f, method.Input(), rule)
s.generateMethodBody(f, method.Input(), rule)
s.generateMethodQuery(f, method.Input(), rule)
f.P(t(3), "let uri = path;")
f.P(t(3), "if (queryParams.length > 0) {")
f.P(t(4), "uri += `?${queryParams.join(\"&\")}`")
f.P(t(3), "}")
f.P(t(3), "return handler({")
f.P(t(4), "path: uri,")
f.P(t(4), "method: ", strconv.Quote(rule.Method), ",")
f.P(t(4), "body,")
f.P(t(3), "}, {")
f.P(t(4), "service: \"", method.Parent().Name(), "\",")
f.P(t(4), "method: \"", method.Name(), "\",")
f.P(t(3), "}) as Promise<", outputType.Reference(), ">;")
f.P(t(2), "},")
return nil
}
func (s serviceGenerator) generateMethodPathValidation(
f *codegen.File,
input protoreflect.MessageDescriptor,
rule httprule.Rule,
) {
for _, seg := range rule.Template.Segments {
if seg.Kind != httprule.SegmentKindVariable {
continue
}
fp := seg.Variable.FieldPath
nullPath := nullPropagationPath(fp, input)
protoPath := strings.Join(fp, ".")
errMsg := "missing required field request." + protoPath
f.P(t(3), "if (!request.", nullPath, ") {")
f.P(t(4), "throw new Error(", strconv.Quote(errMsg), ");")
f.P(t(3), "}")
}
}
func (s serviceGenerator) generateMethodPath(
f *codegen.File,
input protoreflect.MessageDescriptor,
rule httprule.Rule,
) {
pathParts := make([]string, 0, len(rule.Template.Segments))
for _, seg := range rule.Template.Segments {
switch seg.Kind {
case httprule.SegmentKindVariable:
fieldPath := jsonPath(seg.Variable.FieldPath, input)
pathParts = append(pathParts, "${request."+fieldPath+"}")
case httprule.SegmentKindLiteral:
pathParts = append(pathParts, seg.Literal)
case httprule.SegmentKindMatchSingle: // TODO: Double check this and following case
pathParts = append(pathParts, "*")
case httprule.SegmentKindMatchMultiple:
pathParts = append(pathParts, "**")
}
}
path := strings.Join(pathParts, "/")
if rule.Template.Verb != "" {
path += ":" + rule.Template.Verb
}
f.P(t(3), "const path = `", path, "`; // eslint-disable-line quotes")
}
func (s serviceGenerator) generateMethodBody(
f *codegen.File,
input protoreflect.MessageDescriptor,
rule httprule.Rule,
) {
switch {
case rule.Body == "":
f.P(t(3), "const body = null;")
case rule.Body == "*":
f.P(t(3), "const body = JSON.stringify(request);")
default:
nullPath := nullPropagationPath(httprule.FieldPath{rule.Body}, input)
f.P(t(3), "const body = JSON.stringify(request?.", nullPath, " ?? {});")
}
}
func (s serviceGenerator) generateMethodQuery(
f *codegen.File,
input protoreflect.MessageDescriptor,
rule httprule.Rule,
) {
f.P(t(3), "const queryParams: string[] = [];")
// nothing in query
if rule.Body == "*" {
return
}
// fields covered by path
pathCovered := make(map[string]struct{})
for _, segment := range rule.Template.Segments {
if segment.Kind != httprule.SegmentKindVariable {
continue
}
pathCovered[segment.Variable.FieldPath.String()] = struct{}{}
}
walkJSONLeafFields(input, func(path httprule.FieldPath, field protoreflect.FieldDescriptor) {
if _, ok := pathCovered[path.String()]; ok {
return
}
if rule.Body != "" && path[0] == rule.Body {
return
}
nullPath := nullPropagationPath(path, input)
jp := jsonPath(path, input)
f.P(t(3), "if (request.", nullPath, ") {")
switch {
case field.IsList():
f.P(t(4), "request.", jp, ".forEach((x) => {")
f.P(t(5), "queryParams.push(`", jp, "=${encodeURIComponent(x.toString())}`)")
f.P(t(4), "})")
default:
f.P(t(4), "queryParams.push(`", jp, "=${encodeURIComponent(request.", jp, ".toString())}`)")
}
f.P(t(3), "}")
})
}
func supportedMethod(method protoreflect.MethodDescriptor) bool {
_, ok := httprule.Get(method)
return ok && !method.IsStreamingClient() && !method.IsStreamingServer()
}
func jsonPath(path httprule.FieldPath, message protoreflect.MessageDescriptor) string {
return strings.Join(jsonPathSegments(path, message), ".")
}
func nullPropagationPath(path httprule.FieldPath, message protoreflect.MessageDescriptor) string {
return strings.Join(jsonPathSegments(path, message), "?.")
}
func jsonPathSegments(path httprule.FieldPath, message protoreflect.MessageDescriptor) []string {
segs := make([]string, len(path))
for i, p := range path {
field := message.Fields().ByName(protoreflect.Name(p))
segs[i] = field.JSONName()
if i < len(path) {
message = field.Message()
}
}
return segs
}

82
internal/plugin/type.go Normal file
View File

@@ -0,0 +1,82 @@
package plugin
import "google.golang.org/protobuf/reflect/protoreflect"
type Type struct {
IsNamed bool
Name string
IsList bool
IsMap bool
Underlying *Type
}
func (t Type) Reference() string {
switch {
case t.IsMap:
return "{ [key: string]: " + t.Underlying.Reference() + " }"
case t.IsList:
return t.Underlying.Reference() + "[]"
default:
return t.Name
}
}
func typeFromField(pkg protoreflect.FullName, field protoreflect.FieldDescriptor) Type {
switch {
case field.IsMap():
underlying := namedTypeFromField(pkg, field.MapValue())
return Type{
IsMap: true,
Underlying: &underlying,
}
case field.IsList():
underlying := namedTypeFromField(pkg, field)
return Type{
IsList: true,
Underlying: &underlying,
}
default:
return namedTypeFromField(pkg, field)
}
}
func namedTypeFromField(pkg protoreflect.FullName, field protoreflect.FieldDescriptor) Type {
switch field.Kind() {
case protoreflect.StringKind, protoreflect.BytesKind:
return Type{IsNamed: true, Name: "string"}
case protoreflect.BoolKind:
return Type{IsNamed: true, Name: "boolean"}
case
protoreflect.Int32Kind,
protoreflect.Int64Kind,
protoreflect.Uint32Kind,
protoreflect.Uint64Kind,
protoreflect.DoubleKind,
protoreflect.Fixed32Kind,
protoreflect.Fixed64Kind,
protoreflect.Sfixed32Kind,
protoreflect.Sfixed64Kind,
protoreflect.Sint32Kind,
protoreflect.Sint64Kind,
protoreflect.FloatKind:
return Type{IsNamed: true, Name: "number"}
case protoreflect.MessageKind:
return typeFromMessage(pkg, field.Message())
case protoreflect.EnumKind:
desc := field.Enum()
if wkt, ok := WellKnownType(field.Enum()); ok {
return Type{IsNamed: true, Name: wkt.Name()}
}
return Type{IsNamed: true, Name: scopedDescriptorTypeName(pkg, desc)}
default:
return Type{IsNamed: true, Name: "unknown"}
}
}
func typeFromMessage(pkg protoreflect.FullName, message protoreflect.MessageDescriptor) Type {
if wkt, ok := WellKnownType(message); ok {
return Type{IsNamed: true, Name: wkt.Name()}
}
return Type{IsNamed: true, Name: scopedDescriptorTypeName(pkg, message)}
}

View File

@@ -0,0 +1,157 @@
package plugin
import (
"strings"
"google.golang.org/protobuf/reflect/protoreflect"
)
const (
wellKnownPrefix = "google.protobuf."
)
type WellKnown string
// https://developers.google.com/protocol-buffers/docs/reference/google.protobuf
const (
WellKnownAny WellKnown = "google.protobuf.Any"
WellKnownDuration WellKnown = "google.protobuf.Duration"
WellKnownEmpty WellKnown = "google.protobuf.Empty"
WellKnownFieldMask WellKnown = "google.protobuf.FieldMask"
WellKnownStruct WellKnown = "google.protobuf.Struct"
WellKnownTimestamp WellKnown = "google.protobuf.Timestamp"
// Wrapper types.
WellKnownFloatValue WellKnown = "google.protobuf.FloatValue"
WellKnownInt64Value WellKnown = "google.protobuf.Int64Value"
WellKnownInt32Value WellKnown = "google.protobuf.Int32Value"
WellKnownUInt64Value WellKnown = "google.protobuf.UInt64Value"
WellKnownUInt32Value WellKnown = "google.protobuf.UInt32Value"
WellKnownBytesValue WellKnown = "google.protobuf.BytesValue"
WellKnownDoubleValue WellKnown = "google.protobuf.DoubleValue"
WellKnownBoolValue WellKnown = "google.protobuf.BoolValue"
WellKnownStringValue WellKnown = "google.protobuf.StringValue"
// Descriptor types.
WellKnownValue WellKnown = "google.protobuf.Value"
WellKnownNullValue WellKnown = "google.protobuf.NullValue"
WellKnownListValue WellKnown = "google.protobuf.ListValue"
)
func IsWellKnownType(desc protoreflect.Descriptor) bool {
switch desc.(type) {
case protoreflect.MessageDescriptor, protoreflect.EnumDescriptor:
return strings.HasPrefix(string(desc.FullName()), wellKnownPrefix)
default:
return false
}
}
func WellKnownType(desc protoreflect.Descriptor) (WellKnown, bool) {
if !IsWellKnownType(desc) {
return "", false
}
return WellKnown(desc.FullName()), true
}
func (wkt WellKnown) Name() string {
return "wellKnown" + strings.TrimPrefix(string(wkt), wellKnownPrefix)
}
func (wkt WellKnown) TypeDeclaration() string {
var w writer
switch wkt {
case WellKnownAny:
w.P("// If the Any contains a value that has a special JSON mapping,")
w.P("// it will be converted as follows:")
w.P("// {\"@type\": xxx, \"value\": yyy}.")
w.P("// Otherwise, the value will be converted into a JSON object,")
w.P("// and the \"@type\" field will be inserted to indicate the actual data type.")
w.P("interface ", wkt.Name(), " {")
w.P(" ", "\"@type\": string;")
w.P(" [key: string]: unknown;")
w.P("}")
case WellKnownDuration:
w.P("// Generated output always contains 0, 3, 6, or 9 fractional digits,")
w.P("// depending on required precision, followed by the suffix \"s\".")
w.P("// Accepted are any fractional digits (also none) as long as they fit")
w.P("// into nano-seconds precision and the suffix \"s\" is required.")
w.P("type ", wkt.Name(), " = string;")
case WellKnownEmpty:
w.P("// An empty JSON object")
w.P("type ", wkt.Name(), " = Record<never, never>;")
case WellKnownTimestamp:
w.P("// Encoded using RFC 3339, where generated output will always be Z-normalized")
w.P("// and uses 0, 3, 6 or 9 fractional digits.")
w.P("// Offsets other than \"Z\" are also accepted.")
w.P("type ", wkt.Name(), " = string;")
case WellKnownFieldMask:
w.P("// In JSON, a field mask is encoded as a single string where paths are")
w.P("// separated by a comma. Fields name in each path are converted")
w.P("// to/from lower-camel naming conventions.")
w.P("// As an example, consider the following message declarations:")
w.P("//")
w.P("// message Profile {")
w.P("// User user = 1;")
w.P("// Photo photo = 2;")
w.P("// }")
w.P("// message User {")
w.P("// string display_name = 1;")
w.P("// string address = 2;")
w.P("// }")
w.P("//")
w.P("// In proto a field mask for `Profile` may look as such:")
w.P("//")
w.P("// mask {")
w.P("// paths: \"user.display_name\"")
w.P("// paths: \"photo\"")
w.P("// }")
w.P("//")
w.P("// In JSON, the same mask is represented as below:")
w.P("//")
w.P("// {")
w.P("// mask: \"user.displayName,photo\"")
w.P("// }")
w.P("type ", wkt.Name(), " = string;")
case WellKnownFloatValue,
WellKnownDoubleValue,
WellKnownInt64Value,
WellKnownInt32Value,
WellKnownUInt64Value,
WellKnownUInt32Value:
w.P("type ", wkt.Name(), " = number | null;")
case WellKnownBytesValue, WellKnownStringValue:
w.P("type ", wkt.Name(), " = string | null;")
case WellKnownBoolValue:
w.P("type ", wkt.Name(), " = boolean | null;")
case WellKnownStruct:
w.P("// Any JSON value.")
w.P("type ", wkt.Name(), " = Record<string, unknown>;")
case WellKnownValue:
w.P("type ", wkt.Name(), " = unknown;")
case WellKnownNullValue:
w.P("type ", wkt.Name(), " = null;")
case WellKnownListValue:
w.P("type ", wkt.Name(), " = ", WellKnownValue.Name(), "[];")
default:
w.P("// No mapping for this well known type is generated, yet.")
w.P("type ", wkt.Name(), " = unknown;")
}
return w.String()
}
type writer struct {
b strings.Builder
}
func (w *writer) P(ss ...string) {
for _, s := range ss {
// strings.Builder never returns an error, so safe to ignore
_, _ = w.b.WriteString(s)
}
_, _ = w.b.WriteString("\n")
}
func (w *writer) String() string {
return w.b.String()
}