Skip to main content

post_cortex_daemon/
error.rs

1// Copyright (c) 2025, 2026 Julius ML
2// Licensed under the MIT License. See LICENSE at the workspace root.
3
4//! Typed error hierarchy for `post-cortex-daemon`.
5//!
6//! Carries variants for the transport surface (HTTP / gRPC / MCP /
7//! stdio) and delegates domain errors downward. [`Error::grpc_status`]
8//! maps each variant to a `tonic::Status`; [`Error::http_status`] gives
9//! the matching `http::StatusCode` for axum handlers.
10
11use thiserror::Error;
12
13/// Errors produced by the daemon's transport layer.
14#[derive(Debug, Error)]
15pub enum Error {
16    /// Caller request failed validation (invalid UUID, missing field).
17    #[error("invalid request: {0}")]
18    InvalidRequest(String),
19
20    /// Memory / orchestrator error.
21    #[error(transparent)]
22    Memory(#[from] post_cortex_memory::error::Error),
23
24    /// MCP tool error.
25    #[error(transparent)]
26    Mcp(#[from] post_cortex_mcp::error::Error),
27
28    /// Storage error surfaced to the transport.
29    #[error(transparent)]
30    Storage(#[from] post_cortex_storage::error::Error),
31
32    /// I/O error (stdio bridge, TCP, file).
33    #[error("io error: {0}")]
34    Io(#[from] std::io::Error),
35
36    /// Catch-all for migrating call sites that still use `anyhow`.
37    #[error(transparent)]
38    External(#[from] anyhow::Error),
39}
40
41/// Crate-level result alias.
42pub type Result<T, E = Error> = std::result::Result<T, E>;
43
44impl Error {
45    /// A stable discriminant for metrics / structured logs.
46    #[must_use]
47    pub fn kind(&self) -> &'static str {
48        match self {
49            Self::InvalidRequest(_) => "invalid_request",
50            Self::Memory(_) => "memory",
51            Self::Mcp(_) => "mcp",
52            Self::Storage(_) => "storage",
53            Self::Io(_) => "io",
54            Self::External(_) => "external",
55        }
56    }
57
58    /// Map this error to a `tonic::Status` for the gRPC surface.
59    #[must_use]
60    pub fn grpc_status(&self) -> tonic::Status {
61        match self {
62            Self::InvalidRequest(msg) => tonic::Status::invalid_argument(msg),
63            Self::Memory(post_cortex_memory::error::Error::SessionNotFound(_)) => {
64                tonic::Status::not_found(self.to_string())
65            }
66            Self::Memory(post_cortex_memory::error::Error::Timeout { .. }) => {
67                tonic::Status::deadline_exceeded(self.to_string())
68            }
69            Self::Memory(post_cortex_memory::error::Error::Backpressure { .. })
70            | Self::Memory(post_cortex_memory::error::Error::WorkerShutdown { .. }) => {
71                tonic::Status::resource_exhausted(self.to_string())
72            }
73            _ => tonic::Status::internal(self.to_string()),
74        }
75    }
76
77    /// Map this error to an HTTP status code for the axum surface.
78    #[must_use]
79    pub fn http_status(&self) -> u16 {
80        match self {
81            Self::InvalidRequest(_) => 400,
82            Self::Memory(post_cortex_memory::error::Error::SessionNotFound(_)) => 404,
83            Self::Memory(post_cortex_memory::error::Error::Timeout { .. }) => 504,
84            Self::Memory(post_cortex_memory::error::Error::Backpressure { .. })
85            | Self::Memory(post_cortex_memory::error::Error::WorkerShutdown { .. }) => 503,
86            _ => 500,
87        }
88    }
89}