bothan_lib/registry/
post_processor.rs

1//! Post-processing transformations for signal values.
2//!
3//! This module provides post-processors that apply transformations to signal values
4//! after they have been processed from source data. Post-processors can be used to
5//! adjust, normalize, or otherwise transform the values produced by the main processor.
6//!
7//! The module provides:
8//!
9//! - The [`PostProcessor`] enum which represents different post-processing strategies
10//! - Specialized post-processor implementations in submodules
11//! - Error handling for the post-processing operations
12//!
13//! # Available Post-Processors
14//!
15//! The following post-processing strategies are available:
16//!
17//! - [`TickConvertor`](tick::TickPostProcessor) - Converts values to specific tick sizes
18//!
19//! # Extensibility
20//!
21//! This module is designed to be extensible. New post-processing strategies can be added by:
22//!
23//! 1. Creating a new post-processor implementation in a submodule
24//! 2. Adding a new variant to the [`PostProcessor`] enum
25//! 3. Implementing the necessary logic in the `name` and `post_process` methods
26//!
27//! # Usage in Registry
28//!
29//! Post-processors are used within the registry system to define how signal values
30//! should be transformed after the main processing step. Each signal can specify
31//! multiple post-processors that are applied in sequence.
32
33use bincode::{Decode, Encode};
34use rust_decimal::Decimal;
35use serde::{Deserialize, Serialize};
36use thiserror::Error;
37
38pub mod tick;
39
40/// Error type for post-processor operations.
41///
42/// This error is returned when a post-processor encounters an issue while
43/// transforming a value, such as invalid input or mathematical errors.
44///
45/// # Examples
46///
47/// ```
48/// use bothan_lib::registry::post_processor::PostProcessError;
49///
50/// let error = PostProcessError::new("Invalid tick size");
51/// assert_eq!(error.to_string(), "Invalid tick size");
52/// ```
53#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)]
54#[error("{msg}")]
55pub struct PostProcessError {
56    msg: String,
57}
58
59impl PostProcessError {
60    /// Creates a new PostProcessError with the specified message.
61    pub fn new<T: Into<String>>(msg: T) -> Self {
62        PostProcessError { msg: msg.into() }
63    }
64}
65
66/// Represents different strategies for post-processing signal values.
67///
68/// The `PostProcessor` enum encapsulates different algorithms for transforming
69/// signal values after they have been processed from source data. Each variant
70/// contains its own configuration parameters.
71///
72/// # Variants
73///
74/// * `TickConvertor` - Converts values to specific tick sizes
75///
76/// # Examples
77///
78/// Creating a tick convertor post-processor:
79///
80/// ```
81/// use bothan_lib::registry::post_processor::{PostProcessor, tick::TickPostProcessor};
82/// use serde_json::json;
83///
84/// // Create a post-processor from JSON
85/// let json_data = json!({
86///     "function": "tick_convertor",
87///     "params": {}
88/// });
89///
90/// let post_processor: PostProcessor = serde_json::from_value(json_data).unwrap();
91/// assert_eq!(post_processor.name(), "tick_convertor");
92/// ```
93///
94/// Using a post-processor:
95///
96/// ```
97/// use bothan_lib::registry::post_processor::{PostProcessor, tick::TickPostProcessor};
98/// use rust_decimal::Decimal;
99///
100/// // Create a tick convertor post-processor
101/// let post_processor = PostProcessor::TickConvertor(TickPostProcessor {});
102///
103/// // Apply the post-processor to a value
104/// let value = Decimal::new(1234, 0);
105/// let result = post_processor.post_process(value).unwrap();
106/// ```
107#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Encode, Decode)]
108#[serde(rename_all = "snake_case", tag = "function", content = "params")]
109pub enum PostProcessor {
110    /// Tick convertor that adjusts values to specific tick sizes.
111    TickConvertor(tick::TickPostProcessor),
112}
113
114impl PostProcessor {
115    /// Returns the name of the post-processor.
116    ///
117    /// This method returns a string identifier for the post-processor type,
118    /// which can be used for logging, debugging, or serialization purposes.
119    pub fn name(&self) -> &str {
120        match self {
121            PostProcessor::TickConvertor(_) => "tick_convertor",
122        }
123    }
124
125    /// Applies the post-processor to a value.
126    ///
127    /// This method applies the post-processor's transformation algorithm
128    /// to the input value to produce a transformed output value.
129    ///
130    /// # Errors
131    ///
132    /// Returns a `PostProcessError` if the transformation operation fails, such as when:
133    /// - The input value is invalid for the transformation
134    /// - A mathematical error occurs during transformation
135    pub fn post_process(&self, data: Decimal) -> Result<Decimal, PostProcessError> {
136        match self {
137            PostProcessor::TickConvertor(tick) => tick.process(data),
138        }
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use tick::TickPostProcessor;
145
146    use super::*;
147
148    #[test]
149    fn test_deserialize() {
150        let json_str = r#"{ "function": "tick_convertor", "params": { } }"#;
151        let expected_function = PostProcessor::TickConvertor(TickPostProcessor {});
152
153        let deserialized = serde_json::from_str::<PostProcessor>(json_str);
154        assert_eq!(deserialized.unwrap(), expected_function);
155    }
156
157    #[test]
158    fn test_deserialize_with_invalid_parameter() {
159        let json_str = r#"{ "function": "median", "params": { "test": "Jesus" } }"#;
160
161        let deserialized = serde_json::from_str::<PostProcessor>(json_str);
162        assert!(deserialized.is_err());
163    }
164}