diff --git a/internal/app/app_plugins.go b/internal/app/app_plugins.go index 5668f61..baa75d1 100644 --- a/internal/app/app_plugins.go +++ b/internal/app/app_plugins.go @@ -35,29 +35,29 @@ func NewAppPlugins(app *App, pluginConfig map[string]utils.PluginSettings, appAc } } -func (p *AppPlugins) GetPlugin(pluginInfo *PluginInfo, accountName string) (any, error) { +func (p *AppPlugins) GetPlugin(pluginInfo *utils.PluginInfo, accountName string) (any, error) { p.Lock() defer p.Unlock() - plugin, ok := p.plugins[pluginInfo.moduleName] + plugin, ok := p.plugins[pluginInfo.ModuleName] if ok { // Already initialized, use that return plugin, nil } // If account name is specified, use that to lookup the account map - accountLookupName := pluginInfo.pluginPath + accountLookupName := pluginInfo.PluginPath if accountName != "" { - accountLookupName = fmt.Sprintf("%s%s%s", pluginInfo.pluginPath, util.ACCOUNT_SEPERATOR, accountName) + accountLookupName = fmt.Sprintf("%s%s%s", pluginInfo.PluginPath, util.ACCOUNT_SEPERATOR, accountName) } - pluginAccount := pluginInfo.pluginPath + pluginAccount := pluginInfo.PluginPath _, ok = p.accountMap[accountLookupName] if ok { pluginAccount = p.accountMap[accountLookupName] // If it is just account name, make it full plugin path if !strings.Contains(pluginAccount, util.ACCOUNT_SEPERATOR) { - pluginAccount = fmt.Sprintf("%s%s%s", pluginInfo.pluginPath, util.ACCOUNT_SEPERATOR, pluginAccount) + pluginAccount = fmt.Sprintf("%s%s%s", pluginInfo.PluginPath, util.ACCOUNT_SEPERATOR, pluginAccount) } } @@ -66,17 +66,17 @@ func (p *AppPlugins) GetPlugin(pluginInfo *PluginInfo, accountName string) (any, appConfig = p.pluginConfig[pluginAccount] } - pluginContext := &PluginContext{ + pluginContext := &utils.PluginContext{ Logger: p.app.Logger, AppId: p.app.AppEntry.Id, StoreInfo: p.app.storeInfo, Config: appConfig, } - plugin, err := pluginInfo.builder(pluginContext) + plugin, err := pluginInfo.Builder(pluginContext) if err != nil { - return nil, fmt.Errorf("error creating plugin %s: %w", pluginInfo.funcName, err) + return nil, fmt.Errorf("error creating plugin %s: %w", pluginInfo.FuncName, err) } - p.plugins[pluginInfo.pluginPath] = plugin + p.plugins[pluginInfo.PluginPath] = plugin return plugin, nil } diff --git a/internal/app/app_plugins_test.go b/internal/app/app_plugins_test.go index 85ea292..1b507d9 100644 --- a/internal/app/app_plugins_test.go +++ b/internal/app/app_plugins_test.go @@ -33,70 +33,70 @@ func TestGetPlugin(t *testing.T) { appPlugins := NewAppPlugins(app, pluginConfig, appAccounts) // Define the pluginInfo and accountName for testing - pluginInfo := &PluginInfo{ - moduleName: "plugin1", - pluginPath: "plugin1.in", - funcName: "Plugin1Builder", + pluginInfo := &utils.PluginInfo{ + ModuleName: "plugin1", + PluginPath: "plugin1.in", + FuncName: "Plugin1Builder", } // Test with no account, no account link - pluginInfo.builder = func(pluginContext *PluginContext) (any, error) { + pluginInfo.Builder = func(pluginContext *utils.PluginContext) (any, error) { testutil.AssertEqualsString(t, "match key", "v1", pluginContext.Config["key"].(string)) return nil, nil } plugin, err := appPlugins.GetPlugin(pluginInfo, "") testutil.AssertNoError(t, err) - if plugin != appPlugins.plugins[pluginInfo.moduleName] { - t.Errorf("Expected %v, got %v", appPlugins.plugins[pluginInfo.moduleName], plugin) + if plugin != appPlugins.plugins[pluginInfo.ModuleName] { + t.Errorf("Expected %v, got %v", appPlugins.plugins[pluginInfo.ModuleName], plugin) } // Test with no account, with account link - pluginInfo.moduleName = "plugin2" - pluginInfo.pluginPath = "plugin2.in" - pluginInfo.builder = func(pluginContext *PluginContext) (any, error) { + pluginInfo.ModuleName = "plugin2" + pluginInfo.PluginPath = "plugin2.in" + pluginInfo.Builder = func(pluginContext *utils.PluginContext) (any, error) { testutil.AssertEqualsString(t, "match key", "v5", pluginContext.Config["key"].(string)) return nil, nil } plugin, err = appPlugins.GetPlugin(pluginInfo, "") testutil.AssertNoError(t, err) - if plugin != appPlugins.plugins[pluginInfo.moduleName] { - t.Errorf("Expected %v, got %v", appPlugins.plugins[pluginInfo.moduleName], plugin) + if plugin != appPlugins.plugins[pluginInfo.ModuleName] { + t.Errorf("Expected %v, got %v", appPlugins.plugins[pluginInfo.ModuleName], plugin) } // Test with account, with no account link - pluginInfo.pluginPath = "plugin2.in#account1" - pluginInfo.builder = func(pluginContext *PluginContext) (any, error) { + pluginInfo.PluginPath = "plugin2.in#account1" + pluginInfo.Builder = func(pluginContext *utils.PluginContext) (any, error) { testutil.AssertEqualsString(t, "match key", "v4", pluginContext.Config["key"].(string)) return nil, nil } plugin, err = appPlugins.GetPlugin(pluginInfo, "") testutil.AssertNoError(t, err) - if plugin != appPlugins.plugins[pluginInfo.moduleName] { - t.Errorf("Expected %v, got %v", appPlugins.plugins[pluginInfo.moduleName], plugin) + if plugin != appPlugins.plugins[pluginInfo.ModuleName] { + t.Errorf("Expected %v, got %v", appPlugins.plugins[pluginInfo.ModuleName], plugin) } // Test with account, with account link - pluginInfo.pluginPath = "plugin2.in#account2" - pluginInfo.builder = func(pluginContext *PluginContext) (any, error) { + pluginInfo.PluginPath = "plugin2.in#account2" + pluginInfo.Builder = func(pluginContext *utils.PluginContext) (any, error) { testutil.AssertEqualsString(t, "match key", "v6", pluginContext.Config["key"].(string)) return nil, nil } plugin, err = appPlugins.GetPlugin(pluginInfo, "") testutil.AssertNoError(t, err) - if plugin != appPlugins.plugins[pluginInfo.moduleName] { - t.Errorf("Expected %v, got %v", appPlugins.plugins[pluginInfo.moduleName], plugin) + if plugin != appPlugins.plugins[pluginInfo.ModuleName] { + t.Errorf("Expected %v, got %v", appPlugins.plugins[pluginInfo.ModuleName], plugin) } // Test with invalid account - pluginInfo.pluginPath = "plugin2.in#invalid" - pluginInfo.builder = func(pluginContext *PluginContext) (any, error) { + pluginInfo.PluginPath = "plugin2.in#invalid" + pluginInfo.Builder = func(pluginContext *utils.PluginContext) (any, error) { // Config should have no entries testutil.AssertEqualsInt(t, "match key", 0, len(pluginContext.Config)) return nil, nil } plugin, err = appPlugins.GetPlugin(pluginInfo, "") testutil.AssertNoError(t, err) - if plugin != appPlugins.plugins[pluginInfo.moduleName] { - t.Errorf("Expected %v, got %v", appPlugins.plugins[pluginInfo.moduleName], plugin) + if plugin != appPlugins.plugins[pluginInfo.ModuleName] { + t.Errorf("Expected %v, got %v", appPlugins.plugins[pluginInfo.ModuleName], plugin) } } diff --git a/internal/app/plugin.go b/internal/app/plugin.go index 50411f6..b3acf8f 100644 --- a/internal/app/plugin.go +++ b/internal/app/plugin.go @@ -19,71 +19,42 @@ import ( "go.starlark.net/starlarkstruct" ) -type PluginContext struct { - Logger *utils.Logger - AppId utils.AppId - StoreInfo *utils.StoreInfo - Config utils.PluginSettings -} - -type NewPluginFunc func(pluginContext *PluginContext) (any, error) - var ( loaderInitMutex sync.Mutex - builtInPlugins map[string]PluginMap + builtInPlugins map[string]utils.PluginMap ) func init() { - builtInPlugins = make(map[string]PluginMap) + builtInPlugins = make(map[string]utils.PluginMap) } // RegisterPlugin registers a plugin with Clace -func RegisterPlugin(name string, builder NewPluginFunc, funcs []PluginFunc) { +func RegisterPlugin(name string, builder utils.NewPluginFunc, funcs []utils.PluginFunc) { loaderInitMutex.Lock() defer loaderInitMutex.Unlock() pluginPath := fmt.Sprintf("%s.%s", name, util.BUILTIN_PLUGIN_SUFFIX) - pluginMap := make(PluginMap) + pluginMap := make(utils.PluginMap) for _, f := range funcs { - info := PluginInfo{ - moduleName: name, - pluginPath: pluginPath, - funcName: f.name, - isRead: f.isRead, - handlerName: f.functionName, - builder: builder, + info := utils.PluginInfo{ + ModuleName: name, + PluginPath: pluginPath, + FuncName: f.Name, + IsRead: f.IsRead, + HandlerName: f.FunctionName, + Builder: builder, } - pluginMap[f.name] = &info + pluginMap[f.Name] = &info } builtInPlugins[pluginPath] = pluginMap } -// PluginMap is the plugin function mapping to PluginFuncs -type PluginMap map[string]*PluginInfo - -// PluginFunc is the Clace plugin function mapping to starlark function -type PluginFunc struct { - name string - isRead bool - functionName string -} - -// PluginFuncInfo is the Clace plugin function info for the starlark function -type PluginInfo struct { - moduleName string // exec - pluginPath string // exec.in - funcName string // run - isRead bool - handlerName string - builder NewPluginFunc -} - func CreatePluginApi( f func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error), isRead bool, -) PluginFunc { +) utils.PluginFunc { funcVal := runtime.FuncForPC(reflect.ValueOf(f).Pointer()) if funcVal == nil { @@ -101,7 +72,7 @@ func CreatePluginApi( func CreatePluginApiName( f func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error), isRead bool, - name string) PluginFunc { + name string) utils.PluginFunc { funcVal := runtime.FuncForPC(reflect.ValueOf(f).Pointer()) if funcVal == nil { panic(fmt.Errorf("function %s not found during plugin register", name)) @@ -119,10 +90,10 @@ func CreatePluginApiName( panic(fmt.Errorf("function %s is not an exported method during plugin register", funcName)) } - return PluginFunc{ - name: name, - isRead: isRead, - functionName: funcName, + return utils.PluginFunc{ + Name: name, + IsRead: isRead, + FunctionName: funcName, } } @@ -169,7 +140,7 @@ func parseModulePath(moduleFullPath string) (string, string, string) { } // pluginLookup looks up the plugin. Audit checks need to be done by the caller -func (a *App) pluginLookup(_ *starlark.Thread, module string) (PluginMap, error) { +func (a *App) pluginLookup(_ *starlark.Thread, module string) (utils.PluginMap, error) { pluginDict, ok := builtInPlugins[module] if !ok { return nil, fmt.Errorf("module %s not found", module) // TODO extend loading @@ -178,7 +149,7 @@ func (a *App) pluginLookup(_ *starlark.Thread, module string) (PluginMap, error) return pluginDict, nil } -func (a *App) pluginHook(modulePath, accountName, functionName string, pluginInfo *PluginInfo) *starlark.Builtin { +func (a *App) pluginHook(modulePath, accountName, functionName string, pluginInfo *utils.PluginInfo) *starlark.Builtin { hook := func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { a.Trace().Msgf("Plugin called: %s.%s", modulePath, functionName) @@ -220,7 +191,7 @@ func (a *App) pluginHook(modulePath, accountName, functionName string, pluginInf isRead = *p.IsRead } else { // Use the plugin defined isRead value - isRead = pluginInfo.isRead + isRead = pluginInfo.IsRead } if !isRead { @@ -255,7 +226,7 @@ func (a *App) pluginHook(modulePath, accountName, functionName string, pluginInf } // Get the plugin function using reflection - pluginValue := reflect.ValueOf(plugin).MethodByName(pluginInfo.handlerName) + pluginValue := reflect.ValueOf(plugin).MethodByName(pluginInfo.HandlerName) if pluginValue.IsNil() { return nil, fmt.Errorf("plugin func %s.%s cannot be resolved", modulePath, functionName) } diff --git a/internal/app/store/sql_store.go b/internal/app/store/sql_store.go index 0baa9a6..4831458 100644 --- a/internal/app/store/sql_store.go +++ b/internal/app/store/sql_store.go @@ -12,8 +12,9 @@ import ( "sync" "time" - "github.com/claceio/clace/internal/app" "github.com/claceio/clace/internal/utils" + + _ "modernc.org/sqlite" ) const ( @@ -24,7 +25,7 @@ type SqlStore struct { *utils.Logger sync.Mutex isInitialized bool - pluginContext *app.PluginContext + pluginContext *utils.PluginContext db *sql.DB prefix string isSqlite bool // false means postgres, no other options @@ -32,7 +33,7 @@ type SqlStore struct { var _ Store = (*SqlStore)(nil) -func NewSqlStore(pluginContext *app.PluginContext) (*SqlStore, error) { +func NewSqlStore(pluginContext *utils.PluginContext) (*SqlStore, error) { return &SqlStore{ Logger: pluginContext.Logger, pluginContext: pluginContext, @@ -44,12 +45,12 @@ func validateTableName(name string) error { return nil } -func (s *SqlStore) genTableName(collection string) (string, error) { - err := validateTableName(collection) +func (s *SqlStore) genTableName(table string) (string, error) { + err := validateTableName(table) if err != nil { return "", err } - return fmt.Sprintf("'%s_%s'", s.prefix, collection), nil + return fmt.Sprintf("'%s_%s'", s.prefix, table), nil } func (s *SqlStore) initialize() error { @@ -107,24 +108,24 @@ func (s *SqlStore) initStore() error { s.prefix = "db_" + string(s.pluginContext.AppId)[len(utils.ID_PREFIX_APP_PROD):] for _, storeType := range s.pluginContext.StoreInfo.Types { - collection, err := s.genTableName(storeType.Name) + table, err := s.genTableName(storeType.Name) if err != nil { return err } - createStmt := "CREATE TABLE IF NOT EXISTS " + collection + " (id INTEGER PRIMARY KEY AUTOINCREMENT, version INTEGER, created_by TEXT, updated_by TEXT, created_at INTEGER, updated_at INTEGER, data JSON)" + createStmt := "CREATE TABLE IF NOT EXISTS " + table + " (id INTEGER PRIMARY KEY AUTOINCREMENT, version INTEGER, created_by TEXT, updated_by TEXT, created_at INTEGER, updated_at INTEGER, data JSON)" _, err = s.db.Exec(createStmt) if err != nil { - return fmt.Errorf("error creating table %s: %w", collection, err) + return fmt.Errorf("error creating table %s: %w", table, err) } - s.Info().Msgf("Created table %s", collection) + s.Info().Msgf("Created table %s", table) } return nil } // Insert a new entry in the store -func (s *SqlStore) Insert(collection string, entry *Entry) (EntryId, error) { +func (s *SqlStore) Insert(table string, entry *Entry) (EntryId, error) { if err := s.initialize(); err != nil { return -1, err } @@ -133,18 +134,18 @@ func (s *SqlStore) Insert(collection string, entry *Entry) (EntryId, error) { entry.CreatedBy = "admin" // TODO update userid var err error - collection, err = s.genTableName(collection) + table, err = s.genTableName(table) if err != nil { return -1, err } dataJson, err := json.Marshal(entry.Data) if err != nil { - return -1, fmt.Errorf("error marshalling data for collection %s: %w", collection, err) + return -1, fmt.Errorf("error marshalling data for table %s: %w", table, err) } - createStmt := "INSERT INTO " + collection + " (version, created_by, updated_by, created_at, updated_at, data) VALUES (?, ?, ?, ?, ?, ?)" - result, err := s.db.Exec(createStmt, entry.Version, entry.CreatedBy, entry.UpdatedBy, entry.CreatedAt.Unix(), entry.UpdatedAt.Unix(), dataJson) + createStmt := "INSERT INTO " + table + " (version, created_by, updated_by, created_at, updated_at, data) VALUES (?, ?, ?, ?, ?, ?)" + result, err := s.db.Exec(createStmt, entry.Version, entry.CreatedBy, entry.UpdatedBy, entry.CreatedAt.UnixMilli(), entry.UpdatedAt.UnixMilli(), dataJson) if err != nil { return -1, nil } @@ -157,19 +158,19 @@ func (s *SqlStore) Insert(collection string, entry *Entry) (EntryId, error) { } // SelectById returns a single item from the store -func (s *SqlStore) SelectById(collection string, key EntryId) (*Entry, error) { +func (s *SqlStore) SelectById(table string, id EntryId) (*Entry, error) { if err := s.initialize(); err != nil { return nil, err } var err error - collection, err = s.genTableName(collection) + table, err = s.genTableName(table) if err != nil { return nil, err } - query := "SELECT id, version, created_by, updated_by, created_at, updated_at, data FROM " + collection + " WHERE id = ?" - row := s.db.QueryRow(query, key) + query := "SELECT id, version, created_by, updated_by, created_at, updated_at, data FROM " + table + " WHERE id = ?" + row := s.db.QueryRow(query, id) entry := &Entry{} var dataStr string @@ -177,7 +178,7 @@ func (s *SqlStore) SelectById(collection string, key EntryId) (*Entry, error) { err = row.Scan(&entry.Id, &entry.Version, &entry.CreatedBy, &entry.UpdatedBy, &createdAt, &updatedAt, &dataStr) if err != nil { if err == sql.ErrNoRows { - return nil, fmt.Errorf("entry %d not found in collection %s", key, collection) + return nil, fmt.Errorf("entry %d not found in table %s", id, table) } return nil, err } @@ -188,28 +189,84 @@ func (s *SqlStore) SelectById(collection string, key EntryId) (*Entry, error) { } } - entry.CreatedAt = time.Unix(createdAt, 0) - entry.UpdatedAt = time.Unix(updatedAt, 0) + entry.CreatedAt = time.UnixMilli(createdAt) + entry.UpdatedAt = time.UnixMilli(updatedAt) return entry, nil } // Select returns the entries matching the filter -func (s *SqlStore) Select(collection string, filter map[string]any, sort []string, offset, limit int64) (EntryIterator, error) { +func (s *SqlStore) Select(table string, filter map[string]any, sort []string, offset, limit int64) (EntryIterator, error) { return nil, nil } // Update an existing entry in the store -func (s *SqlStore) Update(collection string, Entry *Entry) error { - return nil +func (s *SqlStore) Update(table string, entry *Entry) (int64, error) { + if err := s.initialize(); err != nil { + return 0, err + } + + var err error + if table, err = s.genTableName(table); err != nil { + return 0, err + } + + origUpdateAt := entry.UpdatedAt + entry.UpdatedAt = time.Now() + entry.UpdatedBy = "admin" // TODO update userid + + dataJson, err := json.Marshal(entry.Data) + if err != nil { + return 0, fmt.Errorf("error marshalling data for table %s: %w", table, err) + } + + updateStmt := "UPDATE " + table + " set version = ?, updated_by = ?, updated_at = ?, data = ? where id = ? and updated_at = ?" + result, err := s.db.Exec(updateStmt, entry.Version, entry.UpdatedBy, entry.UpdatedAt.UnixMilli(), dataJson, entry.Id, origUpdateAt.UnixMilli()) + if err != nil { + return 0, err + } + + rows, err := result.RowsAffected() + if err != nil { + return 0, err + } + if rows == 0 { + return 0, fmt.Errorf("entry %d not found or concurrently updated in table %s", entry.Id, table) + } + + return rows, nil } // DeleteById an entry from the store by id -func (s *SqlStore) DeleteById(collection string, key EntryId) error { - return nil +func (s *SqlStore) DeleteById(table string, id EntryId) (int64, error) { + if err := s.initialize(); err != nil { + return 0, err + } + + var err error + if table, err = s.genTableName(table); err != nil { + return 0, err + } + + deleteStmt := "DELETE from " + table + " where id = ?" + result, err := s.db.Exec(deleteStmt, id) + if err != nil { + return 0, err + } + + rows, err := result.RowsAffected() + if err != nil { + return 0, err + } + + if rows == 0 { + return 0, fmt.Errorf("entry %d not found in table %s", id, table) + } + + return rows, nil } // Delete entries from the store matching the filter -func (s *SqlStore) Delete(collection string, filter map[string]any) error { +func (s *SqlStore) Delete(table string, filter map[string]any) error { return nil } diff --git a/internal/app/store/store.go b/internal/app/store/store.go index 4924533..534662b 100644 --- a/internal/app/store/store.go +++ b/internal/app/store/store.go @@ -66,13 +66,13 @@ func (e *Entry) Unpack(value starlark.Value) error { if err != nil { return fmt.Errorf("error reading %s: %w", attr, err) } - e.CreatedAt = time.Unix(createdAt, 0) + e.CreatedAt = time.UnixMilli(createdAt) case "_updated_at": updatedAt, err := util.GetIntAttr(v, attr) if err != nil { return fmt.Errorf("error reading %s: %w", attr, err) } - e.UpdatedAt = time.Unix(updatedAt, 0) + e.UpdatedAt = time.UnixMilli(updatedAt) default: dataVal, err := v.Attr(attr) if err != nil { @@ -107,10 +107,10 @@ type Store interface { Select(table string, filter map[string]any, sort []string, offset, limit int64) (EntryIterator, error) // Update an existing entry in the store - Update(table string, Entry *Entry) error + Update(table string, Entry *Entry) (int64, error) // DeleteById an entry from the store by id - DeleteById(table string, id EntryId) error + DeleteById(table string, id EntryId) (int64, error) // Delete entries from the store matching the filter Delete(table string, filter map[string]any) error @@ -123,8 +123,8 @@ func CreateType(name string, entry *Entry) (*utils.StarlarkType, error) { data["_version"] = starlark.MakeInt(int(entry.Version)) data["_created_by"] = starlark.String(string(entry.CreatedBy)) data["_updated_by"] = starlark.String(string(entry.UpdatedBy)) - data["_created_at"] = starlark.MakeInt(int(entry.CreatedAt.Unix())) - data["_updated_at"] = starlark.MakeInt(int(entry.UpdatedAt.Unix())) + data["_created_at"] = starlark.MakeInt(int(entry.CreatedAt.UnixMilli())) + data["_updated_at"] = starlark.MakeInt(int(entry.UpdatedAt.UnixMilli())) var err error for k, v := range entry.Data { diff --git a/internal/app/store/store_plugin.go b/internal/app/store/store_plugin.go index 24808f5..6a1bb5a 100644 --- a/internal/app/store/store_plugin.go +++ b/internal/app/store/store_plugin.go @@ -13,9 +13,11 @@ import ( func init() { h := &storePlugin{} - pluginFuncs := []app.PluginFunc{ + pluginFuncs := []utils.PluginFunc{ app.CreatePluginApi(h.Insert, false), app.CreatePluginApiName(h.SelectById, false, "select_by_id"), + app.CreatePluginApi(h.Update, false), + app.CreatePluginApiName(h.DeleteById, false, "delete_by_id"), } app.RegisterPlugin("store", NewStorePlugin, pluginFuncs) } @@ -24,7 +26,7 @@ type storePlugin struct { sqlStore *SqlStore } -func NewStorePlugin(pluginContext *app.PluginContext) (any, error) { +func NewStorePlugin(pluginContext *utils.PluginContext) (any, error) { sqlStore, err := NewSqlStore(pluginContext) return &storePlugin{ @@ -44,7 +46,7 @@ func (s *storePlugin) Insert(thread *starlark.Thread, builtin *starlark.Builtin, if err != nil { return utils.NewErrorResponse(err), nil } - return utils.NewResponse(id), nil + return utils.NewResponse(int64(id)), nil } func (s *storePlugin) SelectById(thread *starlark.Thread, builtin *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { @@ -72,3 +74,39 @@ func (s *storePlugin) SelectById(thread *starlark.Thread, builtin *starlark.Buil } return utils.NewResponse(returnType), nil } + +func (s *storePlugin) Update(thread *starlark.Thread, builtin *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var table string + var entry Entry + + if err := starlark.UnpackArgs("update", args, kwargs, "table", &table, "entry", &entry); err != nil { + return nil, err + } + + success, err := s.sqlStore.Update(table, &entry) + if err != nil { + return utils.NewErrorResponse(err), nil + } + return utils.NewResponse(success), nil +} + +func (s *storePlugin) DeleteById(thread *starlark.Thread, builtin *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var table string + var id starlark.Int + + if err := starlark.UnpackArgs("delete_by_id", args, kwargs, "table", &table, "id", &id); err != nil { + return nil, err + } + + var idVal int64 + var ok bool + if idVal, ok = id.Int64(); !ok || idVal < 0 { + return utils.NewErrorResponse(fmt.Errorf("invalid id value")), nil + } + + success, err := s.sqlStore.DeleteById(table, EntryId(idVal)) + if err != nil { + return utils.NewErrorResponse(err), nil + } + return utils.NewResponse(success), nil +} diff --git a/internal/app/testhelper.go b/internal/app/tests/app_test_helper.go similarity index 70% rename from internal/app/testhelper.go rename to internal/app/tests/app_test_helper.go index 3bec322..8e8ef5f 100644 --- a/internal/app/testhelper.go +++ b/internal/app/tests/app_test_helper.go @@ -1,7 +1,7 @@ // Copyright (c) ClaceIO, LLC // SPDX-License-Identifier: Apache-2.0 -package app +package app_test import ( "io/fs" @@ -10,23 +10,31 @@ import ( "text/template" "time" + "github.com/claceio/clace/internal/app" "github.com/claceio/clace/internal/app/util" "github.com/claceio/clace/internal/utils" + + _ "github.com/claceio/clace/internal/app/store" // Register db plugin + _ "github.com/claceio/clace/plugins" // Register builtin plugins ) -func CreateDevModeTestApp(logger *utils.Logger, fileData map[string]string) (*App, *util.WorkFs, error) { - return CreateTestAppInt(logger, "/test", fileData, true) +func CreateDevModeTestApp(logger *utils.Logger, fileData map[string]string) (*app.App, *util.WorkFs, error) { + return CreateTestAppInt(logger, "/test", fileData, true, nil, nil, nil) +} + +func CreateTestApp(logger *utils.Logger, fileData map[string]string) (*app.App, *util.WorkFs, error) { + return CreateTestAppInt(logger, "/test", fileData, false, nil, nil, nil) } -func CreateTestApp(logger *utils.Logger, fileData map[string]string) (*App, *util.WorkFs, error) { - return CreateTestAppInt(logger, "/test", fileData, false) +func CreateTestAppRoot(logger *utils.Logger, fileData map[string]string) (*app.App, *util.WorkFs, error) { + return CreateTestAppInt(logger, "/", fileData, false, nil, nil, nil) } -func CreateTestAppRoot(logger *utils.Logger, fileData map[string]string) (*App, *util.WorkFs, error) { - return CreateTestAppInt(logger, "/", fileData, false) +func CreateTestAppPlugin(logger *utils.Logger, fileData map[string]string, plugins []string, permissions []utils.Permission, pluginConfig map[string]utils.PluginSettings) (*app.App, *util.WorkFs, error) { + return CreateTestAppInt(logger, "/test", fileData, false, plugins, permissions, pluginConfig) } -func CreateTestAppInt(logger *utils.Logger, path string, fileData map[string]string, isDev bool) (*App, *util.WorkFs, error) { +func CreateTestAppInt(logger *utils.Logger, path string, fileData map[string]string, isDev bool, plugins []string, permissions []utils.Permission, pluginConfig map[string]utils.PluginSettings) (*app.App, *util.WorkFs, error) { systemConfig := utils.SystemConfig{TailwindCSSCommand: ""} var fs utils.ReadableFS if isDev { @@ -38,19 +46,37 @@ func CreateTestAppInt(logger *utils.Logger, path string, fileData map[string]str if err != nil { return nil, nil, err } + + if plugins == nil { + plugins = []string{} + } + if permissions == nil { + permissions = []utils.Permission{} + } + + if pluginConfig == nil { + pluginConfig = map[string]utils.PluginSettings{} + } + + metadata := utils.AppMetadata{ + Loads: plugins, + Permissions: permissions, + } workFS := util.NewWorkFs("", &TestWriteFS{TestReadFS: &TestReadFS{fileData: map[string]string{}}}) - a := NewApp(sourceFS, workFS, logger, createTestAppEntry(path, isDev), &systemConfig, map[string]utils.PluginSettings{}) + a := app.NewApp(sourceFS, workFS, logger, + createTestAppEntry(path, isDev, metadata), &systemConfig, pluginConfig) err = a.Initialize() return a, workFS, err } -func createTestAppEntry(path string, isDev bool) *utils.AppEntry { +func createTestAppEntry(path string, isDev bool, metadata utils.AppMetadata) *utils.AppEntry { return &utils.AppEntry{ - Id: "testApp", + Id: "app_prd_testapp", Path: path, Domain: "", SourceUrl: ".", IsDev: isDev, + Metadata: metadata, } } diff --git a/internal/app/tests/basic_test.go b/internal/app/tests/basic_test.go index 02fb2ba..2a588c0 100644 --- a/internal/app/tests/basic_test.go +++ b/internal/app/tests/basic_test.go @@ -10,7 +10,6 @@ import ( "strings" "testing" - "github.com/claceio/clace/internal/app" "github.com/claceio/clace/internal/app/util" "github.com/claceio/clace/internal/testutil" ) @@ -18,25 +17,25 @@ import ( func TestAppLoadError(t *testing.T) { logger := testutil.TestLogger() - _, _, err := app.CreateTestApp(logger, map[string]string{ + _, _, err := CreateTestApp(logger, map[string]string{ "app.star": ``, "index.go.html": `{{.}}`, }) testutil.AssertErrorContains(t, err, "app not defined, check app.star") - _, _, err = app.CreateTestApp(logger, map[string]string{ + _, _, err = CreateTestApp(logger, map[string]string{ "app.star": `app = 1`, "index.go.html": `{{.}}`, }) testutil.AssertErrorContains(t, err, "app not of type ace.app in app.star") - _, _, err = app.CreateTestApp(logger, map[string]string{ + _, _, err = CreateTestApp(logger, map[string]string{ "app.star": `app = ace.app()`, "index.go.html": `{{.}}`, }) testutil.AssertErrorContains(t, err, "missing argument for name") - _, _, err = app.CreateTestApp(logger, map[string]string{ + _, _, err = CreateTestApp(logger, map[string]string{ "app.star": ` app = ace.app("testApp", pages = [ace.page("/")]) handler = 10`, @@ -44,7 +43,7 @@ handler = 10`, }) testutil.AssertErrorContains(t, err, "handler is not a function") - _, _, err = app.CreateTestApp(logger, map[string]string{ + _, _, err = CreateTestApp(logger, map[string]string{ "app.star": ` app = ace.app("testApp", pages = [ace.page("/", handler=10)])`, "index.go.html": `{{.}}`, @@ -55,12 +54,12 @@ app = ace.app("testApp", pages = [ace.page("/", handler=10)])`, func TestAppPages(t *testing.T) { logger := testutil.TestLogger() - _, _, err := app.CreateTestApp(logger, map[string]string{ + _, _, err := CreateTestApp(logger, map[string]string{ "app.star": `app = ace.app("testApp", pages = 2)`, }) testutil.AssertErrorContains(t, err, "got int, want list") - _, _, err = app.CreateTestApp(logger, map[string]string{ + _, _, err = CreateTestApp(logger, map[string]string{ "app.star": `app = ace.app("testApp", pages = ["abc"])`, }) testutil.AssertErrorContains(t, err, "pages entry 1 is not a struct") @@ -77,7 +76,7 @@ def handler(req): `, "index.go.html": `Template got {{ .Data.key }}.`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -107,7 +106,7 @@ def handler(req): "./templates/t1.tmpl": `Template got {{ .Data.key }}.`, util.CONFIG_LOCK_FILE_NAME: `{ "htmx": { "version": "1.8" } }`, } - a, _, err := app.CreateTestApp(logger, fileData) + a, _, err := CreateTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -137,7 +136,7 @@ def handler(req): "./templates/t1.tmpl": `Template got {{ .key }}.`, util.CONFIG_LOCK_FILE_NAME: `{ "htmx": { "version": "1.8" } }`, } - a, _, err := app.CreateTestApp(logger, fileData) + a, _, err := CreateTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -166,7 +165,7 @@ def handler(req): return {"key": "myvalue"}`, "index.go.html": `Template contents {{template "clace_gen.go.html"}}.`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -190,7 +189,7 @@ app = ace.app("testApp", pages = [ace.page("/")]) def handler(req): return {"key": "myvalue"}`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -210,7 +209,7 @@ func TestNoHandler(t *testing.T) { app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")])`, "index.go.html": `Template contents {{.Data}}.`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -230,7 +229,7 @@ func TestFullData(t *testing.T) { app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")])`, "index.go.html": `Template contents {{.}}.`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -250,7 +249,7 @@ func TestFullDataRoot(t *testing.T) { app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")])`, "index.go.html": `Template contents {{.}}.`, } - a, _, err := app.CreateTestAppRoot(logger, fileData) + a, _, err := CreateTestAppRoot(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -274,7 +273,7 @@ def handler(req): "app.go.html": `{{block "clace_body" .}}ABC{{end}}`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -321,7 +320,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")]) def handler(req): return ace.redirect("/new_url", code=302)`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -340,7 +339,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")]) def handler(req): return ace.redirect("/new_url")`, } - a, _, err = app.CreateDevModeTestApp(logger, fileData) + a, _, err = CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -361,7 +360,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/", method="POST def handler(req): return ace.redirect("/new_url", code=302)`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -390,7 +389,7 @@ def handler(req): return ace.response({"key": "myvalue"}, "testtmpl")`, "index.go.html": `Template. {{block "testtmpl" .}}ABC {{.Data.key}} {{end}}`, } - a, _, err := app.CreateTestAppRoot(logger, fileData) + a, _, err := CreateTestAppRoot(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -413,7 +412,7 @@ def handler(req): return ace.response({"key": "myvalue"}, "testtmpl", code=500, retarget="#abc", reswap="outerHTML")`, "index.go.html": `Template. {{block "testtmpl" .}}ABC {{.Data.key}} {{end}}`, } - a, _, err := app.CreateTestAppRoot(logger, fileData) + a, _, err := CreateTestAppRoot(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -455,7 +454,7 @@ def handler(req): `, "index.go.html": `Template. ABC {{.Data}}`, } - a, _, err := app.CreateTestAppRoot(logger, fileData) + a, _, err := CreateTestAppRoot(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } diff --git a/internal/app/tests/fragment_test.go b/internal/app/tests/fragment_test.go index c9b6413..1921ac4 100644 --- a/internal/app/tests/fragment_test.go +++ b/internal/app/tests/fragment_test.go @@ -7,7 +7,6 @@ import ( "net/http/httptest" "testing" - "github.com/claceio/clace/internal/app" "github.com/claceio/clace/internal/testutil" ) @@ -24,7 +23,7 @@ def handler(req): `, "index.go.html": `Template main {{ .Data.key }}. {{ block "ff" . }} fragdata {{ .Data.key2 }} {{ end }}`, } - a, _, err := app.CreateTestApp(logger, fileData) + a, _, err := CreateTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -74,7 +73,7 @@ def handler(req): `, "index.go.html": `Template main {{ .Data.key }}. {{ block "ff" . }} fragdata {{ .Data.key2 }} {{ end }}`, } - a, _, err := app.CreateTestApp(logger, fileData) + a, _, err := CreateTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -126,7 +125,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/abc", `, "index.go.html": `Template main {{ .Data.key }}. {{ block "ff" . }} fragdata {{ .Data.key2 }} {{ end }}`, } - a, _, err := app.CreateTestApp(logger, fileData) + a, _, err := CreateTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -180,7 +179,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/abc", "index.go.html": `Template main {{ .Data.key }}. {{ block "ff" . }} fragdata {{ .Data.key2 }} {{ end }} {{ block "ff2" . }} {{if contains "frag2" .PagePath}} {{.PagePath}} frag2data {{ end }} {{end}}`, } - a, _, err := app.CreateTestApp(logger, fileData) + a, _, err := CreateTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -236,7 +235,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/abc", `, "index.go.html": `Template main {{ .Data.key }}. {{ block "ff" . }} fragdata {{ .Data.key2 }} {{ end }}`, } - _, _, err := app.CreateTestApp(logger, fileData) + _, _, err := CreateTestApp(logger, fileData) testutil.AssertErrorContains(t, err, "got int, want list") fileData = map[string]string{ @@ -249,7 +248,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/abc", `, "index.go.html": `Template main {{ .Data.key }}. {{ block "ff" . }} fragdata {{ .Data.key2 }} {{ end }}`, } - _, _, err = app.CreateTestApp(logger, fileData) + _, _, err = CreateTestApp(logger, fileData) testutil.AssertErrorContains(t, err, "page 1 fragment 1 is not a struct") fileData = map[string]string{ @@ -262,7 +261,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/abc", `, "index.go.html": `Template main {{ .Data.key }}. {{ block "ff" . }} fragdata {{ .Data.key2 }} {{ end }}`, } - _, _, err = app.CreateTestApp(logger, fileData) + _, _, err = CreateTestApp(logger, fileData) testutil.AssertErrorContains(t, err, "unexpected keyword argument \"abc\"") fileData = map[string]string{ @@ -273,7 +272,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/abc", `, "index.go.html": `Template main {{ .Data.key }}. {{ block "ff" . }} fragdata {{ .Data.key2 }} {{ end }}`, } - _, _, err = app.CreateTestApp(logger, fileData) + _, _, err = CreateTestApp(logger, fileData) testutil.AssertErrorContains(t, err, "for parameter \"handler\": got int, want callable") } @@ -290,7 +289,7 @@ def handler(req): `, "index.go.html": `Template main {{ .Data.key }}. {{ block "ff" . }} fragdata {{ .Data.key2 }} {{ end }}`, } - a, _, err := app.CreateTestApp(logger, fileData) + a, _, err := CreateTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } diff --git a/internal/app/tests/library_test.go b/internal/app/tests/library_test.go index 712878b..f07b0d3 100644 --- a/internal/app/tests/library_test.go +++ b/internal/app/tests/library_test.go @@ -10,7 +10,6 @@ import ( "net/http/httptest" "testing" - "github.com/claceio/clace/internal/app" "github.com/claceio/clace/internal/testutil" ) @@ -28,7 +27,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], libraries=["%s"])`, testUrl), } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -64,7 +63,7 @@ func TestLibraryESM(t *testing.T) { app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], libraries=[ace.library("mylib", "1.0.0")])`, } - _, _, err := app.CreateDevModeTestApp(logger, fileData) + _, _, err := CreateDevModeTestApp(logger, fileData) testutil.AssertErrorContains(t, err, `Could not resolve "mylib-1.0.0.js"`) fileData = map[string]string{ @@ -72,7 +71,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], libraries=[ace.library("mylib", "1.0.0", args=["--minify"])])`, } - _, _, err = app.CreateDevModeTestApp(logger, fileData) + _, _, err = CreateDevModeTestApp(logger, fileData) testutil.AssertErrorContains(t, err, `Could not resolve "mylib-1.0.0.js"`) // flag got passed to esbuild fileData = map[string]string{ @@ -80,7 +79,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], libraries=[ace.library("mylib", "1.0.0", args=["--invalid"])])`, } - _, _, err = app.CreateDevModeTestApp(logger, fileData) + _, _, err = CreateDevModeTestApp(logger, fileData) testutil.AssertErrorContains(t, err, `Invalid build flag: "--invalid"`) // esbuild did not like the arg fileData = map[string]string{ @@ -88,6 +87,6 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], libraries=[ace.library("mylib", "1.0.0", args=10)])`, } - _, _, err = app.CreateDevModeTestApp(logger, fileData) + _, _, err = CreateDevModeTestApp(logger, fileData) testutil.AssertErrorContains(t, err, `got int, want list`) } diff --git a/internal/app/tests/loader_test.go b/internal/app/tests/loader_test.go index f6f5cc0..49fca6c 100644 --- a/internal/app/tests/loader_test.go +++ b/internal/app/tests/loader_test.go @@ -7,7 +7,6 @@ import ( "net/http/httptest" "testing" - "github.com/claceio/clace/internal/app" "github.com/claceio/clace/internal/testutil" ) @@ -19,7 +18,7 @@ app = ace.app("testApp", custom_layout=True, pages = testpage)`, "index.go.html": `Template contents {{.AppName}}.`, "test.star": `testpage = [ace.page("/")]`, } - a, _, err := app.CreateTestAppRoot(logger, fileData) + a, _, err := CreateTestAppRoot(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -42,7 +41,7 @@ app = ace.app("testApp", custom_layout=True, pages = testpages)`, testpages = [mypage]`, "test2.star": `mypage = ace.page("/")`, } - a, _, err := app.CreateTestAppRoot(logger, fileData) + a, _, err := CreateTestAppRoot(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -64,7 +63,7 @@ app = ace.app("testApp", custom_layout=True, pages = testpages)`, "test1.star": `load ("app.star", "mypage") testpages = [mypage]`, } - _, _, err := app.CreateTestAppRoot(logger, fileData) + _, _, err := CreateTestAppRoot(logger, fileData) testutil.AssertErrorContains(t, err, "cycle in starlark load graph during load of test1.star") } @@ -78,7 +77,7 @@ app = ace.app("testApp", custom_layout=True, pages = testpages)`, "index.go.html": `Template contents {{.AppName}}.`, "test1.star": `testpages = [ace.page("/")]`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -105,6 +104,6 @@ app = ace.app("testApp", custom_layout=True, pages = testpage)`, "index.go.html": `Template contents {{.AppName}}.`, "test.star": `testpage = [ace.page("/")]`, } - _, _, err := app.CreateTestAppRoot(logger, fileData) + _, _, err := CreateTestAppRoot(logger, fileData) testutil.AssertErrorContains(t, err, "cannot load test2.star: file does not exist") } diff --git a/internal/app/tests/response_test.go b/internal/app/tests/response_test.go index ced728a..e64660b 100644 --- a/internal/app/tests/response_test.go +++ b/internal/app/tests/response_test.go @@ -8,7 +8,6 @@ import ( "net/http/httptest" "testing" - "github.com/claceio/clace/internal/app" "github.com/claceio/clace/internal/testutil" ) @@ -22,7 +21,7 @@ def handler(req): return {"a": "aval", "b": 1}`, "index.go.html": `Template. {{block "testtmpl" .}}ABC {{.Data.key}} {{end}}`, } - a, _, err := app.CreateTestAppRoot(logger, fileData) + a, _, err := CreateTestAppRoot(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -48,7 +47,7 @@ app = ace.app("testApp", pages = [ace.page("/", type="json")]) def handler(req): return {"a": "aval", "b": 1}`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -74,7 +73,7 @@ app = ace.app("testApp", pages = [ace.page("/", fragments=[ace.fragment("frag", def handler(req): return {"a": "aval", "b": 1}`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -100,7 +99,7 @@ app = ace.app("testApp", pages = [ace.page("/")]) def handler(req): return ace.response({"a": "aval", "b": 1}, type="json")`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -126,7 +125,7 @@ app = ace.app("testApp", pages = [ace.page("/", type="json")]) def handler(req): return ace.response({"a": "aval", "b": 1})`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -152,7 +151,7 @@ app = ace.app("testApp", pages = [ace.page("/", fragments=[ace.fragment("frag", def handler(req): return ace.response({"a": "aval", "b": 1})`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -178,7 +177,7 @@ app = ace.app("testApp", pages = [ace.page("/", fragments=[ace.fragment("frag")] def handler(req): return ace.response({"a": "aval", "b": 1})`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -200,6 +199,6 @@ app = ace.app("testApp", pages = [ace.page("/", fragments=[ace.fragment("frag", def handler(req): return ace.response({"a": "aval", "b": 1})`, } - _, _, err := app.CreateDevModeTestApp(logger, fileData) + _, _, err := CreateDevModeTestApp(logger, fileData) testutil.AssertErrorContains(t, err, "invalid type specified : abc") } diff --git a/internal/app/tests/static_test.go b/internal/app/tests/static_test.go index 91e7d74..53ebc9f 100644 --- a/internal/app/tests/static_test.go +++ b/internal/app/tests/static_test.go @@ -7,7 +7,6 @@ import ( "net/http/httptest" "testing" - "github.com/claceio/clace/internal/app" "github.com/claceio/clace/internal/testutil" ) @@ -24,7 +23,7 @@ def handler(req): "static/file2.txt": `file2data`, } - a, _, err := app.CreateTestApp(logger, fileData) + a, _, err := CreateTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -72,7 +71,7 @@ def handler(req): "static/file2.txt": `file2data`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -99,7 +98,7 @@ def handler(req): "static/file3.txt": `file3data`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } diff --git a/internal/app/tests/styling_test.go b/internal/app/tests/styling_test.go index 46e4349..84b7984 100644 --- a/internal/app/tests/styling_test.go +++ b/internal/app/tests/styling_test.go @@ -10,7 +10,6 @@ import ( "net/http/httptest" "testing" - "github.com/claceio/clace/internal/app" "github.com/claceio/clace/internal/testutil" ) @@ -22,7 +21,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], settings={"style":{"library": ""}})`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -50,7 +49,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], "static/mystyle.css": `mystyle contents`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -71,7 +70,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], style=ace.style(library="tailwindcss"))`, } - _, workFS, err := app.CreateDevModeTestApp(logger, fileData) + _, workFS, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -93,7 +92,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], style=ace.style(library="daisyui"))`, } - _, workFS, err := app.CreateDevModeTestApp(logger, fileData) + _, workFS, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -115,7 +114,7 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], style=ace.style(library="daisyui", themes=["dark", "cupcake"]))`, } - _, workFS, err := app.CreateDevModeTestApp(logger, fileData) + _, workFS, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -138,7 +137,7 @@ app = ace.app("testApp", custom_layout=False, pages = [ace.page("/")])`, "app.go.html": `{{block "clace_body" .}}ABC{{end}}`, } - a, _, err := app.CreateDevModeTestApp(logger, fileData) + a, _, err := CreateDevModeTestApp(logger, fileData) if err != nil { t.Fatalf("Error %s", err) } @@ -161,6 +160,6 @@ app = ace.app("testApp", custom_layout=True, pages = [ace.page("/")], style=ace.style(library="unknown"))`, } - _, _, err := app.CreateDevModeTestApp(logger, fileData) + _, _, err := CreateDevModeTestApp(logger, fileData) testutil.AssertErrorContains(t, err, "invalid style library config : unknown") } diff --git a/internal/utils/plugin_response.go b/internal/utils/plugin_types.go similarity index 76% rename from internal/utils/plugin_response.go rename to internal/utils/plugin_types.go index 357985e..8608fc5 100644 --- a/internal/utils/plugin_response.go +++ b/internal/utils/plugin_types.go @@ -9,6 +9,35 @@ import ( "go.starlark.net/starlark" ) +type NewPluginFunc func(pluginContext *PluginContext) (any, error) + +// PluginMap is the plugin function mapping to PluginFuncs +type PluginMap map[string]*PluginInfo + +// PluginFunc is the Clace plugin function mapping to starlark function +type PluginFunc struct { + Name string + IsRead bool + FunctionName string +} + +// PluginFuncInfo is the Clace plugin function info for the starlark function +type PluginInfo struct { + ModuleName string // exec + PluginPath string // exec.in + FuncName string // run + IsRead bool + HandlerName string + Builder NewPluginFunc +} + +type PluginContext struct { + Logger *Logger + AppId AppId + StoreInfo *StoreInfo + Config PluginSettings +} + // PluginResponse is a starlark.Value that represents the response to a plugin request type PluginResponse struct { errorCode int diff --git a/internal/utils/starlark_convert.go b/internal/utils/starlark_convert.go index 747167b..8ab6743 100644 --- a/internal/utils/starlark_convert.go +++ b/internal/utils/starlark_convert.go @@ -293,7 +293,7 @@ func MarshalStarlark(data interface{}) (v starlark.Value, err error) { case Marshaler: v, err = x.MarshalStarlark() default: - return starlark.None, fmt.Errorf("unrecognized type: %#v", x) + return starlark.None, fmt.Errorf("unrecognized type: %#v %t", x, x) } return } diff --git a/plugins/exec.go b/plugins/exec.go index b461f5b..c132411 100644 --- a/plugins/exec.go +++ b/plugins/exec.go @@ -11,6 +11,7 @@ import ( "os/exec" "github.com/claceio/clace/internal/app" + "github.com/claceio/clace/internal/utils" "go.starlark.net/starlark" "go.starlark.net/starlarkstruct" ) @@ -19,7 +20,7 @@ const MAX_BYTES_STDOUT = 100 * 1024 * 1024 // 100MB func init() { e := &ExecPlugin{} - app.RegisterPlugin("exec", NewExecPlugin, []app.PluginFunc{ + app.RegisterPlugin("exec", NewExecPlugin, []utils.PluginFunc{ app.CreatePluginApi(e.Run, false), }) } @@ -67,7 +68,7 @@ func createResponse(err error, stdout io.ReadCloser, stderr bytes.Buffer) *execR type ExecPlugin struct { } -func NewExecPlugin(_ *app.PluginContext) (any, error) { +func NewExecPlugin(_ *utils.PluginContext) (any, error) { return &ExecPlugin{}, nil } diff --git a/plugins/http.go b/plugins/http.go index 3bd6fde..d2c56ca 100644 --- a/plugins/http.go +++ b/plugins/http.go @@ -37,7 +37,7 @@ const ( func init() { h := &httpPlugin{} - pluginFuncs := []app.PluginFunc{ + pluginFuncs := []utils.PluginFunc{ app.CreatePluginApi(h.Get, true), app.CreatePluginApi(h.Head, true), app.CreatePluginApi(h.Options, true), @@ -53,7 +53,7 @@ type httpPlugin struct { client *http.Client } -func NewHttpPlugin(pluginContext *app.PluginContext) (any, error) { +func NewHttpPlugin(pluginContext *utils.PluginContext) (any, error) { return &httpPlugin{client: http.DefaultClient}, nil }