...

Source file src/cuelang.org/go/encoding/protobuf/textproto/encoder.go

Documentation: cuelang.org/go/encoding/protobuf/textproto

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

View as plain text