use std::collections::HashMap;
use std::sync::Arc;
use buffa::Message;
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::shared::error::{ConnectError, ConnectResult};
use super::{CodecFor, JsonCodec, ProtoCodec};
pub struct CodecEntry<Req, Res> {
name: String,
request: Arc<dyn CodecFor<Req>>,
response: Arc<dyn CodecFor<Res>>,
}
pub trait CodecSet<Req, Res> {
fn build(self) -> Vec<CodecEntry<Req, Res>>;
}
fn entry<Req, Res, C>(codec: C) -> CodecEntry<Req, Res>
where
Req: 'static,
Res: 'static,
C: CodecFor<Req> + CodecFor<Res>,
{
let shared = Arc::new(codec);
let request: Arc<dyn CodecFor<Req>> = shared.clone();
let response: Arc<dyn CodecFor<Res>> = shared;
CodecEntry {
name: request.name().to_string(),
request,
response,
}
}
macro_rules! impl_codec_set {
($($idx:tt : $ty:ident),+) => {
impl<Req, Res, $($ty),+> CodecSet<Req, Res> for ($($ty,)+)
where
Req: 'static,
Res: 'static,
$($ty: CodecFor<Req> + CodecFor<Res>,)+
{
fn build(self) -> Vec<CodecEntry<Req, Res>> {
vec![ $( entry(self.$idx) ),+ ]
}
}
};
}
impl_codec_set!(0: C0);
impl_codec_set!(0: C0, 1: C1);
impl_codec_set!(0: C0, 1: C1, 2: C2);
impl_codec_set!(0: C0, 1: C1, 2: C2, 3: C3);
impl_codec_set!(0: C0, 1: C1, 2: C2, 3: C3, 4: C4);
impl_codec_set!(0: C0, 1: C1, 2: C2, 3: C3, 4: C4, 5: C5);
impl_codec_set!(0: C0, 1: C1, 2: C2, 3: C3, 4: C4, 5: C5, 6: C6);
impl_codec_set!(0: C0, 1: C1, 2: C2, 3: C3, 4: C4, 5: C5, 6: C6, 7: C7);
pub struct ProcedureCodecs<Req, Res> {
request: HashMap<String, Arc<dyn CodecFor<Req>>>,
response: HashMap<String, Arc<dyn CodecFor<Res>>>,
names: Vec<String>,
}
impl<Req: 'static, Res: 'static> ProcedureCodecs<Req, Res> {
#[must_use]
pub fn of(set: impl CodecSet<Req, Res>) -> Self {
let mut codecs = Self {
request: HashMap::new(),
response: HashMap::new(),
names: Vec::new(),
};
for e in set.build() {
codecs.insert(e);
}
codecs
}
#[must_use]
pub fn only<C>(codec: C) -> Self
where
C: CodecFor<Req> + CodecFor<Res>,
{
Self::of((codec,))
}
#[must_use]
pub fn with_codec<C>(mut self, codec: C) -> Self
where
C: CodecFor<Req> + CodecFor<Res>,
{
self.insert(entry(codec));
self
}
fn insert(&mut self, e: CodecEntry<Req, Res>) {
if !self.request.contains_key(&e.name) {
self.names.push(e.name.clone());
}
self.request.insert(e.name.clone(), e.request);
self.response.insert(e.name, e.response);
}
pub fn for_request(&self, name: &str) -> ConnectResult<Arc<dyn CodecFor<Req>>> {
self.request
.get(name)
.cloned()
.ok_or_else(|| self.unsupported(name).into())
}
pub fn for_response(&self, name: &str) -> ConnectResult<Arc<dyn CodecFor<Res>>> {
self.response
.get(name)
.cloned()
.ok_or_else(|| self.unsupported(name).into())
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.names.iter().map(String::as_str)
}
#[must_use]
pub fn contains(&self, name: &str) -> bool {
self.request.contains_key(name)
}
#[must_use]
pub fn is_binary(&self, name: &str) -> Option<bool> {
self.request.get(name).map(|c| c.is_binary())
}
fn unsupported(&self, name: &str) -> ConnectError {
ConnectError::unimplemented(format!(
"unsupported codec {:?}, supported: [{}]",
name,
self.names.join(", ")
))
}
}
impl<Req, Res> ProcedureCodecs<Req, Res>
where
Req: Message + Serialize + DeserializeOwned + 'static,
Res: Message + Serialize + DeserializeOwned + 'static,
{
#[must_use]
pub fn defaults() -> Self {
Self::of((ProtoCodec, JsonCodec))
}
}