restructure project

This commit is contained in:
Samuel Lorch 2023-03-26 18:50:18 +02:00
parent dd2db438f3
commit 2ca35d4461
46 changed files with 158 additions and 84 deletions

37
internal/jsonrpc/error.go Normal file
View file

@ -0,0 +1,37 @@
package jsonrpc
import (
"io"
)
type ErrorCode int
const (
ErrParse ErrorCode = -32700
ErrInvalidRequest ErrorCode = -32600
ErrMethodNotFound ErrorCode = -32601
ErrInvalidParams ErrorCode = -32602
ErrInternalError ErrorCode = -32603
// Custom
ErrRequestError ErrorCode = -32000
)
type respError struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
// cannot be omitempty because of frontend library
Data any `json:"data"`
}
func respondError(w io.Writer, id any, code ErrorCode, err error) error {
respond(w, response{
Jsonrpc: "2.0",
ID: id,
Error: &respError{
Code: code,
Message: err.Error(),
},
})
return err
}

View file

@ -0,0 +1,98 @@
package jsonrpc
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"reflect"
"runtime/debug"
"golang.org/x/exp/slog"
"nfsense.net/nfsense/internal/session"
)
type Handler struct {
methods map[string]method
maxRequestSize int64
}
func NewHandler(maxRequestSize int64) *Handler {
return &Handler{
methods: map[string]method{},
maxRequestSize: maxRequestSize,
}
}
func (h *Handler) HandleRequest(ctx context.Context, s *session.Session, r io.Reader, w io.Writer) error {
defer func() {
if r := recover(); r != nil {
slog.Error("Recovered Panic Handling JSONRPC Request", fmt.Errorf("%v", r), "stack", debug.Stack())
}
}()
var req request
bufferedRequest := new(bytes.Buffer)
reqSize, err := bufferedRequest.ReadFrom(io.LimitReader(r, h.maxRequestSize+1))
if err != nil {
return respondError(w, "", ErrInternalError, fmt.Errorf("Reading Request: %w", err))
}
if reqSize > h.maxRequestSize {
return respondError(w, "", ErrParse, fmt.Errorf("Request exceeds Max Request Size"))
}
dec := json.NewDecoder(bufferedRequest)
dec.DisallowUnknownFields()
err = dec.Decode(&req)
if err != nil {
return respondError(w, "", ErrParse, fmt.Errorf("Decodeing Request: %w", err))
}
if req.Jsonrpc != "2.0" {
return respondError(w, req.ID, ErrMethodNotFound, fmt.Errorf("Unsupported Jsonrpc version %v", req.Jsonrpc))
}
if s == nil {
return respondError(w, req.ID, 401, fmt.Errorf("Unauthorized"))
}
method, ok := h.methods[req.Method]
if !ok {
return respondError(w, req.ID, ErrMethodNotFound, fmt.Errorf("Unknown Method %v", req.Method))
}
p := reflect.New(method.inType)
paramPointer := p.Interface()
if len(req.Params) != 0 {
dec = json.NewDecoder(bytes.NewReader(req.Params))
dec.DisallowUnknownFields()
err = dec.Decode(paramPointer)
if err != nil {
return respondError(w, req.ID, ErrInvalidParams, fmt.Errorf("Decoding Parameters: %w", err))
}
}
params := make([]reflect.Value, 3)
params[0] = method.subSystem
params[1] = reflect.ValueOf(ctx)
params[2] = reflect.ValueOf(paramPointer).Elem()
defer func() {
if r := recover(); r != nil {
slog.Error("Recovered Panic Executing API Method", fmt.Errorf("%v", r), "method", req.Method, "id", req.ID, "stack", debug.Stack())
}
}()
res := method.handlerFunc.Call(params)
result := res[0].Interface()
if !res[1].IsNil() {
reqerr := res[1].Interface().(error)
slog.Error("API Method", reqerr, "method", req.Method, "id", req.ID)
respondError(w, req.ID, ErrInternalError, reqerr)
}
respondResult(w, req.ID, result)
return nil
}

View file

@ -0,0 +1,10 @@
package jsonrpc
import "reflect"
type method struct {
subSystem reflect.Value
handlerFunc reflect.Value
inType reflect.Type
outType reflect.Type
}

View file

@ -0,0 +1,46 @@
package jsonrpc
import (
"context"
"fmt"
"reflect"
)
func (h *Handler) Register(subSystemName string, s any) {
subSystem := reflect.ValueOf(s)
for i := 0; i < subSystem.NumMethod(); i++ {
m := subSystem.Type().Method(i)
funcType := m.Func.Type()
if funcType.NumIn() != 3 {
panic(fmt.Errorf("2 parameters are required %v", funcType.NumIn()))
}
if funcType.In(1) != reflect.TypeOf(new(context.Context)).Elem() {
panic(fmt.Errorf("the first argument needs to be a context.Context instead of %v ", funcType.In(1)))
}
if funcType.In(2).Kind() != reflect.Struct {
panic("the second argument needs to be a struct")
}
if funcType.NumOut() != 2 {
panic("2 return types are required")
}
if reflect.TypeOf(new(error)).Implements(funcType.Out(1)) {
panic("the second return type needs to be a error")
}
name := m.Name
if subSystemName != "" {
name = subSystemName + "." + name
}
h.methods[name] = method{
handlerFunc: m.Func,
subSystem: subSystem,
inType: funcType.In(2),
outType: funcType.Out(0),
}
}
}

View file

@ -0,0 +1,10 @@
package jsonrpc
import "encoding/json"
type request struct {
Jsonrpc string `json:"jsonrpc"`
ID any `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
}

View file

@ -0,0 +1,30 @@
package jsonrpc
import (
"encoding/json"
"io"
"golang.org/x/exp/slog"
)
type response struct {
Jsonrpc string `json:"jsonrpc"`
Result any `json:"result,omitempty"`
ID any `json:"id"`
Error *respError `json:"error,omitempty"`
}
func respond(w io.Writer, resp response) {
err := json.NewEncoder(w).Encode(resp)
if err != nil {
slog.Warn("write response", "err", err)
}
}
func respondResult(w io.Writer, id, res any) {
respond(w, response{
Jsonrpc: "2.0",
ID: id,
Result: res,
})
}