cuelang.org/go@v0.10.1/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 "strconv" 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 t, err := v.Bool() 183 if err != nil { 184 e.addErr(err) 185 } 186 value = strconv.FormatBool(t) 187 n.Values = append(n.Values, &pbast.Value{Value: value}) 188 189 case cue.IntKind, cue.FloatKind, cue.NumberKind: 190 d, _ := v.Decimal() 191 value := d.String() 192 193 if info, _ := pbinternal.FromValue("", v); !info.IsEnum { 194 } else if i, err := v.Int64(); err != nil { 195 } else if s := pbinternal.MatchByInt(v, i); s != "" { 196 value = s 197 } 198 199 n.Values = append(n.Values, &pbast.Value{Value: value}) 200 201 default: 202 e.addErr(errors.Newf(v.Pos(), "textproto: unknown type %v", v.Kind())) 203 } 204 }