1 // Copyright 2019 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 codec converts Go to and from CUE and validates Go values based on 16 // CUE constraints. 17 // 18 // CUE constraints can be used to validate Go types as well as fill out 19 // missing struct fields that are implied from the constraints and the values 20 // already defined by the struct value. 21 package gocodec 22 23 import ( 24 "sync" 25 26 "cuelang.org/go/cue" 27 "cuelang.org/go/cue/cuecontext" 28 "cuelang.org/go/internal/value" 29 ) 30 31 // Config has no options yet, but is defined for future extensibility. 32 type Config struct { 33 } 34 35 // A Codec decodes and encodes CUE from and to Go values and validates and 36 // completes Go values based on CUE templates. 37 type Codec struct { 38 runtime *cue.Context 39 mutex sync.RWMutex 40 } 41 42 // New creates a new Codec for the given instance. 43 // 44 // It is safe to use the methods of Codec concurrently as long as the given 45 // Runtime is not used elsewhere while using Codec. However, only the concurrent 46 // use of Decode, Validate, and Complete is efficient. 47 // 48 // Note: calling this with a *cue.Runtime value is deprecated. 49 func New[Ctx *cue.Runtime | *cue.Context](ctx Ctx, c *Config) *Codec { 50 return &Codec{runtime: value.ConvertToContext(ctx)} 51 } 52 53 // ExtractType extracts a CUE value from a Go type. 54 // 55 // The type represented by x is converted as the underlying type. Specific 56 // values, such as map or slice elements or field values of structs are ignored. 57 // If x is of type reflect.Type, the type represented by x is extracted. 58 // 59 // Fields of structs can be annoted using additional constrains using the 'cue' 60 // field tag. The value of the tag is a CUE expression, which may contain 61 // references to the JSON name of other fields in a struct. 62 // 63 // type Sum struct { 64 // A int `cue:"c-b" json:"a,omitempty"` 65 // B int `cue:"c-a" json:"b,omitempty"` 66 // C int `cue:"a+b" json:"c,omitempty"` 67 // } 68 func (c *Codec) ExtractType(x interface{}) (cue.Value, error) { 69 // ExtractType cannot introduce new fields on repeated calls. We could 70 // consider optimizing the lock usage based on this property. 71 c.mutex.Lock() 72 defer c.mutex.Unlock() 73 74 return fromGoType(c.runtime, x) 75 } 76 77 // TODO: allow extracting constraints and type info separately? 78 79 // Decode converts x to a CUE value. 80 // 81 // If x is of type reflect.Value it will convert the value represented by x. 82 func (c *Codec) Decode(x interface{}) (cue.Value, error) { 83 c.mutex.Lock() 84 defer c.mutex.Unlock() 85 86 // Depending on the type, can introduce new labels on repeated calls. 87 return fromGoValue(c.runtime, x, false) 88 } 89 90 // Encode converts v to a Go value. 91 func (c *Codec) Encode(v cue.Value, x interface{}) error { 92 c.mutex.RLock() 93 defer c.mutex.RUnlock() 94 95 return v.Decode(x) 96 } 97 98 var defaultCodec = New(value.ConvertToRuntime(cuecontext.New()), nil) 99 100 // Validate calls Validate on a default Codec for the type of x. 101 func Validate(x interface{}) error { 102 c := defaultCodec 103 c.mutex.RLock() 104 defer c.mutex.RUnlock() 105 106 r := defaultCodec.runtime 107 v, err := fromGoType(r, x) 108 if err != nil { 109 return err 110 } 111 w, err := fromGoValue(r, x, false) 112 if err != nil { 113 return err 114 } 115 v = v.Unify(w) 116 if err := v.Validate(); err != nil { 117 return err 118 } 119 return nil 120 } 121 122 // Validate checks whether x satisfies the constraints defined by v. 123 // 124 // The given value must be created using the same Runtime with which c was 125 // initialized. 126 func (c *Codec) Validate(v cue.Value, x interface{}) error { 127 c.mutex.RLock() 128 defer c.mutex.RUnlock() 129 130 r := checkAndForkContext(c.runtime, v) 131 w, err := fromGoValue(r, x, false) 132 if err != nil { 133 return err 134 } 135 return w.Unify(v).Err() 136 } 137 138 // Complete sets previously undefined values in x that can be uniquely 139 // determined form the constraints defined by v if validation passes, or returns 140 // an error, without modifying anything, otherwise. 141 // 142 // Only undefined values are modified. A value is considered undefined if it is 143 // pointer type and is nil or if it is a field with a zero value that has a json 144 // tag with the omitempty flag. 145 // 146 // The given value must be created using the same Runtime with which c was 147 // initialized. 148 // 149 // Complete does a JSON round trip. This means that data not preserved in such a 150 // round trip, such as the location name of a time.Time, is lost after a 151 // successful update. 152 func (c *Codec) Complete(v cue.Value, x interface{}) error { 153 c.mutex.RLock() 154 defer c.mutex.RUnlock() 155 156 r := checkAndForkContext(c.runtime, v) 157 w, err := fromGoValue(r, x, true) 158 if err != nil { 159 return err 160 } 161 162 w = w.Unify(v) 163 if err := w.Validate(cue.Concrete(true)); err != nil { 164 return err 165 } 166 return w.Decode(x) 167 } 168 169 func fromGoValue(r *cue.Context, x interface{}, allowDefault bool) (cue.Value, error) { 170 v := value.FromGoValue(r, x, allowDefault) 171 if err := v.Err(); err != nil { 172 return v, err 173 } 174 return v, nil 175 } 176 177 func fromGoType(r *cue.Context, x interface{}) (cue.Value, error) { 178 v := value.FromGoType(r, x) 179 if err := v.Err(); err != nil { 180 return v, err 181 } 182 return v, nil 183 } 184 185 func checkAndForkContext(r *cue.Context, v cue.Value) *cue.Context { 186 rr := v.Context() 187 if r != rr { 188 panic("value not from same runtime") 189 } 190 return rr 191 } 192