1use rivet_foundation::FoundationError;
2use rivet_http::{Method, Response};
3use rivet_routing::RouteRegistryError;
4
5#[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#[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#[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 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}