this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

encoding/protobuf/jsonpb: add encoder

Issue #606

Change-Id: I3bf6cbc1ecd5e83c94b5a7d97c845a35f4b84878
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9371
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>

+408 -3
+4 -3
encoding/protobuf/jsonpb/decoder_test.go
··· 12 12 // See the License for the specific language governing permissions and 13 13 // limitations under the License. 14 14 15 - package jsonpb 15 + package jsonpb_test 16 16 17 17 import ( 18 18 "strings" ··· 25 25 "cuelang.org/go/cue/format" 26 26 "cuelang.org/go/cue/parser" 27 27 "cuelang.org/go/encoding/json" 28 + "cuelang.org/go/encoding/protobuf/jsonpb" 28 29 "cuelang.org/go/encoding/yaml" 29 30 "cuelang.org/go/internal/cuetest" 30 31 "cuelang.org/go/internal/cuetxtar" ··· 85 86 } 86 87 87 88 w := t.Writer(f.Name) 88 - err := NewDecoder(schema).RewriteFile(file) 89 + err := jsonpb.NewDecoder(schema).RewriteFile(file) 89 90 if err != nil { 90 91 errors.Print(w, err, nil) 91 92 continue ··· 121 122 t.Fatal(err) 122 123 } 123 124 124 - if err := NewDecoder(inst.Value()).RewriteFile(file); err != nil { 125 + if err := jsonpb.NewDecoder(inst.Value()).RewriteFile(file); err != nil { 125 126 t.Fatal(err) 126 127 } 127 128
+168
encoding/protobuf/jsonpb/encoder.go
··· 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 + // 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 + }
+84
encoding/protobuf/jsonpb/encoder_test.go
··· 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_test 16 + 17 + import ( 18 + "testing" 19 + 20 + "cuelang.org/go/cue" 21 + "cuelang.org/go/cue/ast" 22 + "cuelang.org/go/cue/errors" 23 + "cuelang.org/go/cue/format" 24 + "cuelang.org/go/cue/parser" 25 + "cuelang.org/go/encoding/protobuf/jsonpb" 26 + "cuelang.org/go/internal/cuetest" 27 + "cuelang.org/go/internal/cuetxtar" 28 + ) 29 + 30 + func TestEncoder(t *testing.T) { 31 + test := cuetxtar.TxTarTest{ 32 + Root: "./testdata/encoder", 33 + Name: "jsonpb", 34 + Update: cuetest.UpdateGoldenFiles, 35 + } 36 + 37 + r := cue.Runtime{} 38 + 39 + test.Run(t, func(t *cuetxtar.Test) { 40 + // TODO: use high-level API. 41 + 42 + var schema cue.Value 43 + var file *ast.File 44 + 45 + for _, f := range t.Archive.Files { 46 + switch { 47 + case f.Name == "schema.cue": 48 + inst, err := r.Compile(f.Name, f.Data) 49 + if err != nil { 50 + t.WriteErrors(errors.Promote(err, "test")) 51 + return 52 + } 53 + schema = inst.Value() 54 + 55 + case f.Name == "value.cue": 56 + f, err := parser.ParseFile(f.Name, f.Data, parser.ParseComments) 57 + if err != nil { 58 + t.WriteErrors(errors.Promote(err, "test")) 59 + return 60 + } 61 + file = f 62 + } 63 + } 64 + 65 + if !schema.Exists() { 66 + inst, err := r.CompileFile(file) 67 + if err != nil { 68 + t.WriteErrors(errors.Promote(err, "test")) 69 + } 70 + schema = inst.Value() 71 + } 72 + 73 + err := jsonpb.NewEncoder(schema).RewriteFile(file) 74 + if err != nil { 75 + errors.Print(t, err, nil) 76 + } 77 + 78 + b, err := format.Node(file) 79 + if err != nil { 80 + t.Fatal(err) 81 + } 82 + _, _ = t.Write(b) 83 + }) 84 + }
+2
encoding/protobuf/jsonpb/jsonpb.go
··· 14 14 15 15 // Package jsonpb rewrites a CUE expression based upon the Protobuf 16 16 // interpretation of JSON. 17 + // 18 + // API Status: DRAFT: API may change without notice. 17 19 package jsonpb
+68
encoding/protobuf/jsonpb/testdata/encoder/enums.txtar
··· 1 + -- schema.cue -- 2 + enum: [string]: 3 + { "foo", #enumValue: 1 } | 4 + { "bar", #enumValue: 2 } @protobuf(1,Enum) 5 + 6 + defEnum: [string]: #foo | #bar @protobuf(2,Enum) 7 + 8 + #foo: 1 9 + #bar: 2 10 + 11 + typeEnum: [string]: #Enum @protobuf(3,Enum) 12 + 13 + #Enum: #foo | #bar 14 + 15 + // TODO: consider supporting @symbol(foo) or @json(,symbol=foo) 16 + // symbolEnum: [string]: 17 + // { 1, @symbol(foo) } | 18 + // { 2, @symbol(bar) } 19 + 20 + 21 + singleEnum: #single @protobuf(3,Enum) 22 + 23 + #single: 1 24 + 25 + badEnum: { string, #enumValue: 1 } | { "two", #enumValue: 2 } 26 + 27 + 28 + -- value.cue -- 29 + enum: asIs: "foo" 30 + enum: asIsUnknown: "foobar" 31 + 32 + // Convert integers to strings 33 + defEnum: foo: 1 34 + defEnum: bar: 2 35 + defEnum: baz: 3 36 + 37 + 38 + typeEnum: foo: 1 39 + typeEnum: bar: 2 40 + typeEnum: baz: 3 41 + 42 + 43 + // TODO: consider supporting @symbol(foo) or @json(,symbol=foo) 44 + // symbolEnum: foo: "foo" 45 + // symbolEnum: bar: "bar" 46 + // symbolEnum: baz: "baz" 47 + 48 + singleEnum: 1 49 + 50 + -- out/jsonpb -- 51 + enum: asIs: "foo" 52 + enum: asIsUnknown: "foobar" 53 + 54 + // Convert integers to strings 55 + defEnum: foo: "foo" 56 + defEnum: bar: "bar" 57 + defEnum: baz: 3 58 + 59 + typeEnum: foo: "foo" 60 + typeEnum: bar: "bar" 61 + typeEnum: baz: 3 62 + 63 + // TODO: consider supporting @symbol(foo) or @json(,symbol=foo) 64 + // symbolEnum: foo: "foo" 65 + // symbolEnum: bar: "bar" 66 + // symbolEnum: baz: "baz" 67 + 68 + singleEnum: 1
+27
encoding/protobuf/jsonpb/testdata/encoder/list.txtar
··· 1 + -- schema.cue -- 2 + a: [...#D] 3 + 4 + #D: { 5 + a: int @protobuf(1,int64) 6 + } 7 + 8 + b: [1, ...] // Don't include schema fields if not in value 9 + 10 + c: [{a: 1}, ...] 11 + c: [...#D] 12 + 13 + -- value.cue -- 14 + // Hello 15 + a: [ 16 + {a: 1}, 17 + ] 18 + 19 + c: [{a: 1}, {a: 2}] 20 + 21 + -- out/jsonpb -- 22 + // Hello 23 + a: [ 24 + {a: "1"}, 25 + ] 26 + 27 + c: [{a: "1"}, {a: "2"}]
+27
encoding/protobuf/jsonpb/testdata/encoder/simple.txtar
··· 1 + -- value.cue -- 2 + a: 1 @protobuf(1, int64) 3 + b: 2 @protobuf(1, int32) 4 + c: 3.4 @protobuf(1, int64) 5 + 6 + d: "foo\u1234" 7 + e: '\000' 8 + 9 + f: false 10 + // Doc comment 11 + t: true 12 + 13 + notConcrete: string 14 + 15 + -- out/jsonpb -- 16 + a: "1" @protobuf(1, int64) 17 + b: 2 @protobuf(1, int32) 18 + c: 3.4 @protobuf(1, int64) 19 + 20 + d: "foo\u1234" 21 + e: '\000' 22 + 23 + f: false 24 + // Doc comment 25 + t: true 26 + 27 + notConcrete: string
+28
encoding/protobuf/jsonpb/testdata/encoder/struct.txtar
··· 1 + -- schema.cue -- 2 + a: { 3 + {b: int @protobuf(1,int64)} 4 + 5 + c: int @protobuf(1,int64) 6 + 7 + {d: int @protobuf(1,int32)} 8 + 9 + e: int @protobuf(1,int32) 10 + 11 + } 12 + -- value.cue -- 13 + // Hello 14 + a: { 15 + b: 1 16 + c: 2 17 + d: 3 18 + e: 4 19 + } 20 + 21 + -- out/jsonpb -- 22 + // Hello 23 + a: { 24 + b: "1" 25 + c: "2" 26 + d: 3 27 + e: 4 28 + }