github.com/solo-io/cue@v0.4.7/encoding/protobuf/jsonpb/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 jsonpb 16 17 import ( 18 "encoding/base64" 19 "strings" 20 21 "github.com/cockroachdb/apd/v2" 22 "github.com/solo-io/cue/cue" 23 "github.com/solo-io/cue/cue/ast" 24 "github.com/solo-io/cue/cue/ast/astutil" 25 "github.com/solo-io/cue/cue/errors" 26 "github.com/solo-io/cue/cue/literal" 27 "github.com/solo-io/cue/cue/token" 28 "github.com/solo-io/cue/encoding/protobuf/pbinternal" 29 ) 30 31 // Option is an option. 32 // 33 // There are currently no options. 34 type Option func() 35 36 // A Decoder interprets CUE expressions as JSON protobuf encodings 37 // based on an underlying schema. 38 // 39 // It bases the mapping on the underlying CUE type, without consulting Protobuf 40 // attributes. 41 // 42 // Mappings per CUE type: 43 // for any CUE type: 44 // null is omitted if null is not specifically allowed. 45 // bytes: if the expression is a string, it is reinterpreted using a 46 // base64 encoding. Either standard or URL-safe base64 encoding 47 // with/without paddings are accepted. 48 // int: string values are interpreted as integers 49 // float: string values are interpreted as numbers, and the values "NaN", 50 // "Infinity", and "-Infinity" are allowed and converted to 51 // to corresponding error values. 52 // enums: if a field is of type int and does not have a standard integer 53 // type for its @protobuf attribute, this is assumed to represent 54 // a protobuf enum value. Enum names are converted to integers 55 // by interpreting the definitions of the disjunction constants 56 // as the symbol names. 57 // If CUE uses the string representation for enums, then an 58 // #enumValue integer associated with the string value is used 59 // for the conversion. 60 // {}: JSON objects representing any values will be left as is. 61 // If the CUE type corresponding to the URL can be determined within 62 // the module context it will be unified. 63 // time.Time / time.Duration: 64 // left as is 65 // _: left as is. 66 // 67 type Decoder struct { 68 schema cue.Value 69 } 70 71 // NewDecoder creates a Decoder for the given schema. 72 func NewDecoder(schema cue.Value, options ...Option) *Decoder { 73 return &Decoder{schema: schema} 74 } 75 76 // RewriteFile modifies file, interpreting it in terms of the given schema 77 // according to the protocol buffer to JSON mapping defined in the protocol 78 // buffer spec. 79 // 80 // RewriteFile is idempotent, calling it multiples times on an expression gives 81 // the same result. 82 func (d *Decoder) RewriteFile(file *ast.File) error { 83 var r rewriter 84 r.rewriteDecls(d.schema, file.Decls) 85 return r.errs 86 } 87 88 // RewriteExpr modifies expr, interpreting it in terms of the given schema 89 // according to the protocol buffer to JSON mapping defined in the 90 // protocol buffer spec. 91 // 92 // RewriteExpr is idempotent, calling it multiples times on an expression gives 93 // the same result. 94 func (d *Decoder) RewriteExpr(expr ast.Expr) (ast.Expr, error) { 95 var r rewriter 96 x := r.rewrite(d.schema, expr) 97 return x, r.errs 98 } 99 100 type rewriter struct { 101 errs errors.Error 102 } 103 104 func (r *rewriter) addErr(err errors.Error) { 105 r.errs = errors.Append(r.errs, err) 106 } 107 108 func (r *rewriter) addErrf(p token.Pos, schema cue.Value, format string, args ...interface{}) { 109 format = "%s: " + format 110 args = append([]interface{}{schema.Path()}, args...) 111 r.addErr(errors.Newf(p, format, args...)) 112 } 113 114 func (r *rewriter) rewriteDecls(schema cue.Value, decls []ast.Decl) { 115 for _, f := range decls { 116 field, ok := f.(*ast.Field) 117 if !ok { 118 continue 119 } 120 sel := cue.Label(field.Label) 121 if !sel.IsString() { 122 continue 123 } 124 125 v := schema.LookupPath(cue.MakePath(sel)) 126 if !v.Exists() { 127 f := schema.Template() 128 if f == nil { 129 continue 130 } 131 v = f(sel.String()) 132 } 133 if !v.Exists() { 134 continue 135 } 136 137 field.Value = r.rewrite(v, field.Value) 138 } 139 } 140 141 var enumValuePath = cue.ParsePath("#enumValue").Optional() 142 143 func (r *rewriter) rewrite(schema cue.Value, expr ast.Expr) (x ast.Expr) { 144 defer func() { 145 if expr != x && x != nil { 146 astutil.CopyMeta(x, expr) 147 } 148 }() 149 150 switch x := expr.(type) { 151 case *ast.BasicLit: 152 if x.Kind != token.NULL { 153 break 154 } 155 if schema.IncompleteKind()&cue.NullKind != 0 { 156 break 157 } 158 switch v, _ := schema.Default(); { 159 case v.IsConcrete(): 160 if x, _ := v.Syntax(cue.Final()).(ast.Expr); x != nil { 161 return x 162 } 163 default: // default value for type 164 if x := zeroValue(schema, x); x != nil { 165 return x 166 } 167 } 168 169 case *ast.StructLit: 170 r.rewriteDecls(schema, x.Elts) 171 return x 172 173 case *ast.ListLit: 174 elem, _ := schema.Elem() 175 iter, _ := schema.List() 176 for i, e := range x.Elts { 177 v := elem 178 if iter.Next() { 179 v = iter.Value() 180 } 181 if !v.Exists() { 182 break 183 } 184 x.Elts[i] = r.rewrite(v, e) 185 } 186 187 return x 188 } 189 190 switch schema.IncompleteKind() { 191 case cue.IntKind, cue.FloatKind, cue.NumberKind: 192 x, q, str := stringValue(expr) 193 if x == nil || !q.IsDouble() { 194 break 195 } 196 197 var info literal.NumInfo 198 if err := literal.ParseNum(str, &info); err == nil { 199 x.Value = str 200 x.Kind = token.FLOAT 201 if info.IsInt() { 202 x.Kind = token.INT 203 } 204 break 205 } 206 207 pbinternal.MatchBySymbol(schema, str, x) 208 209 case cue.BytesKind: 210 x, q, str := stringValue(expr) 211 if x == nil && q.IsDouble() { 212 break 213 } 214 215 var b []byte 216 var err error 217 for _, enc := range base64Encodings { 218 if b, err = enc.DecodeString(str); err == nil { 219 break 220 } 221 } 222 if err != nil { 223 r.addErrf(expr.Pos(), schema, "failed to decode base64: %v", err) 224 return expr 225 } 226 227 quoter := literal.Bytes 228 if q.IsMulti() { 229 ws := q.Whitespace() 230 tabs := (strings.Count(ws, " ")+3)/4 + strings.Count(ws, "\t") 231 quoter = quoter.WithTabIndent(tabs) 232 } 233 x.Value = quoter.Quote(string(b)) 234 return x 235 236 case cue.StringKind: 237 if s, ok := expr.(*ast.BasicLit); ok && s.Kind == token.INT { 238 var info literal.NumInfo 239 if err := literal.ParseNum(s.Value, &info); err != nil || !info.IsInt() { 240 break 241 } 242 var d apd.Decimal 243 if err := info.Decimal(&d); err != nil { 244 break 245 } 246 enum, err := d.Int64() 247 if err != nil { 248 r.addErrf(expr.Pos(), schema, "invalid enum index: %v", err) 249 return expr 250 } 251 op, values := schema.Expr() 252 if op != cue.OrOp { 253 values = []cue.Value{schema} // allow single values. 254 } 255 for _, v := range values { 256 i, err := v.LookupPath(enumValuePath).Int64() 257 if err == nil && i == enum { 258 str, err := v.String() 259 if err != nil { 260 r.addErr(errors.Wrapf(err, v.Pos(), "invalid string enum")) 261 return expr 262 } 263 s.Kind = token.STRING 264 s.Value = literal.String.Quote(str) 265 266 return s 267 } 268 } 269 r.addErrf(expr.Pos(), schema, 270 "could not locate integer enum value %d", enum) 271 } 272 273 case cue.StructKind, cue.TopKind: 274 // TODO: Detect and mix in type. 275 } 276 return expr 277 } 278 279 func zeroValue(v cue.Value, x *ast.BasicLit) ast.Expr { 280 switch v.IncompleteKind() { 281 case cue.StringKind: 282 x.Kind = token.STRING 283 x.Value = `""` 284 285 case cue.BytesKind: 286 x.Kind = token.STRING 287 x.Value = `''` 288 289 case cue.BoolKind: 290 x.Kind = token.FALSE 291 x.Value = "false" 292 293 case cue.NumberKind, cue.IntKind, cue.FloatKind: 294 x.Kind = token.INT 295 x.Value = "0" 296 297 case cue.StructKind: 298 return ast.NewStruct() 299 300 case cue.ListKind: 301 return &ast.ListLit{} 302 303 default: 304 return nil 305 } 306 return x 307 } 308 309 func stringValue(x ast.Expr) (b *ast.BasicLit, q literal.QuoteInfo, str string) { 310 b, ok := x.(*ast.BasicLit) 311 if !ok || b.Kind != token.STRING { 312 return nil, q, "" 313 } 314 q, p, _, err := literal.ParseQuotes(b.Value, b.Value) 315 if err != nil { 316 return nil, q, "" 317 } 318 319 str, err = q.Unquote(b.Value[p:]) 320 if err != nil { 321 return nil, q, "" 322 } 323 324 return b, q, str 325 } 326 327 // These are all the allowed base64 encodings. 328 var base64Encodings = []base64.Encoding{ 329 *base64.StdEncoding, 330 *base64.URLEncoding, 331 *base64.RawStdEncoding, 332 *base64.RawURLEncoding, 333 }