...

Source file src/cuelang.org/go/encoding/protobuf/jsonpb/decoder.go

Documentation: cuelang.org/go/encoding/protobuf/jsonpb

     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  	"cuelang.org/go/cue"
    22  	"cuelang.org/go/cue/ast"
    23  	"cuelang.org/go/cue/ast/astutil"
    24  	"cuelang.org/go/cue/errors"
    25  	"cuelang.org/go/cue/literal"
    26  	"cuelang.org/go/cue/token"
    27  	"cuelang.org/go/encoding/protobuf/pbinternal"
    28  	"github.com/cockroachdb/apd/v3"
    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  //
    44  //	for any CUE type:
    45  //	           null is omitted if null is not specifically allowed.
    46  //	bytes:     if the expression is a string, it is reinterpreted using a
    47  //	           base64 encoding. Either standard or URL-safe base64 encoding
    48  //	           with/without paddings are accepted.
    49  //	int:       string values are interpreted as integers
    50  //	float:     string values are interpreted as numbers, and the values "NaN",
    51  //	           "Infinity", and "-Infinity" are allowed and converted
    52  //	           to corresponding error values.
    53  //	enums:     if a field is of type int and does not have a standard integer
    54  //	           type for its @protobuf attribute, this is assumed to represent
    55  //	           a protobuf enum value. Enum names are converted to integers
    56  //	           by interpreting the definitions of the disjunction constants
    57  //	           as the symbol names.
    58  //	           If CUE uses the string representation for enums, then an
    59  //	           #enumValue integer associated with the string value is used
    60  //	           for the conversion.
    61  //	{}:        JSON objects representing any values will be left as is.
    62  //	           If the CUE type corresponding to the URL can be determined within
    63  //	           the module context it will be unified.
    64  //	time.Time / time.Duration:
    65  //	           left as is
    66  //	_:         left as is.
    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  }
   334  

View as plain text