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 }