Skip to main content

zrx_stream/stream/function/traits/
map.rs

1// Copyright (c) 2025-2026 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// All contributions are certified under the DCO
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;
29
30use zrx_scheduler::action::report::IntoReport;
31use zrx_scheduler::action::Result;
32use zrx_scheduler::Value;
33
34use crate::stream::function::adapter::{WithId, WithSplat};
35use crate::stream::function::{catch, Splat};
36
37// ----------------------------------------------------------------------------
38// Traits
39// ----------------------------------------------------------------------------
40
41/// Map function.
42///
43/// This trait defines a function that transforms data of type `T` into data of
44/// type `U`. It's the most commonly used function in stream processing, as it
45/// is accepted by multiple operators. Note that transformations are expected
46/// to be immutable, meaning they can't capture mutable variables. This allows
47/// operators to move function execution to a [`Task`][], which is also why
48/// it expects owned data.
49///
50/// There's a range of different implementations of this trait, allowing you to
51/// use a variety of function shapes, including support for [`Splat`], as well
52/// as support for the [`WithId`] and [`WithSplat`] adapters. Furthermore, the
53/// trait can be implemented for custom types to add new behaviors. Note that
54/// all implementations also allow to return a [`Report`][], which makes it
55/// possible to return diagnostics from the function execution.
56///
57/// The `'static` lifetimes is mandatory as closures must be moved into actions,
58/// so requiring it here allows us to reduce the verbosity of trait bounds.
59///
60/// [`Report`]: zrx_scheduler::action::Report
61/// [`Task`]: zrx_scheduler::effect::Task
62///
63/// # Examples
64///
65/// Transform data:
66///
67/// ```
68/// # use std::error::Error;
69/// # fn main() -> Result<(), Box<dyn Error>> {
70/// use zrx_stream::function::MapFn;
71///
72/// // Define and execute function
73/// let f = |n: i32| n * n;
74/// f.execute(&"id", 42)?;
75/// # Ok(())
76/// # }
77/// ```
78///
79/// Transform data with splat argument:
80///
81/// ```
82/// # use std::error::Error;
83/// # fn main() -> Result<(), Box<dyn Error>> {
84/// use zrx_stream::function::{MapFn, Splat};
85///
86/// // Define and execute function
87/// let f = |a: i32, b: i32| a + b;
88/// f.execute(&"id", Splat::from((1, 2)))?;
89/// # Ok(())
90/// # }
91/// ```
92pub trait MapFn<I, T, U>: Send + 'static {
93    /// Executes the map function.
94    ///
95    /// # Errors
96    ///
97    /// This method returns an error if the function fails to execute.
98    fn execute(&self, id: &I, data: T) -> Result<U>;
99}
100
101// ----------------------------------------------------------------------------
102// Blanket implementations
103// ----------------------------------------------------------------------------
104
105impl<F, R, I, T, U> MapFn<I, T, U> for F
106where
107    F: Fn(T) -> R + Send + 'static,
108    R: IntoReport<U>,
109    I: Display,
110    T: Value,
111{
112    #[cfg_attr(
113        feature = "tracing",
114        tracing::instrument(level = "debug", skip_all, fields(id = %id))
115    )]
116    #[inline]
117    fn execute(&self, id: &I, data: T) -> Result<U> {
118        catch(|| self(data).into_report())
119    }
120}
121
122impl<F, R, I, T, U> MapFn<I, T, U> for WithId<F>
123where
124    F: Fn(&I, T) -> R + Send + 'static,
125    R: IntoReport<U>,
126    I: Display,
127    T: Value,
128{
129    #[cfg_attr(
130        feature = "tracing",
131        tracing::instrument(level = "debug", skip_all, fields(id = %id))
132    )]
133    #[inline]
134    fn execute(&self, id: &I, data: T) -> Result<U> {
135        catch(|| self(id, data).into_report())
136    }
137}
138
139impl<F, I, T, U> MapFn<I, T, U> for WithSplat<F>
140where
141    F: MapFn<I, Splat<T>, U>,
142{
143    #[inline]
144    fn execute(&self, id: &I, data: T) -> Result<U> {
145        F::execute(self, id, Splat::from(data))
146    }
147}
148
149// ----------------------------------------------------------------------------
150// Macros
151// ----------------------------------------------------------------------------
152
153/// Implements map function trait for splat arguments.
154macro_rules! impl_map_fn_for_splat {
155    ($($T:ident),+) => {
156        impl<F, R, I, $($T,)+ U> MapFn<I, Splat<($($T,)+)>, U> for F
157        where
158            F: Fn($($T),+) -> R + Send + 'static,
159            R: IntoReport<U>,
160            I: Display,
161        {
162            #[cfg_attr(
163                feature = "tracing",
164                tracing::instrument(level = "debug", skip_all, fields(id = %id))
165            )]
166            #[inline]
167            fn execute(
168                &self, id: &I, data: Splat<($($T,)+)>
169            ) -> Result<U> {
170                #[allow(non_snake_case)]
171                let ($($T,)+) = data.into_inner();
172                catch(|| self($($T),+).into_report())
173            }
174        }
175    };
176}
177
178// ----------------------------------------------------------------------------
179
180impl_map_fn_for_splat!(T1);
181impl_map_fn_for_splat!(T1, T2);
182impl_map_fn_for_splat!(T1, T2, T3);
183impl_map_fn_for_splat!(T1, T2, T3, T4);
184impl_map_fn_for_splat!(T1, T2, T3, T4, T5);
185impl_map_fn_for_splat!(T1, T2, T3, T4, T5, T6);
186impl_map_fn_for_splat!(T1, T2, T3, T4, T5, T6, T7);
187impl_map_fn_for_splat!(T1, T2, T3, T4, T5, T6, T7, T8);