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