github.com/solo-io/cue@v0.4.7/encoding/protobuf/textproto/decoder.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/ast" 23 "github.com/solo-io/cue/cue/errors" 24 "github.com/solo-io/cue/cue/literal" 25 "github.com/solo-io/cue/cue/token" 26 "github.com/solo-io/cue/encoding/protobuf/pbinternal" 27 "github.com/solo-io/cue/internal/core/adt" 28 "github.com/solo-io/cue/internal/value" 29 30 pbast "github.com/protocolbuffers/txtpbfmt/ast" 31 "github.com/protocolbuffers/txtpbfmt/parser" 32 "github.com/protocolbuffers/txtpbfmt/unquote" 33 ) 34 35 // Option defines options for the decoder. 36 // There are currently no options. 37 type Option func(*options) 38 39 type options struct { 40 } 41 42 // NewDecoder returns a new Decoder 43 func NewDecoder(option ...Option) *Decoder { 44 d := &Decoder{} 45 _ = d.m // work around linter bug. 46 return d 47 } 48 49 // A Decoder caches conversions of cue.Value between calls to its methods. 50 type Decoder struct { 51 m map[*adt.Vertex]*mapping 52 } 53 54 type decoder struct { 55 *Decoder 56 57 // Reset on each call 58 errs errors.Error 59 file *token.File 60 } 61 62 // Parse parses the given textproto bytes and converts them to a CUE expression, 63 // using schema as the guideline for conversion using the following rules: 64 // 65 // - the @protobuf attribute is optional, but is necessary for: 66 // - interpreting protobuf maps 67 // - using a name different from the CUE name 68 // - fields in the textproto that have no corresponding field in 69 // schema are ignored 70 // 71 // NOTE: the filename is used for associating position information. However, 72 // currently no position information is associated with the text proto because 73 // the position information of github.com/protocolbuffers/txtpbfmt is too 74 // unreliable to be useful. 75 func (d *Decoder) Parse(schema cue.Value, filename string, b []byte) (ast.Expr, error) { 76 dec := decoder{Decoder: d} 77 78 // dec.errs = nil 79 80 f := token.NewFile(filename, 0, len(b)) 81 f.SetLinesForContent(b) 82 dec.file = f 83 84 cfg := parser.Config{} 85 nodes, err := parser.ParseWithConfig(b, cfg) 86 if err != nil { 87 return nil, errors.Newf(token.NoPos, "textproto: %v", err) 88 } 89 90 m := dec.parseSchema(schema) 91 if dec.errs != nil { 92 return nil, dec.errs 93 } 94 95 n := dec.decodeMsg(m, nodes) 96 if dec.errs != nil { 97 return nil, dec.errs 98 } 99 100 return n, nil 101 } 102 103 // Don't expose until the protobuf APIs settle down. 104 // func (d *decoder) Decode(schema cue.Value, textpbfmt) (cue.Value, error) { 105 // } 106 107 type mapping struct { 108 children map[string]*fieldInfo 109 } 110 111 type fieldInfo struct { 112 pbinternal.Info 113 msg *mapping 114 // keytype, for now 115 } 116 117 func (d *decoder) addErr(err error) { 118 d.errs = errors.Append(d.errs, errors.Promote(err, "textproto")) 119 } 120 121 func (d *decoder) addErrf(pos pbast.Position, format string, args ...interface{}) { 122 err := errors.Newf(d.protoPos(pos), "textproto: "+format, args...) 123 d.errs = errors.Append(d.errs, err) 124 } 125 126 func (d *decoder) protoPos(p pbast.Position) token.Pos { 127 return d.file.Pos(int(p.Byte), token.NoRelPos) 128 } 129 130 // parseSchema walks over a CUE "type", converts it to an internal data 131 // structure that is used for parsing text proto, and writes it to 132 // 133 func (d *decoder) parseSchema(schema cue.Value) *mapping { 134 _, v := value.ToInternal(schema) 135 if v == nil { 136 return nil 137 } 138 139 if d.m == nil { 140 d.m = map[*adt.Vertex]*mapping{} 141 } else if m := d.m[v]; m != nil { 142 return m 143 } 144 145 m := &mapping{children: map[string]*fieldInfo{}} 146 147 i, err := schema.Fields() 148 if err != nil { 149 d.addErr(err) 150 return nil 151 } 152 153 for i.Next() { 154 info, err := pbinternal.FromIter(i) 155 if err != nil { 156 d.addErr(err) 157 continue 158 } 159 160 var msg *mapping 161 162 switch info.CompositeType { 163 case pbinternal.Normal: 164 switch info.ValueType { 165 case pbinternal.Message: 166 msg = d.parseSchema(i.Value()) 167 } 168 169 case pbinternal.List, pbinternal.Map: 170 e, _ := i.Value().Elem() 171 if e.IncompleteKind() == cue.StructKind { 172 msg = d.parseSchema(e) 173 } 174 } 175 176 m.children[info.Name] = &fieldInfo{ 177 Info: info, 178 msg: msg, 179 } 180 } 181 182 d.m[v] = m 183 return m 184 } 185 186 func (d *decoder) decodeMsg(m *mapping, n []*pbast.Node) ast.Expr { 187 st := &ast.StructLit{} 188 189 var listMap map[string]*ast.ListLit 190 191 for _, x := range n { 192 if x.Values == nil && x.Children == nil { 193 if cg := addComments(x.PreComments...); cg != nil { 194 ast.SetRelPos(cg, token.NewSection) 195 st.Elts = append(st.Elts, cg) 196 continue 197 } 198 } 199 if m == nil { 200 continue 201 } 202 f, ok := m.children[x.Name] 203 if !ok { 204 continue // ignore unknown fields 205 } 206 207 var value ast.Expr 208 209 switch f.CompositeType { 210 default: 211 value = d.decodeValue(f, x) 212 213 case pbinternal.List: 214 if listMap == nil { 215 listMap = make(map[string]*ast.ListLit) 216 } 217 218 list := listMap[f.CUEName] 219 if list == nil { 220 list = &ast.ListLit{} 221 listMap[f.CUEName] = list 222 value = list 223 } 224 225 if len(x.Values) == 1 || f.ValueType == pbinternal.Message { 226 v := d.decodeValue(f, x) 227 if value == nil { 228 if cg := addComments(x.PreComments...); cg != nil { 229 cg.Doc = true 230 ast.AddComment(v, cg) 231 } 232 } 233 if cg := addComments(x.PostValuesComments...); cg != nil { 234 cg.Position = 4 235 ast.AddComment(v, cg) 236 } 237 list.Elts = append(list.Elts, v) 238 break 239 } 240 241 var last ast.Expr 242 // Handle [1, 2, 3] 243 for _, v := range x.Values { 244 if v.Value == "" { 245 if cg := addComments(v.PreComments...); cg != nil { 246 if last != nil { 247 cg.Position = 4 248 ast.AddComment(last, cg) 249 } else { 250 cg.Position = 1 251 ast.AddComment(list, cg) 252 } 253 } 254 continue 255 } 256 y := *x 257 y.Values = []*pbast.Value{v} 258 last = d.decodeValue(f, &y) 259 list.Elts = append(list.Elts, last) 260 } 261 if cg := addComments(x.PostValuesComments...); cg != nil { 262 if last != nil { 263 cg.Position = 4 264 ast.AddComment(last, cg) 265 } else { 266 cg.Position = 1 267 ast.AddComment(list, cg) 268 } 269 } 270 if cg := addComments(x.ClosingBraceComment); cg != nil { 271 cg.Position = 4 272 ast.AddComment(list, cg) 273 } 274 275 case pbinternal.Map: 276 // mapValue: { 277 // key: 123 278 // value: "string" 279 // } 280 if k := len(x.Values); k > 0 { 281 d.addErrf(x.Start, "values not allowed for Message type; found %d", k) 282 } 283 284 var ( 285 key ast.Label 286 val ast.Expr 287 ) 288 289 for _, c := range x.Children { 290 if len(c.Values) != 1 { 291 d.addErrf(x.Start, "expected 1 value, found %d", len(c.Values)) 292 continue 293 } 294 s := c.Values[0].Value 295 296 switch c.Name { 297 case "key": 298 if strings.HasPrefix(s, `"`) { 299 key = &ast.BasicLit{Kind: token.STRING, Value: s} 300 } else { 301 key = ast.NewString(s) 302 } 303 304 case "value": 305 val = d.decodeValue(f, c) 306 307 if cg := addComments(x.ClosingBraceComment); cg != nil { 308 cg.Line = true 309 ast.AddComment(val, cg) 310 } 311 312 default: 313 d.addErrf(c.Start, "unsupported key name %q in map", c.Name) 314 continue 315 } 316 } 317 318 if key != nil && val != nil { 319 value = ast.NewStruct(key, val) 320 } 321 } 322 323 if value != nil { 324 var label ast.Label 325 if s := f.CUEName; ast.IsValidIdent(s) { 326 label = ast.NewIdent(s) 327 } else { 328 label = ast.NewString(s) 329 330 } 331 // TODO: convert line number information. However, position 332 // information in textpbfmt packages is too wonky to be useful 333 f := &ast.Field{ 334 Label: label, 335 Value: value, 336 // Attrs: []*ast.Attribute{{Text: f.attr.}}, 337 } 338 if cg := addComments(x.PreComments...); cg != nil { 339 cg.Doc = true 340 ast.AddComment(f, cg) 341 } 342 st.Elts = append(st.Elts, f) 343 } 344 } 345 346 return st 347 } 348 349 func addComments(lines ...string) (cg *ast.CommentGroup) { 350 var a []*ast.Comment 351 for _, c := range lines { 352 if !strings.HasPrefix(c, "#") { 353 continue 354 } 355 a = append(a, &ast.Comment{Text: "//" + c[1:]}) 356 } 357 if a != nil { 358 cg = &ast.CommentGroup{List: a} 359 } 360 return cg 361 } 362 363 func (d *decoder) decodeValue(f *fieldInfo, n *pbast.Node) (x ast.Expr) { 364 if f.ValueType == pbinternal.Message { 365 if k := len(n.Values); k > 0 { 366 d.addErrf(n.Start, "values not allowed for Message type; found %d", k) 367 } 368 x = d.decodeMsg(f.msg, n.Children) 369 if cg := addComments(n.ClosingBraceComment); cg != nil { 370 cg.Line = true 371 cg.Position = 4 372 ast.AddComment(x, cg) 373 } 374 return x 375 } 376 377 if len(n.Values) != 1 { 378 d.addErrf(n.Start, "expected 1 value, found %d", len(n.Values)) 379 return nil 380 } 381 v := n.Values[0] 382 383 defer func() { 384 if cg := addComments(v.PreComments...); cg != nil { 385 cg.Doc = true 386 ast.AddComment(x, cg) 387 } 388 if cg := addComments(v.InlineComment); cg != nil { 389 cg.Line = true 390 cg.Position = 2 391 ast.AddComment(x, cg) 392 } 393 }() 394 395 switch f.ValueType { 396 case pbinternal.String, pbinternal.Bytes: 397 s, err := unquote.Unquote(n) 398 if err != nil { 399 d.addErrf(n.Start, "invalid string or bytes: %v", err) 400 } 401 if f.ValueType == pbinternal.String { 402 s = literal.String.Quote(s) 403 } else { 404 s = literal.Bytes.Quote(s) 405 } 406 return &ast.BasicLit{Kind: token.STRING, Value: s} 407 408 case pbinternal.Bool: 409 switch v.Value { 410 case "true": 411 return ast.NewBool(true) 412 413 case "false": 414 default: 415 d.addErrf(n.Start, "invalid bool %s", v.Value) 416 } 417 return ast.NewBool(false) 418 419 case pbinternal.Int, pbinternal.Float: 420 s := v.Value 421 switch s { 422 case "inf", "nan": 423 // TODO: include message. 424 return &ast.BottomLit{} 425 } 426 427 var info literal.NumInfo 428 if err := literal.ParseNum(s, &info); err != nil { 429 var x ast.BasicLit 430 if pbinternal.MatchBySymbol(f.Value, s, &x) { 431 return &x 432 } 433 d.addErrf(n.Start, "invalid number %s", s) 434 } 435 if !info.IsInt() { 436 return &ast.BasicLit{Kind: token.FLOAT, Value: s} 437 } 438 return &ast.BasicLit{Kind: token.INT, Value: info.String()} 439 440 default: 441 panic(fmt.Sprintf("unexpected type %v", f.ValueType)) 442 } 443 }