Introduce DAL model ident formatter to simplify the interfaces
This commit is contained in:
parent
fb7e9b5ce1
commit
a0a6314ea9
@ -336,7 +336,9 @@ func (app *CortezaApp) InitServices(ctx context.Context) (err error) {
|
||||
// DB_DSN is the default connection with full capabilities
|
||||
primaryDalConnection.Config.Connection,
|
||||
dal.ConnectionMeta{
|
||||
ConnectionDefaults: primaryDalConnection.ConnectionDefaults(),
|
||||
DefaultModelIdent: primaryDalConnection.Config.DefaultModelIdent,
|
||||
DefaultAttributeIdent: primaryDalConnection.Config.DefaultAttributeIdent,
|
||||
DefaultPartitionFormat: primaryDalConnection.Config.DefaultPartitionFormat,
|
||||
// @todo make it configurable from env
|
||||
SensitivityLevel: 0,
|
||||
Label: primaryDalConnection.Handle,
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
|
||||
type (
|
||||
dalDDL interface {
|
||||
ConnectionDefaults(ctx context.Context, connectionID uint64) (dft dal.ConnectionDefaults, err error)
|
||||
ModelIdentFormatter(connectionID uint64) (f *dal.IdentFormatter, err error)
|
||||
|
||||
ReloadModel(ctx context.Context, models ...*dal.Model) (err error)
|
||||
AddModel(ctx context.Context, models ...*dal.Model) (err error)
|
||||
@ -100,16 +100,14 @@ func (svc *module) ReloadDALModels(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
func (svc *module) moduleToModel(ctx context.Context, ns *types.Namespace, mod *types.Module) (*dal.Model, error) {
|
||||
ccfg, err := svc.dal.ConnectionDefaults(ctx, mod.ModelConfig.ConnectionID)
|
||||
formatter, tplParts, err := svc.prepareModelFormatter(ns, mod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getCodec := moduleFieldCodecBuilder(mod.ModelConfig.Partitioned, ccfg)
|
||||
|
||||
// Metadata
|
||||
out := &dal.Model{
|
||||
ConnectionID: mod.ModelConfig.ConnectionID,
|
||||
Ident: svc.formatPartitionIdent(ns, mod, ccfg),
|
||||
Label: mod.Handle,
|
||||
|
||||
Attributes: make(dal.AttributeSet, len(mod.Fields)),
|
||||
@ -121,6 +119,14 @@ func (svc *module) moduleToModel(ctx context.Context, ns *types.Namespace, mod *
|
||||
Resource: mod.RbacResource(),
|
||||
}
|
||||
|
||||
var ok bool
|
||||
out.Ident, ok = formatter.ModelIdent(ctx, mod.ModelConfig.Partitioned, mod.ModelConfig.PartitionFormat, tplParts...)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid model identifier generated: %s", out.Ident)
|
||||
}
|
||||
|
||||
getCodec := moduleFieldCodecBuilder(mod.ModelConfig.Partitioned, formatter)
|
||||
|
||||
// Handle user-defined fields
|
||||
for i, f := range mod.Fields {
|
||||
out.Attributes[i], err = svc.moduleFieldToAttribute(getCodec, ns, mod, f)
|
||||
@ -286,43 +292,18 @@ func (svc *module) moduleFieldToAttribute(getCodec func(f *types.ModuleField) da
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *module) formatPartitionIdent(ns *types.Namespace, mod *types.Module, cfg dal.ConnectionDefaults) string {
|
||||
if !mod.ModelConfig.Partitioned {
|
||||
return cfg.ModelIdent
|
||||
}
|
||||
|
||||
pfmt := mod.ModelConfig.PartitionFormat
|
||||
if pfmt == "" {
|
||||
pfmt = cfg.PartitionFormat
|
||||
}
|
||||
if pfmt == "" {
|
||||
// @todo put in config or something
|
||||
pfmt = "compose_record_{{namespace}}_{{module}}"
|
||||
}
|
||||
|
||||
// @note we must not use name here since it is translatable
|
||||
mh, _ := handle.Cast(nil, mod.Handle, strconv.FormatUint(mod.ID, 10))
|
||||
nsh, _ := handle.Cast(nil, ns.Slug, strconv.FormatUint(ns.ID, 10))
|
||||
rpl := strings.NewReplacer(
|
||||
"{{module}}", mh,
|
||||
"{{namespace}}", nsh,
|
||||
)
|
||||
|
||||
return rpl.Replace(pfmt)
|
||||
}
|
||||
|
||||
func moduleFieldCodecBuilder(partitioned bool, cfg dal.ConnectionDefaults) func(f *types.ModuleField) dal.Codec {
|
||||
func moduleFieldCodecBuilder(partitioned bool, formatter *dal.IdentFormatter) func(f *types.ModuleField) dal.Codec {
|
||||
return func(f *types.ModuleField) dal.Codec {
|
||||
return moduleFieldCodec(f, partitioned, cfg)
|
||||
return moduleFieldCodec(f, partitioned, formatter)
|
||||
}
|
||||
}
|
||||
|
||||
func moduleFieldCodec(f *types.ModuleField, partitioned bool, cfg dal.ConnectionDefaults) (strat dal.Codec) {
|
||||
func moduleFieldCodec(f *types.ModuleField, partitioned bool, formatter *dal.IdentFormatter) (strat dal.Codec) {
|
||||
if partitioned {
|
||||
strat = &dal.CodecPlain{}
|
||||
} else {
|
||||
ident := cfg.AttributeIdent
|
||||
if ident == "" {
|
||||
ident, ok := formatter.AttributeIdent(partitioned, f.Name)
|
||||
if !ok {
|
||||
// @todo put in configs or something
|
||||
ident = "values"
|
||||
}
|
||||
@ -361,3 +342,19 @@ func (svc *module) addModuleToDAL(ctx context.Context, ns *types.Namespace, mod
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *module) prepareModelFormatter(ns *types.Namespace, mod *types.Module) (formatter *dal.IdentFormatter, tplParts []string, err error) {
|
||||
formatter, err = svc.dal.ModelIdentFormatter(mod.ModelConfig.ConnectionID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
modHandle, _ := handle.Cast(nil, mod.Handle, strconv.FormatUint(mod.ID, 10))
|
||||
nsHandle, _ := handle.Cast(nil, ns.Slug, strconv.FormatUint(ns.ID, 10))
|
||||
tplParts = []string{
|
||||
"module", modHandle,
|
||||
"namespace", nsHandle,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -1,14 +1,27 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/PaesslerAG/gval"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/modern-go/reflect2"
|
||||
)
|
||||
|
||||
type (
|
||||
IdentFormatter struct {
|
||||
defaultModelIdent string
|
||||
defaultAttributeIdent string
|
||||
|
||||
defaultPartitionFormat string
|
||||
|
||||
things map[string]any
|
||||
|
||||
partitionFormatValidator gval.Evaluable
|
||||
}
|
||||
|
||||
// ModelFilter is used to retrieve a model from the DAL based on given params
|
||||
ModelFilter struct {
|
||||
ConnectionID uint64
|
||||
@ -181,3 +194,84 @@ func (m Model) Validate() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *IdentFormatter) AddEvalParam(params map[string]any) {
|
||||
if f.things == nil {
|
||||
f.things = make(map[string]any)
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
f.things[k] = v
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (f IdentFormatter) getEvalParams(ident string) (out map[string]any) {
|
||||
out = map[string]any{
|
||||
"ident": ident,
|
||||
}
|
||||
|
||||
for k, v := range f.things {
|
||||
out[k] = v
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (f IdentFormatter) ModelIdent(ctx context.Context, partitioned bool, tpl string, kv ...string) (out string, ok bool) {
|
||||
ok = true
|
||||
|
||||
if !partitioned {
|
||||
return f.defaultModelIdent, ok
|
||||
}
|
||||
|
||||
// A bit of preprocessing
|
||||
{
|
||||
if len(kv)%2 != 0 {
|
||||
panic("ModelIdentFormatter.ModelIdent requires key/value pairs")
|
||||
}
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
kv[i] = fmt.Sprintf("{{%s}}", kv[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Template preparation with defaulting based on provided KV
|
||||
{
|
||||
if tpl == "" {
|
||||
tpl = f.defaultPartitionFormat
|
||||
}
|
||||
if tpl == "" {
|
||||
pts := make([]string, 0, len(kv)/2)
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
pts = append(pts, kv[i])
|
||||
}
|
||||
tpl = strings.Join(pts, "_")
|
||||
}
|
||||
if tpl == "" {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
rpl := strings.NewReplacer(kv...)
|
||||
out = rpl.Replace(tpl)
|
||||
|
||||
if f.partitionFormatValidator != nil {
|
||||
var err error
|
||||
ok, err = f.partitionFormatValidator.EvalBool(ctx, f.getEvalParams(out))
|
||||
ok = ok && (err == nil)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (f IdentFormatter) AttributeIdent(partitioned bool, ident string) (out string, ok bool) {
|
||||
if !partitioned {
|
||||
if f.defaultAttributeIdent != "" {
|
||||
return f.defaultAttributeIdent, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
return ident, ident != ""
|
||||
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/dal/capabilities"
|
||||
"github.com/cortezaproject/corteza-server/pkg/expr"
|
||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@ -16,21 +17,19 @@ type (
|
||||
sensitivityLevel uint64
|
||||
|
||||
connection Connection
|
||||
Defaults ConnectionDefaults
|
||||
meta ConnectionMeta
|
||||
}
|
||||
|
||||
ConnectionMeta struct {
|
||||
ConnectionDefaults
|
||||
|
||||
SensitivityLevel uint64
|
||||
Label string
|
||||
}
|
||||
|
||||
ConnectionDefaults struct {
|
||||
ModelIdent string
|
||||
AttributeIdent string
|
||||
DefaultModelIdent string
|
||||
DefaultAttributeIdent string
|
||||
|
||||
PartitionFormat string
|
||||
DefaultPartitionFormat string
|
||||
|
||||
PartitionValidator string
|
||||
}
|
||||
|
||||
service struct {
|
||||
@ -71,7 +70,7 @@ func InitGlobalService(ctx context.Context, log *zap.Logger, inDev bool, cp Conn
|
||||
|
||||
var err error
|
||||
cw := &connectionWrap{
|
||||
Defaults: cm.ConnectionDefaults,
|
||||
meta: cm,
|
||||
sensitivityLevel: cm.SensitivityLevel,
|
||||
label: cm.Label,
|
||||
}
|
||||
@ -134,7 +133,7 @@ func (svc *service) AddConnection(ctx context.Context, connectionID uint64, cp C
|
||||
|
||||
cw := &connectionWrap{
|
||||
connectionID: connectionID,
|
||||
Defaults: cm.ConnectionDefaults,
|
||||
meta: cm,
|
||||
sensitivityLevel: cm.SensitivityLevel,
|
||||
label: cm.Label,
|
||||
}
|
||||
@ -183,16 +182,6 @@ func (svc *service) UpdateConnection(ctx context.Context, connectionID uint64, c
|
||||
return svc.AddConnection(ctx, connectionID, cp, cm, capabilities...)
|
||||
}
|
||||
|
||||
// ConnectionDefaultreturns the defaults we can use with this connection
|
||||
func (svc *service) ConnectionDefaults(ctx context.Context, connectionID uint64) (dft ConnectionDefaults, err error) {
|
||||
wrap, _, err := svc.getConnection(ctx, connectionID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return wrap.Defaults, nil
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
@ -280,6 +269,34 @@ func (svc *service) ReloadModel(ctx context.Context, models ...*Model) (err erro
|
||||
return svc.AddModel(ctx, models...)
|
||||
}
|
||||
|
||||
func (svc *service) ModelIdentFormatter(connectionID uint64) (f *IdentFormatter, err error) {
|
||||
// @todo ...
|
||||
c := svc.connections[connectionID]
|
||||
if connectionID == 0 {
|
||||
c = svc.primary
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
err = fmt.Errorf("connection %d does not exist", connectionID)
|
||||
return
|
||||
}
|
||||
|
||||
f = &IdentFormatter{
|
||||
defaultModelIdent: c.meta.DefaultModelIdent,
|
||||
defaultAttributeIdent: c.meta.DefaultAttributeIdent,
|
||||
defaultPartitionFormat: c.meta.DefaultPartitionFormat,
|
||||
}
|
||||
|
||||
if c.meta.PartitionValidator != "" {
|
||||
f.partitionFormatValidator, err = expr.Parser().NewEvaluable(c.meta.PartitionValidator)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AddModel adds support for a new model
|
||||
func (svc *service) AddModel(ctx context.Context, models ...*Model) (err error) {
|
||||
svc.logger.Debug("adding model", zap.Int("count", len(models)))
|
||||
@ -412,7 +429,7 @@ func (svc *service) modelByConnection(models ModelSet) (out map[uint64]ModelSet)
|
||||
|
||||
func (svc *service) registerModel(ctx context.Context, cw *connectionWrap, connectionID uint64, models ModelSet) (err error) {
|
||||
for _, model := range models {
|
||||
svc.logger.Debug("adding model for connection", zap.Uint64("connectionID", connectionID), zap.String("resource type", model.ResourceType), zap.String("resource model", model.Resource))
|
||||
svc.logger.Debug("adding model for connection", zap.Uint64("connectionID", connectionID), zap.String("resource type", model.ResourceType), zap.String("resource model", model.Resource), zap.String("model ident", model.Ident))
|
||||
|
||||
existing := svc.GetModelByResource(connectionID, model.ResourceType, model.Resource)
|
||||
if existing != nil {
|
||||
|
||||
@ -296,13 +296,11 @@ func (svc *dalConnection) reloadConnections(ctx context.Context) (err error) {
|
||||
func (svc *dalConnection) makeConnectionMeta(ctx context.Context, c *types.DalConnection) (cm dal.ConnectionMeta, err error) {
|
||||
// @todo we could probably utilize connection params more here
|
||||
cm = dal.ConnectionMeta{
|
||||
ConnectionDefaults: dal.ConnectionDefaults{
|
||||
ModelIdent: c.Config.DefaultModelIdent,
|
||||
AttributeIdent: c.Config.DefaultAttributeIdent,
|
||||
PartitionFormat: c.Config.DefaultPartitionFormat,
|
||||
},
|
||||
SensitivityLevel: c.SensitivityLevel,
|
||||
Label: c.Handle,
|
||||
DefaultModelIdent: c.Config.DefaultModelIdent,
|
||||
DefaultAttributeIdent: c.Config.DefaultAttributeIdent,
|
||||
DefaultPartitionFormat: c.Config.DefaultPartitionFormat,
|
||||
SensitivityLevel: c.SensitivityLevel,
|
||||
Label: c.Handle,
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@ -78,14 +78,6 @@ var (
|
||||
DalPrimaryConnectionResourceType = "corteza::system:primary_dal_connection"
|
||||
)
|
||||
|
||||
func (c DalConnection) ConnectionDefaults() dal.ConnectionDefaults {
|
||||
return dal.ConnectionDefaults{
|
||||
ModelIdent: c.Config.DefaultModelIdent,
|
||||
AttributeIdent: c.Config.DefaultAttributeIdent,
|
||||
PartitionFormat: c.Config.DefaultPartitionFormat,
|
||||
}
|
||||
}
|
||||
|
||||
func (c DalConnection) ActiveCapabilities() capabilities.Set {
|
||||
return c.Capabilities.Supported.
|
||||
Union(c.Capabilities.Enforced).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user