Merge pull request #1 from KristianJBorgwarth/feat/coms
feat(jsonrpc): NDJSON rpc implementation
This commit is contained in:
commit
5f9eba77eb
7 changed files with 116 additions and 89 deletions
17
cmd/main.go
17
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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"`
|
||||
ID *int `json:"id"`
|
||||
Result any `json:"result,omitempty"`
|
||||
Error any `json:"error,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"`
|
||||
}
|
||||
|
|
|
|||
83
core/rpc/server.go
Normal file
83
core/rpc/server.go
Normal file
|
|
@ -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))
|
||||
}
|
||||
BIN
dendrite
BIN
dendrite
Binary file not shown.
BIN
index.db
Normal file
BIN
index.db
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue