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

     1  // Copyright 2021 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 textproto
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  
    21  	"github.com/solo-io/cue/cue"
    22  	"github.com/solo-io/cue/cue/errors"
    23  	"github.com/solo-io/cue/encoding/protobuf/pbinternal"
    24  
    25  	"github.com/protocolbuffers/txtpbfmt/ast"
    26  	pbast "github.com/protocolbuffers/txtpbfmt/ast"
    27  	"github.com/protocolbuffers/txtpbfmt/parser"
    28  )
    29  
    30  // Encoder marshals CUE into text proto.
    31  //
    32  type Encoder struct {
    33  	// Schema
    34  }
    35  
    36  // NewEncoder returns a new encoder, where the given options are default
    37  // options.
    38  func NewEncoder(options ...Option) *Encoder {
    39  	return &Encoder{}
    40  }
    41  
    42  // Encode converts a CUE value to a text proto file.
    43  //
    44  // Fields do not need to have a @protobuf attribute except for in the following
    45  // cases:
    46  //
    47  //   - it is explicitly required that only fields with an attribute are exported
    48  //   - a struct represents a Protobuf map
    49  //   - custom naming
    50  //
    51  func (e *Encoder) Encode(v cue.Value, options ...Option) ([]byte, error) {
    52  	n := &pbast.Node{}
    53  	enc := &encoder{}
    54  
    55  	enc.encodeMsg(n, v)
    56  
    57  	if enc.errs != nil {
    58  		return nil, enc.errs
    59  	}
    60  
    61  	// Pretty printing does not do errors, and returns a string (why o why?).
    62  	s := parser.Pretty(n.Children, 0)
    63  	return []byte(s), nil
    64  }
    65  
    66  type encoder struct {
    67  	errs errors.Error
    68  }
    69  
    70  func (e *encoder) addErr(err error) {
    71  	e.errs = errors.Append(e.errs, errors.Promote(err, "textproto"))
    72  }
    73  
    74  func (e *encoder) encodeMsg(parent *pbast.Node, v cue.Value) {
    75  	i, err := v.Fields()
    76  	if err != nil {
    77  		e.addErr(err)
    78  		return
    79  	}
    80  	for i.Next() {
    81  		v := i.Value()
    82  		if !v.IsConcrete() {
    83  			continue
    84  		}
    85  
    86  		info, err := pbinternal.FromIter(i)
    87  		if err != nil {
    88  			e.addErr(err)
    89  		}
    90  
    91  		switch info.CompositeType {
    92  		case pbinternal.List:
    93  			elems, err := v.List()
    94  			if err != nil {
    95  				e.addErr(err)
    96  				return
    97  			}
    98  			for first := true; elems.Next(); first = false {
    99  				n := &pbast.Node{Name: info.Name}
   100  				if first {
   101  					copyMeta(n, v)
   102  				}
   103  				elem := elems.Value()
   104  				copyMeta(n, elem)
   105  				parent.Children = append(parent.Children, n)
   106  				e.encodeValue(n, elem)
   107  			}
   108  
   109  		case pbinternal.Map:
   110  			i, err := v.Fields()
   111  			if err != nil {
   112  				e.addErr(err)
   113  				return
   114  			}
   115  			for first := true; i.Next(); first = false {
   116  				n := &pbast.Node{Name: info.Name}
   117  				if first {
   118  					copyMeta(n, v)
   119  				}
   120  				parent.Children = append(parent.Children, n)
   121  				var key *pbast.Node
   122  				switch info.KeyType {
   123  				case pbinternal.String, pbinternal.Bytes:
   124  					key = pbast.StringNode("key", i.Label())
   125  				default:
   126  					key = &pbast.Node{
   127  						Name:   "key",
   128  						Values: []*ast.Value{{Value: i.Label()}},
   129  					}
   130  				}
   131  				n.Children = append(n.Children, key)
   132  
   133  				value := &pbast.Node{Name: "value"}
   134  				e.encodeValue(value, i.Value())
   135  				n.Children = append(n.Children, value)
   136  			}
   137  
   138  		default:
   139  			n := &pbast.Node{Name: info.Name}
   140  			copyMeta(n, v)
   141  			e.encodeValue(n, v)
   142  			// Don't add if there are no values or children.
   143  			parent.Children = append(parent.Children, n)
   144  		}
   145  	}
   146  }
   147  
   148  // copyMeta copies metadata from nodes to values.
   149  //
   150  // TODO: also copy positions. The textproto API is rather messy and complex,
   151  // though, and so far it seems to be quite buggy too. Not sure if it is worth
   152  // the effort.
   153  func copyMeta(x *pbast.Node, v cue.Value) {
   154  	for _, doc := range v.Doc() {
   155  		s := strings.TrimRight(doc.Text(), "\n")
   156  		for _, c := range strings.Split(s, "\n") {
   157  			x.PreComments = append(x.PreComments, "# "+c)
   158  		}
   159  	}
   160  }
   161  
   162  func (e *encoder) encodeValue(n *pbast.Node, v cue.Value) {
   163  	var value string
   164  	switch v.Kind() {
   165  	case cue.StructKind:
   166  		e.encodeMsg(n, v)
   167  
   168  	case cue.StringKind:
   169  		s, err := v.String()
   170  		if err != nil {
   171  			e.addErr(err)
   172  		}
   173  		sn := pbast.StringNode("foo", s)
   174  		n.Values = append(n.Values, sn.Values...)
   175  
   176  	case cue.BytesKind:
   177  		b, err := v.Bytes()
   178  		if err != nil {
   179  			e.addErr(err)
   180  		}
   181  		sn := pbast.StringNode("foo", string(b))
   182  		n.Values = append(n.Values, sn.Values...)
   183  
   184  	case cue.BoolKind:
   185  		value = fmt.Sprint(v)
   186  		n.Values = append(n.Values, &pbast.Value{Value: value})
   187  
   188  	case cue.IntKind, cue.FloatKind, cue.NumberKind:
   189  		d, _ := v.Decimal()
   190  		value := d.String()
   191  
   192  		if info, _ := pbinternal.FromValue("", v); !info.IsEnum {
   193  		} else if i, err := v.Int64(); err != nil {
   194  		} else if s := pbinternal.MatchByInt(v, i); s != "" {
   195  			value = s
   196  		}
   197  
   198  		n.Values = append(n.Values, &pbast.Value{Value: value})
   199  
   200  	default:
   201  		e.addErr(errors.Newf(v.Pos(), "textproto: unknown type %v", v.Kind()))
   202  	}
   203  }