Skip to main content

peer_pressure/
ops.rs

1//! Iterator combinators for [Validate].
2
3use crate::Validate;
4
5#[cfg(feature = "async")]
6use crate::Resolve;
7
8/// Extension trait on iterators whose items implement [Validate].
9#[cfg_attr(
10    feature = "async",
11    doc = "\nWith the `async` feature, also covers items implementing [Resolve](crate::Resolve)."
12)]
13#[cfg_attr(feature = "async", allow(async_fn_in_trait))]
14pub trait IteratorExt: IntoIterator {
15    /// Validates every item within given context, short-circuiting on the first error.
16    fn validate_all_in_context<I>(
17        self,
18        ctx: &<Self::Item as Validate>::Context,
19    ) -> Result<I, <Self::Item as Validate>::Error>
20    where
21        Self::Item: Validate,
22        I: Extend<<Self::Item as Validate>::Output> + Default;
23
24    /// Context-free variant of [validate_all_in_context], available when item `Context = ()`.
25    ///
26    /// # Examples
27    ///
28    /// ```rust
29    /// use peer_pressure::{Validate, IteratorExt};
30    ///
31    /// struct RawNum(i32);
32    /// struct Positive(i32);
33    ///
34    /// impl Validate for RawNum {
35    ///     type Context = ();
36    ///     type Output = Positive;
37    ///     type Error = &'static str;
38    ///     fn validate_in_context(self, _: &()) -> Result<Positive, &'static str> {
39    ///         if self.0 > 0 { Ok(Positive(self.0)) } else { Err("non-positive") }
40    ///     }
41    /// }
42    ///
43    /// let raws = [RawNum(1), RawNum(2), RawNum(3)];
44    /// let out: Result<Vec<_>, _> = raws.validate_all();
45    /// assert!(out.is_ok());
46    /// ```
47    ///
48    /// [validate_all_in_context]: IteratorExt::validate_all_in_context
49    fn validate_all<I>(self) -> Result<I, <Self::Item as Validate>::Error>
50    where
51        Self::Item: Validate,
52        <Self::Item as Validate>::Context: From<()>,
53        I: Extend<<Self::Item as Validate>::Output> + Default;
54
55    /// Validates every item within given context, partitioning successes into `I1` and errors into `I2`.
56    fn partition_validated_in_context<I1, I2>(
57        self,
58        ctx: &<Self::Item as Validate>::Context,
59    ) -> (I1, I2)
60    where
61        Self::Item: Validate,
62        I1: Extend<<Self::Item as Validate>::Output> + Default,
63        I2: Extend<<Self::Item as Validate>::Error> + Default;
64
65    /// Context-free variant of [partition_validated_in_context], available when items' `Context = ()`.
66    ///
67    /// Input order is preserved within each output collection.
68    ///
69    /// # Examples
70    ///
71    /// ```rust
72    /// use peer_pressure::{Validate, IteratorExt};
73    ///
74    /// struct RawNum(i32);
75    /// struct Positive(i32);
76    ///
77    /// impl Validate for RawNum {
78    ///     type Context = ();
79    ///     type Output = Positive;
80    ///     type Error = &'static str;
81    ///     fn validate_in_context(self, _: &()) -> Result<Positive, &'static str> {
82    ///         if self.0 > 0 { Ok(Positive(self.0)) } else { Err("non-positive") }
83    ///     }
84    /// }
85    ///
86    /// let raws = [RawNum(1), RawNum(-1), RawNum(2)];
87    /// let (oks, errs): (Vec<Positive>, Vec<&'static str>) = raws.partition_validated();
88    /// assert_eq!(oks.len(), 2);
89    /// assert_eq!(errs, vec!["non-positive"]);
90    /// ```
91    ///
92    /// [partition_validated_in_context]: IteratorExt::partition_validated_in_context
93    fn partition_validated<I1, I2>(self) -> (I1, I2)
94    where
95        Self::Item: Validate,
96        <Self::Item as Validate>::Context: From<()>,
97        I1: Extend<<Self::Item as Validate>::Output> + Default,
98        I2: Extend<<Self::Item as Validate>::Error> + Default;
99
100    /// Resolves every item within given context, short-circuiting on the first error.
101    #[cfg(feature = "async")]
102    async fn resolve_all_in_context<I>(
103        self,
104        ctx: &<Self::Item as Resolve>::Context,
105    ) -> Result<I, <Self::Item as Resolve>::Error>
106    where
107        Self: Sized,
108        Self::Item: Resolve,
109        I: Extend<<Self::Item as Resolve>::Output> + Default;
110
111    /// Context-free variant of [resolve_all_in_context], available when items' `Context = ()`.
112    ///
113    /// [resolve_all_in_context]: IteratorExt::resolve_all_in_context
114    #[cfg(feature = "async")]
115    async fn resolve_all<I>(self) -> Result<I, <Self::Item as Resolve>::Error>
116    where
117        Self: Sized,
118        Self::Item: Resolve,
119        <Self::Item as Resolve>::Context: From<()>,
120        I: Extend<<Self::Item as Resolve>::Output> + Default;
121
122    /// Resolves every item within given context, partitioning successes into `I1` and errors into `I2`.
123    #[cfg(feature = "async")]
124    async fn partition_resolved_in_context<I1, I2>(
125        self,
126        ctx: &<Self::Item as Resolve>::Context,
127    ) -> (I1, I2)
128    where
129        Self: Sized,
130        Self::Item: Resolve,
131        I1: Extend<<Self::Item as Resolve>::Output> + Default,
132        I2: Extend<<Self::Item as Resolve>::Error> + Default;
133
134    /// Context-free variant of [partition_resolved_in_context], available when items' `Context = ()`.
135    ///
136    /// [partition_resolved_in_context]: IteratorExt::partition_resolved_in_context
137    #[cfg(feature = "async")]
138    async fn partition_resolved<I1, I2>(self) -> (I1, I2)
139    where
140        Self: Sized,
141        Self::Item: Resolve,
142        <Self::Item as Resolve>::Context: From<()>,
143        I1: Extend<<Self::Item as Resolve>::Output> + Default,
144        I2: Extend<<Self::Item as Resolve>::Error> + Default;
145}
146
147impl<Iter: IntoIterator> IteratorExt for Iter {
148    fn validate_all_in_context<I>(
149        self,
150        ctx: &<Self::Item as Validate>::Context,
151    ) -> Result<I, <Self::Item as Validate>::Error>
152    where
153        Self::Item: Validate,
154        I: Extend<<Self::Item as Validate>::Output> + Default,
155    {
156        let mut out = I::default();
157        for item in self {
158            out.extend(Some(item.validate_in_context(ctx)?));
159        }
160        Ok(out)
161    }
162
163    fn validate_all<I>(self) -> Result<I, <Self::Item as Validate>::Error>
164    where
165        Self::Item: Validate,
166        <Self::Item as Validate>::Context: From<()>,
167        I: Extend<<Self::Item as Validate>::Output> + Default,
168    {
169        self.validate_all_in_context(&().into())
170    }
171
172    fn partition_validated_in_context<I1, I2>(
173        self,
174        ctx: &<Self::Item as Validate>::Context,
175    ) -> (I1, I2)
176    where
177        Self::Item: Validate,
178        I1: Extend<<Self::Item as Validate>::Output> + Default,
179        I2: Extend<<Self::Item as Validate>::Error> + Default,
180    {
181        let mut oks = I1::default();
182        let mut errs = I2::default();
183        for item in self {
184            match item.validate_in_context(ctx) {
185                Ok(v) => oks.extend(Some(v)),
186                Err(e) => errs.extend(Some(e)),
187            }
188        }
189        (oks, errs)
190    }
191
192    fn partition_validated<I1, I2>(self) -> (I1, I2)
193    where
194        Self::Item: Validate,
195        <Self::Item as Validate>::Context: From<()>,
196        I1: Extend<<Self::Item as Validate>::Output> + Default,
197        I2: Extend<<Self::Item as Validate>::Error> + Default,
198    {
199        self.partition_validated_in_context(&().into())
200    }
201
202    #[cfg(feature = "async")]
203    async fn resolve_all_in_context<I>(
204        self,
205        ctx: &<Self::Item as Resolve>::Context,
206    ) -> Result<I, <Self::Item as Resolve>::Error>
207    where
208        Self: Sized,
209        Self::Item: Resolve,
210        I: Extend<<Self::Item as Resolve>::Output> + Default,
211    {
212        let mut out = I::default();
213        for item in self {
214            out.extend(Some(item.resolve_in_context(ctx).await?));
215        }
216        Ok(out)
217    }
218
219    #[cfg(feature = "async")]
220    async fn resolve_all<I>(self) -> Result<I, <Self::Item as Resolve>::Error>
221    where
222        Self: Sized,
223        Self::Item: Resolve,
224        <Self::Item as Resolve>::Context: From<()>,
225        I: Extend<<Self::Item as Resolve>::Output> + Default,
226    {
227        self.resolve_all_in_context(&().into()).await
228    }
229
230    #[cfg(feature = "async")]
231    async fn partition_resolved_in_context<I1, I2>(
232        self,
233        ctx: &<Self::Item as Resolve>::Context,
234    ) -> (I1, I2)
235    where
236        Self: Sized,
237        Self::Item: Resolve,
238        I1: Extend<<Self::Item as Resolve>::Output> + Default,
239        I2: Extend<<Self::Item as Resolve>::Error> + Default,
240    {
241        let mut oks = I1::default();
242        let mut errs = I2::default();
243        for item in self {
244            match item.resolve_in_context(ctx).await {
245                Ok(v) => oks.extend(Some(v)),
246                Err(e) => errs.extend(Some(e)),
247            }
248        }
249        (oks, errs)
250    }
251
252    #[cfg(feature = "async")]
253    async fn partition_resolved<I1, I2>(self) -> (I1, I2)
254    where
255        Self: Sized,
256        Self::Item: Resolve,
257        <Self::Item as Resolve>::Context: From<()>,
258        I1: Extend<<Self::Item as Resolve>::Output> + Default,
259        I2: Extend<<Self::Item as Resolve>::Error> + Default,
260    {
261        self.partition_resolved_in_context(&().into()).await
262    }
263}