use crate::codec::{Codec, CodecError, CodecName};
use crate::namespace::Namespace;
use crate::utils::struct_name_of_type;
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::marker::PhantomData;
use std::sync::Arc;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Error)]
pub enum NeuronError {
#[error("Neuron encode error for {neuron_name}: {message}")]
Encode {
neuron_name: String,
message: String,
},
#[error("Neuron decode error for {neuron_name}: {message}")]
Decode {
neuron_name: String,
message: String,
},
}
pub trait Neuron<T, C>: Debug + Send + Sync + 'static
where
C: Codec<T> + CodecName + Send + Sync + 'static,
T: Send + Sync + 'static,
{
fn encode(&self, data: &T) -> Result<Vec<u8>, NeuronError>;
fn decode(&self, data: &[u8]) -> Result<T, NeuronError>;
fn name(&self) -> String;
fn name_without_codec(&self) -> String;
fn schema(&self) -> String;
fn clone_to_box(&self) -> Box<dyn Neuron<T, C> + Send + Sync + 'static>;
fn clone_to_arc(&self) -> Arc<dyn Neuron<T, C> + Send + Sync + 'static>;
}
pub struct NeuronImpl<T, C> {
pub namespace: Arc<dyn Namespace>,
pub schema: Option<String>,
_codec_marker: PhantomData<fn() -> &'static ()>,
_type: PhantomData<T>,
_phantom_codec: PhantomData<C>,
}
impl<T, C> Clone for NeuronImpl<T, C>
where
C: Codec<T> + CodecName,
{
fn clone(&self) -> Self {
NeuronImpl {
namespace: self.namespace.clone(),
schema: self.schema.clone(),
_codec_marker: PhantomData,
_type: PhantomData,
_phantom_codec: PhantomData,
}
}
}
impl<T, C> NeuronImpl<T, C>
where
C: Codec<T> + CodecName + Send + Sync + 'static,
T: Send + Sync + 'static,
{
#[must_use]
pub fn new(ns: Arc<dyn Namespace>) -> NeuronImpl<T, C> {
NeuronImpl {
namespace: ns,
schema: None,
_codec_marker: PhantomData,
_type: PhantomData,
_phantom_codec: PhantomData,
}
}
#[must_use]
pub fn new_arc(ns: Arc<dyn Namespace>) -> Arc<Self> {
Arc::new(Self::new(ns))
}
#[must_use]
pub fn with_schema(mut self, schema: impl Into<String>) -> Self {
self.schema = Some(schema.into());
self
}
}
impl<T, C> Neuron<T, C> for NeuronImpl<T, C>
where
C: Codec<T> + CodecName + Send + Sync + 'static,
T: Send + Sync + 'static,
Self: Send + Sync + 'static,
{
fn encode(&self, data: &T) -> Result<Vec<u8>, NeuronError> {
C::encode(data).map_err(|err| match err {
CodecError::Encode(message) => NeuronError::Encode {
neuron_name: self.name(),
message,
},
CodecError::Decode(message) => NeuronError::Decode {
neuron_name: self.name(),
message,
},
})
}
fn decode(&self, data: &[u8]) -> Result<T, NeuronError> {
C::decode(data).map_err(|err| match err {
CodecError::Encode(message) => NeuronError::Encode {
neuron_name: self.name(),
message,
},
CodecError::Decode(message) => NeuronError::Decode {
neuron_name: self.name(),
message,
},
})
}
fn name(&self) -> String {
self.namespace
.with_suffix(vec![struct_name_of_type::<T>(), C::name()])
}
fn name_without_codec(&self) -> String {
self.namespace.with_suffix(vec![struct_name_of_type::<T>()])
}
fn schema(&self) -> String {
self.schema.clone().unwrap_or_default()
}
fn clone_to_box(&self) -> Box<dyn Neuron<T, C> + Send + Sync + 'static> {
Box::new(self.clone())
}
fn clone_to_arc(&self) -> Arc<dyn Neuron<T, C> + Send + Sync + 'static> {
Arc::new(self.clone())
}
}
impl<T, C> Debug for NeuronImpl<T, C>
where
C: Codec<T> + CodecName,
{
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_struct("NeuronImpl")
.field("namespace", &self.namespace.path())
.field("codec_name", &C::name())
.field("type", &struct_name_of_type::<T>())
.finish()
}
}
impl<T, C> Display for NeuronImpl<T, C>
where
C: Codec<T> + CodecName + Send + Sync + 'static,
T: Send + Sync + 'static,
{
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{DebugCodec, DebugStruct, test_namespace};
#[test]
fn test_neuron() {
let d = DebugStruct {
foo: 42,
bar: "spanish inquisition!".to_string(),
};
let ns = test_namespace();
let neuron: NeuronImpl<DebugStruct, DebugCodec> = NeuronImpl::new(ns.clone());
assert_eq!(neuron.name(), "dev.plexo.DebugStruct.debug");
assert_eq!(neuron.name_without_codec(), "dev.plexo.DebugStruct");
let encoded = neuron.encode(&d).expect("Encoding should succeed in test");
assert_eq!(
String::from_utf8_lossy(&encoded),
r#"DebugStruct { foo: 42, bar: "spanish inquisition!" }"#
);
let decoded = neuron
.decode(&encoded)
.expect("Decoding should succeed in test");
assert_eq!(decoded.foo, d.foo);
assert_eq!(decoded.bar, d.bar);
let boxed_neuron = neuron.clone_to_box();
assert_eq!(boxed_neuron.name(), "dev.plexo.DebugStruct.debug");
let arced_neuron = neuron.clone_to_arc();
assert_eq!(arced_neuron.name(), "dev.plexo.DebugStruct.debug");
}
#[test]
fn test_neuron_display() {
let ns = test_namespace();
let neuron = NeuronImpl::<DebugStruct, DebugCodec>::new(ns);
assert_eq!(format!("{neuron}"), "dev.plexo.DebugStruct.debug");
}
}