1use std::any::Any;
2use std::sync::Arc;
3
4use crate::place::{Place, PlaceRef};
5
6pub type GuardFn = Arc<dyn Fn(&dyn Any) -> bool + Send + Sync>;
8
9#[derive(Clone)]
16pub enum In {
17 One {
19 place: PlaceRef,
20 guard: Option<GuardFn>,
21 },
22 Exactly {
24 place: PlaceRef,
25 count: usize,
26 guard: Option<GuardFn>,
27 },
28 All {
30 place: PlaceRef,
31 guard: Option<GuardFn>,
32 },
33 AtLeast {
35 place: PlaceRef,
36 minimum: usize,
37 guard: Option<GuardFn>,
38 },
39}
40
41impl In {
42 pub fn place(&self) -> &PlaceRef {
44 match self {
45 In::One { place, .. }
46 | In::Exactly { place, .. }
47 | In::All { place, .. }
48 | In::AtLeast { place, .. } => place,
49 }
50 }
51
52 pub fn place_name(&self) -> &str {
54 self.place().name()
55 }
56
57 pub fn has_guard(&self) -> bool {
59 match self {
60 In::One { guard, .. }
61 | In::Exactly { guard, .. }
62 | In::All { guard, .. }
63 | In::AtLeast { guard, .. } => guard.is_some(),
64 }
65 }
66
67 pub fn guard(&self) -> Option<&GuardFn> {
69 match self {
70 In::One { guard, .. }
71 | In::Exactly { guard, .. }
72 | In::All { guard, .. }
73 | In::AtLeast { guard, .. } => guard.as_ref(),
74 }
75 }
76}
77
78impl std::fmt::Debug for In {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 match self {
81 In::One { place, guard } => f
82 .debug_struct("One")
83 .field("place", place)
84 .field("has_guard", &guard.is_some())
85 .finish(),
86 In::Exactly {
87 place,
88 count,
89 guard,
90 } => f
91 .debug_struct("Exactly")
92 .field("place", place)
93 .field("count", count)
94 .field("has_guard", &guard.is_some())
95 .finish(),
96 In::All { place, guard } => f
97 .debug_struct("All")
98 .field("place", place)
99 .field("has_guard", &guard.is_some())
100 .finish(),
101 In::AtLeast {
102 place,
103 minimum,
104 guard,
105 } => f
106 .debug_struct("AtLeast")
107 .field("place", place)
108 .field("minimum", minimum)
109 .field("has_guard", &guard.is_some())
110 .finish(),
111 }
112 }
113}
114
115pub fn one<T: 'static>(place: &Place<T>) -> In {
119 In::One {
120 place: place.as_ref(),
121 guard: None,
122 }
123}
124
125pub fn one_guarded<T: Send + Sync + 'static>(
127 place: &Place<T>,
128 guard: impl Fn(&T) -> bool + Send + Sync + 'static,
129) -> In {
130 In::One {
131 place: place.as_ref(),
132 guard: Some(Arc::new(move |v: &dyn Any| {
133 v.downcast_ref::<T>().is_some_and(&guard)
134 })),
135 }
136}
137
138pub fn exactly<T: 'static>(count: usize, place: &Place<T>) -> In {
143 assert!(count >= 1, "count must be >= 1, got: {count}");
144 In::Exactly {
145 place: place.as_ref(),
146 count,
147 guard: None,
148 }
149}
150
151pub fn exactly_guarded<T: Send + Sync + 'static>(
156 count: usize,
157 place: &Place<T>,
158 guard: impl Fn(&T) -> bool + Send + Sync + 'static,
159) -> In {
160 assert!(count >= 1, "count must be >= 1, got: {count}");
161 In::Exactly {
162 place: place.as_ref(),
163 count,
164 guard: Some(Arc::new(move |v: &dyn Any| {
165 v.downcast_ref::<T>().is_some_and(&guard)
166 })),
167 }
168}
169
170pub fn all<T: 'static>(place: &Place<T>) -> In {
172 In::All {
173 place: place.as_ref(),
174 guard: None,
175 }
176}
177
178pub fn all_guarded<T: Send + Sync + 'static>(
180 place: &Place<T>,
181 guard: impl Fn(&T) -> bool + Send + Sync + 'static,
182) -> In {
183 In::All {
184 place: place.as_ref(),
185 guard: Some(Arc::new(move |v: &dyn Any| {
186 v.downcast_ref::<T>().is_some_and(&guard)
187 })),
188 }
189}
190
191pub fn at_least<T: 'static>(minimum: usize, place: &Place<T>) -> In {
196 assert!(minimum >= 1, "minimum must be >= 1, got: {minimum}");
197 In::AtLeast {
198 place: place.as_ref(),
199 minimum,
200 guard: None,
201 }
202}
203
204pub fn at_least_guarded<T: Send + Sync + 'static>(
209 minimum: usize,
210 place: &Place<T>,
211 guard: impl Fn(&T) -> bool + Send + Sync + 'static,
212) -> In {
213 assert!(minimum >= 1, "minimum must be >= 1, got: {minimum}");
214 In::AtLeast {
215 place: place.as_ref(),
216 minimum,
217 guard: Some(Arc::new(move |v: &dyn Any| {
218 v.downcast_ref::<T>().is_some_and(&guard)
219 })),
220 }
221}
222
223pub fn required_count(spec: &In) -> usize {
227 match spec {
228 In::One { .. } => 1,
229 In::Exactly { count, .. } => *count,
230 In::All { .. } => 1,
231 In::AtLeast { minimum, .. } => *minimum,
232 }
233}
234
235pub fn consumption_count(spec: &In, available: usize) -> usize {
240 let required = required_count(spec);
241 assert!(
242 available >= required,
243 "Cannot consume from '{}': available={}, required={}",
244 spec.place_name(),
245 available,
246 required
247 );
248 match spec {
249 In::One { .. } => 1,
250 In::Exactly { count, .. } => *count,
251 In::All { .. } => available,
252 In::AtLeast { .. } => available,
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn one_required_count() {
262 let p = Place::<i32>::new("p");
263 let spec = one(&p);
264 assert_eq!(required_count(&spec), 1);
265 }
266
267 #[test]
268 fn exactly_required_count() {
269 let p = Place::<i32>::new("p");
270 let spec = exactly(3, &p);
271 assert_eq!(required_count(&spec), 3);
272 }
273
274 #[test]
275 fn all_required_count() {
276 let p = Place::<i32>::new("p");
277 let spec = all(&p);
278 assert_eq!(required_count(&spec), 1);
279 }
280
281 #[test]
282 fn at_least_required_count() {
283 let p = Place::<i32>::new("p");
284 let spec = at_least(5, &p);
285 assert_eq!(required_count(&spec), 5);
286 }
287
288 #[test]
289 fn consumption_count_one() {
290 let p = Place::<i32>::new("p");
291 let spec = one(&p);
292 assert_eq!(consumption_count(&spec, 3), 1);
293 }
294
295 #[test]
296 fn consumption_count_exactly() {
297 let p = Place::<i32>::new("p");
298 let spec = exactly(3, &p);
299 assert_eq!(consumption_count(&spec, 5), 3);
300 }
301
302 #[test]
303 fn consumption_count_all() {
304 let p = Place::<i32>::new("p");
305 let spec = all(&p);
306 assert_eq!(consumption_count(&spec, 7), 7);
307 }
308
309 #[test]
310 fn consumption_count_at_least() {
311 let p = Place::<i32>::new("p");
312 let spec = at_least(3, &p);
313 assert_eq!(consumption_count(&spec, 5), 5);
314 }
315
316 #[test]
317 #[should_panic(expected = "count must be >= 1")]
318 fn exactly_zero_panics() {
319 let p = Place::<i32>::new("p");
320 exactly(0, &p);
321 }
322
323 #[test]
324 #[should_panic(expected = "minimum must be >= 1")]
325 fn at_least_zero_panics() {
326 let p = Place::<i32>::new("p");
327 at_least(0, &p);
328 }
329
330 #[test]
331 #[should_panic(expected = "Cannot consume")]
332 fn consumption_count_insufficient_panics() {
333 let p = Place::<i32>::new("p");
334 let spec = exactly(3, &p);
335 consumption_count(&spec, 2);
336 }
337
338 #[test]
339 fn guarded_input() {
340 let p = Place::<i32>::new("p");
341 let spec = one_guarded(&p, |v| *v > 5);
342 assert!(spec.has_guard());
343 let guard = spec.guard().unwrap();
344 assert!(guard(&10i32 as &dyn Any));
345 assert!(!guard(&3i32 as &dyn Any));
346 }
347}