diff --git a/compose/automation/expr_types.gen.go b/compose/automation/expr_types.gen.go new file mode 100644 index 000000000..383b2e050 --- /dev/null +++ b/compose/automation/expr_types.gen.go @@ -0,0 +1,605 @@ +package automation + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// compose/automation/expr_types.yaml + +import ( + "context" + "fmt" + "github.com/cortezaproject/corteza-server/compose/types" + . "github.com/cortezaproject/corteza-server/pkg/expr" +) + +var _ = context.Background +var _ = fmt.Errorf + +// ComposeModule is an expression type, wrapper for *types.Module type +type ComposeModule struct{ value *types.Module } + +// NewComposeModule creates new instance of ComposeModule expression type +func NewComposeModule(val interface{}) (*ComposeModule, error) { + if c, err := CastToComposeModule(val); err != nil { + return nil, fmt.Errorf("unable to create ComposeModule: %w", err) + } else { + return &ComposeModule{value: c}, nil + } +} + +// Return underlying value on ComposeModule +func (t ComposeModule) Get() interface{} { return t.value } + +// Return type name +func (ComposeModule) Type() string { return "ComposeModule" } + +// Convert value to *types.Module +func (ComposeModule) Cast(val interface{}) (TypedValue, error) { + return NewComposeModule(val) +} + +// Assign new value to ComposeModule +// +// value is first passed through CastToComposeModule +func (t *ComposeModule) Assign(val interface{}) error { + if c, err := CastToComposeModule(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + +func (t *ComposeModule) AssignFieldValue(key string, val interface{}) error { + return assignToComposeModule(t.value, key, val) +} + +// SelectGVal implements gval.Selector requirements +// +// It allows gval lib to access ComposeModule's underlying value (*types.Module) +// and it's fields +// +func (t ComposeModule) SelectGVal(ctx context.Context, k string) (interface{}, error) { + return composeModuleGValSelector(t.value, k) +} + +// Select is field accessor for *types.Module +// +// Similar to SelectGVal but returns typed values +func (t ComposeModule) Select(k string) (TypedValue, error) { + return composeModuleTypedValueSelector(t.value, k) +} + +func (t ComposeModule) Has(k string) bool { + switch k { + case "ID": + return true + case "namespaceID": + return true + case "name": + return true + case "handle": + return true + case "labels": + return true + case "createdAt": + return true + case "updatedAt": + return true + case "deletedAt": + return true + } + return false +} + +// composeModuleGValSelector is field accessor for *types.Module +func composeModuleGValSelector(res *types.Module, k string) (interface{}, error) { + switch k { + case "ID": + return res.ID, nil + case "namespaceID": + return res.NamespaceID, nil + case "name": + return res.Name, nil + case "handle": + return res.Handle, nil + case "labels": + return res.Labels, nil + case "createdAt": + return res.CreatedAt, nil + case "updatedAt": + return res.UpdatedAt, nil + case "deletedAt": + return res.DeletedAt, nil + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// composeModuleTypedValueSelector is field accessor for *types.Module +func composeModuleTypedValueSelector(res *types.Module, k string) (TypedValue, error) { + switch k { + case "ID": + return NewID(res.ID) + case "namespaceID": + return NewID(res.NamespaceID) + case "name": + return NewString(res.Name) + case "handle": + return NewHandle(res.Handle) + case "labels": + return NewKV(res.Labels) + case "createdAt": + return NewDateTime(res.CreatedAt) + case "updatedAt": + return NewDateTime(res.UpdatedAt) + case "deletedAt": + return NewDateTime(res.DeletedAt) + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// assignToComposeModule is field value setter for *types.Module +func assignToComposeModule(res *types.Module, k string, val interface{}) error { + switch k { + case "ID": + return fmt.Errorf("field '%s' is read-only", k) + case "namespaceID": + return fmt.Errorf("field '%s' is read-only", k) + case "name": + aux, err := CastToString(val) + if err != nil { + return err + } + + res.Name = aux + return nil + case "handle": + aux, err := CastToHandle(val) + if err != nil { + return err + } + + res.Handle = aux + return nil + case "labels": + aux, err := CastToKV(val) + if err != nil { + return err + } + + res.Labels = aux + return nil + case "createdAt": + return fmt.Errorf("field '%s' is read-only", k) + case "updatedAt": + return fmt.Errorf("field '%s' is read-only", k) + case "deletedAt": + return fmt.Errorf("field '%s' is read-only", k) + } + + return fmt.Errorf("unknown field '%s'", k) +} + +// ComposeNamespace is an expression type, wrapper for *types.Namespace type +type ComposeNamespace struct{ value *types.Namespace } + +// NewComposeNamespace creates new instance of ComposeNamespace expression type +func NewComposeNamespace(val interface{}) (*ComposeNamespace, error) { + if c, err := CastToComposeNamespace(val); err != nil { + return nil, fmt.Errorf("unable to create ComposeNamespace: %w", err) + } else { + return &ComposeNamespace{value: c}, nil + } +} + +// Return underlying value on ComposeNamespace +func (t ComposeNamespace) Get() interface{} { return t.value } + +// Return type name +func (ComposeNamespace) Type() string { return "ComposeNamespace" } + +// Convert value to *types.Namespace +func (ComposeNamespace) Cast(val interface{}) (TypedValue, error) { + return NewComposeNamespace(val) +} + +// Assign new value to ComposeNamespace +// +// value is first passed through CastToComposeNamespace +func (t *ComposeNamespace) Assign(val interface{}) error { + if c, err := CastToComposeNamespace(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + +func (t *ComposeNamespace) AssignFieldValue(key string, val interface{}) error { + return assignToComposeNamespace(t.value, key, val) +} + +// SelectGVal implements gval.Selector requirements +// +// It allows gval lib to access ComposeNamespace's underlying value (*types.Namespace) +// and it's fields +// +func (t ComposeNamespace) SelectGVal(ctx context.Context, k string) (interface{}, error) { + return composeNamespaceGValSelector(t.value, k) +} + +// Select is field accessor for *types.Namespace +// +// Similar to SelectGVal but returns typed values +func (t ComposeNamespace) Select(k string) (TypedValue, error) { + return composeNamespaceTypedValueSelector(t.value, k) +} + +func (t ComposeNamespace) Has(k string) bool { + switch k { + case "ID": + return true + case "name": + return true + case "slug", "handle": + return true + case "labels": + return true + case "createdAt": + return true + case "updatedAt": + return true + case "deletedAt": + return true + } + return false +} + +// composeNamespaceGValSelector is field accessor for *types.Namespace +func composeNamespaceGValSelector(res *types.Namespace, k string) (interface{}, error) { + switch k { + case "ID": + return res.ID, nil + case "name": + return res.Name, nil + case "slug", "handle": + return res.Slug, nil + case "labels": + return res.Labels, nil + case "createdAt": + return res.CreatedAt, nil + case "updatedAt": + return res.UpdatedAt, nil + case "deletedAt": + return res.DeletedAt, nil + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// composeNamespaceTypedValueSelector is field accessor for *types.Namespace +func composeNamespaceTypedValueSelector(res *types.Namespace, k string) (TypedValue, error) { + switch k { + case "ID": + return NewID(res.ID) + case "name": + return NewString(res.Name) + case "slug", "handle": + return NewHandle(res.Slug) + case "labels": + return NewKV(res.Labels) + case "createdAt": + return NewDateTime(res.CreatedAt) + case "updatedAt": + return NewDateTime(res.UpdatedAt) + case "deletedAt": + return NewDateTime(res.DeletedAt) + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// assignToComposeNamespace is field value setter for *types.Namespace +func assignToComposeNamespace(res *types.Namespace, k string, val interface{}) error { + switch k { + case "ID": + return fmt.Errorf("field '%s' is read-only", k) + case "name": + aux, err := CastToString(val) + if err != nil { + return err + } + + res.Name = aux + return nil + case "slug", "handle": + aux, err := CastToHandle(val) + if err != nil { + return err + } + + res.Slug = aux + return nil + case "labels": + aux, err := CastToKV(val) + if err != nil { + return err + } + + res.Labels = aux + return nil + case "createdAt": + return fmt.Errorf("field '%s' is read-only", k) + case "updatedAt": + return fmt.Errorf("field '%s' is read-only", k) + case "deletedAt": + return fmt.Errorf("field '%s' is read-only", k) + } + + return fmt.Errorf("unknown field '%s'", k) +} + +// ComposeRecord is an expression type, wrapper for *types.Record type +type ComposeRecord struct{ value *types.Record } + +// NewComposeRecord creates new instance of ComposeRecord expression type +func NewComposeRecord(val interface{}) (*ComposeRecord, error) { + if c, err := CastToComposeRecord(val); err != nil { + return nil, fmt.Errorf("unable to create ComposeRecord: %w", err) + } else { + return &ComposeRecord{value: c}, nil + } +} + +// Return underlying value on ComposeRecord +func (t ComposeRecord) Get() interface{} { return t.value } + +// Return type name +func (ComposeRecord) Type() string { return "ComposeRecord" } + +// Convert value to *types.Record +func (ComposeRecord) Cast(val interface{}) (TypedValue, error) { + return NewComposeRecord(val) +} + +// Assign new value to ComposeRecord +// +// value is first passed through CastToComposeRecord +func (t *ComposeRecord) Assign(val interface{}) error { + if c, err := CastToComposeRecord(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + +func (t *ComposeRecord) AssignFieldValue(key string, val interface{}) error { + return assignToComposeRecord(t.value, key, val) +} + +// Select is field accessor for *types.Record +// +// Similar to SelectGVal but returns typed values +func (t ComposeRecord) Select(k string) (TypedValue, error) { + return composeRecordTypedValueSelector(t.value, k) +} + +func (t ComposeRecord) Has(k string) bool { + switch k { + case "ID": + return true + case "moduleID": + return true + case "namespaceID": + return true + case "values": + return true + case "labels": + return true + case "ownedBy": + return true + case "createdAt": + return true + case "createdBy": + return true + case "updatedAt": + return true + case "updatedBy": + return true + case "deletedAt": + return true + case "deletedBy": + return true + } + return false +} + +// composeRecordGValSelector is field accessor for *types.Record +func composeRecordGValSelector(res *types.Record, k string) (interface{}, error) { + switch k { + case "ID": + return res.ID, nil + case "moduleID": + return res.ModuleID, nil + case "namespaceID": + return res.NamespaceID, nil + case "values": + return res.Values, nil + case "labels": + return res.Labels, nil + case "ownedBy": + return res.OwnedBy, nil + case "createdAt": + return res.CreatedAt, nil + case "createdBy": + return res.CreatedBy, nil + case "updatedAt": + return res.UpdatedAt, nil + case "updatedBy": + return res.UpdatedBy, nil + case "deletedAt": + return res.DeletedAt, nil + case "deletedBy": + return res.DeletedBy, nil + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// composeRecordTypedValueSelector is field accessor for *types.Record +func composeRecordTypedValueSelector(res *types.Record, k string) (TypedValue, error) { + switch k { + case "ID": + return NewID(res.ID) + case "moduleID": + return NewID(res.ModuleID) + case "namespaceID": + return NewID(res.NamespaceID) + case "values": + return NewComposeRecordValues(res.Values) + case "labels": + return NewKV(res.Labels) + case "ownedBy": + return NewID(res.OwnedBy) + case "createdAt": + return NewDateTime(res.CreatedAt) + case "createdBy": + return NewID(res.CreatedBy) + case "updatedAt": + return NewDateTime(res.UpdatedAt) + case "updatedBy": + return NewID(res.UpdatedBy) + case "deletedAt": + return NewDateTime(res.DeletedAt) + case "deletedBy": + return NewID(res.DeletedBy) + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// assignToComposeRecord is field value setter for *types.Record +func assignToComposeRecord(res *types.Record, k string, val interface{}) error { + switch k { + case "ID": + return fmt.Errorf("field '%s' is read-only", k) + case "moduleID": + return fmt.Errorf("field '%s' is read-only", k) + case "namespaceID": + return fmt.Errorf("field '%s' is read-only", k) + case "values": + aux, err := CastToComposeRecordValues(val) + if err != nil { + return err + } + + res.Values = aux + return nil + case "labels": + aux, err := CastToKV(val) + if err != nil { + return err + } + + res.Labels = aux + return nil + case "ownedBy": + aux, err := CastToID(val) + if err != nil { + return err + } + + res.OwnedBy = aux + return nil + case "createdAt": + return fmt.Errorf("field '%s' is read-only", k) + case "createdBy": + return fmt.Errorf("field '%s' is read-only", k) + case "updatedAt": + return fmt.Errorf("field '%s' is read-only", k) + case "updatedBy": + return fmt.Errorf("field '%s' is read-only", k) + case "deletedAt": + return fmt.Errorf("field '%s' is read-only", k) + case "deletedBy": + return fmt.Errorf("field '%s' is read-only", k) + } + + return fmt.Errorf("unknown field '%s'", k) +} + +// ComposeRecordValueErrorSet is an expression type, wrapper for *types.RecordValueErrorSet type +type ComposeRecordValueErrorSet struct{ value *types.RecordValueErrorSet } + +// NewComposeRecordValueErrorSet creates new instance of ComposeRecordValueErrorSet expression type +func NewComposeRecordValueErrorSet(val interface{}) (*ComposeRecordValueErrorSet, error) { + if c, err := CastToComposeRecordValueErrorSet(val); err != nil { + return nil, fmt.Errorf("unable to create ComposeRecordValueErrorSet: %w", err) + } else { + return &ComposeRecordValueErrorSet{value: c}, nil + } +} + +// Return underlying value on ComposeRecordValueErrorSet +func (t ComposeRecordValueErrorSet) Get() interface{} { return t.value } + +// Return type name +func (ComposeRecordValueErrorSet) Type() string { return "ComposeRecordValueErrorSet" } + +// Convert value to *types.RecordValueErrorSet +func (ComposeRecordValueErrorSet) Cast(val interface{}) (TypedValue, error) { + return NewComposeRecordValueErrorSet(val) +} + +// Assign new value to ComposeRecordValueErrorSet +// +// value is first passed through CastToComposeRecordValueErrorSet +func (t *ComposeRecordValueErrorSet) Assign(val interface{}) error { + if c, err := CastToComposeRecordValueErrorSet(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + +// ComposeRecordValues is an expression type, wrapper for types.RecordValueSet type +type ComposeRecordValues struct{ value types.RecordValueSet } + +// NewComposeRecordValues creates new instance of ComposeRecordValues expression type +func NewComposeRecordValues(val interface{}) (*ComposeRecordValues, error) { + if c, err := CastToComposeRecordValues(val); err != nil { + return nil, fmt.Errorf("unable to create ComposeRecordValues: %w", err) + } else { + return &ComposeRecordValues{value: c}, nil + } +} + +// Return underlying value on ComposeRecordValues +func (t ComposeRecordValues) Get() interface{} { return t.value } + +// Return type name +func (ComposeRecordValues) Type() string { return "ComposeRecordValues" } + +// Convert value to types.RecordValueSet +func (ComposeRecordValues) Cast(val interface{}) (TypedValue, error) { + return NewComposeRecordValues(val) +} + +// Assign new value to ComposeRecordValues +// +// value is first passed through CastToComposeRecordValues +func (t *ComposeRecordValues) Assign(val interface{}) error { + if c, err := CastToComposeRecordValues(val); err != nil { + return err + } else { + t.value = c + return nil + } +} diff --git a/compose/automation/expr_types.go b/compose/automation/expr_types.go new file mode 100644 index 000000000..4a771ce77 --- /dev/null +++ b/compose/automation/expr_types.go @@ -0,0 +1,221 @@ +package automation + +import ( + "context" + "fmt" + "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/spf13/cast" + "strings" +) + +func CastToComposeNamespace(val interface{}) (out *types.Namespace, err error) { + switch val := val.(type) { + case expr.Iterator: + out = &types.Namespace{} + return out, val.Each(func(k string, v expr.TypedValue) error { + return assignToComposeNamespace(out, k, v) + }) + } + + switch val := expr.UntypedValue(val).(type) { + case *types.Namespace: + return val, nil + default: + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } +} + +func CastToComposeModule(val interface{}) (out *types.Module, err error) { + switch val := val.(type) { + case expr.Iterator: + out = &types.Module{} + return out, val.Each(func(k string, v expr.TypedValue) error { + return assignToComposeModule(out, k, v) + }) + } + + switch val := expr.UntypedValue(val).(type) { + case *types.Module: + return val, nil + default: + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } +} + +func CastToComposeRecord(val interface{}) (out *types.Record, err error) { + switch val := val.(type) { + case expr.Iterator: + out = &types.Record{} + return out, val.Each(func(k string, v expr.TypedValue) error { + return assignToComposeRecord(out, k, v) + }) + } + + switch val := expr.UntypedValue(val).(type) { + case *types.Record: + return val, nil + default: + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } +} + +// SelectGVal implements gval.Selector requirements +// +// It allows gval lib to access Record's underlying value (*types.Record) +// and it's fields +// +func (t ComposeRecord) SelectGVal(ctx context.Context, k string) (interface{}, error) { + if k == "values" { + return t.value.Values.Dict(t.value.GetModule().Fields), nil + } + + return composeRecordGValSelector(t.value, k) +} + +func CastToComposeRecordValues(val interface{}) (out types.RecordValueSet, err error) { + out = types.RecordValueSet{} + + switch val := val.(type) { + case expr.Iterator: + return out, val.Each(func(k string, v expr.TypedValue) error { + // try with slice of strings first: + if ss, err := cast.ToStringSliceE(expr.UntypedValue(v)); err == nil { + for i, v := range ss { + out = out.Set(&types.RecordValue{Name: k, Value: v, Place: uint(i)}) + } + return nil + } + + if str, err := expr.CastToString(v); err != nil { + return err + } else { + out = out.Set(&types.RecordValue{Name: k, Value: str}) + return nil + } + }) + } + + switch val := expr.UntypedValue(val).(type) { + case *types.Record: + return val.Values, nil + case types.RecordValueSet: + return val, nil + case map[string]string: + out = types.RecordValueSet{} + for k, v := range val { + out = out.Set(&types.RecordValue{Name: k, Value: v}) + } + + return + + case map[string][]string: + out = types.RecordValueSet{} + for k, vv := range val { + for i, v := range vv { + out = out.Set(&types.RecordValue{Name: k, Value: v, Place: uint(i)}) + } + } + + return + + default: + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } +} + +func (t *ComposeRecordValues) AssignFieldValue(pp []string, val interface{}) error { + return assignToComposeRecordValues(&t.value, pp, val) +} + +// SelectGVal implements gval.Selector requirements +// +// It allows gval lib to access Record's underlying value (*types.RecordValues) +// and it's fields +// +func (t ComposeRecordValues) SelectGVal(ctx context.Context, k string) (interface{}, error) { + return composeRecordValuesGValSelector(t.value, k) +} + +// Select is field accessor for *types.Record +// +// Similar to SelectGVal but returns typed values +func (t ComposeRecordValues) Select(k string) (expr.TypedValue, error) { + return composeRecordValuesTypedValueSelector(t.value, k) +} + +func (t ComposeRecordValues) Has(k string) bool { + return t.value.Get(k, 0) != nil +} + +// recordGValSelector is field accessor for *types.RecordValueSet +func composeRecordValuesGValSelector(res types.RecordValueSet, k string) (interface{}, error) { + vv := res.FilterByName(k) + + switch len(vv) { + case 0: + return nil, nil + case 1: + return vv[0].Value, nil + default: + out := make([]string, 0, len(vv)) + return out, vv.Walk(func(v *types.RecordValue) error { + out = append(out, v.Value) + return nil + }) + } +} + +// recordValuesTypedValueSelector is field accessor for *types.RecordValueSet +// +// @todo return appropriate types (atm all values are returned as String) +func composeRecordValuesTypedValueSelector(res types.RecordValueSet, k string) (expr.TypedValue, error) { + vv := res.FilterByName(k) + + switch { + case len(vv) == 0: + return nil, nil + case len(vv) == 1: + return expr.NewString(vv[0].Value) + default: + mval := make([]expr.TypedValue, 0, len(vv)) + _ = vv.Walk(func(v *types.RecordValue) error { + mval = append(mval, expr.Must(expr.NewString(v.Value))) + return nil + }) + + return expr.NewArray(mval) + } +} + +// assignToRecordValuesSet is field value setter for *types.Record +func assignToComposeRecordValues(res *types.RecordValueSet, pp []string, val interface{}) (err error) { + if len(pp) < 1 { + return fmt.Errorf("empty path used for assigning record values") + } + + k := pp[0] + rv := &types.RecordValue{Name: k} + if rv.Value, err = cast.ToStringE(expr.UntypedValue(val)); err != nil { + return + } + + if len(pp) == 2 { + if rv.Place, err = cast.ToUintE(expr.UntypedValue(pp[1])); err != nil { + return fmt.Errorf("failed to decode record value place from '%s': %w", strings.Join(pp, "."), err) + } + } + + *res = res.Set(rv) + //return fmt.Errorf("unknown field '%s'", k) + return nil +} + +func CastToComposeRecordValueErrorSet(val interface{}) (out *types.RecordValueErrorSet, err error) { + switch val := expr.UntypedValue(val).(type) { + case *types.RecordValueErrorSet: + return val, nil + default: + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } +} diff --git a/compose/automation/expr_types.yaml b/compose/automation/expr_types.yaml new file mode 100644 index 000000000..e11a646d0 --- /dev/null +++ b/compose/automation/expr_types.yaml @@ -0,0 +1,56 @@ +package: automation +imports: + - github.com/cortezaproject/corteza-server/compose/types + +types: + ComposeNamespace: + as: '*types.Namespace' + struct: + - { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro } + - { name: 'name', exprType: 'String', goType: 'string' } + - { name: 'slug', exprType: 'Handle', goType: 'string', alias: 'handle'} + - { name: 'labels', exprType: 'KV', goType: 'map[string]string' } + - { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro } + - { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + - { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + ComposeModule: + as: '*types.Module' + struct: + - { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro } + - { name: 'namespaceID', exprType: 'ID', goType: 'uint64', mode: ro } + - { name: 'name', exprType: 'String', goType: 'string' } + - { name: 'handle', exprType: 'Handle', goType: 'string' } + - { name: 'labels', exprType: 'KV', goType: 'map[string]string' } + - { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro } + - { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + - { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + + ComposeRecord: + as: '*types.Record' + struct: + - { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro } + - { name: 'moduleID', exprType: 'ID', goType: 'uint64', mode: ro } + - { name: 'namespaceID', exprType: 'ID', goType: 'uint64', mode: ro } + - { name: 'values', exprType: 'ComposeRecordValues', goType: 'map[string]interface{}' } + - { name: 'labels', exprType: 'KV', goType: 'map[string]string' } + - { name: 'ownedBy', exprType: 'ID', goType: 'uint64' } + - { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro } + - { name: 'createdBy', exprType: 'ID', goType: 'uint64', mode: ro } + - { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + - { name: 'updatedBy', exprType: 'ID', goType: 'uint64', mode: ro } + - { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + - { name: 'deletedBy', exprType: 'ID', goType: 'uint64', mode: ro } + customGValSelector: true + + ComposeRecordValues: + as: 'types.RecordValueSet' + castFn: 'castComposeRecordValueSet' + + ComposeRecordValueErrorSet: + as: '*types.RecordValueErrorSet' + +# +# Page: +# as: '*types.Page' +# Chart: +# as: '*types.Chart' diff --git a/compose/automation/expr_types_test.go b/compose/automation/expr_types_test.go new file mode 100644 index 000000000..0f2313a54 --- /dev/null +++ b/compose/automation/expr_types_test.go @@ -0,0 +1,135 @@ +package automation + +import ( + "context" + "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/stretchr/testify/require" + "testing" +) + +func TestSetRecordValuesWithPath(t *testing.T) { + t.Run("some basic aerobics", func(t *testing.T) { + + var ( + r = require.New(t) + rvs = &RecordValues{types.RecordValueSet{}} + ) + + r.NoError(expr.Assign(rvs, "field1", "a")) + r.NoError(expr.Assign(rvs, "field1.1", "a")) + r.True(rvs.value.Has("field1", 0)) + r.True(rvs.value.Has("field1", 1)) + }) + + t.Run("cast string map", func(t *testing.T) { + + var ( + r = require.New(t) + rvs, err = CastToRecordValues(map[string]string{"field2": "b"}) + ) + + r.NoError(err) + r.True(rvs.Has("field2", 0)) + }) + + t.Run("cast string slice map", func(t *testing.T) { + + var ( + r = require.New(t) + rvs, err = CastToRecordValues(map[string][]string{"field2": []string{"a", "b"}}) + ) + + r.NoError(err) + r.True(rvs.Has("field2", 0)) + r.True(rvs.Has("field2", 1)) + }) +} + +func TestRecordFieldValuesAccess(t *testing.T) { + var ( + err error + v expr.TypedValue + + mod = &types.Module{Fields: types.ModuleFieldSet{ + &types.ModuleField{Name: "s1", Multi: false}, + &types.ModuleField{Name: "m1", Multi: true}, + &types.ModuleField{Name: "m2", Multi: true}, + &types.ModuleField{Name: "s2", Multi: false}, + }} + + raw = &types.Record{Values: types.RecordValueSet{ + &types.RecordValue{Name: "s1", Value: "sVal1"}, + &types.RecordValue{Name: "m1", Value: "mVal1.0"}, + &types.RecordValue{Name: "m1", Value: "mVal1.1", Place: 1}, + &types.RecordValue{Name: "m1", Value: "mVal1.2", Place: 2}, + &types.RecordValue{Name: "m2", Value: "mVal2.0"}, + }} + + tval = &Record{value: raw} + scope = expr.RVars{"rec": tval}.Vars() + ) + + // @todo see not above re. back-ref to record + raw.SetModule(mod) + + t.Run("via typed value", func(t *testing.T) { + var ( + req = require.New(t) + ) + + v, err = expr.Select(scope, "rec.values.s1") + req.NoError(err) + req.NotEmpty(v) + req.Equal("sVal1", v.Get()) + + v, err = expr.Select(scope, "rec.values.m1.0") + req.NoError(err) + req.NotEmpty(v) + req.Equal("mVal1.0", v.Get()) + + v, err = expr.Select(scope, "rec.values.m1.1") + req.NoError(err) + req.NotEmpty(v) + req.Equal("mVal1.1", v.Get()) + + // @todo when RecordValueSet supports back-ref to record, + // we can employ better field access: + // - no error on missing values when field exists + // - proper handling of multi-value field values + // - proper value-types that corelate to field types + //v, err = expr.Select(scope, "rec.values.m2.0") + //req.NoError(err) + //req.NotEmpty(v) + //req.Equal("mVal2.0", v.Get()) + }) + + t.Run("via gval selector", func(t *testing.T) { + var ( + req = require.New(t) + parser = expr.NewParser() + ) + + eval, err := parser.Parse(`rec.values.s1 == "sVal1"`) + req.NoError(err) + req.True(eval.Test(context.Background(), scope)) + + eval, err = parser.Parse(`rec.values.s1 != "foo"`) + req.NoError(err) + req.True(eval.Test(context.Background(), scope)) + + eval, err = parser.Parse(`rec.values.m1[0] == "mVal1.0"`) + req.NoError(err) + req.True(eval.Test(context.Background(), scope)) + + eval, err = parser.Parse(`rec.values.m1[1] == "mVal1.1"`) + req.NoError(err) + req.True(eval.Test(context.Background(), scope)) + + eval, err = parser.Parse(`rec.values.m2[0] == "mVal2.0"`) + req.NoError(err) + req.True(eval.Test(context.Background(), scope)) + + }) + +} diff --git a/compose/automation/modules_handler.gen.go b/compose/automation/modules_handler.gen.go index 0db7ccfac..137292543 100644 --- a/compose/automation/modules_handler.gen.go +++ b/compose/automation/modules_handler.gen.go @@ -13,7 +13,6 @@ import ( atypes "github.com/cortezaproject/corteza-server/automation/types" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/expr" - "github.com/cortezaproject/corteza-server/pkg/label" "github.com/cortezaproject/corteza-server/pkg/wfexec" ) @@ -28,19 +27,12 @@ type ( func (h modulesHandler) register() { h.reg.AddFunctions( - h.LookupByID(), - h.Save(), - h.Create(), - h.Update(), - h.Delete(), + h.Lookup(), ) } type ( - modulesLookupByIDArgs struct { - hasRecordID bool - RecordID uint64 - + modulesLookupArgs struct { hasModule bool Module interface{} moduleID uint64 @@ -54,64 +46,55 @@ type ( namespaceRes *types.Namespace } - modulesLookupByIDResults struct { - Record *types.Record + modulesLookupResults struct { + Module *types.Module } ) -func (a modulesLookupByIDArgs) GetModule() (bool, uint64, string, *types.Module) { +func (a modulesLookupArgs) GetModule() (bool, uint64, string, *types.Module) { return a.hasModule, a.moduleID, a.moduleHandle, a.moduleRes } -func (a modulesLookupByIDArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { +func (a modulesLookupArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes } -// LookupByID function Lookup for compose record by ID +// Lookup function Lookup for compose Lookup by ID // -// expects implementation of lookupByID function: -// func (h modulesHandler) lookupByID(ctx context.Context, args *modulesLookupByIDArgs) (results *modulesLookupByIDResults, err error) { +// expects implementation of lookup function: +// func (h modulesHandler) lookup(ctx context.Context, args *modulesLookupArgs) (results *modulesLookupResults, err error) { // return // } -func (h modulesHandler) LookupByID() *atypes.Function { +func (h modulesHandler) Lookup() *atypes.Function { return &atypes.Function{ - Ref: "composeModulesLookupByID", + Ref: "composeModulesLookup", Kind: "function", Meta: &atypes.FunctionMeta{ - Short: "Lookup for compose record by ID", + Short: "Lookup for compose Lookup by ID", }, Parameters: []*atypes.Param{ - { - Name: "recordID", - Types: []string{"ID"}, Required: true, - }, { Name: "module", - Types: []string{"ID", "String", "ComposeModule"}, Required: true, - Meta: &atypes.ParamMeta{ - Label: "Module to set record type", - Description: "Even with unique record ID across all modules, module needs to be known\nbefore doing any record operations. Mainly because records of different\nmodules can be located in different stores.", - }, + Types: []string{"ID", "Handle", "ComposeModule"}, Required: true, }, { Name: "namespace", - Types: []string{"ID", "String", "ComposeNamespace"}, Required: true, + Types: []string{"ID", "Handle", "ComposeNamespace"}, Required: true, }, }, Results: []*atypes.Param{ { - Name: "record", - Types: []string{"ComposeRecord"}, + Name: "module", + Types: []string{"ComposeModule"}, }, }, Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { var ( - args = &modulesLookupByIDArgs{ - hasRecordID: in.Has("recordID"), + args = &modulesLookupArgs{ hasModule: in.Has("module"), hasNamespace: in.Has("namespace"), } @@ -127,7 +110,7 @@ func (h modulesHandler) LookupByID() *atypes.Function { switch aux.Type() { case h.reg.Type("ID").Type(): args.moduleID = aux.Get().(uint64) - case h.reg.Type("String").Type(): + case h.reg.Type("Handle").Type(): args.moduleHandle = aux.Get().(string) case h.reg.Type("ComposeModule").Type(): args.moduleRes = aux.Get().(*types.Module) @@ -140,29 +123,29 @@ func (h modulesHandler) LookupByID() *atypes.Function { switch aux.Type() { case h.reg.Type("ID").Type(): args.namespaceID = aux.Get().(uint64) - case h.reg.Type("String").Type(): + case h.reg.Type("Handle").Type(): args.namespaceHandle = aux.Get().(string) case h.reg.Type("ComposeNamespace").Type(): args.namespaceRes = aux.Get().(*types.Namespace) } } - var results *modulesLookupByIDResults - if results, err = h.lookupByID(ctx, args); err != nil { + var results *modulesLookupResults + if results, err = h.lookup(ctx, args); err != nil { return } out = &expr.Vars{} { - // converting results.Record (*types.Record) to ComposeRecord + // converting results.Module (*types.Module) to ComposeModule var ( tval expr.TypedValue ) - if tval, err = h.reg.Type("ComposeRecord").Cast(results.Record); err != nil { + if tval, err = h.reg.Type("ComposeModule").Cast(results.Module); err != nil { return - } else if err = expr.Assign(out, "record", tval); err != nil { + } else if err = expr.Assign(out, "module", tval); err != nil { return } } @@ -171,495 +154,3 @@ func (h modulesHandler) LookupByID() *atypes.Function { }, } } - -type ( - modulesSaveArgs struct { - hasRecord bool - Record *types.Record - } - - modulesSaveResults struct { - Record *types.Record - } -) - -// Save function Save record -// -// expects implementation of save function: -// func (h modulesHandler) save(ctx context.Context, args *modulesSaveArgs) (results *modulesSaveResults, err error) { -// return -// } -func (h modulesHandler) Save() *atypes.Function { - return &atypes.Function{ - Ref: "composeModulesSave", - Kind: "function", - Meta: &atypes.FunctionMeta{ - Short: "Save record", - }, - - Parameters: []*atypes.Param{ - { - Name: "record", - Types: []string{"ComposeRecord"}, Required: true, - }, - }, - - Results: []*atypes.Param{ - - { - Name: "record", - Types: []string{"ComposeRecord"}, - }, - }, - - Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { - var ( - args = &modulesSaveArgs{ - hasRecord: in.Has("record"), - } - ) - - if err = in.Decode(args); err != nil { - return - } - - var results *modulesSaveResults - if results, err = h.save(ctx, args); err != nil { - return - } - - out = &expr.Vars{} - - { - // converting results.Record (*types.Record) to ComposeRecord - var ( - tval expr.TypedValue - ) - - if tval, err = h.reg.Type("ComposeRecord").Cast(results.Record); err != nil { - return - } else if err = expr.Assign(out, "record", tval); err != nil { - return - } - } - - return - }, - } -} - -type ( - modulesCreateArgs struct { - hasModule bool - Module interface{} - moduleID uint64 - moduleHandle string - moduleRes *types.Module - - hasNamespace bool - Namespace interface{} - namespaceID uint64 - namespaceHandle string - namespaceRes *types.Namespace - - hasValues bool - Values types.RecordValueSet - - hasLabels bool - Labels label.Labels - - hasOwnedBy bool - OwnedBy uint64 - } - - modulesCreateResults struct { - Record *types.Record - } -) - -func (a modulesCreateArgs) GetModule() (bool, uint64, string, *types.Module) { - return a.hasModule, a.moduleID, a.moduleHandle, a.moduleRes -} - -func (a modulesCreateArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { - return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes -} - -// Create function Creates and stores a new record -// -// expects implementation of create function: -// func (h modulesHandler) create(ctx context.Context, args *modulesCreateArgs) (results *modulesCreateResults, err error) { -// return -// } -func (h modulesHandler) Create() *atypes.Function { - return &atypes.Function{ - Ref: "composeModulesCreate", - Kind: "function", - Meta: &atypes.FunctionMeta{ - Short: "Creates and stores a new record", - }, - - Parameters: []*atypes.Param{ - { - Name: "module", - Types: []string{"ID", "String", "ComposeModule"}, Required: true, - Meta: &atypes.ParamMeta{ - Label: "Module to set record type", - Description: "Even with unique record ID across all modules, module needs to be known\nbefore doing any record operations. Mainly because records of different\nmodules can be located in different stores.", - }, - }, - { - Name: "namespace", - Types: []string{"ID", "String", "ComposeNamespace"}, Required: true, - }, - { - Name: "values", - Types: []string{"KV"}, - }, - { - Name: "labels", - Types: []string{"KV"}, - }, - { - Name: "ownedBy", - Types: []string{"ID"}, - Meta: &atypes.ParamMeta{ - Label: "Record owner", - Visual: map[string]interface{}{"ref": "users"}, - }, - }, - }, - - Results: []*atypes.Param{ - - { - Name: "record", - Types: []string{"ComposeRecord"}, - }, - }, - - Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { - var ( - args = &modulesCreateArgs{ - hasModule: in.Has("module"), - hasNamespace: in.Has("namespace"), - hasValues: in.Has("values"), - hasLabels: in.Has("labels"), - hasOwnedBy: in.Has("ownedBy"), - } - ) - - if err = in.Decode(args); err != nil { - return - } - - // Converting Module argument - if args.hasModule { - aux := expr.Must(expr.Select(in, "module")) - switch aux.Type() { - case h.reg.Type("ID").Type(): - args.moduleID = aux.Get().(uint64) - case h.reg.Type("String").Type(): - args.moduleHandle = aux.Get().(string) - case h.reg.Type("ComposeModule").Type(): - args.moduleRes = aux.Get().(*types.Module) - } - } - - // Converting Namespace argument - if args.hasNamespace { - aux := expr.Must(expr.Select(in, "namespace")) - switch aux.Type() { - case h.reg.Type("ID").Type(): - args.namespaceID = aux.Get().(uint64) - case h.reg.Type("String").Type(): - args.namespaceHandle = aux.Get().(string) - case h.reg.Type("ComposeNamespace").Type(): - args.namespaceRes = aux.Get().(*types.Namespace) - } - } - - var results *modulesCreateResults - if results, err = h.create(ctx, args); err != nil { - return - } - - out = &expr.Vars{} - - { - // converting results.Record (*types.Record) to ComposeRecord - var ( - tval expr.TypedValue - ) - - if tval, err = h.reg.Type("ComposeRecord").Cast(results.Record); err != nil { - return - } else if err = expr.Assign(out, "record", tval); err != nil { - return - } - } - - return - }, - } -} - -type ( - modulesUpdateArgs struct { - hasModule bool - Module interface{} - moduleID uint64 - moduleHandle string - moduleRes *types.Module - - hasNamespace bool - Namespace interface{} - namespaceID uint64 - namespaceHandle string - namespaceRes *types.Namespace - - hasValues bool - Values types.RecordValueSet - - hasLabels bool - Labels label.Labels - - hasOwnedBy bool - OwnedBy uint64 - } - - modulesUpdateResults struct { - Record *types.Record - } -) - -func (a modulesUpdateArgs) GetModule() (bool, uint64, string, *types.Module) { - return a.hasModule, a.moduleID, a.moduleHandle, a.moduleRes -} - -func (a modulesUpdateArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { - return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes -} - -// Update function Updates an existing record -// -// expects implementation of update function: -// func (h modulesHandler) update(ctx context.Context, args *modulesUpdateArgs) (results *modulesUpdateResults, err error) { -// return -// } -func (h modulesHandler) Update() *atypes.Function { - return &atypes.Function{ - Ref: "composeModulesUpdate", - Kind: "function", - Meta: &atypes.FunctionMeta{ - Short: "Updates an existing record", - }, - - Parameters: []*atypes.Param{ - { - Name: "module", - Types: []string{"ID", "String", "ComposeModule"}, Required: true, - Meta: &atypes.ParamMeta{ - Label: "Module to set record type", - Description: "Even with unique record ID across all modules, module needs to be known\nbefore doing any record operations. Mainly because records of different\nmodules can be located in different stores.", - }, - }, - { - Name: "namespace", - Types: []string{"ID", "String", "ComposeNamespace"}, Required: true, - }, - { - Name: "values", - Types: []string{"KV"}, - }, - { - Name: "labels", - Types: []string{"KV"}, - }, - { - Name: "ownedBy", - Types: []string{"ID"}, - Meta: &atypes.ParamMeta{ - Label: "Record owner", - Visual: map[string]interface{}{"ref": "users"}, - }, - }, - }, - - Results: []*atypes.Param{ - - { - Name: "record", - Types: []string{"ComposeRecord"}, - }, - }, - - Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { - var ( - args = &modulesUpdateArgs{ - hasModule: in.Has("module"), - hasNamespace: in.Has("namespace"), - hasValues: in.Has("values"), - hasLabels: in.Has("labels"), - hasOwnedBy: in.Has("ownedBy"), - } - ) - - if err = in.Decode(args); err != nil { - return - } - - // Converting Module argument - if args.hasModule { - aux := expr.Must(expr.Select(in, "module")) - switch aux.Type() { - case h.reg.Type("ID").Type(): - args.moduleID = aux.Get().(uint64) - case h.reg.Type("String").Type(): - args.moduleHandle = aux.Get().(string) - case h.reg.Type("ComposeModule").Type(): - args.moduleRes = aux.Get().(*types.Module) - } - } - - // Converting Namespace argument - if args.hasNamespace { - aux := expr.Must(expr.Select(in, "namespace")) - switch aux.Type() { - case h.reg.Type("ID").Type(): - args.namespaceID = aux.Get().(uint64) - case h.reg.Type("String").Type(): - args.namespaceHandle = aux.Get().(string) - case h.reg.Type("ComposeNamespace").Type(): - args.namespaceRes = aux.Get().(*types.Namespace) - } - } - - var results *modulesUpdateResults - if results, err = h.update(ctx, args); err != nil { - return - } - - out = &expr.Vars{} - - { - // converting results.Record (*types.Record) to ComposeRecord - var ( - tval expr.TypedValue - ) - - if tval, err = h.reg.Type("ComposeRecord").Cast(results.Record); err != nil { - return - } else if err = expr.Assign(out, "record", tval); err != nil { - return - } - } - - return - }, - } -} - -type ( - modulesDeleteArgs struct { - hasRecordID bool - RecordID uint64 - - hasModule bool - Module interface{} - moduleID uint64 - moduleHandle string - moduleRes *types.Module - - hasNamespace bool - Namespace interface{} - namespaceID uint64 - namespaceHandle string - namespaceRes *types.Namespace - } -) - -func (a modulesDeleteArgs) GetModule() (bool, uint64, string, *types.Module) { - return a.hasModule, a.moduleID, a.moduleHandle, a.moduleRes -} - -func (a modulesDeleteArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { - return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes -} - -// Delete function Soft deletes compose record by ID -// -// expects implementation of delete function: -// func (h modulesHandler) delete(ctx context.Context, args *modulesDeleteArgs) (err error) { -// return -// } -func (h modulesHandler) Delete() *atypes.Function { - return &atypes.Function{ - Ref: "composeModulesDelete", - Kind: "function", - Meta: &atypes.FunctionMeta{ - Short: "Soft deletes compose record by ID", - }, - - Parameters: []*atypes.Param{ - { - Name: "recordID", - Types: []string{"ID"}, Required: true, - }, - { - Name: "module", - Types: []string{"ID", "String", "ComposeModule"}, Required: true, - Meta: &atypes.ParamMeta{ - Label: "Module to set record type", - Description: "Even with unique record ID across all modules, module needs to be known\nbefore doing any record operations. Mainly because records of different\nmodules can be located in different stores.", - }, - }, - { - Name: "namespace", - Types: []string{"ID", "String", "ComposeNamespace"}, Required: true, - }, - }, - - Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { - var ( - args = &modulesDeleteArgs{ - hasRecordID: in.Has("recordID"), - hasModule: in.Has("module"), - hasNamespace: in.Has("namespace"), - } - ) - - if err = in.Decode(args); err != nil { - return - } - - // Converting Module argument - if args.hasModule { - aux := expr.Must(expr.Select(in, "module")) - switch aux.Type() { - case h.reg.Type("ID").Type(): - args.moduleID = aux.Get().(uint64) - case h.reg.Type("String").Type(): - args.moduleHandle = aux.Get().(string) - case h.reg.Type("ComposeModule").Type(): - args.moduleRes = aux.Get().(*types.Module) - } - } - - // Converting Namespace argument - if args.hasNamespace { - aux := expr.Must(expr.Select(in, "namespace")) - switch aux.Type() { - case h.reg.Type("ID").Type(): - args.namespaceID = aux.Get().(uint64) - case h.reg.Type("String").Type(): - args.namespaceHandle = aux.Get().(string) - case h.reg.Type("ComposeNamespace").Type(): - args.namespaceRes = aux.Get().(*types.Namespace) - } - } - - return out, h.delete(ctx, args) - }, - } -} diff --git a/compose/automation/modules_handler.go b/compose/automation/modules_handler.go index 9f63d74d1..13594c075 100644 --- a/compose/automation/modules_handler.go +++ b/compose/automation/modules_handler.go @@ -2,157 +2,82 @@ package automation import ( "context" + "fmt" "github.com/cortezaproject/corteza-server/compose/types" ) type ( - recordService interface { - FindByID(ctx context.Context, namespaceID, moduleID, recordID uint64) (*types.Record, error) - Find(ctx context.Context, filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) - - Create(ctx context.Context, record *types.Record) (*types.Record, error) - Update(ctx context.Context, record *types.Record) (*types.Record, error) - Bulk(ctx context.Context, oo ...*types.RecordBulkOperation) (types.RecordSet, error) - - DeleteByID(ctx context.Context, namespaceID, moduleID uint64, recordID ...uint64) error - } - moduleService interface { FindByID(ctx context.Context, namespaceID, moduleID uint64) (*types.Module, error) FindByHandle(ctx context.Context, namespaceID uint64, handle string) (*types.Module, error) + Find(ctx context.Context, filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) + + Create(ctx context.Context, module *types.Module) (*types.Module, error) + Update(ctx context.Context, module *types.Module) (*types.Module, error) + + DeleteByID(ctx context.Context, namespaceID uint64, moduleID uint64) error } - namespaceService interface { - FindByID(ctx context.Context, namespaceID uint64) (*types.Namespace, error) - FindByHandle(ctx context.Context, handle string) (*types.Namespace, error) - } - - recordsHandlers struct { - reg recordsHandlerRegistry + modulesHandler struct { + reg modulesHandlerRegistry ns namespaceService mod moduleService - rec recordService + } + + moduleLookup interface { + GetModule() (bool, uint64, string, *types.Module) } ) -func RecordsHandlers(reg recordsHandlerRegistry, ns namespaceService, mod moduleService, rec recordService) *recordsHandlers { - h := &recordsHandlers{ +func ModulesHandler(reg modulesHandlerRegistry, ns namespaceService, mod moduleService) *modulesHandler { + h := &modulesHandler{ reg: reg, ns: ns, mod: mod, - rec: rec, } h.register() return h } -func resolveNamespace(ctx context.Context, svc namespaceService, id *uint64, handle string, res *types.Namespace) (err error) { - if *id == 0 { - if len(handle) > 0 { - if res, err = svc.FindByHandle(ctx, handle); err != nil { - return - } - } - - if res != nil { - *id = res.ID - } - } - +func (h modulesHandler) lookup(ctx context.Context, args *modulesLookupArgs) (results *modulesLookupResults, err error) { + results = &modulesLookupResults{} + results.Module, err = lookupModule(ctx, h.ns, h.mod, args) return } -func resolveModule(ctx context.Context, svc moduleService, namespaceID uint64, id *uint64, handle string, res *types.Module) (err error) { - if *id == 0 { - if len(handle) > 0 { - if res, err = svc.FindByHandle(ctx, namespaceID, handle); err != nil { - return - } - } - - if res != nil { - *id = res.ID - } +func getModuleID(ctx context.Context, nsSvc namespaceService, modSvc moduleService, args moduleLookup) (namespaceID uint64, moduleID uint64, err error) { + namespaceID, err = getNamespaceID(ctx, nsSvc, args.(namespaceLookup)) + if err != nil { + return } - return + var mod *types.Module + if _, moduleID, _, _ = args.GetModule(); moduleID > 0 { + return + } else if mod, err = lookupModule(ctx, nsSvc, modSvc, args); err != nil { + return + } else { + return namespaceID, mod.ID, nil + } } -func (h recordsHandlers) lookupByID(ctx context.Context, args *recordsLookupByIDArgs) (results *recordsLookupByIDResults, err error) { - results = &recordsLookupByIDResults{} - - if err = resolveNamespace(ctx, h.ns, &args.namespaceID, args.namespaceHandle, args.namespaceRes); err != nil { - return nil, err +func lookupModule(ctx context.Context, nsSvc namespaceService, modSvc moduleService, args moduleLookup) (*types.Module, error) { + namespaceID, err := getNamespaceID(ctx, nsSvc, args.(namespaceLookup)) + if err != nil { + return nil, fmt.Errorf("could not load namespace: %w", err) } - if err = resolveModule(ctx, h.mod, args.namespaceID, &args.moduleID, args.moduleHandle, args.moduleRes); err != nil { - return nil, err + _, ID, handle, module := args.GetModule() + + switch { + case module != nil: + return module, nil + case ID > 0: + return modSvc.FindByID(ctx, namespaceID, ID) + case len(handle) > 0: + return modSvc.FindByHandle(ctx, namespaceID, handle) } - results.Record, err = h.rec.FindByID(ctx, args.namespaceID, args.moduleID, args.RecordID) - return -} - -func (h recordsHandlers) create(ctx context.Context, args *recordsCreateArgs) (results *recordsCreateResults, err error) { - results = &recordsCreateResults{} - - if err = resolveNamespace(ctx, h.ns, &args.namespaceID, args.namespaceHandle, args.namespaceRes); err != nil { - return nil, err - } - - if err = resolveModule(ctx, h.mod, args.namespaceID, &args.moduleID, args.moduleHandle, args.moduleRes); err != nil { - return nil, err - } - - rec := &types.Record{ - ModuleID: args.moduleID, - NamespaceID: args.namespaceID, - Values: args.Values, - Labels: args.Labels, - OwnedBy: args.OwnedBy, - } - - results.Record, err = h.rec.Create(ctx, rec) - return -} - -func (h recordsHandlers) save(ctx context.Context, args *recordsSaveArgs) (results *recordsSaveResults, err error) { - results = &recordsSaveResults{} - results.Record, err = h.rec.Update(ctx, args.Record) - return -} - -func (h recordsHandlers) update(ctx context.Context, args *recordsUpdateArgs) (results *recordsUpdateResults, err error) { - results = &recordsUpdateResults{} - if err = resolveNamespace(ctx, h.ns, &args.namespaceID, args.namespaceHandle, args.namespaceRes); err != nil { - return nil, err - } - - if err = resolveModule(ctx, h.mod, args.namespaceID, &args.moduleID, args.moduleHandle, args.moduleRes); err != nil { - return nil, err - } - - rec := &types.Record{ - ModuleID: args.moduleID, - NamespaceID: args.namespaceID, - Values: args.Values, - Labels: args.Labels, - OwnedBy: args.OwnedBy, - } - - results.Record, err = h.rec.Update(ctx, rec) - return -} - -func (h recordsHandlers) delete(ctx context.Context, args *recordsDeleteArgs) (err error) { - if err = resolveNamespace(ctx, h.ns, &args.namespaceID, args.namespaceHandle, args.namespaceRes); err != nil { - return err - } - - if err = resolveModule(ctx, h.mod, args.namespaceID, &args.moduleID, args.moduleHandle, args.moduleRes); err != nil { - return err - } - - return h.rec.DeleteByID(ctx, args.namespaceID, args.moduleID, args.RecordID) + return nil, fmt.Errorf("empty module lookup params") } diff --git a/compose/automation/modules_handler.yaml b/compose/automation/modules_handler.yaml index a22b9dbd4..2c7264269 100644 --- a/compose/automation/modules_handler.yaml +++ b/compose/automation/modules_handler.yaml @@ -2,123 +2,31 @@ prefix: compose imports: - github.com/cortezaproject/corteza-server/compose/types - - github.com/cortezaproject/corteza-server/pkg/label -# - time params: - module: &module + moduleLookup: &moduleLookup required: true types: - - { wf: ID, go: uint64, suffix: ID } - - { wf: String, go: string, suffix: handle } - - { wf: ComposeModule, go: "*types.Module", suffix: res } - meta: - label: Module to set record type - description: |- - Even with unique record ID across all modules, module needs to be known - before doing any record operations. Mainly because records of different - modules can be located in different stores. + - { wf: ID, } + - { wf: Handle, } + - { wf: ComposeModule, suffix: res } - namespace: &namespace + namespaceLookup: &namespaceLookup required: true types: - - { wf: ID, go: uint64, suffix: ID } - - { wf: String, go: string, suffix: handle } - - { wf: ComposeNamespace, go: "*types.Namespace", suffix: res } + - { wf: ID, } + - { wf: Handle, } + - { wf: ComposeNamespace, suffix: res } - record: &record - required: true - types: - - { wf: ComposeRecord, go: "*types.Record" } - - recordID: &recordID - required: true - types: - - { wf: ID, go: uint64 } - - values: &values - types: [ { wf: KV, go: 'types.RecordValueSet' }] - - labels: &labels - types: [ { wf: KV, go: 'label.Labels' }] - - ownedBy: &ownedBy - types: [ { wf: ID, go: uint64 } ] - meta: - label: Record owner - visual: - ref: users - - recordReturnValue: &recordReturnValue - wf: ComposeRecord - go: "*types.Record" + rvModule: &rvModule + wf: ComposeModule functions: - lookupByID: + lookup: meta: - short: Lookup for compose record by ID + short: Lookup for compose Lookup by ID params: - recordID: *recordID - module: *module - namespace: *namespace + module: *moduleLookup + namespace: *namespaceLookup results: - record: *recordReturnValue - - save: - meta: - short: Save record - params: - record: *record - results: - record: *recordReturnValue - - # @todo validate - # -> record - # <- errors RVSE - # <- valid (bool) - - # @todo copy - # -> source Record - # -> target Record - # -> include []string list of fields from source record to copy - # -> exclude []string list of fields from source record to copy - - create: - meta: - short: Creates and stores a new record - params: - module: *module - namespace: *namespace - values: *values - labels: *labels - ownedBy: *ownedBy - results: - record: *recordReturnValue - - update: - meta: - short: Updates an existing record - params: - module: *module - namespace: *namespace - values: *values - labels: *labels - ownedBy: *ownedBy - results: - record: *recordReturnValue - - delete: - meta: - short: Soft deletes compose record by ID - params: - recordID: *recordID - module: *module - namespace: *namespace - -# restore: -# meta: -# short: Soft deletes compose record by ID -# params: -# recordID: *recordID -# module: *module -# namespace: *namespace + module: *rvModule diff --git a/compose/automation/namespaces_handler.gen.go b/compose/automation/namespaces_handler.gen.go index 677604a28..4450dc9de 100644 --- a/compose/automation/namespaces_handler.gen.go +++ b/compose/automation/namespaces_handler.gen.go @@ -33,11 +33,6 @@ func (h namespacesHandler) register() { type ( namespacesLookupArgs struct { - hasModule bool - Module interface{} - moduleID uint64 - moduleHandle string - hasNamespace bool Namespace interface{} namespaceID uint64 @@ -46,19 +41,15 @@ type ( } namespacesLookupResults struct { - Module *types.Module + Namespace *types.Namespace } ) -func (a namespacesLookupArgs) GetModule() (bool, uint64, string) { - return a.hasModule, a.moduleID, a.moduleHandle -} - func (a namespacesLookupArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes } -// Lookup function Lookup for compose module by ID +// Lookup function Lookup for compose namespace by ID // // expects implementation of lookup function: // func (h namespacesHandler) lookup(ctx context.Context, args *namespacesLookupArgs) (results *namespacesLookupResults, err error) { @@ -69,32 +60,27 @@ func (h namespacesHandler) Lookup() *atypes.Function { Ref: "composeNamespacesLookup", Kind: "function", Meta: &atypes.FunctionMeta{ - Short: "Lookup for compose module by ID", + Short: "Lookup for compose namespace by ID", }, Parameters: []*atypes.Param{ - { - Name: "module", - Types: []string{"ID", "String"}, Required: true, - }, { Name: "namespace", - Types: []string{"ID", "String", "ComposeNamespace"}, Required: true, + Types: []string{"ID", "Handle", "ComposeNamespace"}, Required: true, }, }, Results: []*atypes.Param{ { - Name: "module", - Types: []string{"ComposeModule"}, + Name: "namespace", + Types: []string{"ComposeNamespace"}, }, }, Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { var ( args = &namespacesLookupArgs{ - hasModule: in.Has("module"), hasNamespace: in.Has("namespace"), } ) @@ -103,24 +89,13 @@ func (h namespacesHandler) Lookup() *atypes.Function { return } - // Converting Module argument - if args.hasModule { - aux := expr.Must(expr.Select(in, "module")) - switch aux.Type() { - case h.reg.Type("ID").Type(): - args.moduleID = aux.Get().(uint64) - case h.reg.Type("String").Type(): - args.moduleHandle = aux.Get().(string) - } - } - // Converting Namespace argument if args.hasNamespace { aux := expr.Must(expr.Select(in, "namespace")) switch aux.Type() { case h.reg.Type("ID").Type(): args.namespaceID = aux.Get().(uint64) - case h.reg.Type("String").Type(): + case h.reg.Type("Handle").Type(): args.namespaceHandle = aux.Get().(string) case h.reg.Type("ComposeNamespace").Type(): args.namespaceRes = aux.Get().(*types.Namespace) @@ -135,14 +110,14 @@ func (h namespacesHandler) Lookup() *atypes.Function { out = &expr.Vars{} { - // converting results.Module (*types.Module) to ComposeModule + // converting results.Namespace (*types.Namespace) to ComposeNamespace var ( tval expr.TypedValue ) - if tval, err = h.reg.Type("ComposeModule").Cast(results.Module); err != nil { + if tval, err = h.reg.Type("ComposeNamespace").Cast(results.Namespace); err != nil { return - } else if err = expr.Assign(out, "module", tval); err != nil { + } else if err = expr.Assign(out, "namespace", tval); err != nil { return } } diff --git a/compose/automation/namespaces_handler.go b/compose/automation/namespaces_handler.go index a5c30d1af..cc0e660c8 100644 --- a/compose/automation/namespaces_handler.go +++ b/compose/automation/namespaces_handler.go @@ -2,72 +2,71 @@ package automation import ( "context" + "fmt" "github.com/cortezaproject/corteza-server/compose/types" ) type ( - moduleService interface { - FindByID(ctx context.Context, namespaceID, moduleID uint64) (*types.Module, error) - FindByHandle(ctx context.Context, namespaceID uint64, handle string) (*types.Module, error) - Find(ctx context.Context, filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) - - Create(ctx context.Context, module *types.Module) (*types.Module, error) - Update(ctx context.Context, module *types.Module) (*types.Module, error) - - DeleteByID(ctx context.Context, namespaceID uint64, moduleID ...uint64) error - } - - moduleNamespaceService interface { + namespaceService interface { FindByID(ctx context.Context, namespaceID uint64) (*types.Namespace, error) FindByHandle(ctx context.Context, handle string) (*types.Namespace, error) + Find(ctx context.Context, filter types.NamespaceFilter) (set types.NamespaceSet, f types.NamespaceFilter, err error) + + Create(ctx context.Context, namespace *types.Namespace) (*types.Namespace, error) + Update(ctx context.Context, namespace *types.Namespace) (*types.Namespace, error) + + DeleteByID(ctx context.Context, namespaceID uint64) error } - modulesHandlers struct { - reg modulesHandlerRegistry - ns moduleNamespaceService - mod moduleService + namespacesHandler struct { + reg namespacesHandlerRegistry + ns namespaceService + } + + namespaceLookup interface { + GetNamespace() (bool, uint64, string, *types.Namespace) } ) -func ModulesHandlers(reg modulesHandlerRegistry, ns moduleNamespaceService, rec moduleService) *modulesHandlers { - h := &modulesHandlers{ +func NamespacesHandler(reg namespacesHandlerRegistry, ns namespaceService) *namespacesHandler { + h := &namespacesHandler{ reg: reg, ns: ns, - mod: rec, } h.register() return h } -func (h modulesHandlers) resolveNamespace(ctx context.Context, id *uint64, handle string, res *types.Namespace) (err error) { - if *id == 0 { - if len(handle) > 0 { - if res, err = h.ns.FindByHandle(ctx, handle); err != nil { - return - } - } - - if res != nil { - *id = res.ID - } - } +func (h namespacesHandler) lookup(ctx context.Context, args *namespacesLookupArgs) (results *namespacesLookupResults, err error) { + results = &namespacesLookupResults{} + results.Namespace, err = lookupNamespace(ctx, h.ns, args) return } -func (h modulesHandlers) lookup(ctx context.Context, args *modulesLookupArgs) (results *modulesLookupResults, err error) { - results = &modulesLookupResults{} - - if err = h.resolveNamespace(ctx, &args.namespaceID, args.namespaceHandle, args.namespaceRes); err != nil { - return nil, err - } - - if args.moduleID > 0 { - results.Module, err = h.mod.FindByID(ctx, args.namespaceID, args.moduleID) +func getNamespaceID(ctx context.Context, svc namespaceService, args namespaceLookup) (uint64, error) { + _, ID, _, _ := args.GetNamespace() + if ID > 0 { + return ID, nil + } else if ns, err := lookupNamespace(ctx, svc, args); err != nil { + return 0, err } else { - results.Module, err = h.mod.FindByHandle(ctx, args.namespaceID, args.moduleHandle) + return ns.ID, nil + } +} + +func lookupNamespace(ctx context.Context, svc namespaceService, args namespaceLookup) (*types.Namespace, error) { + _, ID, handle, namespace := args.GetNamespace() + + switch { + case namespace != nil: + return namespace, nil + case ID > 0: + return svc.FindByID(ctx, ID) + case len(handle) > 0: + return svc.FindByHandle(ctx, handle) } - return + return nil, fmt.Errorf("empty namespace lookup params") } diff --git a/compose/automation/namespaces_handler.yaml b/compose/automation/namespaces_handler.yaml index 76f71108a..81aa9dc47 100644 --- a/compose/automation/namespaces_handler.yaml +++ b/compose/automation/namespaces_handler.yaml @@ -4,29 +4,21 @@ imports: - github.com/cortezaproject/corteza-server/compose/types params: - module: &module + namespaceLookup: &namespaceLookup required: true types: - - { wf: ID, go: uint64, suffix: ID } - - { wf: String, go: string, suffix: handle } + - { wf: ID, } + - { wf: Handle, } + - { wf: ComposeNamespace, suffix: res } - namespace: &namespace - required: true - types: - - { wf: ID, go: uint64, suffix: ID } - - { wf: String, go: string, suffix: handle } - - { wf: ComposeNamespace, go: "*types.Namespace", suffix: res } - - moduleReturnValue: &moduleReturnValue - wf: ComposeModule - go: "*types.Module" + rvNamespace: &rvNamespace + wf: ComposeNamespace functions: lookup: meta: - short: Lookup for compose module by ID + short: Lookup for compose namespace by ID params: - module: *module - namespace: *namespace + namespace: *namespaceLookup results: - module: *moduleReturnValue + namespace: *rvNamespace diff --git a/compose/automation/records_handler.gen.go b/compose/automation/records_handler.gen.go new file mode 100644 index 000000000..6a00ac077 --- /dev/null +++ b/compose/automation/records_handler.gen.go @@ -0,0 +1,1046 @@ +package automation + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// compose/automation/records_handler.yaml + +import ( + "context" + atypes "github.com/cortezaproject/corteza-server/automation/types" + "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/pkg/wfexec" +) + +var _ wfexec.ExecResponse + +type ( + recordsHandlerRegistry interface { + AddFunctions(ff ...*atypes.Function) + Type(ref string) expr.Type + } +) + +func (h recordsHandler) register() { + h.reg.AddFunctions( + h.Lookup(), + h.Search(), + h.Each(), + h.New(), + h.Validate(), + h.Create(), + h.Update(), + h.Delete(), + ) +} + +type ( + recordsLookupArgs struct { + hasModule bool + Module interface{} + moduleID uint64 + moduleHandle string + moduleRes *types.Module + + hasNamespace bool + Namespace interface{} + namespaceID uint64 + namespaceHandle string + namespaceRes *types.Namespace + + hasRecord bool + Record interface{} + recordID uint64 + recordRes *types.Record + } + + recordsLookupResults struct { + Record *types.Record + } +) + +func (a recordsLookupArgs) GetModule() (bool, uint64, string, *types.Module) { + return a.hasModule, a.moduleID, a.moduleHandle, a.moduleRes +} + +func (a recordsLookupArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { + return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes +} + +func (a recordsLookupArgs) GetRecord() (bool, uint64, *types.Record) { + return a.hasRecord, a.recordID, a.recordRes +} + +// Lookup function Lookup for compose record by ID +// +// expects implementation of lookup function: +// func (h recordsHandler) lookup(ctx context.Context, args *recordsLookupArgs) (results *recordsLookupResults, err error) { +// return +// } +func (h recordsHandler) Lookup() *atypes.Function { + return &atypes.Function{ + Ref: "composeRecordsLookup", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Lookup for compose record by ID", + }, + + Parameters: []*atypes.Param{ + { + Name: "module", + Types: []string{"ID", "Handle", "ComposeModule"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Module to set record type", + Description: "Even with unique record ID across all modules, module needs to be known\nbefore doing any record operations. Mainly because records of different\nmodules can be located in different stores.", + }, + }, + { + Name: "namespace", + Types: []string{"ID", "Handle", "ComposeNamespace"}, Required: true, + }, + { + Name: "record", + Types: []string{"ID", "ComposeRecord"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "record", + Types: []string{"ComposeRecord"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &recordsLookupArgs{ + hasModule: in.Has("module"), + hasNamespace: in.Has("namespace"), + hasRecord: in.Has("record"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Module argument + if args.hasModule { + aux := expr.Must(expr.Select(in, "module")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.moduleID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.moduleHandle = aux.Get().(string) + case h.reg.Type("ComposeModule").Type(): + args.moduleRes = aux.Get().(*types.Module) + } + } + + // Converting Namespace argument + if args.hasNamespace { + aux := expr.Must(expr.Select(in, "namespace")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.namespaceID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.namespaceHandle = aux.Get().(string) + case h.reg.Type("ComposeNamespace").Type(): + args.namespaceRes = aux.Get().(*types.Namespace) + } + } + + // Converting Record argument + if args.hasRecord { + aux := expr.Must(expr.Select(in, "record")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.recordID = aux.Get().(uint64) + case h.reg.Type("ComposeRecord").Type(): + args.recordRes = aux.Get().(*types.Record) + } + } + + var results *recordsLookupResults + if results, err = h.lookup(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Record (*types.Record) to ComposeRecord + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("ComposeRecord").Cast(results.Record); err != nil { + return + } else if err = expr.Assign(out, "record", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + recordsSearchArgs struct { + hasModule bool + Module interface{} + moduleID uint64 + moduleHandle string + moduleRes *types.Module + + hasNamespace bool + Namespace interface{} + namespaceID uint64 + namespaceHandle string + namespaceRes *types.Namespace + + hasQuery bool + Query string + + hasLabels bool + Labels map[string]string + + hasDeleted bool + Deleted uint64 + + hasSort bool + Sort string + + hasLimit bool + Limit uint64 + + hasIncTotal bool + IncTotal bool + + hasIncPageNavigation bool + IncPageNavigation bool + + hasPageCursor bool + PageCursor string + } + + recordsSearchResults struct { + Records []*types.Record + Total uint64 + PageCursor string + } +) + +func (a recordsSearchArgs) GetModule() (bool, uint64, string, *types.Module) { + return a.hasModule, a.moduleID, a.moduleHandle, a.moduleRes +} + +func (a recordsSearchArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { + return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes +} + +// Search function Searches for records and returns them +// +// expects implementation of search function: +// func (h recordsHandler) search(ctx context.Context, args *recordsSearchArgs) (results *recordsSearchResults, err error) { +// return +// } +func (h recordsHandler) Search() *atypes.Function { + return &atypes.Function{ + Ref: "composeRecordsSearch", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Searches for records and returns them", + }, + + Parameters: []*atypes.Param{ + { + Name: "module", + Types: []string{"ID", "Handle", "ComposeModule"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Module to set record type", + Description: "Even with unique record ID across all modules, module needs to be known\nbefore doing any record operations. Mainly because records of different\nmodules can be located in different stores.", + }, + }, + { + Name: "namespace", + Types: []string{"ID", "Handle", "ComposeNamespace"}, Required: true, + }, + { + Name: "query", + Types: []string{"String"}, + }, + { + Name: "labels", + Types: []string{"KV"}, + }, + { + Name: "deleted", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "sort", + Types: []string{"String"}, + }, + { + Name: "limit", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "incTotal", + Types: []string{"Boolean"}, + }, + { + Name: "incPageNavigation", + Types: []string{"Boolean"}, + }, + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "records", + Types: []string{"ComposeRecord"}, + IsArray: true, + }, + + { + Name: "total", + Types: []string{"UnsignedInteger"}, + Meta: &atypes.ParamMeta{ + Label: "Total records found", + Description: "Total items that satisfy given conditions.\n\nNeeds to be explicitly requested with incTotal argument", + }, + }, + + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &recordsSearchArgs{ + hasModule: in.Has("module"), + hasNamespace: in.Has("namespace"), + hasQuery: in.Has("query"), + hasLabels: in.Has("labels"), + hasDeleted: in.Has("deleted"), + hasSort: in.Has("sort"), + hasLimit: in.Has("limit"), + hasIncTotal: in.Has("incTotal"), + hasIncPageNavigation: in.Has("incPageNavigation"), + hasPageCursor: in.Has("pageCursor"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Module argument + if args.hasModule { + aux := expr.Must(expr.Select(in, "module")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.moduleID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.moduleHandle = aux.Get().(string) + case h.reg.Type("ComposeModule").Type(): + args.moduleRes = aux.Get().(*types.Module) + } + } + + // Converting Namespace argument + if args.hasNamespace { + aux := expr.Must(expr.Select(in, "namespace")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.namespaceID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.namespaceHandle = aux.Get().(string) + case h.reg.Type("ComposeNamespace").Type(): + args.namespaceRes = aux.Get().(*types.Namespace) + } + } + + var results *recordsSearchResults + if results, err = h.search(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Records (*types.Record) to Array (of ComposeRecord) + var ( + tval expr.TypedValue + tarr = make([]expr.TypedValue, len(results.Records)) + ) + + for i := range results.Records { + if tarr[i], err = h.reg.Type("ComposeRecord").Cast(results.Records[i]); err != nil { + return + } + } + + if tval, err = expr.NewArray(tarr); err != nil { + return + } else if err = expr.Assign(out, "records", tval); err != nil { + return + } + } + + { + // converting results.Total (uint64) to UnsignedInteger + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("UnsignedInteger").Cast(results.Total); err != nil { + return + } else if err = expr.Assign(out, "total", tval); err != nil { + return + } + } + + { + // converting results.PageCursor (string) to String + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("String").Cast(results.PageCursor); err != nil { + return + } else if err = expr.Assign(out, "pageCursor", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + recordsEachArgs struct { + hasModule bool + Module interface{} + moduleID uint64 + moduleHandle string + moduleRes *types.Module + + hasNamespace bool + Namespace interface{} + namespaceID uint64 + namespaceHandle string + namespaceRes *types.Namespace + + hasQuery bool + Query string + + hasLabels bool + Labels map[string]string + + hasDeleted bool + Deleted uint64 + + hasSort bool + Sort string + + hasLimit bool + Limit uint64 + + hasIncTotal bool + IncTotal bool + + hasIncPageNavigation bool + IncPageNavigation bool + + hasPageCursor bool + PageCursor string + } + + recordsEachResults struct { + Record *types.Record + Index uint64 + Total uint64 + } +) + +func (a recordsEachArgs) GetModule() (bool, uint64, string, *types.Module) { + return a.hasModule, a.moduleID, a.moduleHandle, a.moduleRes +} + +func (a recordsEachArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { + return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes +} + +// Each function Searches for records and iterates over results +// +// expects implementation of each function: +// func (h recordsHandler) each(ctx context.Context, args *recordsEachArgs) (results *recordsEachResults, err error) { +// return +// } +func (h recordsHandler) Each() *atypes.Function { + return &atypes.Function{ + Ref: "composeRecordsEach", + Kind: "iterator", + Meta: &atypes.FunctionMeta{ + Short: "Searches for records and iterates over results", + }, + + Parameters: []*atypes.Param{ + { + Name: "module", + Types: []string{"ID", "Handle", "ComposeModule"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Module to set record type", + Description: "Even with unique record ID across all modules, module needs to be known\nbefore doing any record operations. Mainly because records of different\nmodules can be located in different stores.", + }, + }, + { + Name: "namespace", + Types: []string{"ID", "Handle", "ComposeNamespace"}, Required: true, + }, + { + Name: "query", + Types: []string{"String"}, + }, + { + Name: "labels", + Types: []string{"KV"}, + }, + { + Name: "deleted", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "sort", + Types: []string{"String"}, + }, + { + Name: "limit", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "incTotal", + Types: []string{"Boolean"}, + }, + { + Name: "incPageNavigation", + Types: []string{"Boolean"}, + }, + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "record", + Types: []string{"ComposeRecord"}, + }, + + { + Name: "index", + Types: []string{"UnsignedInteger"}, + Meta: &atypes.ParamMeta{ + Label: "Iteration counter", + Description: "Zero-based number iteration counter", + }, + }, + + { + Name: "total", + Types: []string{"UnsignedInteger"}, + Meta: &atypes.ParamMeta{ + Label: "Total records found", + Description: "Total items that satisfy given conditions.\n\nNeeds to be explicitly requested with incTotal argument", + }, + }, + }, + + Iterator: func(ctx context.Context, in *expr.Vars) (out wfexec.IteratorHandler, err error) { + var ( + args = &recordsEachArgs{ + hasModule: in.Has("module"), + hasNamespace: in.Has("namespace"), + hasQuery: in.Has("query"), + hasLabels: in.Has("labels"), + hasDeleted: in.Has("deleted"), + hasSort: in.Has("sort"), + hasLimit: in.Has("limit"), + hasIncTotal: in.Has("incTotal"), + hasIncPageNavigation: in.Has("incPageNavigation"), + hasPageCursor: in.Has("pageCursor"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Module argument + if args.hasModule { + aux := expr.Must(expr.Select(in, "module")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.moduleID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.moduleHandle = aux.Get().(string) + case h.reg.Type("ComposeModule").Type(): + args.moduleRes = aux.Get().(*types.Module) + } + } + + // Converting Namespace argument + if args.hasNamespace { + aux := expr.Must(expr.Select(in, "namespace")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.namespaceID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.namespaceHandle = aux.Get().(string) + case h.reg.Type("ComposeNamespace").Type(): + args.namespaceRes = aux.Get().(*types.Namespace) + } + } + + return h.each(ctx, args) + }, + } +} + +type ( + recordsNewArgs struct { + hasModule bool + Module interface{} + moduleID uint64 + moduleHandle string + moduleRes *types.Module + + hasNamespace bool + Namespace interface{} + namespaceID uint64 + namespaceHandle string + namespaceRes *types.Namespace + } + + recordsNewResults struct { + Record *types.Record + } +) + +func (a recordsNewArgs) GetModule() (bool, uint64, string, *types.Module) { + return a.hasModule, a.moduleID, a.moduleHandle, a.moduleRes +} + +func (a recordsNewArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { + return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes +} + +// New function Make a new record +// +// expects implementation of new function: +// func (h recordsHandler) new(ctx context.Context, args *recordsNewArgs) (results *recordsNewResults, err error) { +// return +// } +func (h recordsHandler) New() *atypes.Function { + return &atypes.Function{ + Ref: "composeRecordsNew", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Make a new record", + }, + + Parameters: []*atypes.Param{ + { + Name: "module", + Types: []string{"ID", "Handle", "ComposeModule"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Module to set record type", + Description: "Even with unique record ID across all modules, module needs to be known\nbefore doing any record operations. Mainly because records of different\nmodules can be located in different stores.", + }, + }, + { + Name: "namespace", + Types: []string{"ID", "Handle", "ComposeNamespace"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "record", + Types: []string{"ComposeRecord"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &recordsNewArgs{ + hasModule: in.Has("module"), + hasNamespace: in.Has("namespace"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Module argument + if args.hasModule { + aux := expr.Must(expr.Select(in, "module")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.moduleID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.moduleHandle = aux.Get().(string) + case h.reg.Type("ComposeModule").Type(): + args.moduleRes = aux.Get().(*types.Module) + } + } + + // Converting Namespace argument + if args.hasNamespace { + aux := expr.Must(expr.Select(in, "namespace")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.namespaceID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.namespaceHandle = aux.Get().(string) + case h.reg.Type("ComposeNamespace").Type(): + args.namespaceRes = aux.Get().(*types.Namespace) + } + } + + var results *recordsNewResults + if results, err = h.new(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Record (*types.Record) to ComposeRecord + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("ComposeRecord").Cast(results.Record); err != nil { + return + } else if err = expr.Assign(out, "record", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + recordsValidateArgs struct { + hasRecord bool + Record *types.Record + } + + recordsValidateResults struct { + Valid bool + } +) + +// Validate function Validate record +// +// expects implementation of validate function: +// func (h recordsHandler) validate(ctx context.Context, args *recordsValidateArgs) (results *recordsValidateResults, err error) { +// return +// } +func (h recordsHandler) Validate() *atypes.Function { + return &atypes.Function{ + Ref: "composeRecordsValidate", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Validate record", + }, + + Parameters: []*atypes.Param{ + { + Name: "record", + Types: []string{"ComposeRecord"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "valid", + Types: []string{"Boolean"}, + Meta: &atypes.ParamMeta{ + Label: "Set to true when record is valid", + }, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &recordsValidateArgs{ + hasRecord: in.Has("record"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *recordsValidateResults + if results, err = h.validate(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Valid (bool) to Boolean + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("Boolean").Cast(results.Valid); err != nil { + return + } else if err = expr.Assign(out, "valid", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + recordsCreateArgs struct { + hasRecord bool + Record *types.Record + } + + recordsCreateResults struct { + Record *types.Record + } +) + +// Create function Creates and stores a new record +// +// expects implementation of create function: +// func (h recordsHandler) create(ctx context.Context, args *recordsCreateArgs) (results *recordsCreateResults, err error) { +// return +// } +func (h recordsHandler) Create() *atypes.Function { + return &atypes.Function{ + Ref: "composeRecordsCreate", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Creates and stores a new record", + }, + + Parameters: []*atypes.Param{ + { + Name: "record", + Types: []string{"ComposeRecord"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "record", + Types: []string{"ComposeRecord"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &recordsCreateArgs{ + hasRecord: in.Has("record"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *recordsCreateResults + if results, err = h.create(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Record (*types.Record) to ComposeRecord + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("ComposeRecord").Cast(results.Record); err != nil { + return + } else if err = expr.Assign(out, "record", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + recordsUpdateArgs struct { + hasRecord bool + Record *types.Record + } + + recordsUpdateResults struct { + Record *types.Record + } +) + +// Update function Updates an existing record +// +// expects implementation of update function: +// func (h recordsHandler) update(ctx context.Context, args *recordsUpdateArgs) (results *recordsUpdateResults, err error) { +// return +// } +func (h recordsHandler) Update() *atypes.Function { + return &atypes.Function{ + Ref: "composeRecordsUpdate", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Updates an existing record", + }, + + Parameters: []*atypes.Param{ + { + Name: "record", + Types: []string{"ComposeRecord"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "record", + Types: []string{"ComposeRecord"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &recordsUpdateArgs{ + hasRecord: in.Has("record"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *recordsUpdateResults + if results, err = h.update(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Record (*types.Record) to ComposeRecord + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("ComposeRecord").Cast(results.Record); err != nil { + return + } else if err = expr.Assign(out, "record", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + recordsDeleteArgs struct { + hasRecord bool + Record interface{} + recordID uint64 + recordRes *types.Record + } +) + +func (a recordsDeleteArgs) GetRecord() (bool, uint64, *types.Record) { + return a.hasRecord, a.recordID, a.recordRes +} + +// Delete function Soft deletes compose record by ID +// +// expects implementation of delete function: +// func (h recordsHandler) delete(ctx context.Context, args *recordsDeleteArgs) (err error) { +// return +// } +func (h recordsHandler) Delete() *atypes.Function { + return &atypes.Function{ + Ref: "composeRecordsDelete", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Soft deletes compose record by ID", + }, + + Parameters: []*atypes.Param{ + { + Name: "record", + Types: []string{"ID", "ComposeRecord"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &recordsDeleteArgs{ + hasRecord: in.Has("record"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Record argument + if args.hasRecord { + aux := expr.Must(expr.Select(in, "record")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.recordID = aux.Get().(uint64) + case h.reg.Type("ComposeRecord").Type(): + args.recordRes = aux.Get().(*types.Record) + } + } + + return out, h.delete(ctx, args) + }, + } +} diff --git a/compose/automation/records_handler.go b/compose/automation/records_handler.go new file mode 100644 index 000000000..2e7a3b5c9 --- /dev/null +++ b/compose/automation/records_handler.go @@ -0,0 +1,259 @@ +package automation + +import ( + "context" + "fmt" + "github.com/cortezaproject/corteza-server/compose/types" + . "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/wfexec" +) + +type ( + recordService interface { + FindByID(ctx context.Context, namespaceID, moduleID, recordID uint64) (*types.Record, error) + Find(ctx context.Context, filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) + + Create(ctx context.Context, record *types.Record) (*types.Record, error) + Update(ctx context.Context, record *types.Record) (*types.Record, error) + Bulk(ctx context.Context, oo ...*types.RecordBulkOperation) (types.RecordSet, error) + + Validate(ctx context.Context, rec *types.Record) error + + DeleteByID(ctx context.Context, namespaceID, moduleID uint64, recordID ...uint64) error + } + + recordsHandler struct { + reg recordsHandlerRegistry + ns namespaceService + mod moduleService + rec recordService + } + + recordSetIterator struct { + ptr int + set types.RecordSet + filter types.RecordFilter + } + + recordLookup interface { + GetRecord() (bool, uint64, *types.Record) + } +) + +func RecordsHandler(reg recordsHandlerRegistry, ns namespaceService, mod moduleService, rec recordService) *recordsHandler { + h := &recordsHandler{ + reg: reg, + ns: ns, + mod: mod, + rec: rec, + } + + h.register() + return h +} + +func (h recordsHandler) lookup(ctx context.Context, args *recordsLookupArgs) (results *recordsLookupResults, err error) { + results = &recordsLookupResults{} + results.Record, err = h.lookupRecord(ctx, args) + return +} + +func (h recordsHandler) search(ctx context.Context, args *recordsSearchArgs) (results *recordsSearchResults, err error) { + results = &recordsSearchResults{} + + var ( + f = types.RecordFilter{ + Query: args.Query, + Labels: args.Labels, + Deleted: filter.State(args.Deleted), + } + ) + + if ns, mod, err := h.loadCombo(ctx, args); err != nil { + return nil, err + } else { + f.ModuleID = mod.ID + f.NamespaceID = ns.ID + } + + if args.hasSort { + if err = f.Sort.Set(args.Sort); err != nil { + return + } + } + + if args.hasPageCursor { + if err = f.PageCursor.Decode(args.PageCursor); err != nil { + return + } + } + + f.IncTotal = args.IncTotal + f.IncPageNavigation = args.IncPageNavigation + + if args.hasLabels { + f.Labels = args.Labels + } + + if args.hasLimit { + f.Limit = uint(args.Limit) + } + + results.Records, _, err = h.rec.Find(ctx, f) + return +} + +func (h recordsHandler) each(ctx context.Context, args *recordsEachArgs) (out wfexec.IteratorHandler, err error) { + var ( + i = &recordSetIterator{} + f = types.RecordFilter{ + Query: args.Query, + Labels: args.Labels, + Deleted: filter.State(args.Deleted), + } + ) + + if ns, mod, err := h.loadCombo(ctx, args); err != nil { + return nil, err + } else { + f.ModuleID = mod.ID + f.NamespaceID = ns.ID + } + + if args.hasSort { + if err = f.Sort.Set(args.Sort); err != nil { + return nil, err + } + } + + if args.hasPageCursor { + if err = f.PageCursor.Decode(args.PageCursor); err != nil { + return nil, err + } + } + + f.IncTotal = args.IncTotal + f.IncPageNavigation = args.IncPageNavigation + + if args.hasLabels { + f.Labels = args.Labels + } + + if args.hasLimit { + f.Limit = uint(args.Limit) + } + + i.set, i.filter, err = h.rec.Find(ctx, f) + return i, err +} + +func (h recordsHandler) validate(ctx context.Context, args *recordsValidateArgs) (*recordsValidateResults, error) { + results := &recordsValidateResults{Valid: true} + if err := h.rec.Validate(ctx, args.Record); err != nil { + results.Valid = false + + //if rves, is := err.(*types.RecordValueErrorSet); is { + // results.Errors = rves + //} else { + // return nil, err + //} + } + + return results, nil +} + +func (h recordsHandler) new(ctx context.Context, args *recordsNewArgs) (*recordsNewResults, error) { + results := &recordsNewResults{} + namespace, module, err := h.loadCombo(ctx, args) + if err != nil { + return nil, err + } + + results.Record = &types.Record{ + ModuleID: module.ID, + NamespaceID: namespace.ID, + } + + results.Record.SetModule(module) + return results, nil +} + +func (h recordsHandler) create(ctx context.Context, args *recordsCreateArgs) (results *recordsCreateResults, err error) { + results = &recordsCreateResults{} + results.Record, err = h.rec.Create(ctx, args.Record) + return +} + +func (h recordsHandler) update(ctx context.Context, args *recordsUpdateArgs) (results *recordsUpdateResults, err error) { + results = &recordsUpdateResults{} + results.Record, err = h.rec.Update(ctx, args.Record) + return +} + +func (h recordsHandler) delete(ctx context.Context, args *recordsDeleteArgs) error { + if rec, err := h.lookupRecord(ctx, args); err != nil { + return err + } else { + return h.rec.DeleteByID(ctx, rec.NamespaceID, rec.ModuleID, rec.ID) + } +} + +func (h recordsHandler) lookupRecord(ctx context.Context, args recordLookup) (record *types.Record, err error) { + var ( + namespace *types.Namespace + module *types.Module + recordID uint64 + ) + + if _, recordID, record = args.GetRecord(); record != nil { + return + } + + if namespace, module, err = h.loadCombo(ctx, args); err != nil { + return + } + + return h.rec.FindByID(ctx, namespace.ID, module.ID, recordID) +} + +func (h recordsHandler) loadCombo(ctx context.Context, args interface{}) (namespace *types.Namespace, module *types.Module, err error) { + if lkp, is := args.(namespaceLookup); is { + if namespace, err = lookupNamespace(ctx, h.ns, lkp); err != nil { + err = fmt.Errorf("could not load namespace: %w", err) + return + } + } else { + err = fmt.Errorf("could not extract namespace lookup arguments") + return + } + + if lkp, is := args.(moduleLookup); is { + if module, err = lookupModule(ctx, h.ns, h.mod, lkp); err != nil { + err = fmt.Errorf("could not load module: %w", err) + return + } + } else { + err = fmt.Errorf("could not extract module lookup arguments") + return + } + + return +} + +func (i *recordSetIterator) More(context.Context, *Vars) (bool, error) { + return i.ptr < len(i.set), nil +} + +func (i *recordSetIterator) Start(context.Context, *Vars) error { i.ptr = 0; return nil } + +func (i *recordSetIterator) Next(context.Context, *Vars) (*Vars, error) { + out := RVars{ + "record": Must(NewComposeRecord(i.set[i.ptr])), + "index": Must(NewUnsignedInteger(i.ptr)), + "total": Must(NewUnsignedInteger(i.filter.Total)), + } + + i.ptr++ + return out.Vars(), nil +} diff --git a/compose/automation/records_handler.yaml b/compose/automation/records_handler.yaml new file mode 100644 index 000000000..a9465d180 --- /dev/null +++ b/compose/automation/records_handler.yaml @@ -0,0 +1,170 @@ +prefix: compose + +imports: + - github.com/cortezaproject/corteza-server/compose/types + +params: + moduleLookup: &moduleLookup + required: true + types: + - { wf: ID, } + - { wf: Handle, } + - { wf: ComposeModule, suffix: res } + meta: + label: Module to set record type + description: |- + Even with unique record ID across all modules, module needs to be known + before doing any record operations. Mainly because records of different + modules can be located in different stores. + + namespaceLookup: &namespaceLookup + required: true + types: + - { wf: ID, } + - { wf: Handle, } + - { wf: ComposeNamespace, suffix: res } + + recordLookup: &recordLookup + required: true + types: + - { wf: ID, } + - { wf: ComposeRecord, suffix: res } + + record: &record + required: true + types: + - { wf: ComposeRecord, } + + rvRecord: &rvRecord + wf: ComposeRecord + + rvTotal: &rvTotal + wf: UnsignedInteger + meta: + label: Total records found + description: |- + Total items that satisfy given conditions. + + Needs to be explicitly requested with incTotal argument + + rvIndex: &rvIndex + wf: UnsignedInteger + meta: + label: Iteration counter + description: |- + Zero-based number iteration counter + + rvPageCursor: &rvPageCursor + wf: String + + filterParams: &filterParams + module: *moduleLookup + namespace: *namespaceLookup + query: + types: + - { wf: String } + labels: + types: + - { wf: KV } + deleted: + types: + - { wf: UnsignedInteger } + sort: + types: + - { wf: String } + limit: + types: + - { wf: UnsignedInteger } + incTotal: + types: + - { wf: Boolean } + incPageNavigation: + types: + - { wf: Boolean } + pageCursor: + types: + - { wf: String } + +functions: + lookup: + meta: + short: Lookup for compose record by ID + params: + module: *moduleLookup + namespace: *namespaceLookup + record: *recordLookup + results: + record: *rvRecord + + search: + meta: + short: Searches for records and returns them + params: *filterParams + results: + records: + <<: *rvRecord + isArray: true + total: *rvTotal + pageCursor: *rvPageCursor + + each: + kind: iterator + meta: + short: Searches for records and iterates over results + params: *filterParams + results: + record: *rvRecord + index: *rvIndex + total: *rvTotal + + new: + meta: + short: Make a new record + params: + module: *moduleLookup + namespace: *namespaceLookup + results: + record: *rvRecord + + validate: + meta: + short: Validate record + params: + record: *record + results: + valid: + wf: Boolean + meta: + label: Set to true when record is valid + + create: + meta: + short: Creates and stores a new record + params: + record: + <<: *record + required: true + results: + record: *rvRecord + + update: + meta: + short: Updates an existing record + params: + record: + <<: *record + required: true + results: + record: *rvRecord + + delete: + meta: + short: Soft deletes compose record by ID + params: + record: *recordLookup + +# undelete: +# meta: +# short: Recovers deleted record by ID +# params: +# record: *recordLookup diff --git a/compose/service/record.go b/compose/service/record.go index aabc393d0..7c87ac5be 100644 --- a/compose/service/record.go +++ b/compose/service/record.go @@ -84,6 +84,8 @@ type ( Update(ctx context.Context, record *types.Record) (*types.Record, error) Bulk(ctx context.Context, oo ...*types.RecordBulkOperation) (types.RecordSet, error) + Validate(ctx context.Context, rec *types.Record) error + DeleteByID(ctx context.Context, namespaceID, moduleID uint64, recordID ...uint64) error Organize(ctx context.Context, namespaceID, moduleID, recordID uint64, sortingField, sortingValue, sortingFilter, valueField, value string) error @@ -1120,6 +1122,15 @@ func (svc record) Organize(ctx context.Context, namespaceID, moduleID, recordID return svc.recordAction(ctx, aProps, RecordActionOrganize, err) } +func (svc record) Validate(ctx context.Context, rec *types.Record) error { + if m, err := loadModule(ctx, svc.store, rec.ModuleID); err != nil { + return err + } else { + rec.Values = values.Sanitizer().Run(m, rec.Values) + return values.Validator().Run(ctx, svc.store, m, rec) + } +} + // TriggerScript loads requested record sanitizes and validates values and passes all to the automation script // // For backward compatibility (of controllers), it returns module+record diff --git a/compose/service/service.go b/compose/service/service.go index dc61e46ca..818a9fb64 100644 --- a/compose/service/service.go +++ b/compose/service/service.go @@ -3,6 +3,8 @@ package service import ( "context" "fmt" + automationService "github.com/cortezaproject/corteza-server/automation/service" + "github.com/cortezaproject/corteza-server/compose/automation" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/actionlog" "github.com/cortezaproject/corteza-server/pkg/corredor" @@ -151,6 +153,31 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config) RegisterIteratorProviders() + automationService.Registry().AddTypes( + automation.ComposeNamespace{}, + automation.ComposeModule{}, + automation.ComposeRecord{}, + automation.ComposeRecordValues{}, + ) + + automation.RecordsHandler( + automationService.Registry(), + DefaultNamespace, + DefaultModule, + DefaultRecord, + ) + + automation.ModulesHandler( + automationService.Registry(), + DefaultNamespace, + DefaultModule, + ) + + automation.NamespacesHandler( + automationService.Registry(), + DefaultNamespace, + ) + return nil } diff --git a/compose/types/record.go b/compose/types/record.go index e9cf38059..434c29c9d 100644 --- a/compose/types/record.go +++ b/compose/types/record.go @@ -108,6 +108,10 @@ func (r *Record) SetModule(m *Module) { } } +func (r *Record) GetModule() *Module { + return r.module +} + func (r Record) Clone() *Record { c := &r c.Values = r.Values.Clone()