zrx_stream/stream/function/traits/map.rs
1// Copyright (c) Zensical LLC <https://zensical.org>
2
3// SPDX-License-Identifier: MIT
4// Third-party contributions licensed under CLA
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Map function.
27
28use std::fmt::Display;
29use zrx_scheduler::action::report::IntoReport;
30use zrx_scheduler::action::Result;
31use zrx_scheduler::Value;
32
33use crate::stream::function::adapter::{WithId, WithSplat};
34use crate::stream::function::{catch, Splat};
35
36// ----------------------------------------------------------------------------
37// Traits
38// ----------------------------------------------------------------------------
39
40/// Map function.
41///
42/// This trait defines a function that transforms data of type `T` into data of
43/// type `U`. It's the most commonly used function in stream processing, as it
44/// is accepted by multiple operators. Note that transformations are expected
45/// to be immutable, meaning they can't capture mutable variables. This allows
46/// operators to move function execution to a [`Task`][], which is also why
47/// it expects owned data.
48///
49/// There's a range of different implementations of this trait, allowing you to
50/// use a variety of function shapes, including support for [`Splat`], as well
51/// as support for the [`WithId`] and [`WithSplat`] adapters. Furthermore, the
52/// trait can be implemented for custom types to add new behaviors. Note that
53/// all implementations also allow to return a [`Report`][], which makes it
54/// possible to return diagnostics from the function execution.
55///
56/// The `'static` lifetimes is mandatory as closures must be moved into actions,
57/// so requiring it here allows us to reduce the verbosity of trait bounds.
58///
59/// [`Report`]: zrx_scheduler::action::Report
60/// [`Task`]: zrx_scheduler::effect::Task
61///
62/// # Examples
63///
64/// Transform data:
65///
66/// ```
67/// # use std::error::Error;
68/// # fn main() -> Result<(), Box<dyn Error>> {
69/// use zrx_stream::function::MapFn;
70///
71/// // Define and execute function
72/// let f = |n: i32| n * n;
73/// f.execute(&"id", 42)?;
74/// # Ok(())
75/// # }
76/// ```
77///
78/// Transform data with splat argument:
79///
80/// ```
81/// # use std::error::Error;
82/// # fn main() -> Result<(), Box<dyn Error>> {
83/// use zrx_stream::function::{MapFn, Splat};
84///
85/// // Define and execute function
86/// let f = |a: i32, b: i32| a + b;
87/// f.execute(&"id", Splat::from((1, 2)))?;
88/// # Ok(())
89/// # }
90/// ```
91pub trait MapFn<I, T, U>: Send + 'static {
92 /// Executes the map function.
93 ///
94 /// # Errors
95 ///
96 /// This method returns an error if the function fails to execute.
97 fn execute(&self, id: &I, data: T) -> Result<U>;
98}
99
100// ----------------------------------------------------------------------------
101// Blanket implementations
102// ----------------------------------------------------------------------------
103
104impl<F, R, I, T, U> MapFn<I, T, U> for F
105where
106 F: Fn(T) -> R + Send + 'static,
107 R: IntoReport<U>,
108 I: Display,
109 T: Value,
110{
111 #[cfg_attr(
112 feature = "tracing",
113 tracing::instrument(level = "debug", skip_all, fields(id = %id))
114 )]
115 #[inline]
116 fn execute(&self, id: &I, data: T) -> Result<U> {
117 catch(|| self(data).into_report())
118 }
119}
120
121impl<F, R, I, T, U> MapFn<I, T, U> for WithId<F>
122where
123 F: Fn(&I, T) -> R + Send + 'static,
124 R: IntoReport<U>,
125 I: Display,
126 T: Value,
127{
128 #[cfg_attr(
129 feature = "tracing",
130 tracing::instrument(level = "debug", skip_all, fields(id = %id))
131 )]
132 #[inline]
133 fn execute(&self, id: &I, data: T) -> Result<U> {
134 catch(|| self(id, data).into_report())
135 }
136}
137
138impl<F, I, T, U> MapFn<I, T, U> for WithSplat<F>
139where
140 F: MapFn<I, Splat<T>, U>,
141{
142 #[inline]
143 fn execute(&self, id: &I, data: T) -> Result<U> {
144 F::execute(self, id, Splat::from(data))
145 }
146}
147
148// ----------------------------------------------------------------------------
149// Macros
150// ----------------------------------------------------------------------------
151
152/// Implements map function trait for splat arguments.
153macro_rules! impl_map_fn_for_splat {
154 ($($T:ident),+) => {
155 impl<F, R, I, $($T,)+ U> MapFn<I, Splat<($($T,)+)>, U> for F
156 where
157 F: Fn($($T),+) -> R + Send + 'static,
158 R: IntoReport<U>,
159 I: Display,
160 {
161 #[cfg_attr(
162 feature = "tracing",
163 tracing::instrument(level = "debug", skip_all, fields(id = %id))
164 )]
165 #[inline]
166 fn execute(
167 &self, id: &I, data: Splat<($($T,)+)>
168 ) -> Result<U> {
169 #[allow(non_snake_case)]
170 let ($($T,)+) = data.into_inner();
171 catch(|| self($($T),+).into_report())
172 }
173 }
174 };
175}
176
177// ----------------------------------------------------------------------------
178
179impl_map_fn_for_splat!(T1);
180impl_map_fn_for_splat!(T1, T2);
181impl_map_fn_for_splat!(T1, T2, T3);
182impl_map_fn_for_splat!(T1, T2, T3, T4);
183impl_map_fn_for_splat!(T1, T2, T3, T4, T5);
184impl_map_fn_for_splat!(T1, T2, T3, T4, T5, T6);
185impl_map_fn_for_splat!(T1, T2, T3, T4, T5, T6, T7);
186impl_map_fn_for_splat!(T1, T2, T3, T4, T5, T6, T7, T8);