Skip to main content

plexor_core/
neuron.rs

1// Copyright 2025 Alecks Gates
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use 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    /// Helper to create an Arc<NeuronImpl<T, C>>
83    #[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}