1// Copyright 2019 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package protobuf
16
17import (
18 "fmt"
19
20 "cuelang.org/go/cue/ast"
21 "cuelang.org/go/cue/parser"
22 "cuelang.org/go/cue/token"
23 "github.com/emicklei/proto"
24)
25
26func protoToCUE(typ string, options []*proto.Option) ast.Expr {
27 t, ok := scalars[typ]
28 if !ok {
29 return nil
30 }
31 return predeclared(t)
32}
33
34var scalars = map[string]string{
35 // Differing
36 "sint32": "int32",
37 "sint64": "int64",
38 "fixed32": "uint32",
39 "fixed64": "uint64",
40 "sfixed32": "int32",
41 "sfixed64": "int64",
42
43 // Identical to CUE
44 "int32": "int32",
45 "int64": "int64",
46 "uint32": "uint32",
47 "uint64": "uint64",
48
49 "double": "float64",
50 "float": "float32",
51
52 "bool": "bool",
53 "string": "string",
54 "bytes": "bytes",
55}
56
57func predeclared(s string) ast.Expr {
58 return &ast.Ident{
59 Name: s,
60 Node: ast.NewIdent("__" + s),
61 }
62}
63
64func (p *protoConverter) setBuiltin(from string, to func() ast.Expr, pkg *protoConverter) {
65 p.scope[0][from] = mapping{to, pkg}
66}
67
68func (p *protoConverter) setBuiltinParse(from, to string, pkg *protoConverter) {
69 f := func() ast.Expr {
70 expr, err := parser.ParseExpr("", to, parser.ParseComments)
71 if err != nil {
72 panic(fmt.Sprintf("error parsing name %q: %v", to, err))
73 }
74 return expr
75 }
76 p.scope[0][from] = mapping{f, pkg}
77}
78
79var (
80 pkgTime = &protoConverter{cuePkgPath: "time"}
81 pkgStruct = &protoConverter{cuePkgPath: "struct"}
82 importTime = ast.NewImport(nil, "time")
83 importStruct = ast.NewImport(nil, "struct")
84)
85
86func (p *protoConverter) mapBuiltinPackage(file string) (found bool) {
87 // Map some builtin types to their JSON/CUE mappings.
88 switch file {
89 case "cue/cue.proto":
90 return true
91 case "google/protobuf/struct.proto":
92 p.setBuiltin("google.protobuf.Struct", func() ast.Expr {
93 return ast.NewStruct()
94 }, nil)
95
96 p.setBuiltin("google.protobuf.Value", func() ast.Expr {
97 return ast.NewIdent("_")
98 }, nil)
99
100 p.setBuiltin("google.protobuf.NullValue", func() ast.Expr {
101 return ast.NewNull()
102 }, nil)
103
104 p.setBuiltin("google.protobuf.ListValue", func() ast.Expr {
105 return ast.NewList(&ast.Ellipsis{})
106 }, nil)
107
108 p.setBuiltin("google.protobuf.StringValue", func() ast.Expr {
109 return predeclared("string")
110 }, nil)
111
112 p.setBuiltin("google.protobuf.BoolValue", func() ast.Expr {
113 return predeclared("bool")
114 }, nil)
115
116 p.setBuiltin("google.protobuf.NumberValue", func() ast.Expr {
117 return predeclared("number")
118 }, nil)
119
120 return true
121
122 case "google/protobuf/empty.proto":
123 f := func() ast.Expr {
124 time := &ast.Ident{Name: "struct", Node: importStruct}
125 return ast.NewCall(
126 ast.NewSel(time, "MaxFields"),
127 ast.NewLit(token.INT, "0"),
128 )
129 }
130 p.setBuiltin("google.protobuf.Empty", f, pkgStruct)
131 return true
132
133 case "google/protobuf/duration.proto":
134 f := func() ast.Expr {
135 time := &ast.Ident{Name: "time", Node: importTime}
136 return ast.NewSel(time, "Duration")
137 }
138 p.setBuiltin("google.protobuf.Duration", f, pkgTime)
139 return true
140
141 case "google/protobuf/timestamp.proto":
142 f := func() ast.Expr {
143 time := &ast.Ident{Name: "time", Node: importTime}
144 return ast.NewSel(time, "Time")
145 }
146 p.setBuiltin("google.protobuf.Timestamp", f, pkgTime)
147 return true
148
149 case "google/protobuf/any.proto":
150 // TODO: technically, the value should be `_` (anything), but that
151 // will not convert to a valid OpenAPI value. In practice, all
152 // "well-known" types except wrapper types (which will likely not
153 // be used here) are represented as strings.
154 //
155 // In Structural OpenAPI this type cannot be represented.
156 p.setBuiltinParse("google.protobuf.Any", `{
157 // A URL/resource name that uniquely identifies the type of the serialized protocol buffer message. This string must contain at least one "/" character. The last segment of the URL's path must represent the fully qualified name of the type (as in `+
158 "`type.googleapis.com/google.protobuf.Duration`"+`). The name should be in a canonical form (e.g., leading "." is not accepted).
159 // The remaining fields of this object correspond to fields of the proto messsage. If the embedded message is well-known and has a custom JSON representation, that representation is assigned to the 'value' field.
160 "@type": string,
161}`, nil)
162 return true
163
164 case "google/protobuf/wrappers.proto":
165 p.setBuiltinParse("google.protobuf.DoubleValue", `null | float`, nil)
166 p.setBuiltinParse("google.protobuf.FloatValue", `null | float`, nil)
167 p.setBuiltinParse("google.protobuf.Int64Value", `null | int64`, nil)
168 p.setBuiltinParse("google.protobuf.UInt64Value", `null | uint64`, nil)
169 p.setBuiltinParse("google.protobuf.Int32Value", `null | int32`, nil)
170 p.setBuiltinParse("google.protobuf.UInt32Value", `null | uint32`, nil)
171 p.setBuiltinParse("google.protobuf.BoolValue", `null | bool`, nil)
172 p.setBuiltinParse("google.protobuf.StringValue", `null | string`, nil)
173 p.setBuiltinParse("google.protobuf.BytesValue", `null | bytes`, nil)
174 return true
175
176 // case "google/protobuf/field_mask.proto":
177 // p.setBuiltin("google.protobuf.FieldMask", "protobuf.FieldMask", nil)
178
179 // protobuf.Any
180 }
181 return false
182}