Better handling empty/uninit values
This commit is contained in:
@@ -100,6 +100,9 @@ func (t ComposeModule) Has(k string) bool {
|
||||
|
||||
// composeModuleGValSelector is field accessor for *types.Module
|
||||
func composeModuleGValSelector(res *types.Module, k string) (interface{}, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "moduleID":
|
||||
return res.ID, nil
|
||||
@@ -124,6 +127,9 @@ func composeModuleGValSelector(res *types.Module, k string) (interface{}, error)
|
||||
|
||||
// composeModuleTypedValueSelector is field accessor for *types.Module
|
||||
func composeModuleTypedValueSelector(res *types.Module, k string) (TypedValue, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "moduleID":
|
||||
return NewID(res.ID)
|
||||
@@ -268,6 +274,9 @@ func (t ComposeNamespace) Has(k string) bool {
|
||||
|
||||
// composeNamespaceGValSelector is field accessor for *types.Namespace
|
||||
func composeNamespaceGValSelector(res *types.Namespace, k string) (interface{}, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "namespaceID":
|
||||
return res.ID, nil
|
||||
@@ -290,6 +299,9 @@ func composeNamespaceGValSelector(res *types.Namespace, k string) (interface{},
|
||||
|
||||
// composeNamespaceTypedValueSelector is field accessor for *types.Namespace
|
||||
func composeNamespaceTypedValueSelector(res *types.Namespace, k string) (TypedValue, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "namespaceID":
|
||||
return NewID(res.ID)
|
||||
@@ -420,6 +432,9 @@ func (t ComposeRecord) Has(k string) bool {
|
||||
|
||||
// composeRecordGValSelector is field accessor for *types.Record
|
||||
func composeRecordGValSelector(res *types.Record, k string) (interface{}, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "recordID":
|
||||
return res.ID, nil
|
||||
@@ -452,6 +467,9 @@ func composeRecordGValSelector(res *types.Record, k string) (interface{}, error)
|
||||
|
||||
// composeRecordTypedValueSelector is field accessor for *types.Record
|
||||
func composeRecordTypedValueSelector(res *types.Record, k string) (TypedValue, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "recordID":
|
||||
return NewID(res.ID)
|
||||
|
||||
@@ -113,12 +113,17 @@ var _ gval.Selector = &ComposeRecord{}
|
||||
// and it's fields
|
||||
//
|
||||
func (t ComposeRecord) SelectGVal(_ context.Context, k string) (interface{}, error) {
|
||||
if k == "values" {
|
||||
if t.value != nil && k == "values" {
|
||||
if t.value.Values == nil {
|
||||
t.value.Values = types.RecordValueSet{}
|
||||
}
|
||||
|
||||
return t.value.Values.Dict(t.value.GetModule().Fields), nil
|
||||
var ff types.ModuleFieldSet
|
||||
if t.value.GetModule() != nil {
|
||||
ff = t.value.GetModule().Fields
|
||||
}
|
||||
|
||||
return t.value.Values.Dict(ff), nil
|
||||
}
|
||||
|
||||
return composeRecordGValSelector(t.value, k)
|
||||
@@ -128,7 +133,7 @@ func (t ComposeRecord) SelectGVal(_ context.Context, k string) (interface{}, err
|
||||
//
|
||||
// Similar to SelectGVal but returns typed values
|
||||
func (t ComposeRecord) Select(k string) (expr.TypedValue, error) {
|
||||
if k == "values" {
|
||||
if t.value != nil && k == "values" {
|
||||
if t.value.Values == nil {
|
||||
t.value.Values = types.RecordValueSet{}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/expr"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -77,10 +78,21 @@ func TestRecordFieldValuesAccess(t *testing.T) {
|
||||
&types.RecordValue{Name: "ref2", Value: "", Ref: 2, Place: 0},
|
||||
}}
|
||||
|
||||
tval = &ComposeRecord{value: raw}
|
||||
scope, _ = expr.NewVars(map[string]interface{}{
|
||||
"rec": tval,
|
||||
"rec": &ComposeRecord{value: raw},
|
||||
|
||||
// nis is only for testing, gval does not recognise nil (or null) as a keyword!
|
||||
"nil": nil,
|
||||
|
||||
// typed value but empty (this happens when you assign next value in expression but do not set a value to it
|
||||
"initRec": &ComposeRecord{},
|
||||
|
||||
// same as &ComposeRecord{value: &types.Record{}},
|
||||
"validRecZero": &ComposeRecord{value: &types.Record{ID: 0, Values: types.RecordValueSet{}}},
|
||||
"validRecValidID": &ComposeRecord{&types.Record{ID: 42, Values: types.RecordValueSet{}}},
|
||||
|
||||
// "record" (not really) set to nil
|
||||
"fooRec": nil,
|
||||
})
|
||||
)
|
||||
|
||||
@@ -117,13 +129,42 @@ func TestRecordFieldValuesAccess(t *testing.T) {
|
||||
|
||||
t.Run("via gval selector", func(t *testing.T) {
|
||||
tcc := []struct {
|
||||
test bool
|
||||
test interface{}
|
||||
expr string
|
||||
}{
|
||||
{false, `nil`},
|
||||
{true, `rec`},
|
||||
{true, `rec.values`},
|
||||
|
||||
// not set, so false
|
||||
{fmt.Errorf("failed to select 'notSetRec' on *expr.Vars: no such key 'notSetRec'"), `notSetRec`},
|
||||
|
||||
// set but nil
|
||||
{false, `fooRec`},
|
||||
|
||||
// set, initialized
|
||||
{true, `initRec`},
|
||||
|
||||
// set, initialized, but recordID is empty
|
||||
{false, `initRec.recordID`},
|
||||
|
||||
// set, initialized, but recordID (ID is alias) is empty
|
||||
{false, `initRec.ID`},
|
||||
|
||||
// set, initialized, but values are empty
|
||||
{false, `initRec.values`},
|
||||
|
||||
{true, `validRecZero`},
|
||||
{false, `validRecZero.recordID`},
|
||||
{false, `validRecZero.values`},
|
||||
|
||||
{true, `validRecValidID`},
|
||||
{true, `validRecValidID.recordID`},
|
||||
{false, `validRecValidID.values`},
|
||||
|
||||
// but we can not access the values below...
|
||||
{fmt.Errorf("unknown parameter initRec.values.foo"), `initRec.values.foo`},
|
||||
|
||||
// interaction with set values
|
||||
{true, `rec.values.s1 == "sVal1"`},
|
||||
{false, `rec.values.s1 == "sVal2"`},
|
||||
@@ -184,8 +225,18 @@ func TestRecordFieldValuesAccess(t *testing.T) {
|
||||
req.NoError(err)
|
||||
|
||||
test, err := eval.Test(context.Background(), scope)
|
||||
req.NoError(err)
|
||||
req.Equal(tc.test, test)
|
||||
switch tct := tc.test.(type) {
|
||||
case error:
|
||||
req.EqualError(err, tct.Error())
|
||||
|
||||
case bool:
|
||||
req.NoError(err)
|
||||
req.Equal(tc.test, test)
|
||||
|
||||
default:
|
||||
panic("unexpected test case type")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -105,6 +105,12 @@ func (t {{ $exprType }}) Has(k string) bool {
|
||||
|
||||
// {{ unexport $exprType "GValSelector" }} is field accessor for {{ $def.As }}
|
||||
func {{ unexport $exprType "GValSelector" }}(res {{ $def.As }}, k string) (interface{}, error) {
|
||||
{{- if hasPtr $def.As }}
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
{{- end }}
|
||||
switch k {
|
||||
{{- range $def.Struct }}
|
||||
{{- if .ExprType }}
|
||||
@@ -118,6 +124,12 @@ func {{ unexport $exprType "GValSelector" }}(res {{ $def.As }}, k string) (inter
|
||||
|
||||
// {{ unexport $exprType "TypedValueSelector" }} is field accessor for {{ $def.As }}
|
||||
func {{ unexport $exprType "TypedValueSelector" }}(res {{ $def.As }}, k string) (TypedValue, error) {
|
||||
{{- if hasPtr $def.As }}
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
{{- end }}
|
||||
switch k {
|
||||
{{- range $def.Struct }}
|
||||
{{- if .ExprType }}
|
||||
|
||||
@@ -166,6 +166,9 @@ func (t RenderedDocument) Has(k string) bool {
|
||||
|
||||
// renderedDocumentGValSelector is field accessor for *renderedDocument
|
||||
func renderedDocumentGValSelector(res *renderedDocument, k string) (interface{}, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "document":
|
||||
return res.Document, nil
|
||||
@@ -180,6 +183,9 @@ func renderedDocumentGValSelector(res *renderedDocument, k string) (interface{},
|
||||
|
||||
// renderedDocumentTypedValueSelector is field accessor for *renderedDocument
|
||||
func renderedDocumentTypedValueSelector(res *renderedDocument, k string) (TypedValue, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "document":
|
||||
return NewReader(res.Document)
|
||||
@@ -306,6 +312,9 @@ func (t Role) Has(k string) bool {
|
||||
|
||||
// roleGValSelector is field accessor for *types.Role
|
||||
func roleGValSelector(res *types.Role, k string) (interface{}, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "roleID":
|
||||
return res.ID, nil
|
||||
@@ -330,6 +339,9 @@ func roleGValSelector(res *types.Role, k string) (interface{}, error) {
|
||||
|
||||
// roleTypedValueSelector is field accessor for *types.Role
|
||||
func roleTypedValueSelector(res *types.Role, k string) (TypedValue, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "roleID":
|
||||
return NewID(res.ID)
|
||||
@@ -486,6 +498,9 @@ func (t Template) Has(k string) bool {
|
||||
|
||||
// templateGValSelector is field accessor for *types.Template
|
||||
func templateGValSelector(res *types.Template, k string) (interface{}, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "templateID":
|
||||
return res.ID, nil
|
||||
@@ -520,6 +535,9 @@ func templateGValSelector(res *types.Template, k string) (interface{}, error) {
|
||||
|
||||
// templateTypedValueSelector is field accessor for *types.Template
|
||||
func templateTypedValueSelector(res *types.Template, k string) (TypedValue, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "templateID":
|
||||
return NewID(res.ID)
|
||||
@@ -832,6 +850,9 @@ func (t User) Has(k string) bool {
|
||||
|
||||
// userGValSelector is field accessor for *types.User
|
||||
func userGValSelector(res *types.User, k string) (interface{}, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "userID":
|
||||
return res.ID, nil
|
||||
@@ -862,6 +883,9 @@ func userGValSelector(res *types.User, k string) (interface{}, error) {
|
||||
|
||||
// userTypedValueSelector is field accessor for *types.User
|
||||
func userTypedValueSelector(res *types.User, k string) (TypedValue, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "userID":
|
||||
return NewID(res.ID)
|
||||
|
||||
Reference in New Issue
Block a user