bothan_lib/registry/
source.rs

1// ! Source definitions and routing operations for signal computation.
2//!
3//! This module provides structures for defining data sources and routing operations
4//! that transform source data before it is processed. It enables the flexible
5//! combination and transformation of data from various sources.
6//!
7//! The module provides:
8//!
9//! - The [`Operation`] enum which defines basic arithmetic operations
10//! - The [`OperationRoute`] struct which pairs operations with dependent signals
11//! - The [`SourceQuery`] struct which specifies where to obtain input data
12//!
13//! # Source Routing
14//!
15//! Sources can be configured with routes that apply transformations using values
16//! from other signals. This enables complex relationships between signals, such as:
17//!
18//! - Converting between different units (e.g., USDT to USD)
19//! - Adjusting values using scaling factors
20//! - Applying corrections based on other market data
21//!
22//! Routing operations are applied sequentially, with each operation using the
23//! result of the previous operation as its first operand.
24
25use bincode::{Decode, Encode};
26use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub};
27use serde::{Deserialize, Serialize};
28
29/// Arithmetic operations that can be applied in routing transformations.
30///
31/// The `Operation` enum represents the four basic arithmetic operations that
32/// can be used to transform values during the routing process. Each operation
33/// is designed to safely handle potential numerical errors using checked operations.
34///
35/// # Variants
36///
37/// * `Add` - Addition operation (+)
38/// * `Subtract` - Subtraction operation (-)
39/// * `Multiply` - Multiplication operation (*)
40/// * `Divide` - Division operation (/)
41///
42/// # Examples
43///
44/// ```
45/// use bothan_lib::registry::source::Operation;
46/// use rust_decimal::Decimal;
47///
48/// // Create a multiplication operation
49/// let operation = Operation::Multiply;
50///
51/// // Apply the operation to two values
52/// let a = Decimal::new(10, 0);  // 10.0
53/// let b = Decimal::new(15, 0);  // 15.0
54/// let result = operation.execute(a, b).unwrap();
55///
56/// assert_eq!(result, Decimal::new(150, 0));  // 150.0
57/// ```
58#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Encode, Decode)]
59pub enum Operation {
60    /// Addition operation (+)
61    #[serde(rename = "+")]
62    Add,
63    /// Subtraction operation (-)
64    #[serde(rename = "-")]
65    Subtract,
66    /// Multiplication operation (*)
67    #[serde(rename = "*")]
68    Multiply,
69    /// Division operation (/)
70    #[serde(rename = "/")]
71    Divide,
72}
73
74impl Operation {
75    /// Executes the arithmetic operation on two values.
76    ///
77    /// This method applies the operation represented by the enum variant to the
78    /// provided operands. It uses checked operations to prevent numerical errors
79    /// such as overflow or division by zero.
80    ///
81    /// # Examples
82    ///
83    /// ```
84    /// use bothan_lib::registry::source::Operation;
85    ///
86    /// let add_op = Operation::Add;
87    /// assert_eq!(add_op.execute(5, 3), Some(8));
88    ///
89    /// let div_op = Operation::Divide;
90    /// assert_eq!(div_op.execute(10, 2), Some(5));
91    /// assert_eq!(div_op.execute(10, 0), None);  // Division by zero returns None
92    /// ```
93    pub fn execute<T>(&self, a: T, b: T) -> Option<T>
94    where
95        T: CheckedAdd + CheckedSub + CheckedMul + CheckedDiv + Copy,
96    {
97        match self {
98            Operation::Add => a.checked_add(&b),
99            Operation::Subtract => a.checked_sub(&b),
100            Operation::Multiply => a.checked_mul(&b),
101            Operation::Divide => a.checked_div(&b),
102        }
103    }
104}
105
106/// A transformation that applies an operation using a value from another signal.
107///
108/// The `OperationRoute` struct defines a step in a routing transformation chain.
109/// It specifies a signal whose value should be used as the second operand in
110/// an operation, with the first operand being either the original source value
111/// or the result of previous routing operations.
112///
113/// Routes are applied sequentially, allowing for complex transformations to be
114/// built from simple arithmetic operations.
115///
116/// # Examples
117///
118/// ```
119/// use bothan_lib::registry::source::{OperationRoute, Operation};
120///
121/// // Create a route that multiplies by the "USDT-USD" signal's value
122/// let route = OperationRoute::new("USDT-USD", Operation::Multiply);
123/// ```
124#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Encode, Decode)]
125pub struct OperationRoute {
126    /// The identifier of the signal whose value will be used in the operation.
127    ///
128    /// When this route is applied, the system will look up the current value
129    /// of the signal with this ID and use it as the second operand in the operation.
130    pub signal_id: String,
131
132    /// The arithmetic operation to apply.
133    ///
134    /// This defines how the current value will be combined with the value
135    /// from the referenced signal.
136    pub operation: Operation,
137}
138
139impl OperationRoute {
140    /// Creates a new operation route.
141    ///
142    /// This constructor creates an operation route that will apply the specified
143    /// operation using the value of the referenced signal.
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// use bothan_lib::registry::source::{OperationRoute, Operation};
149    ///
150    /// // Create a route that divides by the "USD-EUR" signal's value
151    /// let route = OperationRoute::new("USD-EUR", Operation::Divide);
152    /// ```
153    pub fn new<T: Into<String>>(signal_id: T, operation: Operation) -> Self {
154        OperationRoute {
155            signal_id: signal_id.into(),
156            operation,
157        }
158    }
159}
160
161/// A specification for retrieving and transforming data from a source.
162///
163/// The `SourceQuery` struct defines where to obtain data and how to transform it
164/// before processing. It specifies a data source, an identifier to query within
165/// that source, and optionally a series of routing operations to apply to the
166/// retrieved value.
167///
168/// This structure is a key component of signal definitions, allowing each signal
169/// to combine and transform data from multiple sources.
170///
171/// # Examples
172///
173/// ```
174/// use bothan_lib::registry::source::{SourceQuery, OperationRoute, Operation};
175///
176/// // Create a query for BTC/USDT from Binance, converted to USD
177/// let query = SourceQuery::new(
178///     "binance",
179///     "btcusdt",
180///     vec![
181///         // Convert from USDT to USD using the USDT-USD signal
182///         OperationRoute::new("USDT-USD", Operation::Multiply),
183///     ]
184/// );
185/// ```
186#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Encode, Decode)]
187pub struct SourceQuery {
188    /// The identifier of the data source.
189    ///
190    /// This corresponds to a registered worker that can provide data for this query.
191    /// Examples might include exchange names like "binance" or "coinbase".
192    pub source_id: String,
193
194    /// The query identifier to use within the data source.
195    ///
196    /// This specifies what data to retrieve from the source, such as a trading pair
197    /// like "btcusdt" or an asset identifier like "bitcoin".
198    #[serde(rename = "id")]
199    pub query_id: String,
200
201    /// Routing operations to apply to the retrieved value.
202    ///
203    /// These operations are applied sequentially to transform the source value
204    /// before it is sent to the processor. If not provided, no transformations
205    /// will be applied.
206    #[serde(default)]
207    #[serde(skip_serializing_if = "Vec::is_empty")]
208    pub routes: Vec<OperationRoute>,
209}
210
211impl SourceQuery {
212    /// Creates a new source query.
213    ///
214    /// This constructor creates a source query that will retrieve data from the
215    /// specified source and apply the provided routing operations.
216    ///
217    /// # Examples
218    ///
219    /// ```
220    /// use bothan_lib::registry::source::{SourceQuery, OperationRoute, Operation};
221    ///
222    /// // Create a query for ETH/USD from Coinbase with no transformations
223    /// let direct_query = SourceQuery::new("coinbase", "ETH-USD", vec![]);
224    ///
225    /// // Create a query for ETH/BTC from Kraken, converted to USD
226    /// let converted_query = SourceQuery::new(
227    ///     "kraken",
228    ///     "ethbtc",
229    ///     vec![
230    ///         // Convert from BTC to USD using the BTC-USD signal
231    ///         OperationRoute::new("BTC-USD", Operation::Multiply),
232    ///     ]
233    /// );
234    /// ```
235    pub fn new<T, U>(source_id: T, query_id: U, routes: Vec<OperationRoute>) -> Self
236    where
237        T: Into<String>,
238        U: Into<String>,
239    {
240        SourceQuery {
241            source_id: source_id.into(),
242            query_id: query_id.into(),
243            routes,
244        }
245    }
246}