package daemon
import (
"encoding/json"
"errors"
"fmt"
"net/http"
)
// UpgradeRequiredError identifies a feature missing from the running daemon.
type UpgradeRequiredError struct {
Feature string
}
func (e *UpgradeRequiredError) Error() string {
return "the running copy of Albedo needs an update " + e.Feature + "; restarting will interrupt work in all sessions and clear their Python variables; when you are ready, run albedo daemon --stop, then launch your updated copy of Albedo"
}
// APIError retains the HTTP status and daemon error code independently of copy.
type APIError struct {
Cause error
Code string
Message string
StatusCode int
}
func (e *APIError) Error() string {
if e.Message != "" {
return e.Message
}
if e.Cause != nil {
return fmt.Sprintf("HTTP %d: %v", e.StatusCode, e.Cause)
}
if status := http.StatusText(e.StatusCode); status != "" {
return fmt.Sprintf("HTTP %d: %s", e.StatusCode, status)
}
return fmt.Sprintf("HTTP %d", e.StatusCode)
}
func (e *APIError) Unwrap() error { return e.Cause }
func decodeAPIError(status int, body []byte) error {
var data struct {
Code string `json:"code"`
Error string `json:"error"`
Workspace string `json:"workspace"`
}
apiErr := &APIError{StatusCode: status}
if err := json.Unmarshal(body, &data); err != nil {
return apiErr
}
apiErr.Code, apiErr.Message = data.Code, data.Error
if data.Code == "workspace_missing" && data.Workspace != "" {
apiErr.Cause = &WorkspaceMissingError{Workspace: data.Workspace}
apiErr.Message = apiErr.Cause.Error()
}
return apiErr
}
// UncertainOutcomeError means the daemon may have applied a mutation. Callers
// must reconcile its result before asking the user to submit it again.
type UncertainOutcomeError struct {
Cause error
Operation string
Handle *OperationHandle
}
func (e *UncertainOutcomeError) Error() string {
if e.Handle != nil {
return e.Operation + " admission is uncertain; operation " + e.Handle.ID() + " can be queried"
}
return e.Operation + " may have been accepted; check its result before trying again"
}
func (e *UncertainOutcomeError) Unwrap() error { return e.Cause }
// StreamFailureKind tells subscribers whether reconnecting can make progress.
type StreamFailureKind uint8
const (
StreamTransient StreamFailureKind = iota
StreamTerminal
StreamProtocol
)
// StreamError preserves the cause while defining subscription recovery.
type StreamError struct {
Kind StreamFailureKind
Cause error
}
func (e *StreamError) Error() string { return e.Cause.Error() }
func (e *StreamError) Unwrap() error { return e.Cause }
// CompatibilityError describes a live daemon that cannot serve this client.
type CompatibilityError struct {
Version int
MissingCapabilities []string
}
func (e *CompatibilityError) Error() string {
return fmt.Sprintf("daemon protocol %d is incompatible with this client; required protocol is %d; missing capabilities: %v", e.Version, ProtocolVersion, e.MissingCapabilities)
}
// ProtocolError identifies an invalid successful API response.
type ProtocolError struct {
Cause error
Code string
Operation string
Field string
}
func (e *ProtocolError) Error() string {
if e.Field == "" {
return fmt.Sprintf("invalid response for %s: %v", e.Operation, e.Cause)
}
return fmt.Sprintf("invalid response for %s (%s): %v", e.Operation, e.Field, e.Cause)
}
func (e *ProtocolError) Unwrap() error { return e.Cause }
func invalidResponse(operation operation, field string, cause error) error {
return uncertainOperation(operation, &ProtocolError{Code: "invalid_response", Operation: operation.Name, Field: field, Cause: cause})
}
type responseFieldError struct {
cause error
field string
}
func (e *responseFieldError) Error() string { return e.field + ": " + e.cause.Error() }
func fieldError(field string) error {
return &responseFieldError{field: field, cause: errors.New("required field is missing or invalid")}
}
type WorkspaceMissingError struct {
Workspace string
}
func (e *WorkspaceMissingError) Error() string {
return "workspace folder not found: " + e.Workspace
}