use axum_core::response::IntoResponse;
use futures_util::TryStreamExt;
use http::StatusCode;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use crate::HttpError;
pub type ServerFnResult<T = ()> = std::result::Result<T, ServerFnError>;
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ServerFnError {
#[error("error running server function: {message} (details: {details:#?})")]
ServerError {
message: String,
code: u16,
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<serde_json::Value>,
},
#[error("error reaching server to call server function: {0} ")]
Request(RequestError),
#[error("error reading response body stream: {0}")]
StreamError(String),
#[error("error while trying to register the server function: {0}")]
Registration(String),
#[error("error trying to build `HTTP` method request: {0}")]
UnsupportedRequestMethod(String),
#[error("error running middleware: {0}")]
MiddlewareError(String),
#[error("error deserializing server function results: {0}")]
Deserialization(String),
#[error("error serializing server function arguments: {0}")]
Serialization(String),
#[error("error deserializing server function arguments: {0}")]
Args(String),
#[error("missing argument {0}")]
MissingArg(String),
#[error("error creating response {0}")]
Response(String),
}
impl ServerFnError {
pub fn new(f: impl ToString) -> Self {
ServerFnError::ServerError {
message: f.to_string(),
details: None,
code: 500,
}
}
pub async fn from_axum_response(resp: axum_core::response::Response) -> Self {
let status = resp.status();
let message = resp
.into_body()
.into_data_stream()
.try_fold(Vec::new(), |mut acc, chunk| async move {
acc.extend_from_slice(&chunk);
Ok(acc)
})
.await
.ok()
.and_then(|bytes| String::from_utf8(bytes).ok())
.unwrap_or_else(|| status.canonical_reason().unwrap_or("").to_string());
ServerFnError::ServerError {
message,
code: status.as_u16(),
details: None,
}
}
}
impl From<anyhow::Error> for ServerFnError {
fn from(value: anyhow::Error) -> Self {
ServerFnError::ServerError {
message: value.to_string(),
details: None,
code: 500,
}
}
}
impl From<serde_json::Error> for ServerFnError {
fn from(value: serde_json::Error) -> Self {
ServerFnError::Deserialization(value.to_string())
}
}
impl From<ServerFnError> for http::StatusCode {
fn from(value: ServerFnError) -> Self {
match value {
ServerFnError::ServerError { code, .. } => {
http::StatusCode::from_u16(code).unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR)
}
ServerFnError::Request(err) => match err {
RequestError::Status(_, code) => http::StatusCode::from_u16(code)
.unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR),
_ => http::StatusCode::INTERNAL_SERVER_ERROR,
},
ServerFnError::StreamError(_)
| ServerFnError::Registration(_)
| ServerFnError::UnsupportedRequestMethod(_)
| ServerFnError::MiddlewareError(_)
| ServerFnError::Deserialization(_)
| ServerFnError::Serialization(_)
| ServerFnError::Args(_)
| ServerFnError::MissingArg(_)
| ServerFnError::Response(_) => http::StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
impl From<RequestError> for ServerFnError {
fn from(value: RequestError) -> Self {
ServerFnError::Request(value)
}
}
impl From<ServerFnError> for HttpError {
fn from(value: ServerFnError) -> Self {
let status = StatusCode::from_u16(match &value {
ServerFnError::ServerError { code, .. } => *code,
_ => 500,
})
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
HttpError {
status,
message: Some(value.to_string()),
}
}
}
impl From<HttpError> for ServerFnError {
fn from(value: HttpError) -> Self {
ServerFnError::ServerError {
message: value.message.unwrap_or_else(|| {
value
.status
.canonical_reason()
.unwrap_or("Unknown error")
.to_string()
}),
code: value.status.as_u16(),
details: None,
}
}
}
impl IntoResponse for ServerFnError {
fn into_response(self) -> axum_core::response::Response {
match self {
Self::ServerError {
message,
code,
details,
} => {
let status =
StatusCode::from_u16(code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
let body = if let Some(details) = details {
serde_json::json!({
"error": message,
"details": details,
})
} else {
serde_json::json!({
"error": message,
})
};
let body = axum_core::body::Body::from(
serde_json::to_string(&body)
.unwrap_or_else(|_| "{\"error\":\"Internal Server Error\"}".to_string()),
);
axum_core::response::Response::builder()
.status(status)
.header("Content-Type", "application/json")
.body(body)
.unwrap_or_else(|_| {
axum_core::response::Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(axum_core::body::Body::from(
"{\"error\":\"Internal Server Error\"}",
))
.unwrap()
})
}
_ => {
let status: StatusCode = self.clone().into();
let body = axum_core::body::Body::from(
serde_json::json!({
"error": self.to_string(),
})
.to_string(),
);
axum_core::response::Response::builder()
.status(status)
.header("Content-Type", "application/json")
.body(body)
.unwrap_or_else(|_| {
axum_core::response::Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(axum_core::body::Body::from(
"{\"error\":\"Internal Server Error\"}",
))
.unwrap()
})
}
}
}
}
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RequestError {
#[error("error building request: {0}")]
Builder(String),
#[error("error serializing request body: {0}")]
Serialization(String),
#[error("error following redirect: {0}")]
Redirect(String),
#[error("error receiving status code: {0} ({1})")]
Status(String, u16),
#[error("error timing out: {0}")]
Timeout(String),
#[error("error sending request: {0}")]
Request(String),
#[error("error upgrading connection: {0}")]
Connect(String),
#[error("request or response body error: {0}")]
Body(String),
#[error("error decoding response body: {0}")]
Decode(String),
}
impl RequestError {
pub fn status(&self) -> Option<StatusCode> {
match self {
RequestError::Status(_, code) => Some(StatusCode::from_u16(*code).ok()?),
_ => None,
}
}
pub fn status_code(&self) -> Option<u16> {
match self {
RequestError::Status(_, code) => Some(*code),
_ => None,
}
}
}