1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package protobuf
16
17 import (
18 "bytes"
19 "fmt"
20 "os"
21 "path"
22 "path/filepath"
23 "strconv"
24 "strings"
25 "text/scanner"
26 "unicode"
27
28 "github.com/emicklei/proto"
29
30 "cuelang.org/go/cue/ast"
31 "cuelang.org/go/cue/ast/astutil"
32 "cuelang.org/go/cue/errors"
33 "cuelang.org/go/cue/literal"
34 "cuelang.org/go/cue/parser"
35 "cuelang.org/go/cue/token"
36 "cuelang.org/go/internal/source"
37 )
38
39 func (s *Extractor) parse(filename string, src interface{}) (p *protoConverter, err error) {
40 if filename == "" {
41 return nil, errors.Newf(token.NoPos, "empty filename")
42 }
43 if r, ok := s.fileCache[filename]; ok {
44 return r.p, r.err
45 }
46 defer func() {
47 s.fileCache[filename] = result{p, err}
48 }()
49
50 b, err := source.Read(filename, src)
51 if err != nil {
52 return nil, err
53 }
54
55 parser := proto.NewParser(bytes.NewReader(b))
56 if filename != "" {
57 parser.Filename(filename)
58 }
59 d, err := parser.Parse()
60 if err != nil {
61 return nil, errors.Newf(token.NoPos, "protobuf: %v", err)
62 }
63
64 tfile := token.NewFile(filename, -1, len(b))
65 tfile.SetLinesForContent(b)
66
67 p = &protoConverter{
68 id: filename,
69 state: s,
70 tfile: tfile,
71 imported: map[string]bool{},
72 symbols: map[string]bool{},
73 }
74
75 defer func() {
76 switch x := recover().(type) {
77 case nil:
78 case protoError:
79 err = &protobufError{
80 path: p.path,
81 pos: p.toCUEPos(x.pos),
82 err: x.error,
83 }
84 default:
85 panic(x)
86 }
87 }()
88
89 p.file = &ast.File{Filename: filename}
90
91 p.addNames(d.Elements)
92
93
94 for _, e := range d.Elements {
95 switch x := e.(type) {
96 case *proto.Package:
97 p.protoPkg = x.Name
98 case *proto.Option:
99 if x.Name == "go_package" {
100 str, err := strconv.Unquote(x.Constant.SourceRepresentation())
101 if err != nil {
102 failf(x.Position, "unquoting package filed: %v", err)
103 }
104 split := strings.Split(str, ";")
105 switch {
106 case strings.Contains(split[0], "."):
107 p.cuePkgPath = split[0]
108 switch len(split) {
109 case 1:
110 p.shortPkgName = path.Base(str)
111 case 2:
112 p.shortPkgName = split[1]
113 default:
114 failf(x.Position, "unexpected ';' in %q", str)
115 }
116
117 case len(split) == 1:
118 p.shortPkgName = split[0]
119
120 default:
121 failf(x.Position, "malformed go_package clause %s", str)
122 }
123
124
125 }
126 }
127 }
128
129 if name := p.shortName(); name != "" {
130 p.file.Decls = append(p.file.Decls, &ast.Package{Name: ast.NewIdent(name)})
131 }
132
133 for _, e := range d.Elements {
134 switch x := e.(type) {
135 case *proto.Import:
136 if err := p.doImport(x); err != nil {
137 return nil, err
138 }
139 }
140 }
141
142 for _, e := range d.Elements {
143 p.topElement(e)
144 }
145
146 err = astutil.Sanitize(p.file)
147
148 return p, err
149 }
150
151
152
153 type protoConverter struct {
154 state *Extractor
155 tfile *token.File
156
157 proto3 bool
158
159 id string
160 protoPkg string
161 shortPkgName string
162 cuePkgPath string
163
164 file *ast.File
165 current *ast.StructLit
166
167 imported map[string]bool
168
169 path []string
170 scope []map[string]mapping
171 symbols map[string]bool
172 }
173
174 type mapping struct {
175 cue func() ast.Expr
176 pkg *protoConverter
177 }
178
179 func (p *protoConverter) qualifiedImportPath() string {
180 s := p.importPath()
181 if short := p.shortPkgName; short != "" && short != path.Base(s) {
182 s += ":" + short
183 }
184 return s
185 }
186
187 func (p *protoConverter) importPath() string {
188 if p.cuePkgPath == "" && p.protoPkg != "" {
189 dir := strings.Replace(p.protoPkg, ".", "/", -1)
190 p.cuePkgPath = path.Join("googleapis.com", dir)
191 }
192 return p.cuePkgPath
193 }
194
195 func (p *protoConverter) shortName() string {
196 if p.state.pkgName != "" {
197 return p.state.pkgName
198 }
199 if p.shortPkgName == "" && p.protoPkg != "" {
200 split := strings.Split(p.protoPkg, ".")
201 p.shortPkgName = split[len(split)-1]
202 }
203 return p.shortPkgName
204 }
205
206 func (p *protoConverter) toCUEPos(pos scanner.Position) token.Pos {
207 return p.tfile.Pos(pos.Offset, 0)
208 }
209
210 func (p *protoConverter) addRef(pos scanner.Position, name string, cue func() ast.Expr) {
211 top := p.scope[len(p.scope)-1]
212 if _, ok := top[name]; ok {
213 failf(pos, "entity %q already defined", name)
214 }
215 top[name] = mapping{cue: cue}
216 }
217
218 func (p *protoConverter) addNames(elems []proto.Visitee) {
219 p.scope = append(p.scope, map[string]mapping{})
220 for _, e := range elems {
221 var pos scanner.Position
222 var name string
223 switch x := e.(type) {
224 case *proto.Message:
225 if x.IsExtend {
226 continue
227 }
228 name = x.Name
229 pos = x.Position
230 case *proto.Enum:
231 name = x.Name
232 pos = x.Position
233 case *proto.NormalField:
234 name = x.Name
235 pos = x.Position
236 case *proto.MapField:
237 name = x.Name
238 pos = x.Position
239 case *proto.Oneof:
240 name = x.Name
241 pos = x.Position
242 default:
243 continue
244 }
245 sym := strings.Join(append(p.path, name), ".")
246 p.symbols[sym] = true
247 p.addRef(pos, name, func() ast.Expr { return ast.NewIdent("#" + name) })
248 }
249 }
250
251 func (p *protoConverter) popNames() {
252 p.scope = p.scope[:len(p.scope)-1]
253 }
254
255 func (p *protoConverter) resolve(pos scanner.Position, name string, options []*proto.Option) ast.Expr {
256 if expr := protoToCUE(name, options); expr != nil {
257 ast.SetPos(expr, p.toCUEPos(pos))
258 return expr
259 }
260 if strings.HasPrefix(name, ".") {
261 return p.resolveTopScope(pos, name[1:], options)
262 }
263 for i := len(p.scope) - 1; i > 0; i-- {
264 if m, ok := p.scope[i][name]; ok {
265 return m.cue()
266 }
267 }
268 expr := p.resolveTopScope(pos, name, options)
269 return expr
270 }
271
272 func (p *protoConverter) resolveTopScope(pos scanner.Position, name string, options []*proto.Option) ast.Expr {
273 for i := 0; i < len(name); i++ {
274 k := strings.IndexByte(name[i:], '.')
275 i += k
276 if k == -1 {
277 i = len(name)
278 }
279 if m, ok := p.scope[0][name[:i]]; ok {
280 if m.pkg != nil {
281 p.imported[m.pkg.qualifiedImportPath()] = true
282 }
283 expr := m.cue()
284 for i < len(name) {
285 name = name[i+1:]
286 if i = strings.IndexByte(name, '.'); i == -1 {
287 i = len(name)
288 }
289 expr = ast.NewSel(expr, "#"+name[:i])
290 }
291 ast.SetPos(expr, p.toCUEPos(pos))
292 return expr
293 }
294 }
295 failf(pos, "name %q not found", name)
296 return nil
297 }
298
299 func (p *protoConverter) doImport(v *proto.Import) error {
300 if v.Filename == "cue/cue.proto" {
301 return nil
302 }
303
304 filename := ""
305 for _, p := range p.state.paths {
306 name := filepath.Join(p, v.Filename)
307 _, err := os.Stat(name)
308 if err != nil {
309 continue
310 }
311 filename = name
312 break
313 }
314
315 if filename == "" {
316 err := errors.Newf(p.toCUEPos(v.Position), "could not find import %q", v.Filename)
317 p.state.addErr(err)
318 return err
319 }
320
321 if !p.mapBuiltinPackage(v.Position, v.Filename, filename == "") {
322 return nil
323 }
324
325 imp, err := p.state.parse(filename, nil)
326 if err != nil {
327 fail(v.Position, err)
328 }
329
330 pkgNamespace := strings.Split(imp.protoPkg, ".")
331 curNamespace := strings.Split(p.protoPkg, ".")
332 for {
333 for k := range imp.symbols {
334 ref := k
335 if len(pkgNamespace) > 0 {
336 ref = strings.Join(append(pkgNamespace, k), ".")
337 }
338 if _, ok := p.scope[0][ref]; !ok {
339 pkg := imp
340 a := toCue(k)
341
342 var f func() ast.Expr
343
344 if imp.qualifiedImportPath() == p.qualifiedImportPath() {
345 pkg = nil
346 f = func() ast.Expr { return ast.NewIdent(a[0]) }
347 } else {
348 f = func() ast.Expr {
349 ident := &ast.Ident{
350 Name: imp.shortName(),
351 Node: ast.NewImport(nil, imp.qualifiedImportPath()),
352 }
353 return ast.NewSel(ident, a[0])
354 }
355 }
356 p.scope[0][ref] = mapping{f, pkg}
357 }
358 }
359 if len(pkgNamespace) == 0 {
360 break
361 }
362 if len(curNamespace) == 0 || pkgNamespace[0] != curNamespace[0] {
363 break
364 }
365 pkgNamespace = pkgNamespace[1:]
366 curNamespace = curNamespace[1:]
367 }
368 return nil
369 }
370
371
372 func toCue(name string) []string {
373 a := strings.Split(name, ".")
374 for i, s := range a {
375 a[i] = "#" + s
376 }
377 return a
378 }
379
380 func (p *protoConverter) stringLit(pos scanner.Position, s string) *ast.BasicLit {
381 return &ast.BasicLit{
382 ValuePos: p.toCUEPos(pos),
383 Kind: token.STRING,
384 Value: literal.String.Quote(s)}
385 }
386
387 func (p *protoConverter) ident(pos scanner.Position, name string) *ast.Ident {
388 return &ast.Ident{NamePos: p.toCUEPos(pos), Name: labelName(name)}
389 }
390
391 func (p *protoConverter) ref(pos scanner.Position) *ast.Ident {
392 name := "#" + p.path[len(p.path)-1]
393 return &ast.Ident{NamePos: p.toCUEPos(pos), Name: name}
394 }
395
396 func (p *protoConverter) subref(pos scanner.Position, name string) *ast.Ident {
397 return &ast.Ident{
398 NamePos: p.toCUEPos(pos),
399 Name: "#" + name,
400 }
401 }
402
403 func (p *protoConverter) addTag(f *ast.Field, body string) {
404 tag := "@protobuf(" + body + ")"
405 f.Attrs = append(f.Attrs, &ast.Attribute{Text: tag})
406 }
407
408 func (p *protoConverter) topElement(v proto.Visitee) {
409 switch x := v.(type) {
410 case *proto.Syntax:
411 p.proto3 = x.Value == "proto3"
412
413 case *proto.Comment:
414 addComments(p.file, 0, x, nil)
415
416 case *proto.Enum:
417 p.enum(x)
418
419 case *proto.Package:
420 if doc := x.Doc(); doc != nil {
421 addComments(p.file, 0, doc, nil)
422 }
423
424 case *proto.Message:
425 p.message(x)
426
427 case *proto.Option:
428 case *proto.Import:
429
430
431 case *proto.Service:
432
433
434 case *proto.Extensions, *proto.Reserved:
435
436
437 default:
438 failf(scanner.Position{}, "unsupported type %T", x)
439 }
440 }
441
442 func (p *protoConverter) message(v *proto.Message) {
443 if v.IsExtend {
444
445 return
446 }
447
448 defer func(saved []string) { p.path = saved }(p.path)
449 p.path = append(p.path, v.Name)
450
451 p.addNames(v.Elements)
452 defer p.popNames()
453
454
455
456 s := &ast.StructLit{
457 Lbrace: p.toCUEPos(v.Position),
458
459 Rbrace: token.Newline.Pos(),
460 }
461
462 ref := p.ref(v.Position)
463 if v.Comment == nil {
464 ref.NamePos = newSection
465 }
466 f := &ast.Field{Label: ref, Value: s}
467 addComments(f, 1, v.Comment, nil)
468
469 p.addDecl(f)
470 defer func(current *ast.StructLit) {
471 p.current = current
472 }(p.current)
473 p.current = s
474
475 for i, e := range v.Elements {
476 p.messageField(s, i, e)
477 }
478 }
479
480 func (p *protoConverter) addDecl(d ast.Decl) {
481 if p.current == nil {
482 p.file.Decls = append(p.file.Decls, d)
483 } else {
484 p.current.Elts = append(p.current.Elts, d)
485 }
486 }
487
488 func (p *protoConverter) messageField(s *ast.StructLit, i int, v proto.Visitee) {
489 switch x := v.(type) {
490 case *proto.Comment:
491 s.Elts = append(s.Elts, comment(x, true))
492
493 case *proto.NormalField:
494 f := p.parseField(s, i, x.Field)
495
496 if x.Repeated {
497 f.Value = &ast.ListLit{
498 Lbrack: p.toCUEPos(x.Position),
499 Elts: []ast.Expr{&ast.Ellipsis{Type: f.Value}},
500 }
501 }
502
503 case *proto.MapField:
504 defer func(saved []string) { p.path = saved }(p.path)
505 p.path = append(p.path, x.Name)
506
507 f := &ast.Field{}
508
509
510
511 f.Label = ast.NewList(ast.NewIdent("string"))
512 f.Value = p.resolve(x.Position, x.Type, x.Options)
513
514 name := p.ident(x.Position, x.Name)
515 f = &ast.Field{
516 Label: name,
517 Value: ast.NewStruct(f),
518 }
519 addComments(f, i, x.Comment, x.InlineComment)
520
521 o := optionParser{message: s, field: f}
522 o.tags = fmt.Sprintf(`%d,map[%s]%s`, x.Sequence, x.KeyType, x.Type)
523 if x.Name != name.Name {
524 o.tags += "," + x.Name
525 }
526 s.Elts = append(s.Elts, f)
527 o.parse(x.Options)
528 p.addTag(f, o.tags)
529
530 if !o.required {
531 f.Optional = token.NoSpace.Pos()
532 }
533
534 case *proto.Enum:
535 p.enum(x)
536
537 case *proto.Message:
538 p.message(x)
539
540 case *proto.Oneof:
541 p.oneOf(x)
542
543 case *proto.Extensions, *proto.Reserved:
544
545
546 case *proto.Option:
547 opt := fmt.Sprintf("@protobuf(option %s=%s)", x.Name, x.Constant.Source)
548 attr := &ast.Attribute{
549 At: p.toCUEPos(x.Position),
550 Text: opt,
551 }
552 addComments(attr, i, x.Doc(), x.InlineComment)
553 s.Elts = append(s.Elts, attr)
554
555 default:
556 failf(scanner.Position{}, "unsupported field type %T", v)
557 }
558 }
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578 func (p *protoConverter) enum(x *proto.Enum) {
579
580 if len(x.Elements) == 0 {
581 failf(x.Position, "empty enum")
582 }
583
584 name := p.subref(x.Position, x.Name)
585
586 defer func(saved []string) { p.path = saved }(p.path)
587 p.path = append(p.path, x.Name)
588
589 p.addNames(x.Elements)
590
591 if len(p.path) == 0 {
592 defer func() { p.path = p.path[:0] }()
593 p.path = append(p.path, x.Name)
594 }
595
596
597 enum := &ast.Field{Label: name}
598 addComments(enum, 1, x.Comment, nil)
599 if p.current != nil && len(p.current.Elts) > 0 {
600 ast.SetRelPos(enum, token.NewSection)
601 }
602
603
604 valueName := ast.NewIdent(name.Name + "_value")
605 valueName.NamePos = newSection
606 valueMap := &ast.StructLit{}
607 d := &ast.Field{Label: valueName, Value: valueMap}
608
609
610 if strings.Contains(name.Name, "google") {
611 panic(name.Name)
612 }
613 p.addDecl(enum)
614
615 numEnums := 0
616 for _, v := range x.Elements {
617 if _, ok := v.(*proto.EnumField); ok {
618 numEnums++
619 }
620 }
621
622 lastSingle := false
623
624 firstSpace := token.NewSection
625
626
627
628 var lastComment *proto.Comment
629 for i, v := range x.Elements {
630 switch y := v.(type) {
631 case *proto.EnumField:
632
633 intValue := ast.NewLit(token.INT, strconv.Itoa(y.Integer))
634 f := &ast.Field{
635 Label: p.stringLit(y.Position, y.Name),
636 Value: intValue,
637 }
638 valueMap.Elts = append(valueMap.Elts, f)
639
640 var e ast.Expr
641 switch p.state.enumMode {
642 case "int":
643 e = ast.NewIdent("#" + y.Name)
644 ast.SetRelPos(e, token.Newline)
645
646 f := &ast.Field{
647 Label: ast.NewIdent("#" + y.Name),
648 Value: intValue,
649 }
650 ast.SetRelPos(f, firstSpace)
651 firstSpace = token.Newline
652 addComments(f, 0, y.Comment, y.InlineComment)
653 p.addDecl(f)
654
655 case "", "json":
656
657 value := p.stringLit(y.Position, y.Name)
658 embed := &ast.EmbedDecl{Expr: value}
659 ast.SetRelPos(embed, token.Blank)
660 field := &ast.Field{Label: ast.NewIdent("#enumValue"), Value: intValue}
661 st := &ast.StructLit{
662 Lbrace: token.Blank.Pos(),
663 Elts: []ast.Decl{embed, field},
664 }
665
666 addComments(embed, 0, y.Comment, y.InlineComment)
667 if y.Comment == nil && y.InlineComment == nil {
668 ast.SetRelPos(field, token.Blank)
669 ast.SetRelPos(field.Label, token.Blank)
670 st.Rbrace = token.Blank.Pos()
671 if i > 0 && lastSingle {
672 st.Lbrace = token.Newline.Pos()
673 }
674 lastSingle = true
675 } else {
676 lastSingle = false
677 }
678 e = st
679
680 default:
681 p.state.errs = errors.Append(p.state.errs,
682 errors.Newf(token.NoPos, "unknown enum mode %q", p.state.enumMode))
683 return
684 }
685
686 if enum.Value != nil {
687 e = &ast.BinaryExpr{X: enum.Value, Op: token.OR, Y: e}
688 }
689 enum.Value = e
690
691
692
693 }
694 }
695 p.addDecl(d)
696 addComments(enum.Value, 1, nil, lastComment)
697 }
698
699
700
701
702
703
704 func (p *protoConverter) oneOf(x *proto.Oneof) {
705 s := ast.NewStruct()
706 ast.SetRelPos(s, token.Newline)
707 embed := &ast.EmbedDecl{Expr: s}
708 embed.AddComment(comment(x.Comment, true))
709
710 p.addDecl(embed)
711
712 newStruct := func() {
713 s = &ast.StructLit{
714
715 Rbrace: token.Newline.Pos(),
716 }
717 embed.Expr = ast.NewBinExpr(token.OR, embed.Expr, s)
718 }
719 for _, v := range x.Elements {
720 switch x := v.(type) {
721 case *proto.OneOfField:
722 newStruct()
723 oneOf := p.parseField(s, 0, x.Field)
724 oneOf.Optional = token.NoPos
725
726 case *proto.Comment:
727 cg := comment(x, false)
728 ast.SetRelPos(cg, token.NewSection)
729 s.Elts = append(s.Elts, cg)
730
731 default:
732 newStruct()
733 p.messageField(s, 1, v)
734 }
735
736 }
737 }
738
739 func (p *protoConverter) parseField(s *ast.StructLit, i int, x *proto.Field) *ast.Field {
740 defer func(saved []string) { p.path = saved }(p.path)
741 p.path = append(p.path, x.Name)
742
743 f := &ast.Field{}
744 addComments(f, i, x.Comment, x.InlineComment)
745
746 name := p.ident(x.Position, x.Name)
747 f.Label = name
748 typ := p.resolve(x.Position, x.Type, x.Options)
749 f.Value = typ
750 s.Elts = append(s.Elts, f)
751
752 o := optionParser{message: s, field: f}
753
754
755 o.tags += fmt.Sprintf("%v,%s", x.Sequence, x.Type)
756 if x.Name != name.Name {
757 o.tags += ",name=" + x.Name
758 }
759 o.parse(x.Options)
760 p.addTag(f, o.tags)
761
762 if !o.required {
763 f.Optional = token.NoSpace.Pos()
764 }
765 return f
766 }
767
768 type optionParser struct {
769 message *ast.StructLit
770 field *ast.Field
771 required bool
772 tags string
773 }
774
775 func (p *optionParser) parse(options []*proto.Option) {
776
777
778
779
780 for _, o := range options {
781 switch o.Name {
782 case "(cue.opt).required":
783 p.required = true
784
785
786 case "(cue.val)":
787
788 expr, err := parser.ParseExpr("", o.Constant.Source)
789 if err != nil {
790 failf(o.Position, "invalid cue.val value: %v", err)
791 }
792
793 constraint := &ast.Field{Label: p.field.Label, Value: expr}
794 addComments(constraint, 1, o.Comment, o.InlineComment)
795 p.message.Elts = append(p.message.Elts, constraint)
796 if !p.required {
797 constraint.Optional = token.NoSpace.Pos()
798 }
799 case "(google.api.field_behavior)":
800 if o.Constant.Source == "REQUIRED" {
801 p.required = true
802 }
803 default:
804
805
806
807 source := o.Constant.SourceRepresentation()
808 p.tags += ","
809 switch source {
810 case "true":
811 p.tags += quoteOption(o.Name)
812 default:
813 p.tags += quoteOption(o.Name + "=" + source)
814 }
815 }
816 }
817 }
818
819 func quoteOption(s string) string {
820 needQuote := false
821 for _, r := range s {
822 if !unicode.In(r, unicode.L, unicode.N) {
823 needQuote = true
824 break
825 }
826 }
827 if !needQuote {
828 return s
829 }
830 if !strings.ContainsAny(s, `"\`) {
831 return literal.String.Quote(s)
832 }
833 esc := `\#`
834 for strings.Contains(s, esc) {
835 esc += "#"
836 }
837 return esc[1:] + `"` + s + `"` + esc[1:]
838 }
839
View as plain text