snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
615
server/pkg/expr/vars.go
Normal file
615
server/pkg/expr/vars.go
Normal file
@@ -0,0 +1,615 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/sql"
|
||||
|
||||
"github.com/PaesslerAG/gval"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func (t *Vars) Len() (out int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
return len(t.value)
|
||||
}
|
||||
|
||||
func (t *Vars) Select(k string) (out TypedValue, err error) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
if v, is := t.value[k]; is {
|
||||
return v, nil
|
||||
} else {
|
||||
return nil, errors.NotFound("no such key '%s'", k)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Vars) AssignFieldValue(key string, val TypedValue) (err error) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
|
||||
if t.value == nil {
|
||||
t.value = make(map[string]TypedValue)
|
||||
}
|
||||
|
||||
t.value[key] = val
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Vars) ResolveTypes(res func(typ string) Type) (err error) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
for k, v := range t.value {
|
||||
if u, is := v.(*Unresolved); is {
|
||||
if res(u.Type()) == nil {
|
||||
return errors.NotFound("failed to resolve unknown or unregistered type %q on %q", u.Type(), k)
|
||||
}
|
||||
|
||||
t.value[k], err = res(u.Type()).Cast(t.value[k])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if r, is := t.value[k].(resolvableType); is {
|
||||
if err = r.ResolveTypes(res); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Merge combines the given Vars(es) into Vars
|
||||
// NOTE: It will return CLONE of the original Vars, if it's called without any parameters
|
||||
func (t *Vars) Merge(nn ...Iterator) (out TypedValue, err error) {
|
||||
return t.MustMerge(nn...), nil
|
||||
}
|
||||
|
||||
func (t *Vars) IsEmpty() bool {
|
||||
return t == nil || len(t.value) == 0
|
||||
}
|
||||
|
||||
// MustMerge returns Vars after merging the given Vars(es) into it
|
||||
func (t *Vars) MustMerge(nn ...Iterator) *Vars {
|
||||
if t != nil {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
nn = append([]Iterator{t}, nn...)
|
||||
}
|
||||
|
||||
var (
|
||||
out = &Vars{value: make(map[string]TypedValue, 4)}
|
||||
)
|
||||
|
||||
for _, i := range nn {
|
||||
_ = i.Each(func(k string, v TypedValue) error {
|
||||
out.value[k] = v
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// Copy takes base variables and copies all to dst
|
||||
func (t *Vars) Copy(dst *Vars, kk ...string) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if dst.value == nil {
|
||||
dst.value = make(map[string]TypedValue)
|
||||
}
|
||||
|
||||
for _, k := range kk {
|
||||
dst.value[k] = t.value[k]
|
||||
}
|
||||
}
|
||||
|
||||
// Has returns true key is present
|
||||
func (t *Vars) Has(key string) bool {
|
||||
return t.HasAll(key)
|
||||
}
|
||||
|
||||
// HasAll returns true if all keys are present
|
||||
func (t *Vars) HasAll(key string, kk ...string) bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, key = range append([]string{key}, kk...) {
|
||||
if _, has := t.value[key]; !has {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// HasAny returns true if all keys are present
|
||||
func (t *Vars) HasAny(key string, kk ...string) bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, key = range append([]string{key}, kk...) {
|
||||
if _, has := t.value[key]; has {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var _ gval.Selector = &Vars{}
|
||||
|
||||
func (t *Vars) Dict() (out map[string]interface{}) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
out = make(map[string]interface{})
|
||||
for k, v := range t.value {
|
||||
switch v := v.(type) {
|
||||
case Dict:
|
||||
out[k] = v.Dict()
|
||||
|
||||
case Slice:
|
||||
out[k] = v.Slice()
|
||||
|
||||
case TypedValue:
|
||||
tmp := v.Get()
|
||||
if d, is := tmp.(Dict); is {
|
||||
out[k] = d.Dict()
|
||||
} else {
|
||||
out[k] = tmp
|
||||
}
|
||||
|
||||
default:
|
||||
out[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Vars) Decode(dst interface{}) (err error) {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
dstRef := reflect.ValueOf(dst)
|
||||
|
||||
if dstRef.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("expecting a pointer, not a value")
|
||||
}
|
||||
|
||||
if dstRef.IsNil() {
|
||||
return fmt.Errorf("nil pointer passed")
|
||||
}
|
||||
|
||||
dstRef = dstRef.Elem()
|
||||
|
||||
for i := 0; i < dstRef.NumField(); i++ {
|
||||
var (
|
||||
value TypedValue
|
||||
has bool
|
||||
ftyp = dstRef.Type().Field(i)
|
||||
)
|
||||
|
||||
keyName := ftyp.Tag.Get("var")
|
||||
if keyName == "" {
|
||||
keyName = strings.ToLower(ftyp.Name[:1]) + ftyp.Name[1:]
|
||||
}
|
||||
|
||||
value, has = t.value[keyName]
|
||||
if !has {
|
||||
continue
|
||||
}
|
||||
|
||||
if tvd, is := value.(TypeValueDecoder); is {
|
||||
if err = tvd.Decode(dstRef.Field(i)); err != nil {
|
||||
return
|
||||
}
|
||||
} else if err = decode(dstRef.Field(i), value); err != nil {
|
||||
return fmt.Errorf("failed to decode value to field %s: %w", ftyp.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Vars) Scan(src any) error { return sql.ParseJSON(src, t) }
|
||||
|
||||
func (t *Vars) Value() (driver.Value, error) {
|
||||
if t != nil {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
}
|
||||
|
||||
return json.Marshal(t)
|
||||
}
|
||||
|
||||
func (t *Vars) SelectGVal(_ context.Context, k string) (out interface{}, err error) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
out, err = t.Select(k)
|
||||
switch c := out.(type) {
|
||||
case gval.Selector:
|
||||
return c, err
|
||||
default:
|
||||
return UntypedValue(out), err
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshal JSON value into Vars
|
||||
func (t *Vars) UnmarshalJSON(in []byte) (err error) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
|
||||
var (
|
||||
aux = make(map[string]*typedValueWrap)
|
||||
)
|
||||
|
||||
if t.value == nil {
|
||||
t.value = make(map[string]TypedValue)
|
||||
}
|
||||
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(in, &aux); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range aux {
|
||||
if t.value[k], err = NewUnresolved(v.Type, v.Value); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Vars) Each(fn func(k string, v TypedValue) error) (err error) {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
if t.value == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range t.value {
|
||||
if err = fn(k, v); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Set or update the specific key value in Vars
|
||||
func (t *Vars) Set(k string, v interface{}) (err error) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
if t.value == nil {
|
||||
t.value = make(map[string]TypedValue)
|
||||
}
|
||||
|
||||
t.value[k], err = Typify(v)
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalJSON returns JSON encoding of expression
|
||||
func (t *Vars) MarshalJSON() (out []byte, err error) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
aux := make(map[string]*typedValueWrap)
|
||||
for k, v := range t.value {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
aux[k] = &typedValueWrap{Type: v.Type()}
|
||||
|
||||
if _, is := v.(json.Marshaler); is {
|
||||
aux[k].Value = v
|
||||
} else {
|
||||
aux[k].Value = v.Get()
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(aux)
|
||||
}
|
||||
|
||||
func decode(dst reflect.Value, src TypedValue) (err error) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch r := r.(type) {
|
||||
case error:
|
||||
err = r
|
||||
default:
|
||||
err = fmt.Errorf("%v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if dst.Kind() == reflect.Interface && reflect.ValueOf(src).Type().Implements(dst.Type()) {
|
||||
dst.Set(reflect.ValueOf(src))
|
||||
return
|
||||
}
|
||||
|
||||
if reflect.ValueOf(src).Type().ConvertibleTo(dst.Type()) {
|
||||
dst.Set(reflect.ValueOf(src))
|
||||
return
|
||||
}
|
||||
|
||||
raw := UntypedValue(src)
|
||||
// Optimistically try to decode source to destination by comparing (internal) value type for destination
|
||||
if reflect.ValueOf(raw).Type().ConvertibleTo(dst.Type()) {
|
||||
dst.Set(reflect.ValueOf(raw))
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
vBool bool
|
||||
vInt64 int64
|
||||
vUint64 uint64
|
||||
vFloat64 float64
|
||||
vString string
|
||||
)
|
||||
|
||||
switch dst.Kind() {
|
||||
case reflect.Bool:
|
||||
if vBool, err = cast.ToBoolE(raw); err == nil {
|
||||
dst.SetBool(vBool)
|
||||
}
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if vInt64, err = cast.ToInt64E(raw); err == nil {
|
||||
dst.SetInt(vInt64)
|
||||
}
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if vUint64, err = cast.ToUint64E(raw); err == nil {
|
||||
dst.SetUint(vUint64)
|
||||
}
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if vFloat64, err = cast.ToFloat64E(raw); err == nil {
|
||||
dst.SetFloat(vFloat64)
|
||||
}
|
||||
|
||||
case reflect.String:
|
||||
if vString, err = cast.ToStringE(raw); err == nil {
|
||||
dst.SetString(vString)
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
dst.Set(reflect.ValueOf(src.Get()))
|
||||
|
||||
//case reflect.Interface:
|
||||
// dst.Set(reflect.ValueOf(raw))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("failed to cast %T to %s", raw, dst.Kind())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to cast %T to %s: %w", raw, dst.Kind(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CastToMeta(val interface{}) (out map[string]any, err error) {
|
||||
val = UntypedValue(val)
|
||||
|
||||
if val == nil {
|
||||
return make(map[string]any), nil
|
||||
}
|
||||
|
||||
switch c := val.(type) {
|
||||
case *Vars:
|
||||
c.mux.RLock()
|
||||
defer c.mux.RUnlock()
|
||||
|
||||
out = make(map[string]any)
|
||||
for k, v := range c.value {
|
||||
out[k] = v.Get()
|
||||
}
|
||||
|
||||
return
|
||||
case map[string]string:
|
||||
out = make(map[string]any)
|
||||
for k, v := range c {
|
||||
out[k] = v
|
||||
}
|
||||
|
||||
return
|
||||
case map[string][]string:
|
||||
out = make(map[string]any)
|
||||
for k, v := range c {
|
||||
out[k] = v
|
||||
}
|
||||
|
||||
return
|
||||
case KV:
|
||||
out = make(map[string]any)
|
||||
for k, v := range c.value {
|
||||
out[k] = v
|
||||
}
|
||||
|
||||
return
|
||||
case KVV:
|
||||
out = make(map[string]any)
|
||||
for k, v := range c.value {
|
||||
out[k] = v
|
||||
}
|
||||
|
||||
return
|
||||
case map[string]any:
|
||||
return c, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
|
||||
}
|
||||
func CastToVars(val interface{}) (out map[string]TypedValue, err error) {
|
||||
val = UntypedValue(val)
|
||||
|
||||
if val == nil {
|
||||
return make(map[string]TypedValue), nil
|
||||
}
|
||||
|
||||
switch c := val.(type) {
|
||||
case *Vars:
|
||||
c.mux.RLock()
|
||||
defer c.mux.RUnlock()
|
||||
|
||||
return c.value, nil
|
||||
case map[string]TypedValue:
|
||||
return c, nil
|
||||
case map[string]interface{}:
|
||||
out = make(map[string]TypedValue)
|
||||
for k, v := range c {
|
||||
out[k], err = Typify(v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
|
||||
}
|
||||
|
||||
// Filter take keys returns Vars with only those key value pair
|
||||
func (t *Vars) Filter(keys ...string) (out TypedValue, err error) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
if t.value == nil {
|
||||
return
|
||||
}
|
||||
vars := EmptyVars()
|
||||
|
||||
for _, k := range keys {
|
||||
_, has := t.value[k]
|
||||
if has {
|
||||
vars.value[k] = t.value[k]
|
||||
}
|
||||
}
|
||||
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
// Delete take keys returns Vars without those key value pair
|
||||
func (t *Vars) Delete(keys ...string) (out TypedValue, err error) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
if t.value == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// get cloned Vars
|
||||
out, err = t.Merge()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
vars := out.(*Vars)
|
||||
|
||||
// Delete key from t.value if exist
|
||||
for _, k := range keys {
|
||||
delete(vars.value, k)
|
||||
}
|
||||
|
||||
return vars, nil
|
||||
}
|
||||
Reference in New Issue
Block a user