Skip to main content

zrx_stream/stream/function/traits/
select.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//! Select 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/// Select function.
42///
43/// This trait defines a function that can be used to select data from an item,
44/// e.g., to extract a key to group items or to compute a duration for a timer.
45///
46/// There's a range of different implementations of this trait, allowing you to
47/// use a variety of function shapes, including support for [`Splat`], as well
48/// as support for the [`WithId`] and [`WithSplat`] adapters. Furthermore, the
49/// trait can be implemented for custom types to add new behaviors. Note that
50/// all implementations also allow to return a [`Report`][], which makes it
51/// possible to return diagnostics from the function execution.
52///
53/// The `'static` lifetimes is mandatory as closures must be moved into actions,
54/// so requiring it here allows us to reduce the verbosity of trait bounds.
55///
56/// [`Report`]: zrx_scheduler::action::report::Report
57///
58/// # Examples
59///
60/// Group by odd/even:
61///
62/// ```
63/// # use std::error::Error;
64/// # fn main() -> Result<(), Box<dyn Error>> {
65/// use zrx_stream::function::SelectFn;
66///
67/// // Define and execute function
68/// let f = |&n: &i32| n & 1;
69/// f.execute(&"id", &42)?;
70/// # Ok(())
71/// # }
72/// ```
73pub trait SelectFn<I, T, S>: Send + 'static
74where
75    T: ?Sized,
76{
77    /// Executes the select function.
78    ///
79    /// # Errors
80    ///
81    /// This method returns an error if the function fails to execute.
82    fn execute(&self, id: &I, data: &T) -> Result<S>;
83}
84
85// ----------------------------------------------------------------------------
86// Blanket implementations
87// ----------------------------------------------------------------------------
88
89impl<F, R, I, T, S> SelectFn<I, T, S> for F
90where
91    F: Fn(&T) -> R + Send + 'static,
92    R: IntoReport<S>,
93    I: Display,
94    T: Value + ?Sized,
95{
96    #[cfg_attr(
97        feature = "tracing",
98        tracing::instrument(level = "debug", skip_all, fields(id = %id))
99    )]
100    #[inline]
101    fn execute(&self, id: &I, data: &T) -> Result<S> {
102        catch(|| self(data).into_report())
103    }
104}
105
106impl<F, R, I, T, S> SelectFn<I, T, S> for WithId<F>
107where
108    F: Fn(&I, &T) -> R + Send + 'static,
109    R: IntoReport<S>,
110    I: Display,
111    T: Value + ?Sized,
112{
113    #[cfg_attr(
114        feature = "tracing",
115        tracing::instrument(level = "debug", skip_all, fields(id = %id))
116    )]
117    #[inline]
118    fn execute(&self, id: &I, data: &T) -> Result<S> {
119        catch(|| self(id, data).into_report())
120    }
121}
122
123impl<F, I, T, S> SelectFn<I, T, S> for WithSplat<F>
124where
125    F: SelectFn<I, Splat<T>, S>,
126{
127    #[inline]
128    fn execute(&self, id: &I, data: &T) -> Result<S> {
129        F::execute(self, id, Splat::from_ref(data))
130    }
131}
132
133// ----------------------------------------------------------------------------
134// Macros
135// ----------------------------------------------------------------------------
136
137/// Implements select function trait for splat arguments.
138macro_rules! impl_select_fn_for_splat {
139    ($($T:ident),+) => {
140        impl<F, R, I, $($T,)+ S> SelectFn<I, Splat<($($T,)+)>, S> for F
141        where
142            F: Fn($(&$T),+) -> R + Send + 'static,
143            R: IntoReport<S>,
144            I: Display,
145        {
146            #[cfg_attr(
147                feature = "tracing",
148                tracing::instrument(level = "debug", skip_all, fields(id = %id))
149            )]
150            #[inline]
151            fn execute(
152                &self, id: &I, data: &Splat<($($T,)+)>
153            ) -> Result<S> {
154                #[allow(non_snake_case)]
155                let ($($T,)+) = data.inner();
156                catch(|| self($($T),+).into_report())
157            }
158        }
159    };
160}
161
162// ----------------------------------------------------------------------------
163
164impl_select_fn_for_splat!(T1);
165impl_select_fn_for_splat!(T1, T2);
166impl_select_fn_for_splat!(T1, T2, T3);
167impl_select_fn_for_splat!(T1, T2, T3, T4);
168impl_select_fn_for_splat!(T1, T2, T3, T4, T5);
169impl_select_fn_for_splat!(T1, T2, T3, T4, T5, T6);
170impl_select_fn_for_splat!(T1, T2, T3, T4, T5, T6, T7);
171impl_select_fn_for_splat!(T1, T2, T3, T4, T5, T6, T7, T8);