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	"text/scanner"
20
21	"cuelang.org/go/cue/ast"
22	"cuelang.org/go/cue/parser"
23	"cuelang.org/go/cue/token"
24	"github.com/emicklei/proto"
25)
26
27func protoToCUE(typ string, options []*proto.Option) ast.Expr {
28	t, ok := scalars[typ]
29	if !ok {
30		return nil
31	}
32	return predeclared(t)
33}
34
35var scalars = map[string]string{
36	// Differing
37	"sint32":   "int32",
38	"sint64":   "int64",
39	"fixed32":  "uint32",
40	"fixed64":  "uint64",
41	"sfixed32": "int32",
42	"sfixed64": "int64",
43
44	// Identical to CUE
45	"int32":  "int32",
46	"int64":  "int64",
47	"uint32": "uint32",
48	"uint64": "uint64",
49
50	"double": "float64",
51	"float":  "float32",
52
53	"bool":   "bool",
54	"string": "string",
55	"bytes":  "bytes",
56}
57
58func predeclared(s string) ast.Expr {
59	return &ast.Ident{
60		Name: s,
61		Node: ast.NewIdent("__" + s),
62	}
63}
64
65func (p *protoConverter) setBuiltin(from string, to func() ast.Expr, pkg *protoConverter) {
66	p.scope[0][from] = mapping{to, pkg}
67}
68
69func (p *protoConverter) setBuiltinParse(from, to string, pkg *protoConverter) {
70	f := func() ast.Expr {
71		expr, err := parser.ParseExpr("", to, parser.ParseComments)
72		if err != nil {
73			panic(fmt.Sprintf("error parsing name %q: %v", to, err))
74		}
75		return expr
76	}
77	p.scope[0][from] = mapping{f, pkg}
78}
79
80var (
81	pkgTime      = &protoConverter{cuePkgPath: "time"}
82	pkgStruct    = &protoConverter{cuePkgPath: "struct"}
83	importTime   = ast.NewImport(nil, "time")
84	importStruct = ast.NewImport(nil, "struct")
85)
86
87func (p *protoConverter) mapBuiltinPackage(pos scanner.Position, file string, required bool) (generate bool) {
88	// Map some builtin types to their JSON/CUE mappings.
89	switch file {
90	case "gogoproto/gogo.proto":
91
92	case "google/protobuf/struct.proto":
93		p.setBuiltin("google.protobuf.Struct", func() ast.Expr {
94			return ast.NewStruct()
95		}, nil)
96
97		p.setBuiltin("google.protobuf.Value", func() ast.Expr {
98			return ast.NewIdent("_")
99		}, nil)
100
101		p.setBuiltin("google.protobuf.NullValue", func() ast.Expr {
102			return ast.NewNull()
103		}, nil)
104
105		p.setBuiltin("google.protobuf.ListValue", func() ast.Expr {
106			return ast.NewList(&ast.Ellipsis{})
107		}, nil)
108
109		p.setBuiltin("google.protobuf.StringValue", func() ast.Expr {
110			return predeclared("string")
111		}, nil)
112
113		p.setBuiltin("google.protobuf.BoolValue", func() ast.Expr {
114			return predeclared("bool")
115		}, nil)
116
117		p.setBuiltin("google.protobuf.NumberValue", func() ast.Expr {
118			return predeclared("number")
119		}, nil)
120
121		return false
122
123	case "google/protobuf/empty.proto":
124		f := func() ast.Expr {
125			time := &ast.Ident{Name: "struct", Node: importStruct}
126			return ast.NewCall(
127				ast.NewSel(time, "MaxFields"),
128				ast.NewLit(token.INT, "0"),
129			)
130		}
131		p.setBuiltin("google.protobuf.Empty", f, pkgStruct)
132		return false
133
134	case "google/protobuf/duration.proto":
135		f := func() ast.Expr {
136			time := &ast.Ident{Name: "time", Node: importTime}
137			return ast.NewSel(time, "Duration")
138		}
139		p.setBuiltin("google.protobuf.Duration", f, pkgTime)
140		return false
141
142	case "google/protobuf/timestamp.proto":
143		f := func() ast.Expr {
144			time := &ast.Ident{Name: "time", Node: importTime}
145			return ast.NewSel(time, "Time")
146		}
147		p.setBuiltin("google.protobuf.Timestamp", f, pkgTime)
148		return false
149
150	case "google/protobuf/any.proto":
151		// TODO: technically, the value should be `_` (anything), but that
152		// will not convert to a valid OpenAPI value. In practice, all
153		// "well-known" types except wrapper types (which will likely not
154		// be used here) are represented as strings.
155		//
156		// In Structural OpenAPI this type cannot be represented.
157		p.setBuiltinParse("google.protobuf.Any", `{
158	// 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 `+
159			"`type.googleapis.com/google.protobuf.Duration`"+`). The name should be in a canonical form (e.g., leading "." is not accepted).
160	// 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.
161	"@type": string,
162}`, nil)
163		return false
164
165	case "google/protobuf/wrappers.proto":
166		p.setBuiltinParse("google.protobuf.DoubleValue", `null | float`, nil)
167		p.setBuiltinParse("google.protobuf.FloatValue", `null | float`, nil)
168		p.setBuiltinParse("google.protobuf.Int64Value", `null | int64`, nil)
169		p.setBuiltinParse("google.protobuf.UInt64Value", `null | uint64`, nil)
170		p.setBuiltinParse("google.protobuf.Int32Value", `null | int32`, nil)
171		p.setBuiltinParse("google.protobuf.UInt32Value", `null | uint32`, nil)
172		p.setBuiltinParse("google.protobuf.BoolValue", `null | bool`, nil)
173		p.setBuiltinParse("google.protobuf.StringValue", `null | string`, nil)
174		p.setBuiltinParse("google.protobuf.BytesValue", `null | bytes`, nil)
175		return false
176
177	// case "google/protobuf/field_mask.proto":
178	// 	p.setBuiltin("google.protobuf.FieldMask", "protobuf.FieldMask", nil)
179
180	// 	protobuf.Any
181
182	default:
183		if required {
184			failf(pos, "import %q not found", file)
185		}
186	}
187	return true
188}
189