this repo has no description
at master 158 lines 4.0 kB view raw
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 15package jsonpb 16 17import ( 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). 48type Encoder struct { 49 schema cue.Value 50} 51 52// NewEncoder creates an Encoder for the given schema. 53func 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. 62func (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. 73func (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 79type encoder struct { 80 errs errors.Error 81} 82 83func (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 103func (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}