Compare commits

...

12 Commits

Author SHA1 Message Date
92c89996f5 remove some garbage 2025-07-29 16:46:23 +03:00
1c73d3f87a add examples of lua scripts 2025-07-29 16:44:03 +03:00
e35972b8ad add initial support for lua scripts 2025-07-29 16:43:42 +03:00
0344d58ad4 add jsonrpc errors about methods 2025-07-29 16:43:23 +03:00
cf7bd1ceec add panic reciever to the Gateway router 2025-07-29 16:43:03 +03:00
c3540bfbe1 add CatchPanicWithFallback 2025-07-29 16:42:25 +03:00
bd54628b5c remove unused regexp 2025-07-29 16:42:02 +03:00
b103736a9d add resolveMethodPath to resolve giving methods 2025-07-29 16:41:45 +03:00
7eeedf0b31 remove listAllowedCmd regexp 2025-07-29 16:41:15 +03:00
1675001f24 add panic recover to run function 2025-07-29 13:34:41 +03:00
e01ecdf1db add panic catch functions 2025-07-29 13:33:58 +03:00
febee7cac5 add SafeFetch function 2025-07-29 12:44:25 +03:00
13 changed files with 231 additions and 376 deletions

View File

@@ -229,11 +229,10 @@ var runCmd = &cobra.Command{
}
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
Log: *x.SLog,
Config: x.Config.Conf,
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+$`),
ListAllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9_-]+$`),
Ver: "v1",
Log: *x.SLog,
Config: x.Config.Conf,
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+(>[a-zA-Z0-9]+)*$`),
Ver: "v1",
})
s := gateway.InitGateway(&gateway.GatewayServerInit{
@@ -264,7 +263,24 @@ var runCmd = &cobra.Command{
Level: logs.GlobalLevel,
}, "", 0),
}
nodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
if err := srv.Shutdown(ctxMain); err != nil {
x.Log.Printf("%s: Failed to stop the server gracefully: %s", logs.PrintError(), err.Error())
} else {
x.Log.Printf("Server stopped gracefully")
}
x.Log.Println("Cleaning up...")
if err := run_manager.Clean(); err != nil {
x.Log.Printf("%s: Cleanup error: %s", logs.PrintError(), err.Error())
}
x.Log.Println("bye!")
})
go func() {
defer utils.CatchPanicWithCancel(cancelMain)
if x.Config.Conf.TLS.TlsEnabled {
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port))
if err != nil {
@@ -296,6 +312,7 @@ var runCmd = &cobra.Command{
if x.Config.Conf.Updates.UpdatesEnabled {
go func() {
defer utils.CatchPanicWithCancel(cancelMain)
x.Updated = update.NewUpdater(ctxMain, x.Log, x.Config.Conf, x.Config.Env)
x.Updated.Shutdownfunc(cancelMain)
for {
@@ -316,19 +333,7 @@ var runCmd = &cobra.Command{
}
<-ctxMain.Done()
if err := srv.Shutdown(ctxMain); err != nil {
x.Log.Printf("%s: Failed to stop the server gracefully: %s", logs.PrintError(), err.Error())
} else {
x.Log.Printf("Server stopped gracefully")
}
x.Log.Println("Cleaning up...")
if err := run_manager.Clean(); err != nil {
x.Log.Printf("%s: Cleanup error: %s", logs.PrintError(), err.Error())
}
x.Log.Println("bye!")
nodeApp.CallFallback(ctx)
return nil
})
},

8
com/Utils/Echo.lua Normal file
View File

@@ -0,0 +1,8 @@
if not In.Params.msg or In.Params.msg == "" then
Out.Error = {
message = "there must be a msg parameter"
}
return
end
Out.Result.answer = In.Params.msg

1
com/Utils/Ping.lua Normal file
View File

@@ -0,0 +1 @@
Out.Result.answer = "pong"

View File

@@ -1,13 +0,0 @@
--- #description = "Echoes back the message."
--- #args
--- msg = the message
if not In.Params.msg or In.Params.msg == "" then
Out.Result.status = Status.error
Out.Result.error = "Missing parameter: msg"
return
end
Out.Result.status = Status.ok
Out.Result.answer = In.Params.msg
return

View File

@@ -0,0 +1,60 @@
package utils
import (
"log"
"runtime"
"strings"
"golang.org/x/net/context"
)
// temportary solution, pls dont judge
func trimStackPaths(stack []byte, folderName string) []byte {
lines := strings.Split(string(stack), "\n")
for i, line := range lines {
idx := strings.Index(line, folderName)
if idx != -1 {
indentEnd := strings.LastIndex(line[:idx], "\t")
if indentEnd == -1 {
indentEnd = 0
} else {
indentEnd++
}
start := idx + len(folderName) + 1
if start > len(line) {
start = len(line)
}
lines[i] = line[:indentEnd] + line[start:]
}
}
return []byte(strings.Join(lines, "\n"))
}
func CatchPanic() {
if err := recover(); err != nil {
stack := make([]byte, 8096)
stack = stack[:runtime.Stack(stack, false)]
stack = trimStackPaths(stack, "GoSally-mvp")
log.Printf("recovered panic:\n%s", stack)
}
}
func CatchPanicWithCancel(cancel context.CancelFunc) {
if err := recover(); err != nil {
stack := make([]byte, 8096)
stack = stack[:runtime.Stack(stack, false)]
stack = trimStackPaths(stack, "GoSally-mvp")
log.Printf("recovered panic:\n%s", stack)
cancel()
}
}
func CatchPanicWithFallback(onPanic func(any)) {
if err := recover(); err != nil {
stack := make([]byte, 8096)
stack = stack[:runtime.Stack(stack, false)]
stack = trimStackPaths(stack, "GoSally-mvp")
log.Printf("recovered panic:\n%s", stack)
onPanic(err)
}
}

View File

@@ -0,0 +1,9 @@
package utils
// SafeFetch safely fetches data. If v = nil, a fallback value is returned.
func SafeFetch[T any](v *T, fallback T) T {
if v == nil {
return fallback
}
return *v
}

View File

@@ -6,6 +6,7 @@ import (
"log/slog"
"os"
"os/signal"
"sync"
"syscall"
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
@@ -16,14 +17,20 @@ import (
type AppContract interface {
InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX))
Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error)
Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX))
CallFallback(ctx context.Context)
}
type App struct {
initHooks []func(cs *corestate.CoreState, x *AppX)
runHook func(ctx context.Context, cs *corestate.CoreState, x *AppX) error
fallback func(ctx context.Context, cs *corestate.CoreState, x *AppX)
Corestate *corestate.CoreState
AppX *AppX
fallbackOnce sync.Once
}
type AppX struct {
@@ -46,6 +53,10 @@ func (a *App) InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX)) {
a.initHooks = append(a.initHooks, fn...)
}
func (a *App) Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX)) {
a.fallback = fn
}
func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error) {
a.runHook = fn
@@ -56,9 +67,30 @@ func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
defer stop()
if a.runHook != nil {
if err := a.runHook(ctx, a.Corestate, a.AppX); err != nil {
log.Fatalf("fatal in Run: %v", err)
defer func() {
if r := recover(); r != nil {
a.AppX.Log.Printf("PANIC recovered: %v", r)
if a.fallback != nil {
a.fallback(ctx, a.Corestate, a.AppX)
}
os.Exit(1)
}
}()
var runErr error
if a.runHook != nil {
runErr = a.runHook(ctx, a.Corestate, a.AppX)
}
if runErr != nil {
a.AppX.Log.Fatalf("fatal in Run: %v", runErr)
}
}
func (a *App) CallFallback(ctx context.Context) {
a.fallbackOnce.Do(func() {
if a.fallback != nil {
a.fallback(ctx, a.Corestate, a.AppX)
}
})
}

View File

@@ -7,6 +7,7 @@ import (
"net/http"
"sync"
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
)
@@ -75,7 +76,11 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
}
}
func (gs *GatewayServer) Route(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
func (gs *GatewayServer) Route(r *http.Request, req *rpc.RPCRequest) (resp *rpc.RPCResponse) {
defer utils.CatchPanicWithFallback(func(rec any) {
gs.log.Error("panic caught in handler", slog.Any("error", rec))
resp = rpc.NewError(rpc.ErrInternalError, "Internal server error (panic)", req.ID)
})
if req.JSONRPC != rpc.JSONRPCVersion {
gs.log.Info("invalid request received", slog.String("issue", rpc.ErrInvalidRequestS), slog.String("requested-version", req.JSONRPC))
return rpc.NewError(rpc.ErrInvalidRequest, rpc.ErrInvalidRequestS, req.ID)
@@ -87,7 +92,7 @@ func (gs *GatewayServer) Route(r *http.Request, req *rpc.RPCRequest) *rpc.RPCRes
return rpc.NewError(rpc.ErrContextVersion, rpc.ErrContextVersionS, req.ID)
}
resp := server.Handle(r, req)
resp = server.Handle(r, req)
// checks if request is notification
if req.ID == nil {
return nil

View File

@@ -18,4 +18,10 @@ const (
ErrContextVersion = -32010
ErrContextVersionS = "Invalid context version"
ErrInvalidMethodFormat = -32020
ErrInvalidMethodFormatS = "Invalid method format"
ErrMethodIsMissing = -32020
ErrMethodIsMissingS = "Method is missing"
)

View File

@@ -1,342 +1,97 @@
package sv1
import (
"net/http"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
)
func (h *HandlerV1) Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
return rpc.NewResponse("Hi", req.ID) // test answer to make sure everything works
}
// func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
// var req PettiRequest
// // server, ok := s.servers[serversApiVer(payload.PettiVer)]
// // if !ok {
// // WriteRouterError(w, &RouterError{
// // Status: "error",
// // StatusCode: http.StatusBadRequest,
// // Payload: map[string]any{
// // "Message": InvalidProtovolVersion,
// // },
// // })
// // s.log.Info("invalid request received", slog.String("issue", InvalidProtovolVersion), slog.String("requested-version", payload.PettiVer))
// // return
// // }
// if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
// utils.WriteJSONError(w, http.StatusBadRequest, "невалидный JSON: "+err.Error())
// return
// }
// if req.PettiVer == "" {
// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует PettiVer")
// return
// }
// if req.PettiVer != h.GetVersion() {
// utils.WriteJSONError(w, http.StatusBadRequest, "неподдерживаемая версия PettiVer")
// return
// }
// if req.PackageType.Request == "" {
// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует PackageType.Request")
// return
// }
// if req.Payload == nil {
// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует Payload")
// return
// }
// cmdRaw, ok := req.Payload["Exec"].(string)
// if !ok || cmdRaw == "" {
// utils.WriteJSONError(w, http.StatusBadRequest, "Payload.Exec отсутствует или некорректен")
// return
// }
// cmd := cmdRaw
// if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) || !h.listAllowedCmd.MatchString(cmd) {
// utils.WriteJSONError(w, http.StatusBadRequest, "команда запрещена")
// return
// }
// // ===== Проверка скрипта
// scriptPath := h.comMatch(h.GetVersion(), cmd)
// if scriptPath == "" {
// utils.WriteJSONError(w, http.StatusNotFound, "команда не найдена")
// return
// }
// fullPath := filepath.Join(h.cfg.ComDir, scriptPath)
// if _, err := os.Stat(fullPath); err != nil {
// utils.WriteJSONError(w, http.StatusNotFound, "файл команды не найден")
// return
// }
// // ===== Запуск Lua
// L := lua.NewState()
// defer L.Close()
// inTable := L.NewTable()
// paramsTable := L.NewTable()
// if params, ok := req.Payload["PassedParameters"].(map[string]interface{}); ok {
// for k, v := range params {
// L.SetField(paramsTable, k, utils.ConvertGolangTypesToLua(L, v))
// }
// }
// L.SetField(inTable, "Params", paramsTable)
// L.SetGlobal("In", inTable)
// resultTable := L.NewTable()
// outTable := L.NewTable()
// L.SetField(outTable, "Result", resultTable)
// L.SetGlobal("Out", outTable)
// prepareLua := filepath.Join(h.cfg.ComDir, "_prepare.lua")
// if _, err := os.Stat(prepareLua); err == nil {
// if err := L.DoFile(prepareLua); err != nil {
// utils.WriteJSONError(w, http.StatusInternalServerError, "lua _prepare ошибка: "+err.Error())
// return
// }
// }
// if err := L.DoFile(fullPath); err != nil {
// utils.WriteJSONError(w, http.StatusInternalServerError, "lua exec ошибка: "+err.Error())
// return
// }
// lv := L.GetGlobal("Out")
// tbl, ok := lv.(*lua.LTable)
// if !ok {
// utils.WriteJSONError(w, http.StatusInternalServerError, "'Out' не таблица")
// return
// }
// resultVal := tbl.RawGetString("Result")
// resultTbl, ok := resultVal.(*lua.LTable)
// if !ok {
// utils.WriteJSONError(w, http.StatusInternalServerError, "'Result' не таблица")
// return
// }
// out := make(map[string]any)
// resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
// out[key.String()] = utils.ConvertLuaTypesToGolang(value)
// })
// uuid32, _ := corestate.GetNodeUUID(filepath.Join(config.MetaDir, "uuid"))
// resp := PettiResponse{
// PettiVer: req.PettiVer,
// ResponsibleAgentUUID: uuid32,
// PackageType: struct {
// AnswerOf string `json:"AnswerOf"`
// }{AnswerOf: req.PackageType.Request},
// Payload: map[string]any{
// "RequestedCommand": cmd,
// "Response": out,
// },
// }
// // ===== Финальная проверка на сериализацию (валидность сборки)
// respData, err := json.Marshal(resp)
// if err != nil {
// utils.WriteJSONError(w, http.StatusInternalServerError, "внутренняя ошибка: пакет невалиден")
// return
// }
// w.Header().Set("Content-Type", "application/json")
// w.WriteHeader(http.StatusOK)
// if _, err := w.Write(respData); err != nil {
// h.log.Error("Ошибка при отправке JSON", slog.String("err", err.Error()))
// }
// // ===== Логгирование статуса
// status, _ := out["status"].(string)
// switch status {
// case "ok":
// h.log.Info("Успешно", slog.String("cmd", cmd), slog.Any("out", out))
// case "error":
// h.log.Warn("Ошибка в команде", slog.String("cmd", cmd), slog.Any("out", out))
// default:
// h.log.Info("Неизвестный статус", slog.String("cmd", cmd), slog.Any("out", out))
// }
// }
/*
import (
"encoding/json"
"log/slog"
"net/http"
"os"
"path/filepath"
"github.com/akyaiy/GoSally-mvp/core/config"
"github.com/akyaiy/GoSally-mvp/core/corestate"
"github.com/akyaiy/GoSally-mvp/core/utils"
"github.com/go-chi/chi/v5"
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
lua "github.com/yuin/gopher-lua"
)
// HandlerV1 is the main handler for version 1 of the API.
// The function processes the HTTP request and runs Lua scripts,
// preparing the environment and subsequently transmitting the execution result
func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
uuid16, err := utils.NewUUID(int(config.UUIDLength))
func (h *HandlerV1) Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
if req.Method == "" {
h.log.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
return rpc.NewError(rpc.ErrMethodIsMissing, rpc.ErrMethodIsMissingS, req.ID)
}
method, err := h.resolveMethodPath(req.Method)
if err != nil {
h.log.Error("Failed to generate UUID",
slog.String("error", err.Error()))
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "failed to generate UUID: "+err.Error()); err != nil {
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
if err.Error() == rpc.ErrInvalidMethodFormatS {
h.log.Info("invalid request received", slog.String("issue", rpc.ErrInvalidMethodFormatS), slog.String("requested-method", req.Method))
return rpc.NewError(rpc.ErrInvalidMethodFormat, rpc.ErrInvalidMethodFormatS, req.ID)
} else if err.Error() == rpc.ErrMethodNotFoundS {
h.log.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
return rpc.NewError(rpc.ErrMethodNotFound, rpc.ErrMethodNotFoundS, req.ID)
}
return
}
log := h.log.With(
slog.Group("request",
slog.String("version", h.GetVersion()),
slog.String("url", r.URL.String()),
slog.String("method", r.Method),
),
slog.Group("connection",
slog.String("connection-uuid", uuid16),
slog.String("remote", r.RemoteAddr),
),
)
log.Info("Received request")
cmd := chi.URLParam(r, "cmd")
if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) || !h.listAllowedCmd.MatchString(cmd) {
log.Error("HTTP request error",
slog.String("error", "invalid command"),
slog.String("cmd", cmd),
slog.Int("status", http.StatusBadRequest))
if err := utils.WriteJSONError(w, http.StatusBadRequest, "invalid command"); err != nil {
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
}
return
}
scriptPath := h.comMatch(chi.URLParam(r, "ver"), cmd)
if scriptPath == "" {
log.Error("HTTP request error",
slog.String("error", "command not found"),
slog.String("cmd", cmd),
slog.Int("status", http.StatusNotFound))
if err := utils.WriteJSONError(w, http.StatusNotFound, "command not found"); err != nil {
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
}
return
}
scriptPath = filepath.Join(h.cfg.ComDir, scriptPath)
if _, err := os.Stat(scriptPath); err != nil {
log.Error("HTTP request error",
slog.String("error", "command not found"),
slog.String("cmd", cmd),
slog.Int("status", http.StatusNotFound))
if err := utils.WriteJSONError(w, http.StatusNotFound, "command not found"); err != nil {
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
}
return
}
return h.HandleLUA(method, req)
}
func (h *HandlerV1) HandleLUA(path string, req *rpc.RPCRequest) *rpc.RPCResponse {
L := lua.NewState()
defer L.Close()
inTable := L.NewTable()
paramsTable := L.NewTable()
qt := r.URL.Query()
for k, v := range qt {
if len(v) > 0 {
L.SetField(paramsTable, k, lua.LString(v[0]))
if fetchedParams, ok := req.Params.(map[string]interface{}); ok {
for k, v := range fetchedParams {
L.SetField(paramsTable, k, utils.ConvertGolangTypesToLua(L, v))
}
}
inTable := L.NewTable()
L.SetField(inTable, "Params", paramsTable)
L.SetGlobal("In", inTable)
// Создаем таблицу Out с Result
resultTable := L.NewTable()
outTable := L.NewTable()
resultTable := L.NewTable()
L.SetField(outTable, "Result", resultTable)
L.SetGlobal("Out", outTable)
prepareLuaEnv := filepath.Join(h.cfg.ComDir, "_prepare.lua")
if _, err := os.Stat(prepareLuaEnv); err == nil {
if err := L.DoFile(prepareLuaEnv); err != nil {
log.Error("Failed to prepare lua environment",
slog.String("error", err.Error()))
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "lua error: "+err.Error()); err != nil {
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
}
return
prep := filepath.Join(h.cfg.ComDir, "_prepare.lua")
if _, err := os.Stat(prep); err == nil {
if err := L.DoFile(prep); err != nil {
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
}
} else {
log.Warn("No environment preparation script found, skipping preparation")
}
if err := L.DoFile(scriptPath); err != nil {
log.Error("Failed to execute lua script",
slog.String("error", err.Error()))
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "lua error: "+err.Error()); err != nil {
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
}
return
if err := L.DoFile(path); err != nil {
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
}
lv := L.GetGlobal("Out")
tbl, ok := lv.(*lua.LTable)
outTbl, ok := lv.(*lua.LTable)
if !ok {
log.Error("Lua global 'Out' is not a table")
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "'Out' is not a table"); err != nil {
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
}
return
return rpc.NewError(rpc.ErrInternalError, "Out is not a table", req.ID)
}
resultVal := tbl.RawGetString("Result")
// Check if Out.Error exists
if errVal := outTbl.RawGetString("Error"); errVal != lua.LNil {
if errTbl, ok := errVal.(*lua.LTable); ok {
code := rpc.ErrInternalError
message := "Internal script error"
if c := errTbl.RawGetString("code"); c.Type() == lua.LTNumber {
code = int(c.(lua.LNumber))
}
if msg := errTbl.RawGetString("message"); msg.Type() == lua.LTString {
message = msg.String()
}
return rpc.NewError(code, message, req.ID)
}
return rpc.NewError(rpc.ErrInternalError, "Out.Error is not a table", req.ID)
}
// Otherwise, parse Out.Result
resultVal := outTbl.RawGetString("Result")
resultTbl, ok := resultVal.(*lua.LTable)
if !ok {
log.Error("Lua global 'Result' is not a table")
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "'Result' is not a table"); err != nil {
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
}
return
return rpc.NewError(rpc.ErrInternalError, "Out.Result is not a table", req.ID)
}
out := make(map[string]any)
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
out[key.String()] = utils.ConvertLuaTypesToGolang(value)
})
uuid32, _ := corestate.GetNodeUUID(filepath.Join(config.MetaDir, "uuid"))
response := ResponseFormat{
ResponsibleAgentUUID: uuid32,
RequestedCommand: cmd,
Response: out,
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Error("Failed to encode JSON response",
slog.String("error", err.Error()))
}
status, _ := out["status"].(string)
switch status {
case "error":
log.Info("Command executed with error",
slog.String("cmd", cmd),
slog.Any("result", out))
case "ok":
log.Info("Command executed successfully",
slog.String("cmd", cmd),
slog.Any("result", out))
default:
log.Info("Command executed and returned an unknown status",
slog.String("cmd", cmd),
slog.Any("result", out))
}
log.Info("Session completed")
return rpc.NewResponse(out, req.ID)
}
*/

View File

@@ -0,0 +1,26 @@
package sv1
import (
"errors"
"os"
"path/filepath"
"strings"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
)
func (h *HandlerV1) resolveMethodPath(method string) (string, error) {
if !h.allowedCmd.MatchString(method) {
return "", errors.New(rpc.ErrInvalidMethodFormatS)
}
parts := strings.Split(method, ">")
relPath := filepath.Join(parts...) + ".lua"
fullPath := filepath.Join(h.cfg.ComDir, relPath)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
return "", errors.New(rpc.ErrMethodNotFoundS)
}
return fullPath, nil
}

View File

@@ -25,8 +25,7 @@ type HandlerV1 struct {
cfg *config.Conf
// allowedCmd and listAllowedCmd are regular expressions used to validate command names.
allowedCmd *regexp.Regexp
listAllowedCmd *regexp.Regexp
allowedCmd *regexp.Regexp
ver string
}
@@ -36,11 +35,10 @@ type HandlerV1 struct {
// because there is no validation of parameters in this function.
func InitV1Server(o *HandlerV1InitStruct) *HandlerV1 {
return &HandlerV1{
log: &o.Log,
cfg: o.Config,
allowedCmd: o.AllowedCmd,
listAllowedCmd: o.ListAllowedCmd,
ver: o.Ver,
log: &o.Log,
cfg: o.Config,
allowedCmd: o.AllowedCmd,
ver: o.Ver,
}
}

View File

@@ -1,37 +0,0 @@
package sv1
import (
"log/slog"
"os"
)
func (h *HandlerV1) comMatch(ver string, comName string) string {
files, err := os.ReadDir(h.cfg.ComDir)
if err != nil {
h.log.Error("Failed to read com dir",
slog.String("error", err.Error()))
return ""
}
baseName := comName + ".lua"
verName := comName + "?" + ver + ".lua"
var baseFileFound string
for _, f := range files {
if f.IsDir() {
continue
}
fname := f.Name()
if fname == verName {
return fname
}
if fname == baseName {
baseFileFound = fname
}
}
return baseFileFound
}