use std::{collections::BTreeMap, marker::PhantomData, sync::Arc};
use rama::{Context, Service, service::BoxService};
use tansu_sans_io::{
ApiKey, ApiVersionsRequest, ApiVersionsResponse, Body, ErrorCode, Frame, Header,
RootMessageMeta, api_versions_response::ApiVersion,
};
use crate::Error;
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ApiVersionsService<E> {
supported: Vec<i16>,
error: PhantomData<E>,
}
impl<State, E> Service<State, ApiVersionsRequest> for ApiVersionsService<E>
where
State: Clone + Send + Sync + 'static,
E: std::error::Error + Send + Sync + 'static,
{
type Response = ApiVersionsResponse;
type Error = E;
async fn serve(
&self,
_ctx: Context<State>,
_req: ApiVersionsRequest,
) -> Result<Self::Response, Self::Error> {
Ok::<_, E>(
ApiVersionsResponse::default()
.finalized_features(Some([].into()))
.finalized_features_epoch(Some(-1))
.supported_features(Some([].into()))
.zk_migration_ready(Some(false))
.error_code(ErrorCode::None.into())
.api_keys(Some(
RootMessageMeta::messages()
.requests()
.iter()
.filter(|(api_key, _)| self.supported.contains(api_key))
.map(|(_, meta)| {
ApiVersion::default()
.api_key(meta.api_key)
.min_version(meta.version.valid.start)
.max_version(meta.version.valid.end)
})
.collect(),
))
.throttle_time_ms(Some(0)),
)
}
}
impl<State, E> Service<State, Body> for ApiVersionsService<E>
where
State: Clone + Send + Sync + 'static,
E: std::error::Error + From<tansu_sans_io::Error> + Send + Sync + 'static,
{
type Response = Body;
type Error = E;
async fn serve(&self, ctx: Context<State>, req: Body) -> Result<Self::Response, Self::Error> {
let req = ApiVersionsRequest::try_from(req)?;
self.serve(ctx, req).await.map(Into::into)
}
}
impl<State, E> Service<State, Frame> for ApiVersionsService<E>
where
State: Clone + Send + Sync + 'static,
E: std::error::Error + From<tansu_sans_io::Error> + Send + Sync + 'static,
{
type Response = Frame;
type Error = E;
async fn serve(&self, ctx: Context<State>, req: Frame) -> Result<Self::Response, Self::Error> {
let correlation_id = req.correlation_id()?;
self.serve(ctx, req.body).await.map(|body| Frame {
size: 0,
header: Header::Response { correlation_id },
body,
})
}
}
#[derive(Clone, Debug, Default)]
pub struct FrameRouteService<State = (), E = Error> {
routes: Arc<BTreeMap<i16, BoxService<State, Frame, Frame, E>>>,
}
impl<State, E> FrameRouteService<State, E>
where
State: Clone + Send + Sync + 'static,
E: std::error::Error + From<tansu_sans_io::Error> + From<Error> + Send + Sync + 'static,
{
pub fn new(routes: Arc<BTreeMap<i16, BoxService<State, Frame, Frame, E>>>) -> Self {
Self { routes }
}
pub fn builder() -> FrameRouteBuilder<State, E> {
FrameRouteBuilder::<State, E>::new()
}
}
impl<State, E> Service<State, Frame> for FrameRouteService<State, E>
where
State: Clone + Send + Sync + 'static,
E: std::error::Error + From<tansu_sans_io::Error> + From<Error> + Send + Sync + 'static,
{
type Response = Frame;
type Error = E;
async fn serve(&self, ctx: Context<State>, req: Frame) -> Result<Self::Response, Self::Error> {
let api_key = req.api_key()?;
if let Some(service) = self.routes.get(&api_key) {
service.serve(ctx, req).await
} else {
Err(E::from(Error::UnknownServiceFrame(Box::new(req))))
}
}
}
#[derive(Debug)]
pub struct FrameRouteBuilder<State, E> {
routes: BTreeMap<i16, BoxService<State, Frame, Frame, E>>,
}
impl<State, E> FrameRouteBuilder<State, E>
where
State: Clone + Send + Sync + 'static,
E: std::error::Error + From<tansu_sans_io::Error> + Send + Sync + 'static,
{
fn new() -> Self {
Self {
routes: BTreeMap::new(),
}
}
pub fn with_service<S>(self, service: S) -> Result<Self, Error>
where
S: Into<BoxService<State, Frame, Frame, E>> + ApiKey,
{
self.with_route(S::KEY, service.into())
}
pub fn with_route(
mut self,
api_key: i16,
service: BoxService<State, Frame, Frame, E>,
) -> Result<Self, Error> {
self.routes
.insert(api_key, service)
.map_or(Ok(self), |_existing| Err(Error::DuplicateRoute(api_key)))
}
pub fn build(self) -> Result<FrameRouteService<State, E>, Error> {
let api_key = ApiVersionsRequest::KEY;
let mut supported = self.routes.keys().copied().collect::<Vec<_>>();
supported.push(api_key);
self.with_route(
api_key,
ApiVersionsService {
supported,
error: PhantomData,
}
.boxed(),
)
.map(|builder| FrameRouteService {
routes: Arc::new(builder.routes),
})
}
}