...

Source file src/cuelang.org/go/encoding/protobuf/jsonpb/encoder.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  	"strconv"
    19  
    20  	"cuelang.org/go/cue"
    21  	"cuelang.org/go/cue/ast"
    22  	"cuelang.org/go/cue/errors"
    23  	"cuelang.org/go/cue/literal"
    24  	"cuelang.org/go/cue/token"
    25  	"cuelang.org/go/encoding/protobuf/pbinternal"
    26  )
    27  
    28  // TODO: Options:
    29  // - Convert integer strings.
    30  // - URL encoder
    31  // - URL decoder
    32  
    33  // An Encoder rewrites CUE values according to the Protobuf to JSON mappings,
    34  // based on a given CUE schema.
    35  //
    36  // It bases the mapping on the underlying CUE type, without consulting Protobuf
    37  // attributes.
    38  //
    39  // Mappings per CUE type:
    40  //
    41  //	for any CUE type:
    42  //	int:       if the expression value is an integer and the schema value is
    43  //	           an int64, it is converted to a string.
    44  //	{}:        JSON objects representing any values will be left as is.
    45  //	           If the CUE type corresponding to the URL can be determined within
    46  //	           the module context it will be unified.
    47  //	_:         Adds a `@type` URL (TODO).
    48  type Encoder struct {
    49  	schema cue.Value
    50  }
    51  
    52  // NewEncoder creates an Encoder for the given schema.
    53  func NewEncoder(schema cue.Value, options ...Option) *Encoder {
    54  	return &Encoder{schema: schema}
    55  }
    56  
    57  // RewriteFile modifies file, modifying it to conform to the Protocol buffer
    58  // to JSON mapping it in terms of the given schema.
    59  //
    60  // RewriteFile is idempotent, calling it multiples times on an expression gives
    61  // the same result.
    62  func (e *Encoder) RewriteFile(file *ast.File) error {
    63  	var enc encoder
    64  	enc.rewriteDecls(e.schema, file.Decls)
    65  	return enc.errs
    66  }
    67  
    68  // RewriteExpr modifies file, modifying it to conform to the Protocol buffer
    69  // to JSON mapping it in terms of the given schema.
    70  //
    71  // RewriteExpr is idempotent, calling it multiples times on an expression gives
    72  // the same result.
    73  func (e *Encoder) RewriteExpr(expr ast.Expr) (ast.Expr, error) {
    74  	var enc encoder
    75  	x := enc.rewrite(e.schema, expr)
    76  	return x, enc.errs
    77  }
    78  
    79  type encoder struct {
    80  	errs errors.Error
    81  }
    82  
    83  func (e *encoder) rewriteDecls(schema cue.Value, decls []ast.Decl) {
    84  	for _, f := range decls {
    85  		field, ok := f.(*ast.Field)
    86  		if !ok {
    87  			continue
    88  		}
    89  		sel := cue.Label(field.Label)
    90  		if !sel.IsString() {
    91  			continue
    92  		}
    93  
    94  		v := schema.LookupPath(cue.MakePath(sel.Optional()))
    95  		if !v.Exists() {
    96  			continue
    97  		}
    98  
    99  		field.Value = e.rewrite(v, field.Value)
   100  	}
   101  }
   102  
   103  func (e *encoder) rewrite(schema cue.Value, expr ast.Expr) (x ast.Expr) {
   104  	switch x := expr.(type) {
   105  	case *ast.ListLit:
   106  		for i, elem := range x.Elts {
   107  			v := schema.LookupPath(cue.MakePath(cue.Index(i).Optional()))
   108  			if !v.Exists() {
   109  				break
   110  			}
   111  			x.Elts[i] = e.rewrite(v, elem)
   112  		}
   113  		return expr
   114  
   115  	case *ast.StructLit:
   116  		e.rewriteDecls(schema, x.Elts)
   117  		return expr
   118  
   119  	case *ast.BasicLit:
   120  		if x.Kind != token.INT {
   121  			break
   122  		}
   123  
   124  		info, err := pbinternal.FromValue("", schema)
   125  		if err != nil {
   126  			break
   127  		}
   128  
   129  		switch info.Type {
   130  		case "int64", "fixed64", "sfixed64", "uint64":
   131  			b, ok := expr.(*ast.BasicLit)
   132  			if schema.IncompleteKind() == cue.IntKind && ok && b.Kind == token.INT {
   133  				b.Kind = token.STRING
   134  				b.Value = literal.String.Quote(b.Value)
   135  			}
   136  
   137  		case "int32", "fixed32", "sfixed32", "uint32", "float", "double":
   138  		case "varint":
   139  
   140  		default:
   141  			if !info.IsEnum {
   142  				break
   143  			}
   144  
   145  			i, err := strconv.ParseInt(x.Value, 10, 32)
   146  			if err != nil {
   147  				break
   148  			}
   149  
   150  			if s := pbinternal.MatchByInt(schema, i); s != "" {
   151  				x.Kind = token.STRING
   152  				x.Value = literal.String.Quote(s)
   153  			}
   154  		}
   155  	}
   156  
   157  	return expr
   158  }
   159  

View as plain text