Skip to main content

rivet_core/
error.rs

1use rivet_foundation::FoundationError;
2use rivet_http::{Method, Response};
3use rivet_routing::RouteRegistryError;
4
5/// Top-level rivet error surface returned by build and dispatch APIs.
6#[derive(Debug, thiserror::Error)]
7pub enum RivetError {
8    #[error("build failed: {0}")]
9    Build(String),
10    #[error("provider failed: {0}")]
11    Provider(String),
12    #[error("routing failed: {0}")]
13    Routing(String),
14    #[error("dispatch failed: {0}")]
15    Dispatch(String),
16    #[error(transparent)]
17    Internal(#[from] anyhow::Error),
18}
19
20/// Typed build-stage errors used to construct deterministic build failures.
21#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
22pub enum BuildError {
23    #[error("module '{module}' configure: {message}")]
24    Configure {
25        module: &'static str,
26        message: String,
27    },
28    #[error("register '{provider}': {source}")]
29    ProviderRegister {
30        provider: String,
31        source: FoundationError,
32    },
33    #[error("boot '{provider}': {source}")]
34    ProviderBoot {
35        provider: String,
36        source: FoundationError,
37    },
38    #[error("module '{module}' routes: {message}")]
39    Routes {
40        module: &'static str,
41        message: String,
42    },
43}
44
45/// Typed dispatch errors used for route and runtime failure mapping.
46#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
47pub enum DispatchError {
48    #[error("route not found")]
49    NotFound,
50    #[error("method not allowed")]
51    MethodNotAllowed { allow: Vec<Method> },
52    #[error("middleware failed: {0}")]
53    Middleware(String),
54    #[error("handler failed: {0}")]
55    Handler(String),
56    #[error("internal dispatch failure: {0}")]
57    Internal(String),
58}
59
60impl DispatchError {
61    /// Convert a dispatch failure into a stable HTTP response contract.
62    pub(crate) fn into_response(self) -> Response {
63        match self {
64            Self::NotFound => Response::not_found(),
65            Self::MethodNotAllowed { allow } => Response::method_not_allowed(&allow),
66            Self::Middleware(_) | Self::Handler(_) | Self::Internal(_) => {
67                Response::internal_error()
68            }
69        }
70    }
71}
72
73impl From<BuildError> for RivetError {
74    fn from(value: BuildError) -> Self {
75        Self::Build(value.to_string())
76    }
77}
78
79impl From<DispatchError> for RivetError {
80    fn from(value: DispatchError) -> Self {
81        Self::Dispatch(value.to_string())
82    }
83}
84
85impl From<FoundationError> for RivetError {
86    fn from(value: FoundationError) -> Self {
87        Self::Provider(value.to_string())
88    }
89}
90
91impl From<RouteRegistryError> for RivetError {
92    fn from(value: RouteRegistryError) -> Self {
93        Self::Routing(value.to_string())
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn build_error_converts_to_rivet_error() {
103        let err: RivetError = BuildError::Configure {
104            module: "users",
105            message: "invalid config".to_string(),
106        }
107        .into();
108
109        assert_eq!(
110            err.to_string(),
111            "build failed: module 'users' configure: invalid config"
112        );
113    }
114
115    #[test]
116    fn dispatch_error_converts_to_rivet_error() {
117        let err: RivetError = DispatchError::Handler("boom".to_string()).into();
118
119        assert_eq!(err.to_string(), "dispatch failed: handler failed: boom");
120    }
121
122    #[test]
123    fn foundation_and_routing_errors_convert_to_rivet_error() {
124        let provider_err: RivetError =
125            FoundationError::ServiceBoot("boot failed".to_string()).into();
126        assert_eq!(
127            provider_err.to_string(),
128            "provider failed: service boot failed: boot failed"
129        );
130
131        let routing_err: RivetError = RouteRegistryError::Duplicate {
132            method: Method::Get,
133            path: "/users".to_string(),
134        }
135        .into();
136        assert_eq!(
137            routing_err.to_string(),
138            "routing failed: duplicate route: Get /users"
139        );
140    }
141
142    #[test]
143    fn dispatch_error_maps_to_stable_http_response() {
144        let not_found = DispatchError::NotFound.into_response();
145        assert_eq!(not_found.status, 404);
146
147        let method_not_allowed = DispatchError::MethodNotAllowed {
148            allow: vec![Method::Post],
149        }
150        .into_response();
151        assert_eq!(method_not_allowed.status, 405);
152        assert_eq!(
153            method_not_allowed.headers.get("allow"),
154            Some(&"POST".to_string())
155        );
156
157        let handler_failure = DispatchError::Handler("failed".to_string()).into_response();
158        assert_eq!(handler_failure.status, 500);
159    }
160}