Marcel van Lohuizen | 240a995 | 2019-08-05 10:29:13 +0200 | [diff] [blame] | 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/internal" |
| 28 | ) |
| 29 | |
| 30 | // Config has no options yet, but is defined for future extensibility. |
| 31 | type Config struct { |
| 32 | } |
| 33 | |
| 34 | // A Codec decodes and encodes CUE from and to Go values and validates and |
| 35 | // completes Go values based on CUE templates. |
| 36 | type Codec struct { |
| 37 | runtime *cue.Runtime |
| 38 | mutex sync.RWMutex |
| 39 | } |
| 40 | |
| 41 | // New creates a new Codec for the given instance. |
| 42 | // |
| 43 | // It is safe to use the methods of Codec concurrently as long as the given |
| 44 | // Runtime is not used elsewhere while using Codec. However, only the concurrent |
| 45 | // use of Decode, Validate, and Complete is efficient. |
| 46 | func New(r *cue.Runtime, c *Config) *Codec { |
| 47 | return &Codec{runtime: r} |
| 48 | } |
| 49 | |
| 50 | // ExtractType extracts a CUE value from a Go type. |
| 51 | // |
| 52 | // The type represented by x is converted as the underlying type. Specific |
| 53 | // values, such as map or slice elements or field values of structs are ignored. |
| 54 | // If x is of type reflect.Type, the type represented by x is extracted. |
| 55 | // |
| 56 | // Fields of structs can be annoted using additional constrains using the 'cue' |
| 57 | // field tag. The value of the tag is a CUE expression, which may contain |
| 58 | // references to the JSON name of other fields in a struct. |
| 59 | // |
| 60 | // type Sum struct { |
| 61 | // A int `cue:"c-b" json:"a,omitempty"` |
| 62 | // B int `cue:"c-a" json:"b,omitempty"` |
| 63 | // C int `cue:"a+b" json:"c,omitempty"` |
| 64 | // } |
| 65 | // |
| 66 | func (c *Codec) ExtractType(x interface{}) (cue.Value, error) { |
| 67 | // ExtractType cannot introduce new fields on repeated calls. We could |
| 68 | // consider optimizing the lock usage based on this property. |
| 69 | c.mutex.Lock() |
| 70 | defer c.mutex.Unlock() |
| 71 | |
| 72 | return fromGoType(c.runtime, x) |
| 73 | } |
| 74 | |
| 75 | // TODO: allow extracting constraints and type info separately? |
| 76 | |
| 77 | // Decode converts x to a CUE value. |
| 78 | // |
| 79 | // If x is of type reflect.Value it will convert the value represented by x. |
| 80 | func (c *Codec) Decode(x interface{}) (cue.Value, error) { |
| 81 | c.mutex.Lock() |
| 82 | defer c.mutex.Unlock() |
| 83 | |
| 84 | // Depending on the type, can introduce new labels on repeated calls. |
| 85 | return fromGoValue(c.runtime, x, false) |
| 86 | } |
| 87 | |
| 88 | // Encode converts v to a Go value. |
| 89 | func (c *Codec) Encode(v cue.Value, x interface{}) error { |
| 90 | c.mutex.RLock() |
| 91 | defer c.mutex.RUnlock() |
| 92 | |
| 93 | return v.Decode(x) |
| 94 | } |
| 95 | |
| 96 | // Validate checks whether x satisfies the constraints defined by v. |
| 97 | // |
| 98 | // The given value must be created using the same Runtime with which c was |
| 99 | // initialized. |
| 100 | func (c *Codec) Validate(v cue.Value, x interface{}) error { |
| 101 | c.mutex.RLock() |
| 102 | defer c.mutex.RUnlock() |
| 103 | |
| 104 | r := checkAndForkRuntime(c.runtime, v) |
| 105 | w, err := fromGoValue(r, x, false) |
| 106 | if err != nil { |
| 107 | return err |
| 108 | } |
| 109 | return w.Unify(v).Err() |
| 110 | } |
| 111 | |
| 112 | // Complete sets previously undefined values in x that can be uniquely |
| 113 | // determined form the constraints defined by v if validation passes, or returns |
| 114 | // an error, without modifying anything, otherwise. |
| 115 | // |
| 116 | // Only undefined values are modified. A value is considered undefined if it is |
| 117 | // pointer type and is nil or if it is a field with a zero value that has a json |
| 118 | // tag with the omitempty flag. |
| 119 | // |
| 120 | // The given value must be created using the same Runtime with which c was |
| 121 | // initialized. |
| 122 | // |
| 123 | // Complete does a JSON round trip. This means that data not preserved in such a |
| 124 | // round trip, such as the location name of a time.Time, is lost after a |
| 125 | // successful update. |
| 126 | func (c *Codec) Complete(v cue.Value, x interface{}) error { |
| 127 | c.mutex.RLock() |
| 128 | defer c.mutex.RUnlock() |
| 129 | |
| 130 | r := checkAndForkRuntime(c.runtime, v) |
| 131 | w, err := fromGoValue(r, x, true) |
| 132 | if err != nil { |
| 133 | return err |
| 134 | } |
| 135 | |
| 136 | return w.Unify(v).Decode(x) |
| 137 | } |
| 138 | |
| 139 | func fromGoValue(r *cue.Runtime, x interface{}, allowDefault bool) (cue.Value, error) { |
| 140 | v := internal.FromGoValue(r, x, allowDefault).(cue.Value) |
| 141 | if err := v.Err(); err != nil { |
| 142 | return v, err |
| 143 | } |
| 144 | return v, nil |
| 145 | } |
| 146 | |
| 147 | func fromGoType(r *cue.Runtime, x interface{}) (cue.Value, error) { |
| 148 | v := internal.FromGoType(r, x).(cue.Value) |
| 149 | if err := v.Err(); err != nil { |
| 150 | return v, err |
| 151 | } |
| 152 | return v, nil |
| 153 | } |
| 154 | |
| 155 | func checkAndForkRuntime(r *cue.Runtime, v cue.Value) *cue.Runtime { |
| 156 | return internal.CheckAndForkRuntime(r, v).(*cue.Runtime) |
| 157 | } |