this repo has no description
at master 191 lines 5.6 kB view raw
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 gocodec 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. 21package gocodec 22 23import ( 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. 32type 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. 37type 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. 49func New[Ctx *cue.Runtime | *cue.Context](ctx Ctx, c *Config) *Codec { 50 return &Codec{runtime: value.Context(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// } 68func (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. 82func (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. 91func (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 98var defaultCodec = New(cuecontext.New(), nil) 99 100// Validate calls Validate on a default Codec for the type of x. 101func 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. 126func (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. 152func (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 169func 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 177func 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 185func 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}