starfish-sdk 1.0.0

海星SDK
Documentation
use anyhow::Result;
use axum::{
	extract::{FromRequest, Request},
	http::StatusCode,
	response::{IntoResponse, Response},
	Json, RequestExt,
};
use serde::de::DeserializeOwned;
use serde_json::{json, Value};
use std::{
	borrow::Cow,
	fmt::{Debug, Display},
};

pub use axum::{
	routing::{get as WebGetRoute, post as WebPostRoute},
	Router as WebRouter,
};

#[derive(Debug)]
pub struct WebErrMsg(pub i64, pub Cow<'static, str>);

impl Display for WebErrMsg {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "错误码({}), 消息({})", self.0, self.1)
	}
}

#[derive(Debug, Clone)]
pub enum WebCode {
	Service,
	RequestParam(String),
}

impl WebCode {
	pub fn to_err_msg(&self) -> WebErrMsg {
		let (code, msg) = match self {
			Self::Service => (1, Cow::from("服务异常")),
			Self::RequestParam(msg) => (2, Cow::from(format!("请求参数错误({})", msg))),
		};
		WebErrMsg(code, msg)
	}
}

pub struct WebError(pub anyhow::Error);

impl IntoResponse for WebError {
	fn into_response(self) -> Response {
		tracing::error!("{}", self.0.to_string());
		let code: i64;
		let msg: Cow<'static, str>;
		if let Some(err_msg) = self.0.downcast_ref::<WebErrMsg>() {
			code = err_msg.0;
			msg = err_msg.1.to_owned();
		} else {
			let err_msg = WebCode::Service.to_err_msg();
			code = err_msg.0;
			msg = err_msg.1.to_owned();
		}
		(
			StatusCode::OK,
			Json(json!({
				"code" : code,
				"msg" :msg,
				"data" : Value::Null,
			})),
		)
			.into_response()
	}
}

impl<E> From<E> for WebError
where
	E: Into<anyhow::Error>,
{
	fn from(err: E) -> Self {
		Self(err.into())
	}
}

pub type WebResult<T> = core::result::Result<T, WebError>;

pub struct WebJson<T>(pub T);

impl<T: serde::Serialize> IntoResponse for WebJson<T> {
	fn into_response(self) -> Response {
		(
			StatusCode::OK,
			Json(json!({
				"code":0,
				"msg":Value::Null,
				"data":self.0,
			})),
		)
			.into_response()
	}
}

#[async_trait::async_trait]
impl<T, S> FromRequest<S> for WebJson<T>
where
	T: DeserializeOwned,
	S: Send + Sync,
	T: 'static,
{
	type Rejection = Response;

	async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
		match req.extract().await {
			Ok(Json(payload)) => return Ok(Self(payload)),
			Err(err) => {
				tracing::error!("{}", err.to_string());
				let err_msg = WebCode::RequestParam(err.to_string()).to_err_msg();
				Err((
					StatusCode::OK,
					Json(json!({
						"code":err_msg.0,
						"msg":err_msg.1,
						"data":Value::Null,
					})),
				)
					.into_response())
			},
		}
	}
}

pub async fn init_web<A: tokio::net::ToSocketAddrs>(bind: A, register: fn(WebRouter) -> WebRouter) -> Result<()> {
	let router = WebRouter::new();
	let router = register(router);
	let router = router.layer(tower_http::trace::TraceLayer::new_for_http().make_span_with(tracing::Span::current()));
	let listener = tokio::net::TcpListener::bind(bind).await?;
	axum::serve(listener, router).await?;
	Ok(())
}