feat: edge gateway service

This commit is contained in:
Ilia Denisov
2026-04-02 19:18:42 +02:00
committed by GitHub
parent 8cde99936c
commit 436c97a38b
95 changed files with 20504 additions and 57 deletions
+108
View File
@@ -0,0 +1,108 @@
// Package downstream defines the verified internal command contract used by the
// gateway after the authenticated edge pipeline succeeds.
package downstream
import (
"context"
"errors"
)
var (
// ErrRouteNotFound reports that Router does not have an exact-match handler
// for the supplied authenticated message type.
ErrRouteNotFound = errors.New("downstream route not found")
// ErrDownstreamUnavailable reports that the resolved downstream dependency is
// temporarily unavailable.
ErrDownstreamUnavailable = errors.New("downstream service is unavailable")
)
// AuthenticatedCommand is the minimum verified unary command context the
// gateway may forward to downstream business services.
type AuthenticatedCommand struct {
// ProtocolVersion is the authenticated transport protocol version accepted
// by the gateway.
ProtocolVersion string
// UserID is the authenticated user identity resolved from SessionCache.
UserID string
// DeviceSessionID is the authenticated device session that originated the
// command.
DeviceSessionID string
// MessageType is the stable exact-match downstream routing key.
MessageType string
// TimestampMS is the client-supplied request timestamp that already passed
// freshness verification.
TimestampMS int64
// RequestID is the transport correlation and anti-replay identifier.
RequestID string
// TraceID is the optional client-supplied correlation identifier.
TraceID string
// PayloadBytes carries the verified opaque business payload bytes.
PayloadBytes []byte
}
// UnaryResult is the minimum downstream unary result the gateway needs in
// order to build a signed authenticated client response.
type UnaryResult struct {
// ResultCode is the stable opaque downstream result code returned to the
// client without business reinterpretation by the gateway.
ResultCode string
// PayloadBytes carries the opaque downstream response payload bytes.
PayloadBytes []byte
}
// Client executes a verified authenticated unary command against one concrete
// downstream service or adapter.
type Client interface {
// ExecuteCommand executes command and returns the downstream unary result.
ExecuteCommand(ctx context.Context, command AuthenticatedCommand) (UnaryResult, error)
}
// Router resolves the downstream unary client for one exact authenticated
// message_type value.
type Router interface {
// Route returns the downstream client for messageType. Implementations must
// wrap ErrRouteNotFound when the route table does not contain messageType.
Route(messageType string) (Client, error)
}
// StaticRouter resolves exact message_type literals from an immutable route
// map supplied at construction time.
type StaticRouter struct {
routes map[string]Client
}
// NewStaticRouter constructs a StaticRouter with a defensive copy of routes.
func NewStaticRouter(routes map[string]Client) *StaticRouter {
clonedRoutes := make(map[string]Client, len(routes))
for messageType, client := range routes {
if client == nil {
continue
}
clonedRoutes[messageType] = client
}
return &StaticRouter{routes: clonedRoutes}
}
// Route returns the exact-match client for messageType.
func (r *StaticRouter) Route(messageType string) (Client, error) {
if r == nil {
return nil, ErrRouteNotFound
}
client, ok := r.routes[messageType]
if !ok || client == nil {
return nil, ErrRouteNotFound
}
return client, nil
}
@@ -0,0 +1,39 @@
package downstream
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestStaticRouterRoutesExactMessageType(t *testing.T) {
t.Parallel()
want := &stubClient{}
router := NewStaticRouter(map[string]Client{
"fleet.move": want,
})
got, err := router.Route("fleet.move")
require.NoError(t, err)
assert.Same(t, want, got)
}
func TestStaticRouterRejectsUnknownMessageType(t *testing.T) {
t.Parallel()
router := NewStaticRouter(map[string]Client{
"fleet.move": &stubClient{},
})
_, err := router.Route("fleet.rename")
require.ErrorIs(t, err, ErrRouteNotFound)
}
type stubClient struct{}
func (*stubClient) ExecuteCommand(context.Context, AuthenticatedCommand) (UnaryResult, error) {
return UnaryResult{}, nil
}