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);