1
0
Fork 0

refactor: move http api to a new transport layer

This commit is contained in:
Arthur K. 2026-01-23 09:56:01 +03:00
parent 476c4b056f
commit 0448f66ab2
Signed by: wzray
GPG key ID: B97F30FDC4636357
41 changed files with 822 additions and 390 deletions

View file

@ -0,0 +1,5 @@
package transport
type Caller interface {
Call(host string, path string, data any, out any) error
}

View file

@ -0,0 +1,16 @@
package codec
type Codec interface {
Decode(data []byte, out any) error
Encode(data any) ([]byte, error)
}
func Decode[T any](c Codec, data []byte) (T, error) {
var out T
err := c.Decode(data, &out)
return out, err
}
func Encode[T any](c Codec, data T) ([]byte, error) {
return c.Encode(data)
}

View file

@ -0,0 +1,15 @@
package codec
import "encoding/json"
type jsonCodec struct{}
var JSON = jsonCodec{}
func (jsonCodec) Decode(data []byte, out any) error {
return json.Unmarshal(data, out)
}
func (jsonCodec) Encode(data any) ([]byte, error) {
return json.Marshal(data)
}

View file

@ -0,0 +1,20 @@
package dns
import (
"git.wzray.com/homelab/hivemind/internal/transport"
"git.wzray.com/homelab/hivemind/internal/types"
)
type Client struct {
caller transport.Caller
}
func New(caller transport.Caller) *Client {
return &Client{
caller: caller,
}
}
func (c *Client) Callback(endpoint string, state types.HostState) (bool, error) {
return callbackRoute.Call(c.caller, endpoint, state)
}

View file

@ -0,0 +1,14 @@
package dns
import (
"git.wzray.com/homelab/hivemind/internal/transport"
"git.wzray.com/homelab/hivemind/internal/types"
)
type DnsHandlers interface {
Callback(types.HostState) (bool, error)
}
func Register(registrator transport.Registrator, h DnsHandlers) {
callbackRoute.Register(registrator, h.Callback)
}

View file

@ -0,0 +1,10 @@
package dns
import (
"git.wzray.com/homelab/hivemind/internal/transport"
"git.wzray.com/homelab/hivemind/internal/types"
)
var (
callbackRoute = transport.NewRoute[types.HostState, bool]("/dns/callback")
)

View file

@ -0,0 +1,9 @@
package transport
func WithoutInput[T any](f func() (T, error)) func(struct{}) (T, error) {
return func(s struct{}) (T, error) { return f() }
}
func WithoutOutput[T any](f func(T) error) func(T) (struct{}, error) {
return func(t T) (struct{}, error) { return struct{}{}, f(t) }
}

View file

@ -0,0 +1,24 @@
package host
import (
"git.wzray.com/homelab/hivemind/internal/transport"
"git.wzray.com/homelab/hivemind/internal/types"
)
type Client struct {
caller transport.Caller
}
func New(caller transport.Caller) *Client {
return &Client{
caller: caller,
}
}
func (c *Client) Dns(endpoint string) (types.HostState, error) {
return dnsRoute.CallNoInput(c.caller, endpoint)
}
func (c *Client) Nameserver(endpoint string) (types.HostState, error) {
return nsRoute.CallNoInput(c.caller, endpoint)
}

View file

@ -0,0 +1,16 @@
package host
import (
"git.wzray.com/homelab/hivemind/internal/transport"
"git.wzray.com/homelab/hivemind/internal/types"
)
type HostHandlers interface {
Dns() (types.HostState, error)
Nameserver() (types.HostState, error)
}
func Register(registrator transport.Registrator, h HostHandlers) {
dnsRoute.Register(registrator, transport.WithoutInput(h.Dns))
nsRoute.Register(registrator, transport.WithoutInput(h.Nameserver))
}

View file

@ -0,0 +1,11 @@
package host
import (
"git.wzray.com/homelab/hivemind/internal/transport"
"git.wzray.com/homelab/hivemind/internal/types"
)
var (
dnsRoute = transport.NewRoute[struct{}, types.HostState]("/host/dns")
nsRoute = transport.NewRoute[struct{}, types.HostState]("/host/ns")
)

View file

@ -0,0 +1,40 @@
package master
import (
"git.wzray.com/homelab/hivemind/internal/transport"
"git.wzray.com/homelab/hivemind/internal/types"
)
type Client struct {
caller transport.Caller
}
func New(caller transport.Caller) *Client {
return &Client{
caller: caller,
}
}
func (c *Client) Heartbeat(endpoint string, n types.Node) (types.Nodes, error) {
return heartbeatRoute.Call(c.caller, endpoint, n)
}
func (c *Client) Join(endpoint string, n types.Node) (types.Nodes, error) {
return joinRoute.Call(c.caller, endpoint, n)
}
func (c *Client) Leave(endpoint string, n types.Node) error {
return leaveRoute.CallNoOutput(c.caller, endpoint, n)
}
func (c *Client) EventHeartbeat(endpoint string, n types.Node) (types.Nodes, error) {
return eventHeartbeatRoute.Call(c.caller, endpoint, n)
}
func (c *Client) EventJoin(endpoint string, n types.Node) (types.Nodes, error) {
return eventJoinRoute.Call(c.caller, endpoint, n)
}
func (c *Client) EventLeave(endpoint string, n types.Node) error {
return eventLeaveRoute.CallNoOutput(c.caller, endpoint, n)
}

