1use crate::codec::{Codec, CodecError, CodecName};
8use crate::namespace::Namespace;
9use crate::utils::struct_name_of_type;
10use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
11use std::marker::PhantomData;
12use std::sync::Arc;
13use thiserror::Error;
14
15#[derive(Debug, Clone, PartialEq, Error)]
16pub enum NeuronError {
17 #[error("Neuron encode error for {neuron_name}: {message}")]
18 Encode {
19 neuron_name: String,
20 message: String,
21 },
22 #[error("Neuron decode error for {neuron_name}: {message}")]
23 Decode {
24 neuron_name: String,
25 message: String,
26 },
27}
28
29pub trait Neuron<T, C>: Debug + Send + Sync + 'static
30where
31 C: Codec<T> + CodecName + Send + Sync + 'static,
32 T: Send + Sync + 'static,
33{
34 fn encode(&self, data: &T) -> Result<Vec<u8>, NeuronError>;
35 fn decode(&self, data: &[u8]) -> Result<T, NeuronError>;
36 fn name(&self) -> String;
37 fn name_without_codec(&self) -> String;
38 fn schema(&self) -> String;
39 fn clone_to_box(&self) -> Box<dyn Neuron<T, C> + Send + Sync + 'static>;
40 fn clone_to_arc(&self) -> Arc<dyn Neuron<T, C> + Send + Sync + 'static>;
41}
42
43pub struct NeuronImpl<T, C> {
44 pub namespace: Arc<dyn Namespace>,
45 pub schema: Option<String>,
46 _codec_marker: PhantomData<fn() -> &'static ()>,
47 _type: PhantomData<T>,
48 _phantom_codec: PhantomData<C>,
49}
50
51impl<T, C> Clone for NeuronImpl<T, C>
52where
53 C: Codec<T> + CodecName,
54{
55 fn clone(&self) -> Self {
56 NeuronImpl {
57 namespace: self.namespace.clone(),
58 schema: self.schema.clone(),
59 _codec_marker: PhantomData,
60 _type: PhantomData,
61 _phantom_codec: PhantomData,
62 }
63 }
64}
65
66impl<T, C> NeuronImpl<T, C>
67where
68 C: Codec<T> + CodecName + Send + Sync + 'static,
69 T: Send + Sync + 'static,
70{
71 #[must_use]
72 pub fn new(ns: Arc<dyn Namespace>) -> NeuronImpl<T, C> {
73 NeuronImpl {
74 namespace: ns,
75 schema: None,
76 _codec_marker: PhantomData,
77 _type: PhantomData,
78 _phantom_codec: PhantomData,
79 }
80 }
81
82 #[must_use]
84 pub fn new_arc(ns: Arc<dyn Namespace>) -> Arc<Self> {
85 Arc::new(Self::new(ns))
86 }
87
88 #[must_use]
89 pub fn with_schema(mut self, schema: impl Into<String>) -> Self {
90 self.schema = Some(schema.into());
91 self
92 }
93}
94
95impl<T, C> Neuron<T, C> for NeuronImpl<T, C>
96where
97 C: Codec<T> + CodecName + Send + Sync + 'static,
98 T: Send + Sync + 'static,
99 Self: Send + Sync + 'static,
100{
101 fn encode(&self, data: &T) -> Result<Vec<u8>, NeuronError> {
102 C::encode(data).map_err(|err| match err {
103 CodecError::Encode(message) => NeuronError::Encode {
104 neuron_name: self.name(),
105 message,
106 },
107 CodecError::Decode(message) => NeuronError::Decode {
108 neuron_name: self.name(),
109 message,
110 },
111 })
112 }
113
114 fn decode(&self, data: &[u8]) -> Result<T, NeuronError> {
115 C::decode(data).map_err(|err| match err {
116 CodecError::Encode(message) => NeuronError::Encode {
117 neuron_name: self.name(),
118 message,
119 },
120 CodecError::Decode(message) => NeuronError::Decode {
121 neuron_name: self.name(),
122 message,
123 },
124 })
125 }
126
127 fn name(&self) -> String {
128 self.namespace
129 .with_suffix(vec![struct_name_of_type::<T>(), C::name()])
130 }
131
132 fn name_without_codec(&self) -> String {
133 self.namespace.with_suffix(vec![struct_name_of_type::<T>()])
134 }
135
136 fn schema(&self) -> String {
137 self.schema.clone().unwrap_or_default()
138 }
139
140 fn clone_to_box(&self) -> Box<dyn Neuron<T, C> + Send + Sync + 'static> {
141 Box::new(self.clone())
142 }
143
144 fn clone_to_arc(&self) -> Arc<dyn Neuron<T, C> + Send + Sync + 'static> {
145 Arc::new(self.clone())
146 }
147}
148
149impl<T, C> Debug for NeuronImpl<T, C>
150where
151 C: Codec<T> + CodecName,
152{
153 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
154 f.debug_struct("NeuronImpl")
155 .field("namespace", &self.namespace.path())
156 .field("codec_name", &C::name())
157 .field("type", &struct_name_of_type::<T>())
158 .finish()
159 }
160}
161
162impl<T, C> Display for NeuronImpl<T, C>
163where
164 C: Codec<T> + CodecName + Send + Sync + 'static,
165 T: Send + Sync + 'static,
166{
167 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
168 write!(f, "{}", self.name())
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::test_utils::{DebugCodec, DebugStruct, test_namespace};
176
177 #[test]
178 fn test_neuron() {
179 let d = DebugStruct {
180 foo: 42,
181 bar: "spanish inquisition!".to_string(),
182 };
183
184 let ns = test_namespace();
185
186 let neuron: NeuronImpl<DebugStruct, DebugCodec> = NeuronImpl::new(ns.clone());
187 assert_eq!(neuron.name(), "dev.plexo.DebugStruct.debug");
188 assert_eq!(neuron.name_without_codec(), "dev.plexo.DebugStruct");
189
190 let encoded = neuron.encode(&d).expect("Encoding should succeed in test");
191 assert_eq!(
192 String::from_utf8_lossy(&encoded),
193 r#"DebugStruct { foo: 42, bar: "spanish inquisition!" }"#
194 );
195 let decoded = neuron
196 .decode(&encoded)
197 .expect("Decoding should succeed in test");
198 assert_eq!(decoded.foo, d.foo);
199 assert_eq!(decoded.bar, d.bar);
200
201 let boxed_neuron = neuron.clone_to_box();
202 assert_eq!(boxed_neuron.name(), "dev.plexo.DebugStruct.debug");
203
204 let arced_neuron = neuron.clone_to_arc();
205 assert_eq!(arced_neuron.name(), "dev.plexo.DebugStruct.debug");
206 }
207
208 #[test]
209 fn test_neuron_display() {
210 let ns = test_namespace();
211 let neuron = NeuronImpl::<DebugStruct, DebugCodec>::new(ns);
212 assert_eq!(format!("{neuron}"), "dev.plexo.DebugStruct.debug");
213 }
214}