Skip to content

Commit b01abbb

Browse files
authored
Merge pull request #344 from input-output-hk/lowhung/introduce-new-error-types
feat: Add `RESTError` and `QueryError`
2 parents c8b9cd9 + 70e2740 commit b01abbb

File tree

4 files changed

+252
-0
lines changed

4 files changed

+252
-0
lines changed

common/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub mod protocol_params;
1515
pub mod queries;
1616
pub mod rational_number;
1717
pub mod resolver;
18+
pub mod rest_error;
1819
pub mod rest_helper;
1920
pub mod serialization;
2021
pub mod snapshot;

common/src/queries/errors.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use serde::{Deserialize, Serialize};
2+
use thiserror::Error;
3+
4+
/// Common error type for all state query responses
5+
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
6+
pub enum QueryError {
7+
/// The requested resource was not found
8+
#[error("Not found: {resource}")]
9+
NotFound { resource: String },
10+
11+
/// An error occurred while processing the query
12+
#[error("Internal error: {message}")]
13+
Internal { message: String },
14+
15+
/// Storage backend is disabled in configuration
16+
#[error("{storage_type} storage is not enabled")]
17+
StorageDisabled { storage_type: String },
18+
19+
/// Invalid request parameters
20+
#[error("Invalid request: {message}")]
21+
InvalidRequest { message: String },
22+
23+
/// Query variant is not implemented yet
24+
#[error("Query not implemented: {query}")]
25+
NotImplemented { query: String },
26+
}
27+
28+
impl QueryError {
29+
pub fn not_found(resource: impl Into<String>) -> Self {
30+
Self::NotFound {
31+
resource: resource.into(),
32+
}
33+
}
34+
35+
pub fn internal_error(message: impl Into<String>) -> Self {
36+
Self::Internal {
37+
message: message.into(),
38+
}
39+
}
40+
41+
pub fn storage_disabled(storage_type: impl Into<String>) -> Self {
42+
Self::StorageDisabled {
43+
storage_type: storage_type.into(),
44+
}
45+
}
46+
47+
pub fn invalid_request(message: impl Into<String>) -> Self {
48+
Self::InvalidRequest {
49+
message: message.into(),
50+
}
51+
}
52+
53+
pub fn not_implemented(query: impl Into<String>) -> Self {
54+
Self::NotImplemented {
55+
query: query.into(),
56+
}
57+
}
58+
}

common/src/queries/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod addresses;
77
pub mod assets;
88
pub mod blocks;
99
pub mod epochs;
10+
pub mod errors;
1011
pub mod governance;
1112
pub mod ledger;
1213
pub mod mempool;

