Update dependencies in go.mod and go.sum to latest versions for improved stability and performance
This commit is contained in:
parent
0b5d7fcc41
commit
06f5134083
12 changed files with 2101 additions and 0 deletions
47
dsl/api/api.go
Normal file
47
dsl/api/api.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/yaoapp/yao/dsl/types"
|
||||
)
|
||||
|
||||
// YaoAPI is the MCP client DSL manager
|
||||
type YaoAPI struct {
|
||||
root string // The relative path of the MCP client DSL
|
||||
}
|
||||
|
||||
// New returns a new connector DSL manager
|
||||
func New(root string) types.Manager {
|
||||
return &YaoAPI{root: root}
|
||||
}
|
||||
|
||||
// Loaded return all loaded DSLs
|
||||
func (api *YaoAPI) Loaded(ctx context.Context) (map[string]*types.Info, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Load will unload the DSL first, then load the DSL from DB or file system
|
||||
func (api *YaoAPI) Load(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||
func (api *YaoAPI) Reload(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unload will unload the DSL from memory
|
||||
func (api *YaoAPI) Unload(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate will validate the DSL from source
|
||||
func (api *YaoAPI) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Execute will execute the DSL
|
||||
func (api *YaoAPI) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
47
dsl/connector/connector.go
Normal file
47
dsl/connector/connector.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/yaoapp/yao/dsl/types"
|
||||
)
|
||||
|
||||
// YaoConnector is the connector DSL manager
|
||||
type YaoConnector struct {
|
||||
root string // The relative path of the connector DSL
|
||||
}
|
||||
|
||||
// New returns a new connector DSL manager
|
||||
func New(root string) types.Manager {
|
||||
return &YaoConnector{root: root}
|
||||
}
|
||||
|
||||
// Loaded return all loaded DSLs
|
||||
func (c *YaoConnector) Loaded(ctx context.Context) (map[string]*types.Info, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Load will unload the DSL first, then load the DSL from DB or file system
|
||||
func (c *YaoConnector) Load(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unload will unload the DSL from memory
|
||||
func (c *YaoConnector) Unload(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||
func (c *YaoConnector) Reload(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate will validate the DSL from source
|
||||
func (c *YaoConnector) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Execute will execute the DSL
|
||||
func (c *YaoConnector) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
277
dsl/db.go
Normal file
277
dsl/db.go
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
package dsl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou/model"
|
||||
"github.com/yaoapp/yao/dsl/types"
|
||||
)
|
||||
|
||||
// getInfoFromDB get the info from the db
|
||||
func (dsl *DSL) dbInspect(id string) (*types.Info, bool, error) {
|
||||
|
||||
// Get from database
|
||||
m := model.Select("__yao.dsl")
|
||||
|
||||
// Get the info
|
||||
var info types.Info
|
||||
rows, err := m.Get(model.QueryParam{
|
||||
Wheres: []model.QueryWhere{{Column: "dsl_id", Value: id}},
|
||||
Select: []interface{}{
|
||||
"dsl_id",
|
||||
"type",
|
||||
"label",
|
||||
"path",
|
||||
"sort",
|
||||
"tags",
|
||||
"description",
|
||||
"status",
|
||||
"store",
|
||||
"mtime",
|
||||
"ctime",
|
||||
},
|
||||
Limit: 1,
|
||||
Orders: []model.QueryOrder{{Column: "sort", Option: "asc"}, {Column: "mtime", Option: "desc"}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
raw, err := jsoniter.Marshal(rows[0])
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
err = jsoniter.Unmarshal(raw, &info)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return &info, true, nil
|
||||
}
|
||||
|
||||
// getSourceFromDB get the source from the db
|
||||
func (dsl *DSL) dbSource(id string) (string, bool, error) {
|
||||
|
||||
// Get from database
|
||||
m := model.Select("__yao.dsl")
|
||||
|
||||
// Get the source
|
||||
rows, err := m.Get(model.QueryParam{
|
||||
Wheres: []model.QueryWhere{{Column: "dsl_id", Value: id}},
|
||||
Select: []interface{}{"source"},
|
||||
Limit: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
source, ok := rows[0]["source"].(string)
|
||||
if !ok {
|
||||
return "", true, fmt.Errorf("%s %s source is not a string", dsl.Type, id)
|
||||
}
|
||||
|
||||
return source, true, nil
|
||||
}
|
||||
|
||||
// getListFromDB get the list from the db
|
||||
func (dsl *DSL) dbList(options *types.ListOptions) ([]*types.Info, error) {
|
||||
|
||||
// Get from database
|
||||
m := model.Select("__yao.dsl")
|
||||
|
||||
var orders []model.QueryOrder = []model.QueryOrder{{Column: "mtime", Option: "desc"}}
|
||||
if options.Sort == "sort" {
|
||||
orders = []model.QueryOrder{{Column: "sort", Option: "asc"}}
|
||||
}
|
||||
|
||||
var wheres []model.QueryWhere = []model.QueryWhere{{Column: "type", Value: dsl.Type}}
|
||||
|
||||
// Filter by tags
|
||||
if len(options.Tags) > 0 {
|
||||
var orwheres []model.QueryWhere = []model.QueryWhere{}
|
||||
for _, tag := range options.Tags {
|
||||
match := "%" + strings.TrimSpace(tag) + "%"
|
||||
orwheres = append(orwheres, model.QueryWhere{Column: "tags", Value: match, OP: "like", Method: "orwhere"})
|
||||
}
|
||||
wheres = append(wheres, model.QueryWhere{Wheres: orwheres})
|
||||
}
|
||||
|
||||
// Get the list
|
||||
rows, err := m.Get(model.QueryParam{
|
||||
Wheres: []model.QueryWhere{{Column: "type", Value: dsl.Type}},
|
||||
Select: []interface{}{"dsl_id", "label", "path", "sort", "tags", "description", "status", "store", "mtime", "ctime"},
|
||||
Orders: orders,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var infos []*types.Info
|
||||
raw, err := jsoniter.Marshal(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = jsoniter.Unmarshal(raw, &infos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func (dsl *DSL) dbCreate(options *types.CreateOptions) error {
|
||||
|
||||
if options.Source == "" {
|
||||
return fmt.Errorf("%s %s source is required", dsl.Type, options.ID)
|
||||
}
|
||||
|
||||
// Get info from source
|
||||
var info types.Info
|
||||
err := jsoniter.Unmarshal([]byte(options.Source), &info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the info
|
||||
m := model.Select("__yao.dsl")
|
||||
data := map[string]interface{}{
|
||||
"source": options.Source,
|
||||
"dsl_id": options.ID,
|
||||
"type": dsl.Type,
|
||||
"label": info.Label,
|
||||
"path": info.Path,
|
||||
"sort": info.Sort,
|
||||
"tags": info.Tags,
|
||||
"description": info.Description,
|
||||
"status": info.Status,
|
||||
"store": info.Store,
|
||||
"mtime": time.Now().Unix(),
|
||||
"ctime": time.Now().Unix(),
|
||||
}
|
||||
|
||||
_, err = m.Create(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dsl *DSL) dbUpdate(options *types.UpdateOptions) error {
|
||||
if options.Source == "" && options.Info == nil {
|
||||
return fmt.Errorf("%s %s one of source or info is required", dsl.Type, options.ID)
|
||||
}
|
||||
|
||||
m := model.Select("__yao.dsl")
|
||||
|
||||
// Check if the dsl exists
|
||||
rows, err := m.Get(model.QueryParam{
|
||||
Wheres: []model.QueryWhere{{Column: "dsl_id", Value: options.ID}},
|
||||
Select: []interface{}{"id", "dsl_id"},
|
||||
Limit: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return fmt.Errorf("%s %s not found", dsl.Type, options.ID)
|
||||
}
|
||||
|
||||
row := rows[0]
|
||||
|
||||
// update source
|
||||
var data map[string]interface{} = map[string]interface{}{}
|
||||
if options.Source != "" {
|
||||
data["source"] = options.Source
|
||||
} else {
|
||||
// Update info
|
||||
if options.Info.Label != "" {
|
||||
data["label"] = options.Info.Label
|
||||
}
|
||||
|
||||
if options.Info.Path != "" {
|
||||
data["path"] = options.Info.Path
|
||||
}
|
||||
|
||||
if options.Info.Sort != 0 {
|
||||
data["sort"] = options.Info.Sort
|
||||
}
|
||||
|
||||
if options.Info.Tags != nil {
|
||||
data["tags"] = options.Info.Tags
|
||||
}
|
||||
|
||||
if options.Info.Description != "" {
|
||||
data["description"] = options.Info.Description
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update the data
|
||||
err = m.Update(row["id"], data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dsl *DSL) dbDelete(id string) error {
|
||||
|
||||
// Get from database
|
||||
m := model.Select("__yao.dsl")
|
||||
|
||||
// Check if the dsl exists
|
||||
rows, err := m.Get(model.QueryParam{
|
||||
Wheres: []model.QueryWhere{{Column: "dsl_id", Value: id}},
|
||||
Select: []interface{}{"id", "dsl_id"},
|
||||
Limit: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return fmt.Errorf("%s %s not found", dsl.Type, id)
|
||||
}
|
||||
|
||||
// Delete the dsl
|
||||
row := rows[0]
|
||||
return m.Delete(row["id"])
|
||||
}
|
||||
|
||||
func (dsl *DSL) dbExists(id string) (bool, error) {
|
||||
|
||||
// Get from database
|
||||
m := model.Select("__yao.dsl")
|
||||
|
||||
// Check if the dsl exists
|
||||
rows, err := m.Get(model.QueryParam{
|
||||
Wheres: []model.QueryWhere{{Column: "dsl_id", Value: id}},
|
||||
Select: []interface{}{"id", "dsl_id"},
|
||||
Limit: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(rows) > 0, nil
|
||||
}
|
||||
319
dsl/dsl.go
Normal file
319
dsl/dsl.go
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
package dsl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/yaoapp/yao/dsl/api"
|
||||
"github.com/yaoapp/yao/dsl/connector"
|
||||
"github.com/yaoapp/yao/dsl/mcp"
|
||||
"github.com/yaoapp/yao/dsl/model"
|
||||
"github.com/yaoapp/yao/dsl/types"
|
||||
)
|
||||
|
||||
// DSL is the base DSL struct
|
||||
type DSL struct {
|
||||
Type types.Type
|
||||
exts []string
|
||||
root string
|
||||
manager types.Manager
|
||||
}
|
||||
|
||||
// New returns a new DSL manager
|
||||
func New(typ types.Type) (types.DSL, error) {
|
||||
var manager types.Manager
|
||||
|
||||
// Get the root path and the extensions of the type
|
||||
root, exts := types.TypeRootAndExts(typ)
|
||||
|
||||
// Create the manager
|
||||
switch typ {
|
||||
case types.TypeConnector:
|
||||
exts = []string{".conn.yao", ".conn.jsonc", ".conn.json"}
|
||||
manager = connector.New(root)
|
||||
|
||||
case types.TypeModel:
|
||||
exts = []string{".mod.yao", ".mod.jsonc", ".mod.json"}
|
||||
manager = model.New(root)
|
||||
|
||||
case types.TypeMCPClient:
|
||||
exts = []string{".mcp.yao", ".mcp.jsonc", ".mcp.json"}
|
||||
manager = mcp.NewClient(root)
|
||||
|
||||
// case types.TypeMCPServer:
|
||||
// exts = []string{".mcp.yao", ".mcp.jsonc", ".mcp.json"}
|
||||
// manager = mcp.NewServer(root)
|
||||
|
||||
case types.TypeAPI:
|
||||
exts = []string{".http.yao", ".http.jsonc", ".http.json"}
|
||||
manager = api.New(root)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("dsl manager is not initialized, %s not supported", typ)
|
||||
}
|
||||
|
||||
return &DSL{Type: typ, manager: manager, root: root, exts: exts}, nil
|
||||
}
|
||||
|
||||
// Inspect DSL
|
||||
func (dsl *DSL) Inspect(ctx context.Context, id string) (*types.Info, error) {
|
||||
|
||||
// Get the info from the db
|
||||
info, exists, err := dsl.dbInspect(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
// Get the info from the file
|
||||
info, exists, err = dsl.fsInspect(types.ToPath(dsl.Type, id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("dsl not found, %s", id)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the status from the manager
|
||||
loaded, err := dsl.manager.Loaded(ctx)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
// Check if the DSL is loaded
|
||||
if _, ok := loaded[id]; ok {
|
||||
info.Status = types.StatusLoaded
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Path Get Path by id, ( If the DSL is saved as file, return the file path )
|
||||
func (dsl *DSL) Path(ctx context.Context, id string) (string, error) {
|
||||
return types.ToPath(dsl.Type, id), nil
|
||||
}
|
||||
|
||||
// Source Get Source by id
|
||||
func (dsl *DSL) Source(ctx context.Context, id string) (string, error) {
|
||||
|
||||
// Get the source from the db
|
||||
source, exists, err := dsl.dbSource(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
// Get the source from the file
|
||||
source, exists, err = dsl.fsSource(types.ToPath(dsl.Type, id))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return "", fmt.Errorf("%s DSL not found, %s", dsl.Type, id)
|
||||
}
|
||||
}
|
||||
|
||||
return source, nil
|
||||
}
|
||||
|
||||
// List DSLs
|
||||
func (dsl *DSL) List(ctx context.Context, opts *types.ListOptions) ([]*types.Info, error) {
|
||||
// Get the list from the db
|
||||
dbList, err := dsl.dbList(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the list from the file
|
||||
fileList, err := dsl.fsList(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Merge the list and unique
|
||||
list := []*types.Info{}
|
||||
unique := make(map[string]bool)
|
||||
for _, info := range dbList {
|
||||
if _, ok := unique[info.ID]; !ok {
|
||||
list = append(list, info)
|
||||
unique[info.ID] = true
|
||||
}
|
||||
}
|
||||
for _, info := range fileList {
|
||||
if _, ok := unique[info.ID]; !ok {
|
||||
list = append(list, info)
|
||||
unique[info.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the status from the manager
|
||||
loaded, err := dsl.manager.Loaded(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Merge the status from the manager
|
||||
for _, info := range list {
|
||||
if _, ok := loaded[info.ID]; ok {
|
||||
info.Status = types.StatusLoaded
|
||||
}
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// Create DSL
|
||||
func (dsl *DSL) Create(ctx context.Context, options *types.CreateOptions) error {
|
||||
if options.Store == types.StoreTypeDB {
|
||||
err := dsl.dbCreate(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if options.Store == types.StoreTypeFile {
|
||||
err := dsl.fsCreate(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Load the DSL
|
||||
err := dsl.Load(ctx, options.ID, options.LoadOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists Check if the DSL exists
|
||||
func (dsl *DSL) Exists(ctx context.Context, id string) (bool, error) {
|
||||
// Check if the DSL exists in the db
|
||||
exists, err := dsl.dbExists(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if exists {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Check if the DSL exists in the file
|
||||
return dsl.fsExists(id)
|
||||
}
|
||||
|
||||
// Update DSL
|
||||
func (dsl *DSL) Update(ctx context.Context, options *types.UpdateOptions) error {
|
||||
|
||||
// Exists
|
||||
info, exists, err := dsl.dbInspect(options.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
info, exists, err = dsl.fsInspect(types.ToPath(dsl.Type, options.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("%s DSL not found, %s", dsl.Type, options.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the DSL in the db
|
||||
if info.Store == types.StoreTypeDB {
|
||||
err := dsl.dbUpdate(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reload the DSL
|
||||
return dsl.manager.Reload(ctx, options.ID, options.ReloadOptions)
|
||||
}
|
||||
|
||||
// Update the DSL in the file
|
||||
err = dsl.fsUpdate(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reload the DSL
|
||||
return dsl.manager.Reload(ctx, options.ID, options.ReloadOptions)
|
||||
}
|
||||
|
||||
// Delete DSL
|
||||
func (dsl *DSL) Delete(ctx context.Context, id string, options ...interface{}) error {
|
||||
// Exists
|
||||
info, exists, err := dsl.dbInspect(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
info, exists, err = dsl.fsInspect(types.ToPath(dsl.Type, id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("%s DSL not found, %s", dsl.Type, id)
|
||||
}
|
||||
}
|
||||
|
||||
var unloadOptions interface{}
|
||||
if len(options) > 0 {
|
||||
unloadOptions = options[0]
|
||||
}
|
||||
|
||||
if info.Store == types.StoreTypeDB {
|
||||
err = dsl.dbDelete(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unload the DSL
|
||||
return dsl.manager.Unload(ctx, id, unloadOptions)
|
||||
}
|
||||
|
||||
err = dsl.fsDelete(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unload the DSL
|
||||
return dsl.manager.Unload(ctx, id, unloadOptions)
|
||||
|
||||
}
|
||||
|
||||
// Load DSL
|
||||
func (dsl *DSL) Load(ctx context.Context, id string, options interface{}) error {
|
||||
return dsl.manager.Load(ctx, id, options)
|
||||
}
|
||||
|
||||
// Unload DSL
|
||||
func (dsl *DSL) Unload(ctx context.Context, id string, options ...interface{}) error {
|
||||
var unloadOptions interface{}
|
||||
if len(options) > 0 {
|
||||
unloadOptions = options[0]
|
||||
}
|
||||
return dsl.manager.Unload(ctx, id, unloadOptions)
|
||||
}
|
||||
|
||||
// Reload DSL
|
||||
func (dsl *DSL) Reload(ctx context.Context, id string, options interface{}) error {
|
||||
return dsl.manager.Reload(ctx, id, options)
|
||||
}
|
||||
|
||||
// Execute DSL (Some DSLs can be executed)
|
||||
func (dsl *DSL) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||
return dsl.manager.Execute(ctx, method, args...)
|
||||
}
|
||||
|
||||
// Validate DSL
|
||||
func (dsl *DSL) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||
return dsl.manager.Validate(ctx, source)
|
||||
}
|
||||
203
dsl/fs.go
Normal file
203
dsl/fs.go
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
package dsl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou/application"
|
||||
"github.com/yaoapp/yao/dsl/types"
|
||||
)
|
||||
|
||||
// getInfoFromFile get the info from the file
|
||||
func (dsl *DSL) fsInspect(id string, path ...string) (*types.Info, bool, error) {
|
||||
file := ""
|
||||
if len(path) > 0 {
|
||||
file = path[0]
|
||||
} else {
|
||||
file = types.ToPath(dsl.Type, id)
|
||||
}
|
||||
|
||||
var info types.Info = types.Info{ID: id, Path: file}
|
||||
exists, err := application.App.Exists(file)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// Read the file
|
||||
data, err := application.App.Read(file)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Unmarshal the data to the info
|
||||
err = application.Parse(file, data, &info)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
|
||||
// Merge the mtime and ctime
|
||||
fileInfo, err := application.App.Info(file)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
|
||||
info.Mtime = fileInfo.ModTime()
|
||||
info.Ctime = fileInfo.ModTime()
|
||||
return &info, true, nil
|
||||
}
|
||||
|
||||
// getSourceFromFile get the source from the file
|
||||
func (dsl *DSL) fsSource(id string) (string, bool, error) {
|
||||
path := types.ToPath(dsl.Type, id)
|
||||
exists, err := application.App.Exists(path)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if !exists {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// Read the file
|
||||
data, err := application.App.Read(path)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return string(data), true, nil
|
||||
}
|
||||
|
||||
// getListFromPath get the list from the path
|
||||
func (dsl *DSL) fsList(options *types.ListOptions) ([]*types.Info, error) {
|
||||
root, exts := types.TypeRootAndExts(dsl.Type)
|
||||
var infos []*types.Info = []*types.Info{}
|
||||
patterns := []string{}
|
||||
for _, ext := range exts {
|
||||
patterns = append(patterns, "*"+ext)
|
||||
}
|
||||
var errs []error
|
||||
err := application.App.Walk(root, func(root, file string, isdir bool) error {
|
||||
if isdir {
|
||||
return nil
|
||||
}
|
||||
id := types.WithTypeToID(dsl.Type, file)
|
||||
info, _, err := dsl.fsInspect(id, file)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filter by options
|
||||
if len(options.Tags) > 0 {
|
||||
if len(info.Tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, tag := range options.Tags {
|
||||
for _, t := range info.Tags {
|
||||
if t == tag {
|
||||
infos = append(infos, info)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add to the list
|
||||
infos = append(infos, info)
|
||||
return err
|
||||
}, patterns...)
|
||||
|
||||
return infos, err
|
||||
}
|
||||
|
||||
func (dsl *DSL) fsCreate(options *types.CreateOptions) error {
|
||||
|
||||
path := types.ToPath(dsl.Type, options.ID)
|
||||
|
||||
// Check if the file is a directory
|
||||
exists, err := application.App.Exists(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists {
|
||||
return fmt.Errorf("%v %s already exists", dsl.Type, options.ID)
|
||||
}
|
||||
|
||||
// Create the file
|
||||
return application.App.Write(path, []byte(options.Source))
|
||||
}
|
||||
|
||||
func (dsl *DSL) fsUpdate(options *types.UpdateOptions) error {
|
||||
|
||||
// Validate the options
|
||||
if options.Source == "" && options.Info == nil {
|
||||
return fmt.Errorf("%v %s one of source or info is required", dsl.Type, options.ID)
|
||||
}
|
||||
|
||||
path := types.ToPath(dsl.Type, options.ID)
|
||||
|
||||
// Check if the file exists
|
||||
exists, err := application.App.Exists(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("%v %s not found", dsl.Type, options.ID)
|
||||
}
|
||||
|
||||
// Update source
|
||||
if options.Source != "" {
|
||||
return application.App.Write(path, []byte(options.Source))
|
||||
}
|
||||
|
||||
// Update info
|
||||
var source map[string]interface{}
|
||||
data, err := application.App.Read(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = application.Parse(path, data, &source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the info
|
||||
source["id"] = options.ID
|
||||
source["label"] = options.Info.Label
|
||||
source["tags"] = options.Info.Tags
|
||||
source["description"] = options.Info.Description
|
||||
new, err := jsoniter.MarshalIndent(source, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return application.App.Write(path, []byte(new))
|
||||
}
|
||||
|
||||
func (dsl *DSL) fsDelete(id string) error {
|
||||
|
||||
path := types.ToPath(dsl.Type, id)
|
||||
|
||||
// Check if the file is a directory
|
||||
exists, err := application.App.Exists(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("%v %s not found", dsl.Type, id)
|
||||
}
|
||||
|
||||
// Delete the file
|
||||
return application.App.Remove(path)
|
||||
}
|
||||
|
||||
func (dsl *DSL) fsExists(id string) (bool, error) {
|
||||
path := types.ToPath(dsl.Type, id)
|
||||
return application.App.Exists(path)
|
||||
}
|
||||
52
dsl/mcp/client.go
Normal file
52
dsl/mcp/client.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package mcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/yaoapp/yao/dsl/types"
|
||||
)
|
||||
|
||||
// YaoMCPClient is the MCP client DSL manager
|
||||
type YaoMCPClient struct {
|
||||
root string // The relative path of the MCP client DSL
|
||||
}
|
||||
|
||||
// NewClient returns a new MCP client DSL manager
|
||||
func NewClient(root string) types.Manager {
|
||||
return New(root)
|
||||
}
|
||||
|
||||
// New returns a new connector DSL manager
|
||||
func New(root string) types.Manager {
|
||||
return &YaoMCPClient{root: root}
|
||||
}
|
||||
|
||||
// Loaded return all loaded DSLs
|
||||
func (client *YaoMCPClient) Loaded(ctx context.Context) (map[string]*types.Info, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Load will unload the DSL first, then load the DSL from DB or file system
|
||||
func (client *YaoMCPClient) Load(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unload will unload the DSL from memory
|
||||
func (client *YaoMCPClient) Unload(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||
func (client *YaoMCPClient) Reload(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate will validate the DSL from source
|
||||
func (client *YaoMCPClient) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Execute will execute the DSL
|
||||
func (client *YaoMCPClient) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
47
dsl/mcp/server.go
Normal file
47
dsl/mcp/server.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package mcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/yaoapp/yao/dsl/types"
|
||||
)
|
||||
|
||||
// YaoMCPServer is the MCP client DSL manager
|
||||
type YaoMCPServer struct {
|
||||
root string // The relative path of the MCP client DSL
|
||||
}
|
||||
|
||||
// NewServer returns a new MCP server DSL manager
|
||||
func NewServer(root string) types.Manager {
|
||||
return &YaoMCPServer{root: root}
|
||||
}
|
||||
|
||||
// Loaded return all loaded DSLs
|
||||
func (server *YaoMCPServer) Loaded(ctx context.Context) (map[string]*types.Info, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Load will unload the DSL first, then load the DSL from DB or file system
|
||||
func (server *YaoMCPServer) Load(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unload will unload the DSL from memory
|
||||
func (server *YaoMCPServer) Unload(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||
func (server *YaoMCPServer) Reload(ctx context.Context, id string, options interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate will validate the DSL from source
|
||||
func (server *YaoMCPServer) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Execute will execute the DSL
|
||||
func (server *YaoMCPServer) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
139
dsl/model/model.go
Normal file
139
dsl/model/model.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/yaoapp/gou/model"
|
||||
"github.com/yaoapp/yao/dsl/types"
|
||||
)
|
||||
|
||||
// YaoModel is the MCP client DSL manager
|
||||
type YaoModel struct {
|
||||
root string // The relative path of the MCP client DSL
|
||||
}
|
||||
|
||||
// NewClient returns a new MCP client DSL manager
|
||||
func NewClient(root string) types.Manager {
|
||||
return New(root)
|
||||
}
|
||||
|
||||
// New returns a new connector DSL manager
|
||||
func New(root string) types.Manager {
|
||||
return &YaoModel{root: root}
|
||||
}
|
||||
|
||||
// Loaded return all loaded DSLs
|
||||
func (m *YaoModel) Loaded(ctx context.Context) (map[string]*types.Info, error) {
|
||||
|
||||
infos := map[string]*types.Info{}
|
||||
for id, mod := range model.Models {
|
||||
infos[id] = &types.Info{
|
||||
ID: id,
|
||||
Type: types.TypeModel,
|
||||
Label: mod.MetaData.Name,
|
||||
Path: mod.File,
|
||||
Sort: 999,
|
||||
Tags: []string{},
|
||||
Description: "Description",
|
||||
}
|
||||
}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
// Load will unload the DSL first, then load the DSL from DB or file system
|
||||
func (m *YaoModel) Load(ctx context.Context, id string, options interface{}) error {
|
||||
var opts map[string]interface{}
|
||||
if v, ok := options.(map[string]interface{}); ok {
|
||||
opts = v
|
||||
}
|
||||
|
||||
var migration bool = false
|
||||
if v, ok := opts["migration"]; ok {
|
||||
migration = v.(bool)
|
||||
}
|
||||
|
||||
var reset bool = false
|
||||
if v, ok := opts["reset"]; ok {
|
||||
reset = v.(bool)
|
||||
}
|
||||
|
||||
path := types.ToPath(types.TypeModel, id)
|
||||
mod, err := model.LoadSync(path, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if migration || reset {
|
||||
return mod.Migrate(reset, model.WithDonotInsertValues(true))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unload will unload the DSL from memory
|
||||
func (m *YaoModel) Unload(ctx context.Context, id string, options interface{}) error {
|
||||
|
||||
var opts map[string]interface{}
|
||||
if v, ok := options.(map[string]interface{}); ok {
|
||||
opts = v
|
||||
}
|
||||
|
||||
var dropTable bool = false
|
||||
if v, ok := opts["dropTable"]; ok {
|
||||
dropTable = v.(bool)
|
||||
}
|
||||
|
||||
mod := model.Select(id)
|
||||
if mod == nil {
|
||||
return fmt.Errorf("model %s not found", id)
|
||||
}
|
||||
|
||||
if dropTable {
|
||||
return mod.DropTable()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||
func (m *YaoModel) Reload(ctx context.Context, id string, options interface{}) error {
|
||||
|
||||
var opts map[string]interface{}
|
||||
if v, ok := options.(map[string]interface{}); ok {
|
||||
opts = v
|
||||
}
|
||||
|
||||
var migration bool = false
|
||||
if v, ok := opts["migration"]; ok {
|
||||
migration = v.(bool)
|
||||
}
|
||||
|
||||
var reset bool = false
|
||||
if v, ok := opts["reset"]; ok {
|
||||
reset = v.(bool)
|
||||
}
|
||||
|
||||
// Reload the model
|
||||
path := types.ToPath(types.TypeModel, id)
|
||||
mod, err := model.LoadSync(path, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if migration || reset {
|
||||
return mod.Migrate(reset, model.WithDonotInsertValues(true))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate will validate the DSL from source
|
||||
func (m *YaoModel) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||
return true, []types.LintMessage{}
|
||||
}
|
||||
|
||||
// Execute will execute the DSL
|
||||
func (m *YaoModel) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||
return nil, fmt.Errorf("Not implemented")
|
||||
}
|
||||
49
dsl/types/interfaces.go
Normal file
49
dsl/types/interfaces.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package types
|
||||
|
||||
import "context"
|
||||
|
||||
// DSL interface
|
||||
type DSL interface {
|
||||
Inspect(ctx context.Context, id string) (*Info, error) // Inspect DSL
|
||||
Path(ctx context.Context, id string) (string, error) // Get Path by id, ( If the DSL is saved as file, return the file path )
|
||||
Source(ctx context.Context, id string) (string, error) // Get Source by id
|
||||
List(ctx context.Context, opts *ListOptions) ([]*Info, error) // List All DSLs including unloaded/error DSLs
|
||||
Exists(ctx context.Context, id string) (bool, error) // Check if the DSL exists
|
||||
|
||||
// DSL Operations
|
||||
Create(ctx context.Context, options *CreateOptions) error // Create DSL, Create will unload the DSL first, then create the DSL to DB
|
||||
Update(ctx context.Context, options *UpdateOptions) error // Update DSL, Update will unload the DSL first, then update the DSL, if update info only, will not unload the DSL
|
||||
Delete(ctx context.Context, id string, unloadOptions ...interface{}) error // Delete DSL, Delete will unload the DSL first, then delete the DSL file
|
||||
|
||||
// Load manager
|
||||
Load(ctx context.Context, id string, options interface{}) error // Load DSL, Load will unload the DSL first, then load the DSL from DB or file system
|
||||
Reload(ctx context.Context, id string, options interface{}) error // Reload DSL, Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||
Unload(ctx context.Context, id string, options ...interface{}) error // Unload DSL, Unload will unload the DSL from memory
|
||||
|
||||
// Execute
|
||||
Execute(ctx context.Context, method string, args ...any) (any, error) // Execute DSL (Some DSLs can be executed)
|
||||
|
||||
// Validate
|
||||
Validate(ctx context.Context, source string) (bool, []LintMessage) // Validate DSL, Validate will validate the DSL from source
|
||||
}
|
||||
|
||||
// Manager interface
|
||||
type Manager interface {
|
||||
// Get all loaded DSLs
|
||||
Loaded(ctx context.Context) (map[string]*Info, error) // Get all loaded DSLs
|
||||
|
||||
// Load DSL, Load will unload the DSL first, then load the DSL from DB or file system
|
||||
Load(ctx context.Context, id string, options interface{}) error
|
||||
|
||||
// Unload DSL, Unload will unload the DSL from memory
|
||||
Unload(ctx context.Context, id string, options interface{}) error
|
||||
|
||||
// Reload DSL, Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||
Reload(ctx context.Context, id string, options interface{}) error
|
||||
|
||||
// Validate DSL, Validate will validate the DSL from source
|
||||
Validate(ctx context.Context, source string) (bool, []LintMessage)
|
||||
|
||||
// Execute DSL (Some DSLs can be executed)
|
||||
Execute(ctx context.Context, method string, args ...any) (any, error)
|
||||
}
|
||||
126
dsl/types/types.go
Normal file
126
dsl/types/types.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Type for DSL
|
||||
type Type string
|
||||
|
||||
// Status for DSL
|
||||
type Status string
|
||||
|
||||
// StoreType for DSL store
|
||||
type StoreType string
|
||||
|
||||
// LintSeverity for DSL linter
|
||||
type LintSeverity string
|
||||
|
||||
// StoreType for DSL store
|
||||
const (
|
||||
StoreTypeDB StoreType = "db"
|
||||
StoreTypeFile StoreType = "file"
|
||||
)
|
||||
|
||||
// Status for DSL
|
||||
const (
|
||||
StatusLoading Status = "loading"
|
||||
StatusLoaded Status = "loaded"
|
||||
StatusError Status = "error"
|
||||
)
|
||||
|
||||
// LintSeverity for DSL linter
|
||||
const (
|
||||
LintSeverityError LintSeverity = "error"
|
||||
LintSeverityWarning LintSeverity = "warning"
|
||||
LintSeverityInfo LintSeverity = "info"
|
||||
LintSeverityHint LintSeverity = "hint"
|
||||
)
|
||||
|
||||
// Type for DSL
|
||||
const (
|
||||
// TypeModel for model
|
||||
TypeModel Type = "model"
|
||||
// TypeAPI for api
|
||||
TypeAPI Type = "api"
|
||||
// TypeConnector for connector
|
||||
TypeConnector Type = "connector"
|
||||
// TypeMCPServer for MCP server
|
||||
TypeMCPServer Type = "mcp-server"
|
||||
// TypeMCPClient for MCP client
|
||||
TypeMCPClient Type = "mcp-client"
|
||||
// TypeStore for store
|
||||
TypeStore Type = "store"
|
||||
// TypeSchedule for schedule
|
||||
TypeSchedule Type = "schedule"
|
||||
|
||||
// TypeTable for table
|
||||
TypeTable Type = "table"
|
||||
// TypeForm for form
|
||||
TypeForm Type = "form"
|
||||
// TypeList for list
|
||||
TypeList Type = "list"
|
||||
// TypeChart for chart
|
||||
TypeChart Type = "chart"
|
||||
// TypeDashboard for dashboard
|
||||
TypeDashboard Type = "dashboard"
|
||||
|
||||
// TypeFlow for flow
|
||||
TypeFlow Type = "flow"
|
||||
// TypePipe for pipe
|
||||
TypePipe Type = "pipe"
|
||||
// TypeAIGC for aigc
|
||||
TypeAIGC Type = "aigc"
|
||||
|
||||
// TypeUnknown for unknown
|
||||
TypeUnknown Type = "unknown"
|
||||
)
|
||||
|
||||
// Info for DSL
|
||||
type Info struct {
|
||||
ID string
|
||||
Type Type
|
||||
Sort int
|
||||
Path string
|
||||
Label string
|
||||
Description string
|
||||
Tags []string
|
||||
Status Status
|
||||
Store StoreType
|
||||
Mtime time.Time
|
||||
Ctime time.Time
|
||||
}
|
||||
|
||||
// ListOptions for DSL list
|
||||
type ListOptions struct {
|
||||
Sort string
|
||||
Order string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// CreateOptions for DSL upsert
|
||||
type CreateOptions struct {
|
||||
ID string // ID is the id of the DSL, if not provided, a new id will be generated, required
|
||||
Source string // Source is the source of the DSL, if not provided, the DSL will be loaded from the file system
|
||||
Store StoreType // Store is the store type of the DSL, if not provided, the DSL will be loaded from the file system
|
||||
LoadOptions interface{} // LoadOptions is the options for the DSL, if not provided, the DSL will be loaded from the file system
|
||||
}
|
||||
|
||||
// UpdateOptions for DSL upsert
|
||||
type UpdateOptions struct {
|
||||
ID string // ID is the id of the DSL, if not provided, a new id will be generated, required
|
||||
Info *Info // Info is the info of the DSL, if not provided, the DSL will be loaded from the file system, one of info or source must be provided
|
||||
Source string // Source is the source of the DSL, if not provided, the DSL will be loaded from the file system, one of info or source must be provided
|
||||
ReloadOptions interface{} // ReloadOptions is the options for the DSL, if not provided, the DSL will be loaded from the file system
|
||||
}
|
||||
|
||||
// LintMessage for DSL linter
|
||||
type LintMessage struct {
|
||||
File string
|
||||
Line int
|
||||
Column int
|
||||
Message string
|
||||
Severity LintSeverity
|
||||
}
|
||||
|
||||
var lintMessages []LintMessage
|
||||
194
dsl/types/utils.go
Normal file
194
dsl/types/utils.go
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ToPath convert id to path
|
||||
func ToPath(typ Type, id string) string {
|
||||
|
||||
// Get the root path and the extensions of the type
|
||||
root, exts := TypeRootAndExts(typ)
|
||||
ext := ".yao"
|
||||
if len(exts) > 0 {
|
||||
ext = exts[0]
|
||||
}
|
||||
|
||||
// 1. Replace all . to /
|
||||
path := strings.ReplaceAll(id, ".", string(os.PathSeparator))
|
||||
// 2. Replace all __ to .
|
||||
path = strings.ReplaceAll(path, "__", ".")
|
||||
// 3. Join the root path
|
||||
return filepath.Join(root, path) + ext
|
||||
}
|
||||
|
||||
// ToID convert file path to id
|
||||
func ToID(path string) string {
|
||||
typ := DetectType(path)
|
||||
return WithTypeToID(typ, path)
|
||||
}
|
||||
|
||||
// WithTypeToID convert file path to id
|
||||
func WithTypeToID(typ Type, path string) string {
|
||||
|
||||
// Get the root path and the extensions of the type
|
||||
root, exts := TypeRootAndExts(typ)
|
||||
|
||||
// 0. if the first character is /, remove it
|
||||
if strings.HasPrefix(path, string(os.PathSeparator)) {
|
||||
path = strings.TrimPrefix(path, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
// 1. Split the path by /
|
||||
parts := strings.Split(path, string(os.PathSeparator))
|
||||
if len(parts) > 0 && parts[0] == root {
|
||||
// Skip the root path
|
||||
parts = parts[1:]
|
||||
|
||||
// Remove the extension only if parts is not empty
|
||||
if len(parts) > 0 {
|
||||
last := parts[len(parts)-1]
|
||||
for _, ext := range exts {
|
||||
if strings.HasSuffix(last, ext) {
|
||||
parts[len(parts)-1] = strings.TrimSuffix(last, ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Join the parts
|
||||
path = strings.Join(parts, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
// 2. Replace All . to __
|
||||
path = strings.ReplaceAll(path, ".", "__")
|
||||
|
||||
// 3. Replace all / to .
|
||||
path = strings.ReplaceAll(path, string(os.PathSeparator), ".")
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// DetectType detect the type by the file path
|
||||
func DetectType(path string) Type {
|
||||
parts := strings.Split(path, string(os.PathSeparator))
|
||||
if len(parts) < 2 {
|
||||
return TypeUnknown
|
||||
}
|
||||
|
||||
root := parts[0]
|
||||
last := parts[len(parts)-1]
|
||||
extParts := strings.Split(last, ".")
|
||||
if len(extParts) < 2 {
|
||||
return TypeUnknown
|
||||
}
|
||||
ext := extParts[len(extParts)-2]
|
||||
|
||||
// Detect the type by the extension
|
||||
switch ext {
|
||||
case "http":
|
||||
return TypeAPI
|
||||
case "sch":
|
||||
return TypeSchedule
|
||||
case "table":
|
||||
return TypeTable
|
||||
case "form":
|
||||
return TypeForm
|
||||
case "list":
|
||||
return TypeList
|
||||
case "chart":
|
||||
return TypeChart
|
||||
case "dash":
|
||||
return TypeDashboard
|
||||
case "flow":
|
||||
return TypeFlow
|
||||
case "pipe":
|
||||
return TypePipe
|
||||
case "ai":
|
||||
return TypeAIGC
|
||||
case "mod":
|
||||
return TypeModel
|
||||
case "conn":
|
||||
return TypeConnector
|
||||
case "lru", "redis", "mongo", "badger":
|
||||
return TypeStore
|
||||
}
|
||||
|
||||
// Detect the type by the root path
|
||||
switch root {
|
||||
case "models":
|
||||
return TypeModel
|
||||
case "connectors":
|
||||
return TypeConnector
|
||||
case "mcps":
|
||||
return TypeMCPClient
|
||||
case "apis":
|
||||
if ext == "http" {
|
||||
return TypeAPI
|
||||
}
|
||||
if ext == "mcp" {
|
||||
return TypeMCPServer
|
||||
}
|
||||
return TypeUnknown
|
||||
case "schedules":
|
||||
return TypeSchedule
|
||||
case "tables":
|
||||
return TypeTable
|
||||
case "forms":
|
||||
return TypeForm
|
||||
case "lists":
|
||||
return TypeList
|
||||
case "charts":
|
||||
return TypeChart
|
||||
case "dashboards":
|
||||
return TypeDashboard
|
||||
case "flows":
|
||||
return TypeFlow
|
||||
case "pipes":
|
||||
return TypePipe
|
||||
case "aigcs":
|
||||
return TypeAIGC
|
||||
case "stores":
|
||||
return TypeStore
|
||||
default:
|
||||
return TypeUnknown
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TypeRootAndExts return the root path and the extensions of the type
|
||||
func TypeRootAndExts(typ Type) (string, []string) {
|
||||
switch typ {
|
||||
case TypeModel:
|
||||
return "models", []string{".mod.yao", ".mod.jsonc", ".mod.json"}
|
||||
case TypeConnector:
|
||||
return "connectors", []string{".conn.yao", ".conn.jsonc", ".conn.json"}
|
||||
case TypeMCPClient, TypeMCPServer:
|
||||
return "mcps", []string{".mcp.yao", ".mcp.jsonc", ".mcp.json"}
|
||||
case TypeAPI:
|
||||
return "apis", []string{".http.yao", ".http.jsonc", ".http.json"}
|
||||
case TypeSchedule:
|
||||
return "schedules", []string{".sch.yao", ".sch.jsonc", ".sch.json"}
|
||||
case TypeTable:
|
||||
return "tables", []string{".table.yao", ".table.jsonc", ".table.json"}
|
||||
case TypeForm:
|
||||
return "forms", []string{".form.yao", ".form.jsonc", ".form.json"}
|
||||
case TypeList:
|
||||
return "lists", []string{".list.yao", ".list.jsonc", ".list.json"}
|
||||
case TypeChart:
|
||||
return "charts", []string{".chart.yao", ".chart.jsonc", ".chart.json"}
|
||||
case TypeDashboard:
|
||||
return "dashboards", []string{".dash.yao", ".dash.jsonc", ".dash.json"}
|
||||
case TypeFlow:
|
||||
return "flows", []string{".flow.yao", ".flow.jsonc", ".flow.json"}
|
||||
case TypePipe:
|
||||
return "pipes", []string{".pipe.yao", ".pipe.jsonc", ".pipe.json"}
|
||||
case TypeAIGC:
|
||||
return "aigcs", []string{".ai.yao", ".ai.jsonc", ".ai.json"}
|
||||
case TypeStore:
|
||||
return "stores", []string{".lru.yao", ".redis.yao", ".mongo.yao", ".badger.yao", ".store.yao", ".store.jsonc", ".store.json"}
|
||||
default:
|
||||
return "", []string{}
|
||||
}
|
||||
}
|
||||
601
dsl/types/utils_test.go
Normal file
601
dsl/types/utils_test.go
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestToPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
typ Type
|
||||
id string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Model with dots and underscores",
|
||||
typ: TypeModel,
|
||||
id: "user__profile.admin",
|
||||
want: filepath.Join("models", "user.profile", "admin.mod.yao"),
|
||||
},
|
||||
{
|
||||
name: "API with simple id",
|
||||
typ: TypeAPI,
|
||||
id: "user.login",
|
||||
want: filepath.Join("apis", "user", "login.http.yao"),
|
||||
},
|
||||
{
|
||||
name: "Unknown type (defaults to .yao)",
|
||||
typ: TypeUnknown,
|
||||
id: "test",
|
||||
want: filepath.Join("", "test.yao"),
|
||||
},
|
||||
{
|
||||
name: "Connector with nested path",
|
||||
typ: TypeConnector,
|
||||
id: "database.mysql__config",
|
||||
want: filepath.Join("connectors", "database", "mysql.config.conn.yao"),
|
||||
},
|
||||
{
|
||||
name: "Type with no extensions",
|
||||
typ: Type("unknown"),
|
||||
id: "test",
|
||||
want: filepath.Join("", "test.yao"),
|
||||
},
|
||||
{
|
||||
name: "Type with empty extensions",
|
||||
typ: Type(""),
|
||||
id: "test",
|
||||
want: filepath.Join("", "test.yao"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ToPath(tt.typ, tt.id); got != tt.want {
|
||||
t.Errorf("ToPath() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Model file path",
|
||||
path: filepath.Join("models", "user.mod.yao"),
|
||||
want: "user",
|
||||
},
|
||||
{
|
||||
name: "API file path",
|
||||
path: filepath.Join("apis", "user", "login.http.yao"),
|
||||
want: "user.login",
|
||||
},
|
||||
{
|
||||
name: "Form file path with dots",
|
||||
path: filepath.Join("forms", "user.profile", "edit.form.yao"),
|
||||
want: "user__profile.edit",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ToID(tt.path); got != tt.want {
|
||||
t.Errorf("ToID() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithTypeToID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
typ Type
|
||||
path string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Path with leading separator",
|
||||
typ: TypeModel,
|
||||
path: string(os.PathSeparator) + filepath.Join("models", "user.mod.yao"),
|
||||
want: "user",
|
||||
},
|
||||
{
|
||||
name: "Path without leading separator",
|
||||
typ: TypeModel,
|
||||
path: filepath.Join("models", "user.mod.yao"),
|
||||
want: "user",
|
||||
},
|
||||
{
|
||||
name: "Path with root not matching",
|
||||
typ: TypeModel,
|
||||
path: filepath.Join("other", "user.mod.yao"),
|
||||
want: "other.user__mod__yao",
|
||||
},
|
||||
{
|
||||
name: "Nested path with dots",
|
||||
typ: TypeForm,
|
||||
path: filepath.Join("forms", "user.profile", "edit.form.yao"),
|
||||
want: "user__profile.edit",
|
||||
},
|
||||
{
|
||||
name: "Multiple extensions matching",
|
||||
typ: TypeModel,
|
||||
path: filepath.Join("models", "user.mod.jsonc"),
|
||||
want: "user",
|
||||
},
|
||||
{
|
||||
name: "No extension matching",
|
||||
typ: TypeModel,
|
||||
path: filepath.Join("models", "user.txt"),
|
||||
want: "user__txt",
|
||||
},
|
||||
{
|
||||
name: "Path with single part",
|
||||
typ: TypeModel,
|
||||
path: "user.mod.yao",
|
||||
want: "user__mod__yao",
|
||||
},
|
||||
{
|
||||
name: "Store type with multiple extensions",
|
||||
typ: TypeStore,
|
||||
path: filepath.Join("stores", "cache.redis.yao"),
|
||||
want: "cache",
|
||||
},
|
||||
{
|
||||
name: "Empty path",
|
||||
typ: TypeModel,
|
||||
path: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "Path with root matching but no parts",
|
||||
typ: TypeModel,
|
||||
path: "models",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := WithTypeToID(tt.typ, tt.path); got != tt.want {
|
||||
t.Errorf("WithTypeToID() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
want Type
|
||||
}{
|
||||
// Test by extension
|
||||
{
|
||||
name: "HTTP API",
|
||||
path: filepath.Join("apis", "user.http.yao"),
|
||||
want: TypeAPI,
|
||||
},
|
||||
{
|
||||
name: "Schedule",
|
||||
path: filepath.Join("schedules", "backup.sch.yao"),
|
||||
want: TypeSchedule,
|
||||
},
|
||||
{
|
||||
name: "Table",
|
||||
path: filepath.Join("tables", "user.table.yao"),
|
||||
want: TypeTable,
|
||||
},
|
||||
{
|
||||
name: "Form",
|
||||
path: filepath.Join("forms", "user.form.yao"),
|
||||
want: TypeForm,
|
||||
},
|
||||
{
|
||||
name: "List",
|
||||
path: filepath.Join("lists", "user.list.yao"),
|
||||
want: TypeList,
|
||||
},
|
||||
{
|
||||
name: "Chart",
|
||||
path: filepath.Join("charts", "sales.chart.yao"),
|
||||
want: TypeChart,
|
||||
},
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: filepath.Join("dashboards", "main.dash.yao"),
|
||||
want: TypeDashboard,
|
||||
},
|
||||
{
|
||||
name: "Flow",
|
||||
path: filepath.Join("flows", "process.flow.yao"),
|
||||
want: TypeFlow,
|
||||
},
|
||||
{
|
||||
name: "Pipe",
|
||||
path: filepath.Join("pipes", "transform.pipe.yao"),
|
||||
want: TypePipe,
|
||||
},
|
||||
{
|
||||
name: "AIGC",
|
||||
path: filepath.Join("aigcs", "chat.ai.yao"),
|
||||
want: TypeAIGC,
|
||||
},
|
||||
{
|
||||
name: "Model by extension",
|
||||
path: filepath.Join("models", "user.mod.yao"),
|
||||
want: TypeModel,
|
||||
},
|
||||
{
|
||||
name: "Connector by extension",
|
||||
path: filepath.Join("connectors", "db.conn.yao"),
|
||||
want: TypeConnector,
|
||||
},
|
||||
{
|
||||
name: "Store LRU",
|
||||
path: filepath.Join("stores", "cache.lru.yao"),
|
||||
want: TypeStore,
|
||||
},
|
||||
{
|
||||
name: "LRU extension in non-stores directory",
|
||||
path: filepath.Join("other", "cache.lru.yao"),
|
||||
want: TypeStore,
|
||||
},
|
||||
{
|
||||
name: "Store Redis",
|
||||
path: filepath.Join("stores", "cache.redis.yao"),
|
||||
want: TypeStore,
|
||||
},
|
||||
{
|
||||
name: "Store Mongo",
|
||||
path: filepath.Join("stores", "cache.mongo.yao"),
|
||||
want: TypeStore,
|
||||
},
|
||||
{
|
||||
name: "Store Badger",
|
||||
path: filepath.Join("stores", "cache.badger.yao"),
|
||||
want: TypeStore,
|
||||
},
|
||||
{
|
||||
name: "Store by extension",
|
||||
path: filepath.Join("stores", "cache.store.yao"),
|
||||
want: TypeStore,
|
||||
},
|
||||
{
|
||||
name: "MCP extension in non-apis directory",
|
||||
path: filepath.Join("other", "service.mcp.yao"),
|
||||
want: TypeUnknown,
|
||||
},
|
||||
// Test by root path
|
||||
{
|
||||
name: "Model by root",
|
||||
path: filepath.Join("models", "user.yao"),
|
||||
want: TypeModel,
|
||||
},
|
||||
{
|
||||
name: "Connector by root",
|
||||
path: filepath.Join("connectors", "db.yao"),
|
||||
want: TypeConnector,
|
||||
},
|
||||
{
|
||||
name: "MCP Client",
|
||||
path: filepath.Join("mcps", "client.yao"),
|
||||
want: TypeMCPClient,
|
||||
},
|
||||
{
|
||||
name: "API by root with http ext",
|
||||
path: filepath.Join("apis", "user.http.yao"),
|
||||
want: TypeAPI,
|
||||
},
|
||||
{
|
||||
name: "MCP Server",
|
||||
path: filepath.Join("apis", "server.mcp.yao"),
|
||||
want: TypeMCPServer,
|
||||
},
|
||||
{
|
||||
name: "MCP by extension",
|
||||
path: filepath.Join("mcps", "client.mcp.yao"),
|
||||
want: TypeMCPClient,
|
||||
},
|
||||
{
|
||||
name: "API by root unknown ext",
|
||||
path: filepath.Join("apis", "user.unknown.yao"),
|
||||
want: TypeUnknown,
|
||||
},
|
||||
{
|
||||
name: "Schedule by root",
|
||||
path: filepath.Join("schedules", "backup.yao"),
|
||||
want: TypeSchedule,
|
||||
},
|
||||
{
|
||||
name: "Table by root",
|
||||
path: filepath.Join("tables", "user.yao"),
|
||||
want: TypeTable,
|
||||
},
|
||||
{
|
||||
name: "Form by root",
|
||||
path: filepath.Join("forms", "user.yao"),
|
||||
want: TypeForm,
|
||||
},
|
||||
{
|
||||
name: "List by root",
|
||||
path: filepath.Join("lists", "user.yao"),
|
||||
want: TypeList,
|
||||
},
|
||||
{
|
||||
name: "Chart by root",
|
||||
path: filepath.Join("charts", "sales.yao"),
|
||||
want: TypeChart,
|
||||
},
|
||||
{
|
||||
name: "Dashboard by root",
|
||||
path: filepath.Join("dashboards", "main.yao"),
|
||||
want: TypeDashboard,
|
||||
},
|
||||
{
|
||||
name: "Flow by root",
|
||||
path: filepath.Join("flows", "process.yao"),
|
||||
want: TypeFlow,
|
||||
},
|
||||
{
|
||||
name: "Pipe by root",
|
||||
path: filepath.Join("pipes", "transform.yao"),
|
||||
want: TypePipe,
|
||||
},
|
||||
{
|
||||
name: "AIGC by root",
|
||||
path: filepath.Join("aigcs", "chat.yao"),
|
||||
want: TypeAIGC,
|
||||
},
|
||||
{
|
||||
name: "Store by root",
|
||||
path: filepath.Join("stores", "cache.yao"),
|
||||
want: TypeStore,
|
||||
},
|
||||
{
|
||||
name: "Unknown root",
|
||||
path: filepath.Join("unknown", "file.yao"),
|
||||
want: TypeUnknown,
|
||||
},
|
||||
// Edge cases
|
||||
{
|
||||
name: "Path with less than 2 parts",
|
||||
path: "file.yao",
|
||||
want: TypeUnknown,
|
||||
},
|
||||
{
|
||||
name: "File without extension",
|
||||
path: filepath.Join("models", "user"),
|
||||
want: TypeUnknown,
|
||||
},
|
||||
{
|
||||
name: "File with single dot",
|
||||
path: filepath.Join("models", "user.yao"),
|
||||
want: TypeModel,
|
||||
},
|
||||
{
|
||||
name: "Empty path",
|
||||
path: "",
|
||||
want: TypeUnknown,
|
||||
},
|
||||
{
|
||||
name: "Path with single component",
|
||||
path: "file",
|
||||
want: TypeUnknown,
|
||||
},
|
||||
{
|
||||
name: "File with extension parts length < 2",
|
||||
path: filepath.Join("models", "user"),
|
||||
want: TypeUnknown,
|
||||
},
|
||||
{
|
||||
name: "File with extension matching filename",
|
||||
path: filepath.Join("models", "http.yao"),
|
||||
want: TypeAPI,
|
||||
},
|
||||
{
|
||||
name: "File with extension matching filename - sch",
|
||||
path: filepath.Join("schedules", "sch.yao"),
|
||||
want: TypeSchedule,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := DetectType(tt.path); got != tt.want {
|
||||
t.Errorf("DetectType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeRootAndExts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
typ Type
|
||||
wantRoot string
|
||||
wantExts []string
|
||||
}{
|
||||
{
|
||||
name: "Model",
|
||||
typ: TypeModel,
|
||||
wantRoot: "models",
|
||||
wantExts: []string{".mod.yao", ".mod.jsonc", ".mod.json"},
|
||||
},
|
||||
{
|
||||
name: "Connector",
|
||||
typ: TypeConnector,
|
||||
wantRoot: "connectors",
|
||||
wantExts: []string{".conn.yao", ".conn.jsonc", ".conn.json"},
|
||||
},
|
||||
{
|
||||
name: "MCP Client",
|
||||
typ: TypeMCPClient,
|
||||
wantRoot: "mcps",
|
||||
wantExts: []string{".mcp.yao", ".mcp.jsonc", ".mcp.json"},
|
||||
},
|
||||
{
|
||||
name: "MCP Server",
|
||||
typ: TypeMCPServer,
|
||||
wantRoot: "mcps",
|
||||
wantExts: []string{".mcp.yao", ".mcp.jsonc", ".mcp.json"},
|
||||
},
|
||||
{
|
||||
name: "API",
|
||||
typ: TypeAPI,
|
||||
wantRoot: "apis",
|
||||
wantExts: []string{".http.yao", ".http.jsonc", ".http.json"},
|
||||
},
|
||||
{
|
||||
name: "Schedule",
|
||||
typ: TypeSchedule,
|
||||
wantRoot: "schedules",
|
||||
wantExts: []string{".sch.yao", ".sch.jsonc", ".sch.json"},
|
||||
},
|
||||
{
|
||||
name: "Table",
|
||||
typ: TypeTable,
|
||||
wantRoot: "tables",
|
||||
wantExts: []string{".table.yao", ".table.jsonc", ".table.json"},
|
||||
},
|
||||
{
|
||||
name: "Form",
|
||||
typ: TypeForm,
|
||||
wantRoot: "forms",
|
||||
wantExts: []string{".form.yao", ".form.jsonc", ".form.json"},
|
||||
},
|
||||
{
|
||||
name: "List",
|
||||
typ: TypeList,
|
||||
wantRoot: "lists",
|
||||
wantExts: []string{".list.yao", ".list.jsonc", ".list.json"},
|
||||
},
|
||||
{
|
||||
name: "Chart",
|
||||
typ: TypeChart,
|
||||
wantRoot: "charts",
|
||||
wantExts: []string{".chart.yao", ".chart.jsonc", ".chart.json"},
|
||||
},
|
||||
{
|
||||
name: "Dashboard",
|
||||
typ: TypeDashboard,
|
||||
wantRoot: "dashboards",
|
||||
wantExts: []string{".dash.yao", ".dash.jsonc", ".dash.json"},
|
||||
},
|
||||
{
|
||||
name: "Flow",
|
||||
typ: TypeFlow,
|
||||
wantRoot: "flows",
|
||||
wantExts: []string{".flow.yao", ".flow.jsonc", ".flow.json"},
|
||||
},
|
||||
{
|
||||
name: "Pipe",
|
||||
typ: TypePipe,
|
||||
wantRoot: "pipes",
|
||||
wantExts: []string{".pipe.yao", ".pipe.jsonc", ".pipe.json"},
|
||||
},
|
||||
{
|
||||
name: "AIGC",
|
||||
typ: TypeAIGC,
|
||||
wantRoot: "aigcs",
|
||||
wantExts: []string{".ai.yao", ".ai.jsonc", ".ai.json"},
|
||||
},
|
||||
{
|
||||
name: "Store",
|
||||
typ: TypeStore,
|
||||
wantRoot: "stores",
|
||||
wantExts: []string{".lru.yao", ".redis.yao", ".mongo.yao", ".badger.yao", ".store.yao", ".store.jsonc", ".store.json"},
|
||||
},
|
||||
{
|
||||
name: "Unknown",
|
||||
typ: TypeUnknown,
|
||||
wantRoot: "",
|
||||
wantExts: []string{},
|
||||
},
|
||||
{
|
||||
name: "Empty type",
|
||||
typ: Type(""),
|
||||
wantRoot: "",
|
||||
wantExts: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotRoot, gotExts := TypeRootAndExts(tt.typ)
|
||||
if gotRoot != tt.wantRoot {
|
||||
t.Errorf("TypeRootAndExts() root = %v, want %v", gotRoot, tt.wantRoot)
|
||||
}
|
||||
if len(gotExts) != len(tt.wantExts) {
|
||||
t.Errorf("TypeRootAndExts() exts length = %v, want %v", len(gotExts), len(tt.wantExts))
|
||||
return
|
||||
}
|
||||
for i, ext := range gotExts {
|
||||
if ext != tt.wantExts[i] {
|
||||
t.Errorf("TypeRootAndExts() exts[%d] = %v, want %v", i, ext, tt.wantExts[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test integration scenarios
|
||||
func TestIntegration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
typ Type
|
||||
id string
|
||||
wantPath string
|
||||
wantID string
|
||||
}{
|
||||
{
|
||||
name: "Model round trip",
|
||||
typ: TypeModel,
|
||||
id: "user__profile.admin",
|
||||
wantPath: filepath.Join("models", "user.profile", "admin.mod.yao"),
|
||||
wantID: "user__profile.admin",
|
||||
},
|
||||
{
|
||||
name: "API round trip",
|
||||
typ: TypeAPI,
|
||||
id: "user.login",
|
||||
wantPath: filepath.Join("apis", "user", "login.http.yao"),
|
||||
wantID: "user.login",
|
||||
},
|
||||
{
|
||||
name: "Complex nested path",
|
||||
typ: TypeForm,
|
||||
id: "admin__panel.user__management.edit",
|
||||
wantPath: filepath.Join("forms", "admin.panel", "user.management", "edit.form.yao"),
|
||||
wantID: "admin__panel.user__management.edit",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Test ID to Path
|
||||
path := ToPath(tt.typ, tt.id)
|
||||
if path != tt.wantPath {
|
||||
t.Errorf("ToPath() = %v, want %v", path, tt.wantPath)
|
||||
}
|
||||
|
||||
// Test Path to ID
|
||||
id := WithTypeToID(tt.typ, path)
|
||||
if id != tt.wantID {
|
||||
t.Errorf("WithTypeToID() = %v, want %v", id, tt.wantID)
|
||||
}
|
||||
|
||||
// Test DetectType
|
||||
detectedType := DetectType(path)
|
||||
if detectedType != tt.typ {
|
||||
t.Errorf("DetectType() = %v, want %v", detectedType, tt.typ)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue