mirror of
https://github.com/AThilenius/axum-connect.git
synced 2025-01-05 01:39:14 +00:00
Add Into Req/Res traits and large cleanup.
This commit is contained in:
parent
c676f0ffe9
commit
673d79c73e
10 changed files with 489 additions and 235 deletions
|
@ -12,6 +12,6 @@ repository = "https://github.com/AThilenius/axum-connect"
|
|||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
convert_case = "0.6.0"
|
||||
protobuf = { git = "https://github.com/AThilenius/rust-protobuf.git" }
|
||||
protobuf-codegen = { git = "https://github.com/AThilenius/rust-protobuf.git" }
|
||||
protobuf-parse = { git = "https://github.com/AThilenius/rust-protobuf.git" }
|
||||
protobuf = { git = "https://github.com/AThilenius/rust-protobuf.git", tag = "v3.2.1" }
|
||||
protobuf-codegen = { git = "https://github.com/AThilenius/rust-protobuf.git", tag = "v3.2.1" }
|
||||
protobuf-parse = { git = "https://github.com/AThilenius/rust-protobuf.git", tag = "v3.2.1" }
|
||||
|
|
|
@ -118,7 +118,7 @@ use axum::{
|
|||
Router,
|
||||
};
|
||||
|
||||
use axum_connect::{HandlerFuture, RpcRouter};
|
||||
use axum_connect::{handler::HandlerFuture, router::RpcRouter};
|
||||
";
|
||||
|
||||
const SERVICE_TEMPLATE: &str = "
|
||||
|
@ -129,9 +129,9 @@ impl @@SERVICE_NAME@@ {
|
|||
}";
|
||||
|
||||
const METHOD_TEMPLATE: &str = "
|
||||
pub fn @@METHOD_NAME@@<T, H, S, B>(handler: H) -> impl FnOnce(Router<S, B>) -> RpcRouter<S, B>
|
||||
pub fn @@METHOD_NAME@@<T, H, R, S, B>(handler: H) -> impl FnOnce(Router<S, B>) -> RpcRouter<S, B>
|
||||
where
|
||||
H: HandlerFuture<super::@@INPUT_TYPE@@, super::@@OUTPUT_TYPE@@, T, S, B>,
|
||||
H: HandlerFuture<super::@@INPUT_TYPE@@, super::@@OUTPUT_TYPE@@, R, T, S, B>,
|
||||
T: 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
B: HttpBody + Send + 'static,
|
||||
|
@ -143,9 +143,7 @@ const METHOD_TEMPLATE: &str = "
|
|||
\"@@ROUTE@@\",
|
||||
post(|State(state): State<S>, request: Request<B>| async move {
|
||||
let res = handler.call(request, state).await;
|
||||
::axum_connect::protobuf_json_mapping::print_to_string(&res)
|
||||
.unwrap()
|
||||
.into_response()
|
||||
res.into_response()
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use axum::{extract::Host, Router};
|
||||
use axum_connect::*;
|
||||
use axum_connect::{error::RpcError, prelude::*};
|
||||
use proto::hello::{HelloRequest, HelloResponse, HelloWorldService};
|
||||
|
||||
mod proto {
|
||||
|
@ -13,7 +13,11 @@ async fn main() {
|
|||
// Build our application with a route. Note the `rpc` method which was added by `axum-connect`.
|
||||
// It expect a service method handler, wrapped in it's respective type. The handler (below) is
|
||||
// just a normal Rust function. Just like Axum, it also supports extractors!
|
||||
let app = Router::new().rpc(HelloWorldService::say_hello(say_hello_handler));
|
||||
let app = Router::new()
|
||||
.rpc(HelloWorldService::say_hello(say_hello_success))
|
||||
.rpc(HelloWorldService::say_hello(say_hello_error))
|
||||
.rpc(HelloWorldService::say_hello(say_hello_result))
|
||||
.rpc(HelloWorldService::say_hello(say_hello_error_code));
|
||||
|
||||
// Axum boilerplate to start the server.
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
|
@ -24,10 +28,7 @@ async fn main() {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
// This is the magic. This is the TYPED handler for the `say_hello` method, changes to the proto
|
||||
// definition will need to be reflected here. But the first N arguments can be standard Axum
|
||||
// extracts, to get at what ever info or state you need.
|
||||
async fn say_hello_handler(Host(host): Host, request: HelloRequest) -> HelloResponse {
|
||||
async fn say_hello_success(Host(host): Host, request: HelloRequest) -> HelloResponse {
|
||||
HelloResponse {
|
||||
message: format!(
|
||||
"Hello {}! You're addressing the hostname: {}.",
|
||||
|
@ -36,3 +37,18 @@ async fn say_hello_handler(Host(host): Host, request: HelloRequest) -> HelloResp
|
|||
special_fields: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn say_hello_error(_request: HelloRequest) -> RpcError {
|
||||
RpcError::new(RpcErrorCode::Unimplemented, "Not implemented".to_string())
|
||||
}
|
||||
|
||||
async fn say_hello_error_code(_request: HelloRequest) -> RpcErrorCode {
|
||||
RpcErrorCode::Unimplemented
|
||||
}
|
||||
|
||||
async fn say_hello_result(_request: HelloRequest) -> RpcResult<HelloResponse> {
|
||||
Ok(HelloResponse {
|
||||
message: "Hello World!".to_string(),
|
||||
special_fields: Default::default(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ readme = "../README.md"
|
|||
repository = "https://github.com/AThilenius/axum-connect"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.64"
|
||||
axum = "0.6.9"
|
||||
futures = "0.3.26"
|
||||
protobuf = "3.2.0"
|
||||
|
|
134
axum-connect/src/error.rs
Normal file
134
axum-connect/src/error.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use protobuf::MessageFull;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{prelude::RpcResponse, response::RpcIntoResponse};
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct RpcError {
|
||||
pub code: RpcErrorCode,
|
||||
pub message: String,
|
||||
pub details: Vec<RpcErrorDetail>,
|
||||
}
|
||||
|
||||
pub trait RpcIntoError {
|
||||
fn rpc_into_error(self) -> RpcError;
|
||||
}
|
||||
|
||||
impl RpcIntoError for RpcError {
|
||||
fn rpc_into_error(self) -> RpcError {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RpcError {
|
||||
pub fn new(code: RpcErrorCode, message: String) -> Self {
|
||||
Self {
|
||||
code,
|
||||
message,
|
||||
details: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, M> RpcIntoError for (C, M)
|
||||
where
|
||||
C: Into<RpcErrorCode>,
|
||||
M: Into<String>,
|
||||
{
|
||||
fn rpc_into_error(self) -> RpcError {
|
||||
RpcError {
|
||||
code: self.0.into(),
|
||||
message: self.1.into(),
|
||||
details: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct RpcErrorDetail {
|
||||
#[serde(rename = "type")]
|
||||
pub proto_type: String,
|
||||
#[serde(rename = "value")]
|
||||
pub proto_b62_value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RpcErrorCode {
|
||||
Canceled,
|
||||
Unknown,
|
||||
InvalidArgument,
|
||||
DeadlineExceeded,
|
||||
NotFound,
|
||||
AlreadyExists,
|
||||
PermissionDenied,
|
||||
ResourceExhausted,
|
||||
FailedPrecondition,
|
||||
Aborted,
|
||||
OutOfRange,
|
||||
Unimplemented,
|
||||
Internal,
|
||||
Unavailable,
|
||||
DataLoss,
|
||||
Unauthenticated,
|
||||
}
|
||||
|
||||
impl From<RpcErrorCode> for StatusCode {
|
||||
fn from(val: RpcErrorCode) -> Self {
|
||||
match val {
|
||||
// Spec: https://connect.build/docs/protocol/#error-codes
|
||||
RpcErrorCode::Canceled => StatusCode::REQUEST_TIMEOUT,
|
||||
RpcErrorCode::Unknown => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
RpcErrorCode::InvalidArgument => StatusCode::BAD_REQUEST,
|
||||
RpcErrorCode::DeadlineExceeded => StatusCode::REQUEST_TIMEOUT,
|
||||
RpcErrorCode::NotFound => StatusCode::NOT_FOUND,
|
||||
RpcErrorCode::AlreadyExists => StatusCode::CONFLICT,
|
||||
RpcErrorCode::PermissionDenied => StatusCode::FORBIDDEN,
|
||||
RpcErrorCode::ResourceExhausted => StatusCode::TOO_MANY_REQUESTS,
|
||||
RpcErrorCode::FailedPrecondition => StatusCode::PRECONDITION_FAILED,
|
||||
RpcErrorCode::Aborted => StatusCode::CONFLICT,
|
||||
RpcErrorCode::OutOfRange => StatusCode::BAD_REQUEST,
|
||||
RpcErrorCode::Unimplemented => StatusCode::NOT_FOUND,
|
||||
RpcErrorCode::Internal => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
RpcErrorCode::Unavailable => StatusCode::SERVICE_UNAVAILABLE,
|
||||
RpcErrorCode::DataLoss => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
RpcErrorCode::Unauthenticated => StatusCode::UNAUTHORIZED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RpcIntoResponse<T> for RpcErrorCode
|
||||
where
|
||||
T: MessageFull,
|
||||
{
|
||||
fn rpc_into_response(self) -> RpcResponse<T> {
|
||||
RpcResponse {
|
||||
response: Err(RpcError::new(self, "".to_string())),
|
||||
parts: Response::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RpcIntoResponse<T> for RpcError
|
||||
where
|
||||
T: MessageFull,
|
||||
{
|
||||
fn rpc_into_response(self) -> RpcResponse<T> {
|
||||
RpcResponse {
|
||||
response: Err(self),
|
||||
parts: Response::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for RpcError {
|
||||
fn into_response(self) -> Response {
|
||||
let status_code = StatusCode::from(self.code.clone());
|
||||
let json = serde_json::to_string(&self).expect("serialize error type");
|
||||
(status_code, json).into_response()
|
||||
}
|
||||
}
|
154
axum-connect/src/handler.rs
Normal file
154
axum-connect/src/handler.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use std::pin::Pin;
|
||||
|
||||
use axum::{body::HttpBody, extract::FromRequest, http::Request, BoxError};
|
||||
use futures::Future;
|
||||
use protobuf::MessageFull;
|
||||
|
||||
pub use protobuf;
|
||||
pub use protobuf_json_mapping;
|
||||
|
||||
pub use crate::{error::RpcIntoError, parts::RpcFromRequestParts, response::RpcIntoResponse};
|
||||
use crate::{
|
||||
error::{RpcError, RpcErrorCode},
|
||||
prelude::RpcResponse,
|
||||
};
|
||||
|
||||
pub trait HandlerFuture<TReq, TRes, Res, T, S, B>: Clone + Send + Sized + 'static {
|
||||
type Future: Future<Output = RpcResponse<TRes>> + Send + 'static;
|
||||
|
||||
fn call(self, req: Request<B>, state: S) -> Self::Future;
|
||||
}
|
||||
|
||||
// #[allow(unused_parens, non_snake_case, unused_mut)]
|
||||
// impl<TReq, TRes, Res, F, Fut, S, B, T1> HandlerFuture<TReq, TRes, Res, (T1, TReq), S, B> for F
|
||||
// where
|
||||
// TReq: MessageFull + Send + 'static,
|
||||
// TRes: MessageFull + Send + 'static,
|
||||
// Res: RpcIntoResponse<TRes>,
|
||||
// F: FnOnce(T1, TReq) -> Fut + Clone + Send + 'static,
|
||||
// Fut: Future<Output = Res> + Send,
|
||||
// B: HttpBody + Send + 'static,
|
||||
// B::Data: Send,
|
||||
// B::Error: Into<BoxError>,
|
||||
// S: Send + Sync + 'static,
|
||||
// T1: RpcFromRequestParts<TRes, S> + Send,
|
||||
// {
|
||||
// type Future = Pin<Box<dyn Future<Output = RpcResponse<TRes>> + Send>>;
|
||||
|
||||
// fn call(self, req: Request<B>, state: S) -> Self::Future {
|
||||
// Box::pin(async move {
|
||||
// let (mut parts, body) = req.into_parts();
|
||||
// let state = &state;
|
||||
|
||||
// let t1 = match T1::rpc_from_request_parts(&mut parts, state).await {
|
||||
// Ok(value) => value,
|
||||
// Err(e) => return e.rpc_into_error().rpc_into_response(),
|
||||
// };
|
||||
|
||||
// let req = Request::from_parts(parts, body);
|
||||
|
||||
// let body = match String::from_request(req, state).await {
|
||||
// Ok(value) => value,
|
||||
// Err(e) => {
|
||||
// return RpcError::new(RpcErrorCode::FailedPrecondition, e.to_string())
|
||||
// .rpc_into_response()
|
||||
// }
|
||||
// };
|
||||
|
||||
// let proto_req: TReq = match protobuf_json_mapping::parse_from_str(&body) {
|
||||
// Ok(value) => value,
|
||||
// Err(_e) => {
|
||||
// return RpcError::new(
|
||||
// RpcErrorCode::InvalidArgument,
|
||||
// "Failed to parse request".to_string(),
|
||||
// )
|
||||
// .rpc_into_response()
|
||||
// }
|
||||
// };
|
||||
|
||||
// let res = self(t1, proto_req).await;
|
||||
|
||||
// res.rpc_into_response()
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
macro_rules! impl_handler {
|
||||
(
|
||||
[$($ty:ident),*]
|
||||
) => {
|
||||
#[allow(unused_parens, non_snake_case, unused_mut)]
|
||||
impl<TReq, TRes, Res, F, Fut, S, B, $($ty,)*>
|
||||
HandlerFuture<TReq, TRes, Res, ($($ty,)* TReq), S, B> for F
|
||||
where
|
||||
TReq: MessageFull + Send + 'static,
|
||||
TRes: MessageFull + Send + 'static,
|
||||
Res: RpcIntoResponse<TRes>,
|
||||
F: FnOnce($($ty,)* TReq) -> Fut + Clone + Send + 'static,
|
||||
Fut: Future<Output = Res> + Send,
|
||||
B: HttpBody + Send + 'static,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send + Sync + 'static,
|
||||
$( $ty: RpcFromRequestParts<TRes, S> + Send, )*
|
||||
{
|
||||
type Future = Pin<Box<dyn Future<Output = RpcResponse<TRes>> + Send>>;
|
||||
|
||||
fn call(self, req: Request<B>, state: S) -> Self::Future {
|
||||
Box::pin(async move {
|
||||
let (mut parts, body) = req.into_parts();
|
||||
let state = &state;
|
||||
|
||||
$(
|
||||
let $ty = match $ty::rpc_from_request_parts(&mut parts, state).await {
|
||||
Ok(value) => value,
|
||||
Err(e) => return e.rpc_into_error().rpc_into_response(),
|
||||
};
|
||||
)*
|
||||
|
||||
let req = Request::from_parts(parts, body);
|
||||
|
||||
let body = match String::from_request(req, state).await {
|
||||
Ok(value) => value,
|
||||
Err(e) => {
|
||||
return RpcError::new(RpcErrorCode::FailedPrecondition, e.to_string())
|
||||
.rpc_into_response()
|
||||
}
|
||||
};
|
||||
|
||||
let proto_req: TReq = match protobuf_json_mapping::parse_from_str(&body) {
|
||||
Ok(value) => value,
|
||||
Err(_e) => {
|
||||
return RpcError::new(
|
||||
RpcErrorCode::InvalidArgument,
|
||||
"Failed to parse request".to_string(),
|
||||
)
|
||||
.rpc_into_response()
|
||||
}
|
||||
};
|
||||
|
||||
let res = self($($ty,)* proto_req).await;
|
||||
|
||||
res.rpc_into_response()
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_handler!([]);
|
||||
impl_handler!([T1]);
|
||||
impl_handler!([T1, T2]);
|
||||
impl_handler!([T1, T2, T3]);
|
||||
impl_handler!([T1, T2, T3, T4]);
|
||||
impl_handler!([T1, T2, T3, T4, T5]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]);
|
|
@ -1,224 +1,16 @@
|
|||
use std::pin::Pin;
|
||||
|
||||
use axum::{
|
||||
body::{Body, HttpBody},
|
||||
extract::{FromRequest, FromRequestParts},
|
||||
http::{Request, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
BoxError, Router,
|
||||
};
|
||||
use futures::Future;
|
||||
use protobuf::MessageFull;
|
||||
use serde::Serialize;
|
||||
|
||||
// Re-export protobuf and protobuf_json_mapping for downstream use.
|
||||
pub use protobuf;
|
||||
pub use protobuf_json_mapping;
|
||||
|
||||
pub trait RpcRouterExt<S, B>: Sized {
|
||||
fn rpc<F>(self, register: F) -> Self
|
||||
where
|
||||
F: FnOnce(Self) -> RpcRouter<S, B>;
|
||||
pub mod error;
|
||||
pub mod handler;
|
||||
pub mod parts;
|
||||
pub mod response;
|
||||
pub mod router;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::error::*;
|
||||
pub use crate::parts::*;
|
||||
pub use crate::response::*;
|
||||
pub use crate::router::RpcRouterExt;
|
||||
}
|
||||
|
||||
impl<S, B> RpcRouterExt<S, B> for Router<S, B> {
|
||||
fn rpc<F>(self, register: F) -> Self
|
||||
where
|
||||
F: FnOnce(Self) -> RpcRouter<S, B>,
|
||||
{
|
||||
register(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub type RpcRouter<S, B> = Router<S, B>;
|
||||
|
||||
pub trait RegisterRpcService<S, B>: Sized {
|
||||
fn register(self, router: Router<S, B>) -> Self;
|
||||
}
|
||||
|
||||
pub trait IntoRpcResponse<T>
|
||||
where
|
||||
T: MessageFull,
|
||||
{
|
||||
fn into_response(self) -> Response;
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct RpcError {
|
||||
pub code: RpcErrorCode,
|
||||
pub message: String,
|
||||
pub details: Vec<RpcErrorDetail>,
|
||||
}
|
||||
|
||||
impl RpcError {
|
||||
pub fn new(code: RpcErrorCode, message: String) -> Self {
|
||||
Self {
|
||||
code,
|
||||
message,
|
||||
details: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct RpcErrorDetail {
|
||||
#[serde(rename = "type")]
|
||||
pub proto_type: String,
|
||||
#[serde(rename = "value")]
|
||||
pub proto_b62_value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RpcErrorCode {
|
||||
Canceled,
|
||||
Unknown,
|
||||
InvalidArgument,
|
||||
DeadlineExceeded,
|
||||
NotFound,
|
||||
AlreadyExists,
|
||||
PermissionDenied,
|
||||
ResourceExhausted,
|
||||
FailedPrecondition,
|
||||
Aborted,
|
||||
OutOfRange,
|
||||
Unimplemented,
|
||||
Internal,
|
||||
Unavailable,
|
||||
DataLoss,
|
||||
Unauthenticated,
|
||||
}
|
||||
|
||||
impl From<RpcErrorCode> for StatusCode {
|
||||
fn from(val: RpcErrorCode) -> Self {
|
||||
match val {
|
||||
// Spec: https://connect.build/docs/protocol/#error-codes
|
||||
RpcErrorCode::Canceled => StatusCode::REQUEST_TIMEOUT,
|
||||
RpcErrorCode::Unknown => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
RpcErrorCode::InvalidArgument => StatusCode::BAD_REQUEST,
|
||||
RpcErrorCode::DeadlineExceeded => StatusCode::REQUEST_TIMEOUT,
|
||||
RpcErrorCode::NotFound => StatusCode::NOT_FOUND,
|
||||
RpcErrorCode::AlreadyExists => StatusCode::CONFLICT,
|
||||
RpcErrorCode::PermissionDenied => StatusCode::FORBIDDEN,
|
||||
RpcErrorCode::ResourceExhausted => StatusCode::TOO_MANY_REQUESTS,
|
||||
RpcErrorCode::FailedPrecondition => StatusCode::PRECONDITION_FAILED,
|
||||
RpcErrorCode::Aborted => StatusCode::CONFLICT,
|
||||
RpcErrorCode::OutOfRange => StatusCode::BAD_REQUEST,
|
||||
RpcErrorCode::Unimplemented => StatusCode::NOT_FOUND,
|
||||
RpcErrorCode::Internal => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
RpcErrorCode::Unavailable => StatusCode::SERVICE_UNAVAILABLE,
|
||||
RpcErrorCode::DataLoss => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
RpcErrorCode::Unauthenticated => StatusCode::UNAUTHORIZED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for RpcError {
|
||||
fn into_response(self) -> Response {
|
||||
let status_code = StatusCode::from(self.code.clone());
|
||||
let json = serde_json::to_string(&self).expect("serialize error type");
|
||||
(status_code, json).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> IntoRpcResponse<T> for Result<T, E>
|
||||
where
|
||||
T: MessageFull,
|
||||
E: Into<RpcError>,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
Ok(res) => rpc_to_response(res),
|
||||
Err(err) => err.into().into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HandlerFuture<TReq, TRes, T, S, B = Body>: Clone + Send + Sized + 'static {
|
||||
type Future: Future<Output = TRes> + Send + 'static;
|
||||
|
||||
fn call(self, req: Request<B>, state: S) -> Self::Future;
|
||||
}
|
||||
|
||||
fn rpc_to_response<T>(res: T) -> Response
|
||||
where
|
||||
T: MessageFull,
|
||||
{
|
||||
protobuf_json_mapping::print_to_string(&res)
|
||||
.map_err(|_e| {
|
||||
RpcError::new(
|
||||
RpcErrorCode::Internal,
|
||||
"Failed to serialize response".to_string(),
|
||||
)
|
||||
})
|
||||
.into_response()
|
||||
}
|
||||
|
||||
macro_rules! impl_handler {
|
||||
(
|
||||
[$($ty:ident),*]
|
||||
) => {
|
||||
#[allow(unused_parens, non_snake_case, unused_mut)]
|
||||
impl<TReq, TRes, F, Fut, S, B, $($ty,)*> HandlerFuture<TReq, TRes, ($($ty,)* TReq), S, B> for F
|
||||
where
|
||||
TReq: MessageFull + Send + 'static,
|
||||
TRes: MessageFull + Send + 'static,
|
||||
F: FnOnce($($ty,)* TReq) -> Fut + Clone + Send + 'static,
|
||||
Fut: Future<Output = TRes> + Send,
|
||||
B: HttpBody + Send + 'static,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send + Sync + 'static,
|
||||
$( $ty: FromRequestParts<S> + Send, )*
|
||||
{
|
||||
type Future = Pin<Box<dyn Future<Output = TRes> + Send>>;
|
||||
|
||||
fn call(self, req: Request<B>, state: S) -> Self::Future {
|
||||
Box::pin(async move {
|
||||
let (mut parts, body) = req.into_parts();
|
||||
let state = &state;
|
||||
|
||||
// This would be done by macro expansion. It also wouldn't be unwrapped, but
|
||||
// there is no error union so I can't return a rejection.
|
||||
$(
|
||||
let $ty = match $ty::from_request_parts(&mut parts, state).await {
|
||||
Ok(value) => value,
|
||||
Err(_e) => unreachable!(),
|
||||
};
|
||||
)*
|
||||
|
||||
let req = Request::from_parts(parts, body);
|
||||
|
||||
let body = match String::from_request(req, state).await {
|
||||
Ok(value) => value,
|
||||
Err(_e) => unreachable!(),
|
||||
};
|
||||
|
||||
let proto_req: TReq = match protobuf_json_mapping::parse_from_str(&body) {
|
||||
Ok(value) => value,
|
||||
Err(_e) => unreachable!(),
|
||||
};
|
||||
|
||||
let res = self($($ty,)* proto_req).await;
|
||||
res
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_handler!([]);
|
||||
impl_handler!([T1]);
|
||||
impl_handler!([T1, T2]);
|
||||
impl_handler!([T1, T2, T3]);
|
||||
impl_handler!([T1, T2, T3, T4]);
|
||||
impl_handler!([T1, T2, T3, T4, T5]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]);
|
||||
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]);
|
||||
|
|
73
axum-connect/src/parts.rs
Normal file
73
axum-connect/src/parts.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use async_trait::async_trait;
|
||||
use axum::{
|
||||
extract::{FromRequestParts, Host, Query},
|
||||
http::{self},
|
||||
};
|
||||
use protobuf::MessageFull;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::error::{RpcError, RpcErrorCode, RpcIntoError};
|
||||
|
||||
#[async_trait]
|
||||
pub trait RpcFromRequestParts<T, S>: Sized
|
||||
where
|
||||
T: MessageFull,
|
||||
S: Send + Sync,
|
||||
{
|
||||
/// If the extractor fails it'll use this "rejection" type. A rejection is
|
||||
/// a kind of error that can be converted into a response.
|
||||
type Rejection: RpcIntoError;
|
||||
|
||||
/// Perform the extraction.
|
||||
async fn rpc_from_request_parts(
|
||||
parts: &mut http::request::Parts,
|
||||
state: &S,
|
||||
) -> Result<Self, Self::Rejection>;
|
||||
}
|
||||
|
||||
/// Macro to convert standard Axum `FromRequestParts` into `RpcFromRequestParts` by transforming
|
||||
/// their error type.
|
||||
macro_rules! impl_rpc_from_request_parts {
|
||||
($t:ident, $code:expr) => {
|
||||
#[async_trait]
|
||||
impl<M, S> RpcFromRequestParts<M, S> for $t
|
||||
where
|
||||
M: MessageFull,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = RpcError;
|
||||
|
||||
async fn rpc_from_request_parts(
|
||||
parts: &mut http::request::Parts,
|
||||
state: &S,
|
||||
) -> Result<Self, Self::Rejection> {
|
||||
Ok($t::from_request_parts(parts, state)
|
||||
.await
|
||||
.map_err(|e| ($code, e.to_string()).rpc_into_error())?)
|
||||
}
|
||||
}
|
||||
};
|
||||
([$($tin:ident),*], $t:ident, $code:expr) => {
|
||||
#[async_trait]
|
||||
impl<M, S, $($tin,)*> RpcFromRequestParts<M, S> for $t<$($tin,)*>
|
||||
where
|
||||
M: MessageFull,
|
||||
S: Send + Sync,
|
||||
$( $tin: DeserializeOwned, )*
|
||||
{
|
||||
type Rejection = RpcError;
|
||||
|
||||
async fn rpc_from_request_parts(
|
||||
parts: &mut http::request::Parts,
|
||||
state: &S,
|
||||
) -> Result<Self, Self::Rejection> {
|
||||
Ok($t::from_request_parts(parts, state)
|
||||
.await
|
||||
.map_err(|e| ($code, e.to_string()).rpc_into_error())?)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_rpc_from_request_parts!(Host, RpcErrorCode::Internal);
|
||||
impl_rpc_from_request_parts!([T], Query, RpcErrorCode::Internal);
|
67
axum-connect/src/response.rs
Normal file
67
axum-connect/src/response.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use axum::response::{IntoResponse, Response};
|
||||
use protobuf::MessageFull;
|
||||
|
||||
use crate::error::{RpcError, RpcErrorCode, RpcIntoError};
|
||||
|
||||
pub type RpcResult<T> = Result<T, RpcError>;
|
||||
|
||||
pub struct RpcResponse<T> {
|
||||
pub(crate) response: RpcResult<T>,
|
||||
pub(crate) parts: Response,
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for RpcResponse<T>
|
||||
where
|
||||
T: MessageFull,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let rpc_call_response: Response = {
|
||||
match self.response {
|
||||
Ok(value) => protobuf_json_mapping::print_to_string(&value)
|
||||
.map_err(|_e| {
|
||||
RpcError::new(
|
||||
RpcErrorCode::Internal,
|
||||
"Failed to serialize response".to_string(),
|
||||
)
|
||||
})
|
||||
.into_response(),
|
||||
Err(e) => e.into_response(),
|
||||
}
|
||||
};
|
||||
|
||||
let (parts, _) = self.parts.into_parts();
|
||||
(parts, rpc_call_response).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RpcIntoResponse<T>: Send + Sync + 'static
|
||||
where
|
||||
T: MessageFull,
|
||||
{
|
||||
fn rpc_into_response(self) -> RpcResponse<T>;
|
||||
}
|
||||
|
||||
impl<T> RpcIntoResponse<T> for T
|
||||
where
|
||||
T: MessageFull,
|
||||
{
|
||||
fn rpc_into_response(self) -> RpcResponse<T> {
|
||||
RpcResponse {
|
||||
response: Ok(self),
|
||||
parts: Response::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> RpcIntoResponse<T> for Result<T, E>
|
||||
where
|
||||
T: MessageFull,
|
||||
E: RpcIntoError + Send + Sync + 'static,
|
||||
{
|
||||
fn rpc_into_response(self) -> RpcResponse<T> {
|
||||
match self {
|
||||
Ok(res) => res.rpc_into_response(),
|
||||
Err(err) => err.rpc_into_error().rpc_into_response(),
|
||||
}
|
||||
}
|
||||
}
|
19
axum-connect/src/router.rs
Normal file
19
axum-connect/src/router.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use axum::Router;
|
||||
|
||||
pub trait RpcRouterExt<S, B>: Sized {
|
||||
fn rpc<F>(self, register: F) -> Self
|
||||
where
|
||||
F: FnOnce(Self) -> RpcRouter<S, B>;
|
||||
}
|
||||
|
||||
impl<S, B> RpcRouterExt<S, B> for Router<S, B> {
|
||||
fn rpc<F>(self, register: F) -> Self
|
||||
where
|
||||
F: FnOnce(Self) -> RpcRouter<S, B>,
|
||||
{
|
||||
register(self)
|
||||
// unsafe { std::mem::transmute::<RpcRouter<S, B>, Router<S, B>>(register(self)) }
|
||||
}
|
||||
}
|
||||
|
||||
pub type RpcRouter<S, B> = Router<S, B>;
|
Loading…
Reference in a new issue