#[cfg(feature = "mresult")]
use http::StatusCode;
#[cfg(feature = "salvo")]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
use salvo::oapi::{EndpointOutRegister, ToSchema};
#[cfg(feature = "salvo")]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
use salvo::{Depot, Request, Response};
#[cfg(feature = "salvo")]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
use salvo::Writer as ServerResponseWriter;
#[cfg(feature = "mresult")]
#[derive(Clone)]
pub struct ServerError {
pub status_code: Option<StatusCode>,
pub public_msg: Option<String>,
pub private_msg: Option<Vec<String>>,
}
#[cfg(feature = "mresult")]
impl std::fmt::Debug for ServerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!(
"Error: `{}` status code\n Error message: \"{}\"{}",
self.status_code.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR).as_str(),
self.decide_public_msg(),
if let Some(privates) = self.private_msg.as_ref() {
format!(
"\n{}",
privates
.iter()
.map(|e| format!(" Caused by: {e}"))
.collect::<Vec<_>>()
.join("\n")
)
} else {
String::new()
}
))
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[cfg_attr(
all(feature = "salvo", not(any(target_arch = "wasm32", target_arch = "wasm64"))),
derive(salvo::oapi::ToSchema)
)]
pub struct ErrorResponse {
pub err: String,
}
#[cfg(feature = "mresult")]
impl std::fmt::Display for ServerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!(
"Error: `{}` status code\n Error message: \"{}\"{}",
self.status_code.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR).as_str(),
self.decide_public_msg(),
if let Some(privates) = self.private_msg.as_ref() {
format!(
"\n{}",
privates
.iter()
.map(|e| format!(" Caused by: {e}"))
.collect::<Vec<_>>()
.join("\n")
)
} else {
String::new()
}
))
}
}
#[cfg(feature = "mresult")]
impl std::error::Error for ServerError {}
impl std::fmt::Display for ErrorResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!("Error: {}", self.err))
}
}
#[cfg(feature = "mresult")]
impl std::error::Error for ErrorResponse {}
#[cfg(feature = "cresult")]
#[derive(Clone)]
pub struct ClientError {
pub message: String,
}
#[cfg(feature = "cresult")]
impl std::fmt::Display for ClientError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.message.as_str())
}
}
#[cfg(feature = "cresult")]
impl std::fmt::Debug for ClientError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.message.as_str())
}
}
#[cfg(feature = "cresult")]
impl std::error::Error for ClientError {}
#[cfg(all(feature = "salvo", feature = "mresult"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
impl crate::responses::ExplicitServerWrite for ServerError {
async fn explicit_write(self, res: &mut Response) {
res.status_code(self.status_code.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR));
tracing::error!("{}", self);
res
.add_header(salvo::http::header::CONTENT_TYPE, "application/json", true)
.ok();
res
.write_body(
sonic_rs::to_string(&ErrorResponse {
err: self.decide_public_msg(),
})
.unwrap_or(r#"{"err":"Unknown server error"}"#.to_string()),
)
.ok();
}
}
#[cfg(all(feature = "salvo", feature = "mresult"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[salvo::async_trait]
impl ServerResponseWriter for ServerError {
async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
crate::responses::ExplicitServerWrite::explicit_write(self, res).await
}
}
#[cfg(all(feature = "salvo", feature = "mresult"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
impl EndpointOutRegister for ServerError {
fn register(components: &mut salvo::oapi::Components, operation: &mut salvo::oapi::Operation) {
operation.responses.insert(
"400",
salvo::oapi::Response::new("Bad request").add_content("application/json", ErrorResponse::to_schema(components)),
);
operation.responses.insert(
"401",
salvo::oapi::Response::new("Unauthorized").add_content("application/json", ErrorResponse::to_schema(components)),
);
operation.responses.insert(
"403",
salvo::oapi::Response::new("Forbidden").add_content("application/json", ErrorResponse::to_schema(components)),
);
operation.responses.insert(
"404",
salvo::oapi::Response::new("Not found").add_content("application/json", ErrorResponse::to_schema(components)),
);
operation.responses.insert(
"405",
salvo::oapi::Response::new("Method not allowed")
.add_content("application/json", ErrorResponse::to_schema(components)),
);
operation.responses.insert(
"500",
salvo::oapi::Response::new("Internal server error")
.add_content("application/json", ErrorResponse::to_schema(components)),
);
}
}
#[cfg(any(feature = "mresult", all(feature = "reqwest", feature = "cresult")))]
pub(crate) fn public_msg_from(status_code: &Option<u16>) -> &'static str {
match status_code {
Some(400) => "Bad request.",
Some(401) => "Unauthorized request.",
Some(403) => "Access denied.",
Some(404) => "Page or method not found.",
Some(405) => "Method not allowed.",
Some(500) => "Internal server error. Contact the administrator.",
_ => "Specific error. Check with the administrator for details.",
}
}
#[cfg(feature = "mresult")]
impl ServerError {
fn decide_public_msg(&self) -> String {
if let Some(public_msg) = self.public_msg.as_ref() {
public_msg.to_owned()
} else {
public_msg_from(&self.status_code.as_ref().map(|v| v.as_u16())).to_string()
}
}
fn format_error(error: &(dyn std::error::Error + 'static)) -> Vec<String> {
let mut result = vec![];
let mut current_error: Option<&(dyn std::error::Error + 'static)> = Some(error);
while let Some(err) = current_error {
result.push(err.to_string());
current_error = err.source();
}
result
}
pub fn from_private(err: impl std::error::Error + 'static) -> Self {
let err_data = Self::format_error(&err);
Self {
status_code: None,
public_msg: None,
private_msg: Some(err_data),
}
}
pub fn from_private_str(err: impl Into<String>) -> Self {
Self {
status_code: None,
public_msg: None,
private_msg: Some(vec![err.into()]),
}
}
pub fn with_private_str(mut self, new_private_msg: impl Into<String>) -> Self {
if let Some(privates) = self.private_msg.as_mut() {
privates.insert(0, new_private_msg.into());
} else {
self.private_msg = Some(vec![new_private_msg.into()]);
}
self
}
pub fn with_public(mut self, new_public_msg: impl Into<String>) -> Self {
if let Some(old_public_msg) = self.public_msg.take() {
if let Some(privates) = self.private_msg.as_mut() {
privates.insert(0, old_public_msg);
} else {
self.private_msg = Some(vec![old_public_msg]);
}
}
self.public_msg = Some(new_public_msg.into());
self
}
pub fn from_public(public_msg: impl Into<String>) -> Self {
Self {
status_code: None,
public_msg: Some(public_msg.into()),
private_msg: None,
}
}
pub fn with_400(mut self) -> Self {
self.status_code = Some(StatusCode::BAD_REQUEST);
self
}
pub fn with_401(mut self) -> Self {
self.status_code = Some(StatusCode::UNAUTHORIZED);
self
}
pub fn with_403(mut self) -> Self {
self.status_code = Some(StatusCode::FORBIDDEN);
self
}
pub fn with_404(mut self) -> Self {
self.status_code = Some(StatusCode::NOT_FOUND);
self
}
pub fn with_405(mut self) -> Self {
self.status_code = Some(StatusCode::METHOD_NOT_ALLOWED);
self
}
pub fn with_500(mut self) -> Self {
self.status_code = Some(StatusCode::INTERNAL_SERVER_ERROR);
self
}
pub fn with_code(mut self, code: StatusCode) -> Self {
self.status_code = Some(code);
self
}
pub fn bail<T>(self) -> Result<T, Self> {
Err(self)
}
}
#[cfg(feature = "cresult")]
impl ClientError {
pub fn from<T: std::error::Error>(value: T) -> Self {
Self {
message: value.to_string(),
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(value: impl Into<String>) -> Self {
Self { message: value.into() }
}
pub fn context(mut self, value: impl Into<String>) -> Self {
self.message = value.into() + "\n Caused by: " + &self.message;
self
}
}
#[cfg(feature = "cresult")]
impl AsRef<str> for ClientError {
fn as_ref(&self) -> &str {
self.message.as_str()
}
}