View file

@ -0,0 +1,26 @@
package master
import (
"git.wzray.com/homelab/hivemind/internal/transport"
"git.wzray.com/homelab/hivemind/internal/types"
)
type MasterHandlers interface {
Heartbeat(types.Node) (types.Nodes, error)
Join(types.Node) (types.Nodes, error)
Leave(types.Node) error
EventHeartbeat(types.Node) (types.Nodes, error)
EventJoin(types.Node) (types.Nodes, error)
EventLeave(types.Node) error
}
func Register(registrator transport.Registrator, h MasterHandlers) {
heartbeatRoute.Register(registrator, h.Heartbeat)
joinRoute.Register(registrator, h.Join)
leaveRoute.Register(registrator, transport.WithoutOutput(h.Leave))
eventHeartbeatRoute.Register(registrator, h.EventHeartbeat)
eventJoinRoute.Register(registrator, h.EventJoin)
eventLeaveRoute.Register(registrator, transport.WithoutOutput(h.EventLeave))
}

View file

@ -0,0 +1,16 @@
package master
import (
"git.wzray.com/homelab/hivemind/internal/transport"
"git.wzray.com/homelab/hivemind/internal/types"
)
var (
heartbeatRoute = transport.NewRoute[types.Node, types.Nodes]("/master/heartbeat")
joinRoute = transport.NewRoute[types.Node, types.Nodes]("/master/join")
leaveRoute = transport.NewRoute[types.Node, struct{}]("/master/leave")
eventHeartbeatRoute = transport.NewRoute[types.Node, types.Nodes]("/master/event/heartbeat")
eventJoinRoute = transport.NewRoute[types.Node, types.Nodes]("/master/event/join")
eventLeaveRoute = transport.NewRoute[types.Node, struct{}]("/master/event/leave")
)

View file

@ -0,0 +1,17 @@
package node
import (
"git.wzray.com/homelab/hivemind/internal/transport"
)
type Client struct {
caller transport.Caller
}
func New(caller transport.Caller) *Client {
return &Client{caller: caller}
}
func (c *Client) Healthcheck(endpoint string) (string, error) {
return healthcheckRoute.CallNoInput(c.caller, endpoint)
}

View file

@ -0,0 +1,13 @@
package node
import (
"git.wzray.com/homelab/hivemind/internal/transport"
)
type NodeHandlers interface {
Healthcheck() (string, error)
}
func Register(registrator transport.Registrator, h NodeHandlers) {
healthcheckRoute.Register(registrator, transport.WithoutInput(h.Healthcheck))
}

View file

@ -0,0 +1,9 @@
package node
import (
"git.wzray.com/homelab/hivemind/internal/transport"
)
var (
healthcheckRoute = transport.NewRoute[struct{}, string]("/node/healthcheck")
)

View file

@ -0,0 +1,22 @@
package transport
import (
"git.wzray.com/homelab/hivemind/internal/transport/codec"
)
type Handler struct {
path string
handler func(codec.Codec, []byte) ([]byte, error)
}
func (h Handler) Path() string {
return h.path
}
func (h Handler) Handle(c codec.Codec, v []byte) ([]byte, error) {
return h.handler(c, v)
}
type Registrator interface {
Register(endpoint Handler)
}

View file

@ -0,0 +1,65 @@
package transport
import (
"fmt"
"git.wzray.com/homelab/hivemind/internal/transport/codec"
)
type route[In, Out any] struct {
path string
}
func NewRoute[In, Out any](path string) route[In, Out] {
return route[In, Out]{
path: path,
}
}
func routeToHandler[In, Out any](r route[In, Out], handler func(In) (Out, error)) Handler {
return Handler{
path: r.path,
handler: func(c codec.Codec, b []byte) ([]byte, error) {
data, err := codec.Decode[In](c, b)
if err != nil {
return nil, fmt.Errorf("unable to decode body: %w", err)
}
out, err := handler(data)
if err != nil {
return nil, fmt.Errorf("error while handling request: %w", err)
}
raw, err := codec.Encode(c, out)
if err != nil {
return nil, fmt.Errorf("unable to encode body: %w", err)
}
return raw, nil
},
}
}
func (e route[In, Out]) Call(caller Caller, host string, data In) (Out, error) {
var out Out
err := caller.Call(host, e.path, data, &out)
return out, err
}
func (e route[In, Out]) CallNoInput(caller Caller, host string) (Out, error) {
var out Out
err := caller.Call(host, e.path, struct{}{}, &out)
return out, err
}
func (e route[In, Out]) CallNoOutput(caller Caller, host string, data In) error {
return caller.Call(host, e.path, data, nil)
}
func (e route[In, Out]) Path() string {
return e.path
}
func (e route[In, Out]) Register(r Registrator, h func(In) (Out, error)) {
r.Register(routeToHandler(e, h))
}