cuelang.org/go@v0.10.1/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 "cuelang.org/go/cue" 22 "cuelang.org/go/cue/ast" 23 "cuelang.org/go/cue/errors" 24 "cuelang.org/go/cue/literal" 25 "cuelang.org/go/cue/token" 26 "cuelang.org/go/encoding/protobuf/pbinternal" 27 "cuelang.org/go/internal/core/adt" 28 "cuelang.org/go/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, -1, 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 func (d *decoder) parseSchema(schema cue.Value) *mapping { 133 _, v := value.ToInternal(schema) 134 if v == nil { 135 return nil 136 } 137 138 if d.m == nil { 139 d.m = map[*adt.Vertex]*mapping{} 140 } else if m := d.m[v]; m != nil { 141 return m 142 } 143 144 m := &mapping{children: map[string]*fieldInfo{}} 145 146 i, err := schema.Fields(cue.Optional(true)) 147 if err != nil { 148 d.addErr(err) 149 return nil 150 } 151 152 for i.Next() { 153 info, err := pbinternal.FromIter(i) 154 if err != nil { 155 d.addErr(err) 156 continue 157 } 158 159 var msg *mapping 160 161 switch info.CompositeType { 162 case pbinternal.Normal: 163 switch info.ValueType { 164 case pbinternal.Message: 165 msg = d.parseSchema(i.Value()) 166 } 167 168 case pbinternal.List, pbinternal.Map: 169 e, _ := i.Value().Elem() 170 if e.IncompleteKind() == cue.StructKind { 171 msg = d.parseSchema(e) 172 } 173 } 174 175 m.children[info.Name] = &fieldInfo{ 176 Info: info, 177 msg: msg, 178 } 179 } 180 181 d.m[v] = m 182 return m 183 } 184 185 func (d *decoder) decodeMsg(m *mapping, n []*pbast.Node) ast.Expr { 186 st := &ast.StructLit{} 187 188 var listMap map[string]*ast.ListLit 189 190 for _, x := range n { 191 if x.Values == nil && x.Children == nil { 192 if cg := addComments(x.PreComments...); cg != nil { 193 ast.SetRelPos(cg, token.NewSection) 194 st.Elts = append(st.Elts, cg) 195 continue 196 } 197 } 198 if m == nil { 199 continue 200 } 201 f, ok := m.children[x.Name] 202 if !ok { 203 continue // ignore unknown fields 204 } 205 206 var value ast.Expr 207 208 switch f.CompositeType { 209 default: 210 value = d.decodeValue(f, x) 211 212 case pbinternal.List: 213 if listMap == nil { 214 listMap = make(map[string]*ast.ListLit) 215 } 216 217 list := listMap[f.CUEName] 218 if list == nil { 219 list = &ast.ListLit{} 220 listMap[f.CUEName] = list 221 value = list 222 } 223 224 if len(x.Values) == 1 || f.ValueType == pbinternal.Message { 225 v := d.decodeValue(f, x) 226 if value == nil { 227 if cg := addComments(x.PreComments...); cg != nil { 228 cg.Doc = true 229 ast.AddComment(v, cg) 230 } 231 } 232 if cg := addComments(x.PostValuesComments...); cg != nil { 233 cg.Position = 4 234 ast.AddComment(v, cg) 235 } 236 list.Elts = append(list.Elts, v) 237 break 238 } 239 240 var last ast.Expr 241 // Handle [1, 2, 3] 242 for _, v := range x.Values { 243 if v.Value == "" { 244 if cg := addComments(v.PreComments...); cg != nil { 245 if last != nil { 246 cg.Position = 4 247 ast.AddComment(last, cg) 248 } else { 249 cg.Position = 1 250 ast.AddComment(list, cg) 251 } 252 } 253 continue 254 } 255 y := *x 256 y.Values = []*pbast.Value{v} 257 last = d.decodeValue(f, &y) 258 list.Elts = append(list.Elts, last) 259 } 260 if cg := addComments(x.PostValuesComments...); cg != nil { 261 if last != nil { 262 cg.Position = 4 263 ast.AddComment(last, cg) 264 } else { 265 cg.Position = 1 266 ast.AddComment(list, cg) 267 } 268 } 269 if cg := addComments(x.ClosingBraceComment); cg != nil { 270 cg.Position = 4 271 ast.AddComment(list, cg) 272 } 273 274 case pbinternal.Map: 275 // mapValue: { 276 // key: 123 277 // value: "string" 278 // } 279 if k := len(x.Values); k > 0 { 280 d.addErrf(x.Start, "values not allowed for Message type; found %d", k) 281 } 282 283 var ( 284 key ast.Label 285 val ast.Expr 286 ) 287 288 for _, c := range x.Children { 289 if len(c.Values) != 1 { 290 d.addErrf(x.Start, "expected 1 value, found %d", len(c.Values)) 291 continue 292 } 293 s := c.Values[0].Value 294 295 switch c.Name { 296 case "key": 297 if strings.HasPrefix(s, `"`) { 298 key = &ast.BasicLit{Kind: token.STRING, Value: s} 299 } else { 300 key = ast.NewString(s) 301 } 302 303 case "value": 304 val = d.decodeValue(f, c) 305 306 if cg := addComments(x.ClosingBraceComment); cg != nil { 307 cg.Line = true 308 ast.AddComment(val, cg) 309 } 310 311 default: 312 d.addErrf(c.Start, "unsupported key name %q in map", c.Name) 313 continue 314 } 315 } 316 317 if key != nil && val != nil { 318 value = ast.NewStruct(key, val) 319 } 320 } 321 322 if value != nil { 323 var label ast.Label 324 if s := f.CUEName; ast.IsValidIdent(s) { 325 label = ast.NewIdent(s) 326 } else { 327 label = ast.NewString(s) 328 329 } 330 // TODO: convert line number information. However, position 331 // information in textpbfmt packages is too wonky to be useful 332 f := &ast.Field{ 333 Label: label, 334 Value: value, 335 // Attrs: []*ast.Attribute{{Text: f.attr.}}, 336 } 337 if cg := addComments(x.PreComments...); cg != nil { 338 cg.Doc = true 339 ast.AddComment(f, cg) 340 } 341 st.Elts = append(st.Elts, f) 342 } 343 } 344 345 return st 346 } 347 348 func addComments(lines ...string) (cg *ast.CommentGroup) { 349 var a []*ast.Comment 350 for _, c := range lines { 351 if !strings.HasPrefix(c, "#") { 352 continue 353 } 354 a = append(a, &ast.Comment{Text: "//" + c[1:]}) 355 } 356 if a != nil { 357 cg = &ast.CommentGroup{List: a} 358 } 359 return cg 360 } 361 362 func (d *decoder) decodeValue(f *fieldInfo, n *pbast.Node) (x ast.Expr) { 363 if f.ValueType == pbinternal.Message { 364 if k := len(n.Values); k > 0 { 365 d.addErrf(n.Start, "values not allowed for Message type; found %d", k) 366 } 367 x = d.decodeMsg(f.msg, n.Children) 368 if cg := addComments(n.ClosingBraceComment); cg != nil { 369 cg.Line = true 370 cg.Position = 4 371 ast.AddComment(x, cg) 372 } 373 return x 374 } 375 376 if len(n.Values) != 1 { 377 d.addErrf(n.Start, "expected 1 value, found %d", len(n.Values)) 378 return nil 379 } 380 v := n.Values[0] 381 382 defer func() { 383 if cg := addComments(v.PreComments...); cg != nil { 384 cg.Doc = true 385 ast.AddComment(x, cg) 386 } 387 if cg := addComments(v.InlineComment); cg != nil { 388 cg.Line = true 389 cg.Position = 2 390 ast.AddComment(x, cg) 391 } 392 }() 393 394 switch f.ValueType { 395 case pbinternal.String, pbinternal.Bytes: 396 s, err := unquote.Unquote(n) 397 if err != nil { 398 d.addErrf(n.Start, "invalid string or bytes: %v", err) 399 } 400 if f.ValueType == pbinternal.String { 401 s = literal.String.Quote(s) 402 } else { 403 s = literal.Bytes.Quote(s) 404 } 405 return &ast.BasicLit{Kind: token.STRING, Value: s} 406 407 case pbinternal.Bool: 408 switch v.Value { 409 case "true": 410 return ast.NewBool(true) 411 412 case "false": 413 default: 414 d.addErrf(n.Start, "invalid bool %s", v.Value) 415 } 416 return ast.NewBool(false) 417 418 case pbinternal.Int, pbinternal.Float: 419 s := v.Value 420 switch s { 421 case "inf", "nan": 422 // TODO: include message. 423 return &ast.BottomLit{} 424 } 425 426 var info literal.NumInfo 427 if err := literal.ParseNum(s, &info); err != nil { 428 var x ast.BasicLit 429 if pbinternal.MatchBySymbol(f.Value, s, &x) { 430 return &x 431 } 432 d.addErrf(n.Start, "invalid number %s", s) 433 } 434 if !info.IsInt() { 435 return &ast.BasicLit{Kind: token.FLOAT, Value: s} 436 } 437 return &ast.BasicLit{Kind: token.INT, Value: info.String()} 438 439 default: 440 panic(fmt.Sprintf("unexpected type %v", f.ValueType)) 441 } 442 }