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  }