Add Into Req/Res traits and large cleanup.

This commit is contained in:
Alec Thilenius 2023-03-03 20:48:20 +00:00
parent c676f0ffe9
commit 673d79c73e
10 changed files with 489 additions and 235 deletions

View file

@ -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" }

View file

@ -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()
}),
)
}

View file

@ -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(),
})
}

View file

@ -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
View 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
View 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]);

View file

@ -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
View 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);

View 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(),
}
}
}

View 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>;