this repo has no description
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}