github.com/solo-io/cue@v0.4.7/encoding/protobuf/jsonpb/encoder.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 "strconv" 19 20 "github.com/solo-io/cue/cue" 21 "github.com/solo-io/cue/cue/ast" 22 "github.com/solo-io/cue/cue/errors" 23 "github.com/solo-io/cue/cue/literal" 24 "github.com/solo-io/cue/cue/token" 25 "github.com/solo-io/cue/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 // for any CUE type: 41 // int: if the expression value is an integer and the schema value is 42 // an int64, it is converted to a string. 43 // {}: JSON objects representing any values will be left as is. 44 // If the CUE type corresponding to the URL can be determined within 45 // the module context it will be unified. 46 // _: Adds a `@type` URL (TODO). 47 // 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) addErr(err errors.Error) { 84 e.errs = errors.Append(e.errs, err) 85 } 86 87 func (e *encoder) addErrf(p token.Pos, schema cue.Value, format string, args ...interface{}) { 88 format = "%s: " + format 89 args = append([]interface{}{schema.Path()}, args...) 90 e.addErr(errors.Newf(p, format, args...)) 91 } 92 93 func (e *encoder) rewriteDecls(schema cue.Value, decls []ast.Decl) { 94 for _, f := range decls { 95 field, ok := f.(*ast.Field) 96 if !ok { 97 continue 98 } 99 sel := cue.Label(field.Label) 100 if !sel.IsString() { 101 continue 102 } 103 104 v := schema.LookupPath(cue.MakePath(sel.Optional())) 105 if !v.Exists() { 106 continue 107 } 108 109 field.Value = e.rewrite(v, field.Value) 110 } 111 } 112 113 func (e *encoder) rewrite(schema cue.Value, expr ast.Expr) (x ast.Expr) { 114 switch x := expr.(type) { 115 case *ast.ListLit: 116 for i, elem := range x.Elts { 117 v := schema.LookupPath(cue.MakePath(cue.Index(i).Optional())) 118 if !v.Exists() { 119 break 120 } 121 x.Elts[i] = e.rewrite(v, elem) 122 } 123 return expr 124 125 case *ast.StructLit: 126 e.rewriteDecls(schema, x.Elts) 127 return expr 128 129 case *ast.BasicLit: 130 if x.Kind != token.INT { 131 break 132 } 133 134 info, err := pbinternal.FromValue("", schema) 135 if err != nil { 136 break 137 } 138 139 switch info.Type { 140 case "int64", "fixed64", "sfixed64", "uint64": 141 b, ok := expr.(*ast.BasicLit) 142 if schema.IncompleteKind() == cue.IntKind && ok && b.Kind == token.INT { 143 b.Kind = token.STRING 144 b.Value = literal.String.Quote(b.Value) 145 } 146 147 case "int32", "fixed32", "sfixed32", "uint32", "float", "double": 148 case "varint": 149 150 default: 151 if !info.IsEnum { 152 break 153 } 154 155 i, err := strconv.ParseInt(x.Value, 10, 32) 156 if err != nil { 157 break 158 } 159 160 if s := pbinternal.MatchByInt(schema, i); s != "" { 161 x.Kind = token.STRING 162 x.Value = literal.String.Quote(s) 163 } 164 } 165 } 166 167 return expr 168 }