github.com/solo-io/cue@v0.4.7/encoding/protobuf/parse.go (about)

     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  
    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  	"github.com/solo-io/cue/cue/ast"
    31  	"github.com/solo-io/cue/cue/ast/astutil"
    32  	"github.com/solo-io/cue/cue/errors"
    33  	"github.com/solo-io/cue/cue/literal"
    34  	"github.com/solo-io/cue/cue/parser"
    35  	"github.com/solo-io/cue/cue/token"
    36  	"github.com/solo-io/cue/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, 0, 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  	// Parse package definitions.
    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  				// name.AddComment(comment(x.Comment, true))
   124  				// name.AddComment(comment(x.InlineComment, false))
   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  // A protoConverter converts a proto definition to CUE. Proto files map to
   152  // CUE files one to one.
   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 // for symbols resolution within package.
   171  	symbols map[string]bool      // symbols provided by package
   172  }
   173  
   174  type mapping struct {
   175  	cue func() ast.Expr // needs to be a new copy as position changes
   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  // TODO: this doesn't work. Do something more principled.
   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  		// already handled.
   430  
   431  	case *proto.Service:
   432  		// TODO: handle services.
   433  
   434  	case *proto.Extensions, *proto.Reserved:
   435  		// no need to handle
   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  		// TODO: we are not handling extensions as for now.
   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  	// TODO: handle IsExtend/ proto2
   455  
   456  	s := &ast.StructLit{
   457  		Lbrace: p.toCUEPos(v.Position),
   458  		// TODO: set proto file position.
   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  		// All keys are converted to strings.
   510  		// TODO: support integer keys.
   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  		// no need to handle
   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  // enum converts a proto enum definition to CUE.
   561  //
   562  // An enum will generate two top-level definitions:
   563  //
   564  //    Enum:
   565  //      "Value1" |
   566  //      "Value2" |
   567  //      "Value3"
   568  //
   569  // and
   570  //
   571  //    Enum_value: {
   572  //        "Value1": 0
   573  //        "Value2": 1
   574  //    }
   575  //
   576  // Enums are always defined at the top level. The name of a nested enum
   577  // will be prefixed with the name of its parent and an underscore.
   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  	// Top-level enum entry.
   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  	// Top-level enum values entry.
   604  	valueName := ast.NewIdent(name.Name + "_value")
   605  	valueName.NamePos = newSection
   606  	valueMap := &ast.StructLit{}
   607  	d := &ast.Field{Label: valueName, Value: valueMap}
   608  	// addComments(valueMap, 1, x.Comment, nil)
   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  	// The line comments for an enum field need to attach after the '|', which
   627  	// is only known at the next iteration.
   628  	var lastComment *proto.Comment
   629  	for i, v := range x.Elements {
   630  		switch y := v.(type) {
   631  		case *proto.EnumField:
   632  			// Add enum value to map
   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  				// add to enum disjunction
   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  			// a := fmt.Sprintf("@protobuf(enum,name=%s)", y.Name)
   692  			// f.Attrs = append(f.Attrs, &ast.Attribute{Text: a})
   693  		}
   694  	}
   695  	p.addDecl(d)
   696  	addComments(enum.Value, 1, nil, lastComment)
   697  }
   698  
   699  // oneOf converts a Proto OneOf field to CUE. Note that Protobuf defines
   700  // a oneOf to be at most one of the fields. Rather than making each field
   701  // optional, we define oneOfs as all required fields, but add one more
   702  // disjunction allowing no fields. This makes it easier to constrain the
   703  // result to include at least one of the values.
   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  			// TODO: make this the default in the formatter.
   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  	// body of @protobuf tag: sequence,type[,name=<name>][,...]
   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  	// TODO: handle options
   778  	// - translate options to tags
   779  	// - interpret CUE options.
   780  	for _, o := range options {
   781  		// For each option, allow defining the message from the innermost or outermost scope:
   782  		// https://developers.google.com/protocol-buffers/docs/proto3#packages_and_name_resolution
   783  		switch o.Name {
   784  		case "(.solo.io.cue.opt).required":
   785  			fallthrough
   786  		case "(solo.io.cue.opt).required":
   787  			p.required = true
   788  			// TODO: Dropping comments. Maybe add a dummy tag?
   789  
   790  		case "(.solo.io.cue.opt).disable_openapi_validation":
   791  			fallthrough
   792  		case "(solo.io.cue.opt).disable_openapi_validation":
   793  			// this overrides the type used by the OpenAPI parser to use an ast.Struct, equivalent to a google.protobuf.Struct, which permits all fields.
   794  			lit := ast.NewStruct()
   795  			p.field.Value = lit
   796  
   797  		case "(.solo.io.cue.opt).disable_openapi_type_validation":
   798  			fallthrough
   799  		case "(solo.io.cue.opt).disable_openapi_type_validation":
   800  			// this overrides the type used by the OpenAPI parser to use an ast.Top, equivalent to a google.protobuf.Value, which permits all JSON types.
   801  			p.field.Value = ast.NewIdent("_")
   802  
   803  		case "(.solo.io.cue.val)":
   804  			fallthrough
   805  		case "(solo.io.cue.val)":
   806  			// TODO: set filename and base offset.
   807  			expr, err := parser.ParseExpr("", o.Constant.Source)
   808  			if err != nil {
   809  				failf(o.Position, "invalid cue.val value: %v", err)
   810  			}
   811  			// Any further checks will be done at the end.
   812  			constraint := &ast.Field{Label: p.field.Label, Value: expr}
   813  			addComments(constraint, 1, o.Comment, o.InlineComment)
   814  			p.message.Elts = append(p.message.Elts, constraint)
   815  			if !p.required {
   816  				constraint.Optional = token.NoSpace.Pos()
   817  			}
   818  
   819  		default:
   820  			// TODO: dropping comments. Maybe add dummy tag?
   821  
   822  			// TODO: should CUE support nested attributes?
   823  			source := o.Constant.SourceRepresentation()
   824  			p.tags += ","
   825  			switch source {
   826  			case "true":
   827  				p.tags += quoteOption(o.Name)
   828  			default:
   829  				p.tags += quoteOption(o.Name + "=" + source)
   830  			}
   831  		}
   832  	}
   833  }
   834  
   835  func quoteOption(s string) string {
   836  	needQuote := false
   837  	for _, r := range s {
   838  		if !unicode.In(r, unicode.L, unicode.N) {
   839  			needQuote = true
   840  			break
   841  		}
   842  	}
   843  	if !needQuote {
   844  		return s
   845  	}
   846  	if !strings.ContainsAny(s, `"\`) {
   847  		return literal.String.Quote(s)
   848  	}
   849  	esc := `\#`
   850  	for strings.Contains(s, esc) {
   851  		esc += "#"
   852  	}
   853  	return esc[1:] + `"` + s + `"` + esc[1:]
   854  }