Files
restic-manager/internal/api/wire.go
T
steve f9718e6077
CI / Test (rest) (pull_request) Successful in 40s
CI / Test (store) (pull_request) Successful in 42s
CI / Lint (pull_request) Successful in 11s
CI / Build (windows/amd64) (pull_request) Successful in 8s
CI / Build (linux/arm64) (pull_request) Successful in 7s
CI / Build (linux/amd64) (pull_request) Successful in 8s
CI / Test (server-http) (pull_request) Successful in 1m32s
e2e / Playwright vs docker-compose (pull_request) Successful in 1m26s
Make snapshot projections self-diagnosing
2026-08-22 11:00:46 +01:00

100 lines
3.6 KiB
Go

package api
import (
"encoding/json"
"fmt"
)
// MessageType enumerates every kind of envelope that can flow over
// the agent ↔ server WebSocket. Keeping these as string constants
// (not iota ints) makes traffic readable in logs and packet captures.
type MessageType string
// MaxWebSocketMessageBytes is the protocol-wide upper bound for one agent ↔
// server envelope. Snapshot projections and restic JSON log events can exceed
// coder/websocket's 32 KiB default on ordinary repositories, so both peers set
// this limit explicitly. It remains bounded to protect either process from an
// untrusted or malfunctioning peer allocating without limit.
const MaxWebSocketMessageBytes int64 = 8 << 20
// Agent → server message types.
const (
MsgHello MessageType = "hello"
MsgHeartbeat MessageType = "heartbeat"
MsgJobStarted MessageType = "job.started"
MsgJobProgress MessageType = "job.progress"
MsgJobFinished MessageType = "job.finished"
MsgSnapshotsRpt MessageType = "snapshots.report"
MsgRepoStats MessageType = "repo.stats"
MsgLogStream MessageType = "log.stream"
MsgScheduleAck MessageType = "schedule.ack"
MsgScheduleFire MessageType = "schedule.fire" // agent: a local cron entry fired, please dispatch a job
MsgCommandResult MessageType = "command.result" // ack for command.run
MsgTreeListResult MessageType = "tree.list.result" // reply to a server-driven tree.list
MsgError MessageType = "error"
)
// Server → agent message types.
const (
MsgCommandRun MessageType = "command.run"
MsgCommandCancel MessageType = "command.cancel"
MsgScheduleSet MessageType = "schedule.set"
MsgConfigUpdate MessageType = "config.update"
MsgCommandUpdate MessageType = "command.update"
MsgTreeList MessageType = "tree.list" // sync RPC: list a snapshot's children
MsgSnapshotsRefresh MessageType = "snapshots.refresh"
)
// Envelope is the framing for every WS message in either direction.
// Payload is parsed into the concrete struct chosen by Type.
//
// ID is set on RPC-style messages (command.run / command.result) so
// responses can be correlated. For one-shot pushes (heartbeat,
// job.progress) it is empty.
type Envelope struct {
Type MessageType `json:"type"`
ID string `json:"id,omitempty"`
Payload json.RawMessage `json:"payload,omitempty"`
}
// Marshal builds an envelope from a concrete payload struct.
func Marshal(t MessageType, id string, payload any) (Envelope, error) {
if payload == nil {
return Envelope{Type: t, ID: id}, nil
}
raw, err := json.Marshal(payload)
if err != nil {
return Envelope{}, fmt.Errorf("marshal %s payload: %w", t, err)
}
return Envelope{Type: t, ID: id, Payload: raw}, nil
}
// UnmarshalPayload decodes the envelope's payload into v.
func (e Envelope) UnmarshalPayload(v any) error {
if len(e.Payload) == 0 {
return nil
}
return json.Unmarshal(e.Payload, v)
}
// ErrorCode enumerates error reasons surfaced over the wire.
// These are stable identifiers; client code may switch on them.
type ErrorCode string
// Stable ErrorCode values surfaced over the wire. Clients switch on
// these; renaming requires a wire-version bump.
const (
ErrProtocolTooOld ErrorCode = "protocol_too_old"
ErrProtocolTooNew ErrorCode = "protocol_too_new"
ErrUnauthorized ErrorCode = "unauthorised"
ErrBadRequest ErrorCode = "bad_request"
ErrInternal ErrorCode = "internal"
)
// ErrorPayload is the body of an `error` envelope.
type ErrorPayload struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
HelpURL string `json:"help_url,omitempty"`
}