diff --git a/cmd/main.go b/cmd/main.go index 99465d2..24145c7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,13 +1,20 @@ package main import ( - "github.com/KristianJBorgwarth/dendrite.daemon/core/http" - "github.com/KristianJBorgwarth/dendrite.daemon/core/logging" + "log/slog" + "os" + "github.com/KristianJBorgwarth/dendrite.daemon/core/rpc" + "github.com/KristianJBorgwarth/dendrite.daemon/core/handlers" _ "modernc.org/sqlite" ) + func main() { - logging.Init() - srv := server.New(":6969") - srv.Start() + server := rpc.NewServer() + + server.Register("initialize", handlers.InitializeHandler{}) + + if err := server.Serve(os.Stdin, os.Stdout); err != nil { + slog.Error("server error", "error", err) + } } diff --git a/core/handlers/init.go b/core/handlers/init.go index b8fb4b7..d86c2d5 100644 --- a/core/handlers/init.go +++ b/core/handlers/init.go @@ -5,6 +5,7 @@ import ( "log/slog" "github.com/KristianJBorgwarth/dendrite.daemon/config" + "github.com/KristianJBorgwarth/dendrite.daemon/core/rpc" "github.com/KristianJBorgwarth/dendrite.daemon/persistence" ) @@ -24,12 +25,17 @@ type initializeCommand struct { } `json:"dailyNote"` } -func Initialize(raw json.RawMessage) (*config.Config, error) { +type InitializeHandler struct{} + +func (h InitializeHandler) Handle(raw json.RawMessage) (any, *rpc.Error) { var params initializeCommand slog.Info("initializing with params", "params", string(raw)) if err := json.Unmarshal(raw, ¶ms); err != nil { - return nil, err + return nil, &rpc.Error{ + Code: -32602, + Message: "invalid params: " + err.Error(), + } } cfg := &config.Config{ @@ -50,7 +56,10 @@ func Initialize(raw json.RawMessage) (*config.Config, error) { err := persistence.InitializeIndex(cfg.VaultPath) if err != nil { - return nil, err + return nil, &rpc.Error{ + Code: -1, + Message: "failed to initialize index: " + err.Error(), + } } return cfg, nil diff --git a/core/http/server.go b/core/http/server.go deleted file mode 100644 index f240a9a..0000000 --- a/core/http/server.go +++ /dev/null @@ -1,76 +0,0 @@ -// Package server provides the implementation of the server component of the application. -package server - -import ( - "encoding/json" - "log/slog" - "net/http" - - "github.com/KristianJBorgwarth/dendrite.daemon/config" - "github.com/KristianJBorgwarth/dendrite.daemon/core/handlers" - "github.com/KristianJBorgwarth/dendrite.daemon/core/rpc" -) - -type Server struct { - addr string - config *config.Config -} - -func New(addr string) *Server { - return &Server{ - addr: addr, - } -} - -func (s *Server) Start() error { - slog.Info("server started") - mux := http.NewServeMux() - - mux.HandleFunc("/rpc", s.RPCHandler) - mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("ok")) - }) - - return http.ListenAndServe(s.addr, mux) -} - -func (s *Server) RPCHandler(w http.ResponseWriter, r *http.Request) { - var req rpc.Request - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) - return - } - - resp := rpc.Response{ - Jsonrpc: "2.0", - ID: req.ID, - } - - switch req.Method { - case "initialize": - cfg, err := handlers.Initialize(req.Params) - if err != nil { - resp.Error = map[string]any{ - "code": -1, - "message": err.Error(), - } - } else { - s.config = cfg - resp.Result = map[string]any{"status": "ok"} - } - default: - slog.Warn("Unknown method", "method", req.Method) - resp.Error = map[string]any{ - "code": -32601, - "message": "method not found", - } - } - - w.Header().Set("Content-Type", "application/json") - - if err := json.NewEncoder(w).Encode(resp); err != nil { - slog.Error("failed to write response", "error", err) - } -} diff --git a/core/rpc/models.go b/core/rpc/models.go index ca359f5..a2d2245 100644 --- a/core/rpc/models.go +++ b/core/rpc/models.go @@ -7,16 +7,16 @@ import ( type Request struct { Jsonrpc string `json:"jsonrpc"` - ID int `json:"id"` + ID *int `json:"id"` Method string `json:"method"` Params json.RawMessage `json:"params"` } type Response struct { - Jsonrpc string `json:"jsonrpc"` - ID int `json:"id"` - Result any `json:"result,omitempty"` - Error any `json:"error,omitempty"` + Jsonrpc string `json:"jsonrpc"` + ID *int `json:"id"` + Result any `json:"result,omitempty"` + Error *Error `json:"error,omitempty"` } type Notification struct { @@ -25,3 +25,7 @@ type Notification struct { Params json.RawMessage `json:"params,omitempty"` } +type Error struct { + Code int `json:"code"` + Message string `json:"message"` +} diff --git a/core/rpc/server.go b/core/rpc/server.go new file mode 100644 index 0000000..46ff838 --- /dev/null +++ b/core/rpc/server.go @@ -0,0 +1,83 @@ +package rpc + +import ( + "bufio" + "encoding/json" + "fmt" + "io" +) + +type Handler interface { + Handle(json.RawMessage) (any, *Error) +} + +type Server struct { + handlers map[string]Handler +} + +func NewServer() *Server { + return &Server{ + handlers: make(map[string]Handler), + } +} + +func (s *Server) Register(method string, handler Handler) { + s.handlers[method] = handler +} + +func (s *Server) Serve(r io.Reader, w io.Writer) error { + scanner := bufio.NewScanner(r) + + for scanner.Scan() { + line := scanner.Bytes() + var req Request + if err := json.Unmarshal(line, &req); err != nil { + s.write(w, Response{ + Jsonrpc: "2.0", + Error: &Error{Code: -32700, Message: "parse error"}, + }) + continue + } + + s.handle(w, req) + + } + + return scanner.Err() +} + +func (s *Server) handle(w io.Writer, req Request) { + handler, ok := s.handlers[req.Method] + + if !ok { + s.respond(w, req.ID, nil, &Error{ + Code: -32601, + Message: "method not found", + }) + return + } + + result, err := handler.Handle(req.Params) + + if req.ID == nil { + return + } + + s.respond(w, req.ID, result, err) +} + +func (s *Server) respond(w io.Writer, id *int, result any, err *Error) { + resp := Response{ + Jsonrpc: "2.0", + ID: id, + Result: result, + Error: err, + } + + s.write(w, resp) +} + +func (s *Server) write(w io.Writer, resp Response) { + data, _ := json.Marshal(resp) + fmt.Fprintln(w, string(data)) +} diff --git a/dendrite b/dendrite index 6782f21..0118eac 100755 Binary files a/dendrite and b/dendrite differ diff --git a/index.db b/index.db new file mode 100644 index 0000000..ee96800 Binary files /dev/null and b/index.db differ