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]
94 pub fn new(ns: Arc<dyn Namespace>) -> NeuronImpl<T, C> {
95 NeuronImpl {
96 namespace: ns,
97 schema: None,
98 _codec_marker: PhantomData,
99 _type: PhantomData,
100 _phantom_codec: PhantomData,
101 }
102 }
103
104 #[must_use]
106 pub fn new_arc(ns: Arc<dyn Namespace>) -> Arc<Self> {
107 Arc::new(Self::new(ns))
108 }
109
110 #[must_use]
111 pub fn with_schema(mut self, schema: impl Into<String>) -> Self {
112 self.schema = Some(schema.into());
113 self
114 }
115}
116
117impl<T, C> Neuron<T, C> for NeuronImpl<T, C>
118where
119 C: Codec<T> + CodecName + Send + Sync + 'static,
120 T: Send + Sync + 'static,
121 Self: Send + Sync + 'static,
122{
123 fn encode(&self, data: &T) -> Result<Vec<u8>, NeuronError> {
124 C::encode(data).map_err(|err| match err {
125 CodecError::Encode(message) => NeuronError::Encode {
126 neuron_name: self.name(),
127 message,
128 },
129 CodecError::Decode(message) => NeuronError::Decode {
130 neuron_name: self.name(),
131 message,
132 },
133 })
134 }
135
136 fn decode(&self, data: &[u8]) -> Result<T, NeuronError> {
137 C::decode(data).map_err(|err| match err {
138 CodecError::Encode(message) => NeuronError::Encode {
139 neuron_name: self.name(),
140 message,
141 },
142 CodecError::Decode(message) => NeuronError::Decode {
143 neuron_name: self.name(),
144 message,
145 },
146 })
147 }
148
149 fn name(&self) -> String {
150 self.namespace
151 .with_suffix(vec![struct_name_of_type::<T>(), C::name()])
152 }
153
154 fn name_without_codec(&self) -> String {
155 self.namespace.with_suffix(vec![struct_name_of_type::<T>()])
156 }
157
158 fn schema(&self) -> String {
159 self.schema.clone().unwrap_or_default()
160 }
161
162 fn clone_to_box(&self) -> Box<dyn Neuron<T, C> + Send + Sync + 'static> {
163 Box::new(self.clone())
164 }
165
166 fn clone_to_arc(&self) -> Arc<dyn Neuron<T, C> + Send + Sync + 'static> {
167 Arc::new(self.clone())
168 }
169}
170
171impl<T, C> Debug for NeuronImpl<T, C>
172where
173 C: Codec<T> + CodecName,
174{
175 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
176 f.debug_struct("NeuronImpl")
177 .field("namespace", &self.namespace.path())
178 .field("codec_name", &C::name())
179 .field("type", &struct_name_of_type::<T>())
180 .finish()
181 }
182}
183
184impl<T, C> Display for NeuronImpl<T, C>
185where
186 C: Codec<T> + CodecName + Send + Sync + 'static,
187 T: Send + Sync + 'static,
188{
189 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
190 write!(f, "{}", self.name())
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::test_utils::{DebugCodec, DebugStruct, test_namespace};
198
199 #[test]
200 fn test_neuron() {
201 let d = DebugStruct {
202 foo: 42,
203 bar: "spanish inquisition!".to_string(),
204 };
205
206 let ns = test_namespace();
207
208 let neuron: NeuronImpl<DebugStruct, DebugCodec> = NeuronImpl::new(ns.clone());
209 assert_eq!(neuron.name(), "dev.plexo.DebugStruct.debug");
210 assert_eq!(neuron.name_without_codec(), "dev.plexo.DebugStruct");
211
212 let encoded = neuron.encode(&d).expect("Encoding should succeed in test");
213 assert_eq!(
214 String::from_utf8_lossy(&encoded),
215 r#"DebugStruct { foo: 42, bar: "spanish inquisition!" }"#
216 );
217 let decoded = neuron
218 .decode(&encoded)
219 .expect("Decoding should succeed in test");
220 assert_eq!(decoded.foo, d.foo);
221 assert_eq!(decoded.bar, d.bar);
222
223 let boxed_neuron = neuron.clone_to_box();
224 assert_eq!(boxed_neuron.name(), "dev.plexo.DebugStruct.debug");
225
226 let arced_neuron = neuron.clone_to_arc();
227 assert_eq!(arced_neuron.name(), "dev.plexo.DebugStruct.debug");
228 }
229
230 #[test]
231 fn test_neuron_display() {
232 let ns = test_namespace();
233 let neuron = NeuronImpl::<DebugStruct, DebugCodec>::new(ns);
234 assert_eq!(format!("{neuron}"), "dev.plexo.DebugStruct.debug");
235 }
236}