common/src/rest_error.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
use crate::queries::errors::QueryError;
2+
use anyhow::Error as AnyhowError;
3+
use caryatid_module_rest_server::messages::RESTResponse;
4+
use thiserror::Error;
5+
6+
/// Standard REST error types
7+
#[derive(Debug, Error)]
8+
pub enum RESTError {
9+
#[error("{0}")]
10+
BadRequest(String),
11+
12+
#[error("{0}")]
13+
NotFound(String),
14+
15+
#[error("{0}")]
16+
InternalServerError(String),
17+
18+
#[error("{0}")]
19+
NotImplemented(String),
20+
}
21+
22+
impl RESTError {
23+
/// Get the HTTP status code for this error
24+
pub fn status_code(&self) -> u16 {
25+
match self {
26+
RESTError::BadRequest(_) => 400,
27+
RESTError::NotFound(_) => 404,
28+
RESTError::InternalServerError(_) => 500,
29+
RESTError::NotImplemented(_) => 501,
30+
}
31+
}
32+
33+
/// Get the error message
34+
pub fn message(&self) -> &str {
35+
match self {
36+
RESTError::BadRequest(msg) => msg,
37+
RESTError::NotFound(msg) => msg,
38+
RESTError::InternalServerError(msg) => msg,
39+
RESTError::NotImplemented(msg) => msg,
40+
}
41+
}
42+
43+
/// Parameter missing error
44+
pub fn param_missing(param_name: &str) -> Self {
45+
RESTError::BadRequest(format!("{} parameter is missing", param_name))
46+
}
47+
48+
/// Invalid parameter error
49+
pub fn invalid_param(param_name: &str, reason: &str) -> Self {
50+
RESTError::BadRequest(format!("Invalid {}: {}", param_name, reason))
51+
}
52+
53+
/// Invalid hex string error
54+
pub fn invalid_hex() -> Self {
55+
RESTError::BadRequest("Invalid hex string".to_string())
56+
}
57+
58+
/// Resource not found error
59+
pub fn not_found(message: &str) -> Self {
60+
RESTError::NotFound(message.to_string())
61+
}
62+
63+
/// Feature not implemented error
64+
pub fn not_implemented(message: &str) -> Self {
65+
RESTError::NotImplemented(message.to_string())
66+
}
67+
68+
/// Storage disabled error
69+
pub fn storage_disabled(storage_type: &str) -> Self {
70+
RESTError::NotImplemented(format!("{} storage is disabled in config", storage_type))
71+
}
72+
73+
/// Unexpected response error
74+
pub fn unexpected_response(message: &str) -> Self {
75+
RESTError::InternalServerError(message.to_string())
76+
}
77+
78+
/// Query failed error
79+
pub fn query_failed(message: &str) -> Self {
80+
RESTError::InternalServerError(message.to_string())
81+
}
82+
83+
/// Encoding failed error
84+
pub fn encoding_failed(what: &str) -> Self {
85+
RESTError::InternalServerError(format!("Failed to encode {}", what))
86+
}
87+
}
88+
89+
/// Convert RESTError to RESTResponse
90+
impl From<RESTError> for RESTResponse {
91+
fn from(error: RESTError) -> Self {
92+
RESTResponse::with_text(error.status_code(), error.message())
93+
}
94+
}
95+
96+
/// Convert anyhow::Error to RESTError (default to 500)
97+
impl From<AnyhowError> for RESTError {
98+
fn from(error: AnyhowError) -> Self {
99+
RESTError::InternalServerError(error.to_string())
100+
}
101+
}
102+
103+
/// Convert hex decode errors to RESTError (400 Bad Request)
104+
impl From<hex::FromHexError> for RESTError {
105+
fn from(error: hex::FromHexError) -> Self {
106+
RESTError::BadRequest(format!("Invalid hex string: {}", error))
107+
}
108+
}
109+
110+
/// Convert bech32 decode errors to RESTError (400 Bad Request)
111+
impl From<bech32::DecodeError> for RESTError {
112+
fn from(error: bech32::DecodeError) -> Self {
113+
RESTError::BadRequest(format!("Invalid bech32 encoding: {}", error))
114+
}
115+
}
116+
117+
/// Convert bech32 encode errors to RESTError (500 Internal Server Error)
118+
impl From<bech32::EncodeError> for RESTError {
119+
fn from(error: bech32::EncodeError) -> Self {
120+
RESTError::InternalServerError(format!("Failed to encode bech32: {}", error))
121+
}
122+
}
123+
124+
/// Convert serde_json errors to RESTError (500 Internal Server Error)
125+
impl From<serde_json::Error> for RESTError {
126+
fn from(error: serde_json::Error) -> Self {
127+
RESTError::InternalServerError(format!("JSON serialization failed: {}", error))
128+
}
129+
}
130+
131+
/// Convert QueryError to RESTError
132+
impl From<QueryError> for RESTError {
133+
fn from(error: QueryError) -> Self {
134+
match error {
135+
QueryError::NotFound { resource } => RESTError::NotFound(resource),
136+
QueryError::Internal { message } => RESTError::InternalServerError(message),
137+
QueryError::StorageDisabled { storage_type } => {
138+
RESTError::storage_disabled(&storage_type)
139+
}
140+
QueryError::InvalidRequest { message } => RESTError::BadRequest(message),
141+
QueryError::NotImplemented { query } => RESTError::NotImplemented(query),
142+
}
143+
}
144+
}
145+
146+
#[cfg(test)]
147+
mod tests {
148+
use super::*;
149+
150+
#[test]
151+
fn test_bad_request_error() {
152+
let error = RESTError::BadRequest("Invalid parameter".to_string());
153+
assert_eq!(error.status_code(), 400);
154+
assert_eq!(error.message(), "Invalid parameter");
155+
}
156+
157+
#[test]
158+
fn test_not_found_error() {
159+
let error = RESTError::NotFound("Account not found".to_string());
160+
assert_eq!(error.status_code(), 404);
161+
assert_eq!(error.message(), "Account not found");
162+
}
163+
164+
#[test]
165+
fn test_internal_error() {
166+
let error = RESTError::InternalServerError("Database connection failed".to_string());
167+
assert_eq!(error.status_code(), 500);
168+
assert_eq!(error.message(), "Database connection failed");
169+
}
170+
171+
#[test]
172+
fn test_from_anyhow() {
173+
let anyhow_err = anyhow::anyhow!("Something went wrong");
174+
let app_error = RESTError::from(anyhow_err);
175+
assert_eq!(app_error.status_code(), 500);
176+
}
177+
178+
#[test]
179+
fn test_from_hex_error() {
180+
let result = hex::decode("not_hex_gg");
181+
let app_error: RESTError = result.unwrap_err().into();
182+
assert_eq!(app_error.status_code(), 400);
183+
}
184+
185+
#[test]
186+
fn test_to_rest_response() {
187+
let error = RESTError::BadRequest("Invalid stake address".to_string());
188+
let response: RESTResponse = error.into();
189+
assert_eq!(response.code, 400);
190+
assert_eq!(response.body, "Invalid stake address");
191+
}
192+
}

0 commit comments

Comments
 (0)