hegel/generators/
combinators.rs1use super::{BasicGenerator, BoxedGenerator, Generator, TestCase, integers, labels};
2use crate::cbor_utils::{cbor_array, cbor_map};
3use ciborium::Value;
4use std::borrow::Cow;
5use std::marker::PhantomData;
6
7pub struct SampledFromGenerator<'a, T: Clone> {
9 elements: Cow<'a, [T]>,
10}
11
12impl<'a, T: Clone + Send + Sync + 'a> Generator<T> for SampledFromGenerator<'a, T> {
13 fn do_draw(&self, tc: &TestCase) -> T {
14 if let Some(basic) = self.as_basic() {
15 return basic.do_draw(tc);
16 }
17
18 let indices = integers::<usize>()
20 .min_value(0)
21 .max_value(self.elements.len() - 1);
22 let index = indices.do_draw(tc);
23 self.elements[index].clone()
24 }
26
27 fn as_basic(&self) -> Option<BasicGenerator<'_, T>> {
28 if self.elements.is_empty() {
29 return None; }
31
32 let schema = cbor_map! {
33 "type" => "integer",
34 "min_value" => 0u64,
35 "max_value" => (self.elements.len() - 1) as u64
36 };
37 let elements: &[T] = &self.elements;
38 Some(BasicGenerator::new(schema, move |raw| {
39 let index: usize = super::deserialize_value(raw);
40 elements[index].clone()
41 }))
42 }
43
44 fn enumerate_values(&self) -> Option<Vec<T>> {
45 Some(self.elements.to_vec())
46 }
47}
48
49pub fn sampled_from<'a, T, S>(elements: S) -> SampledFromGenerator<'a, T>
58where
59 T: Clone + Send + Sync,
60 S: Into<Cow<'a, [T]>>,
61{
62 let elements = elements.into();
63 assert!(
64 !elements.is_empty(),
65 "Collection passed to sampled_from cannot be empty"
66 );
67 SampledFromGenerator { elements }
68}
69
70pub struct OneOfGenerator<'a, T> {
72 generators: Vec<BoxedGenerator<'a, T>>,
73}
74
75impl<T> Generator<T> for OneOfGenerator<'_, T> {
76 fn do_draw(&self, tc: &TestCase) -> T {
77 if let Some(basic) = self.as_basic() {
78 basic.do_draw(tc)
79 } else {
80 tc.start_span(labels::ONE_OF);
81 let index = integers::<usize>()
82 .min_value(0)
83 .max_value(self.generators.len() - 1)
84 .do_draw(tc);
85 let result = self.generators[index].do_draw(tc);
86 tc.stop_span(false);
87 result
88 }
89 }
90
91 fn as_basic(&self) -> Option<BasicGenerator<'_, T>> {
92 let basics: Vec<BasicGenerator<'_, T>> = self
93 .generators
94 .iter()
95 .map(|g| g.as_basic())
96 .collect::<Option<Vec<_>>>()?;
97
98 let schemas: Vec<Value> = basics.iter().map(|b| b.schema().clone()).collect();
99 let schema = cbor_map! {"type" => "one_of", "generators" => Value::Array(schemas)};
100
101 Some(BasicGenerator::new(schema, move |raw| {
102 let [idx, value]: [Value; 2] = raw.into_array().unwrap().try_into().unwrap();
104 let index = i128::from(idx.into_integer().unwrap()) as usize;
105 basics[index].parse_raw(value)
106 }))
107 }
108}
109
110pub fn one_of<'a, T, I>(generators: I) -> OneOfGenerator<'a, T>
116where
117 I: IntoIterator<Item = BoxedGenerator<'a, T>>,
118{
119 let generators: Vec<BoxedGenerator<'a, T>> = generators.into_iter().collect();
120 assert!(
121 !generators.is_empty(),
122 "one_of requires at least one generator"
123 );
124 OneOfGenerator { generators }
125}
126
127#[macro_export]
146macro_rules! one_of {
147 ($($generator:expr),+ $(,)?) => {
148 $crate::generators::one_of(vec![
149 $($crate::generators::Generator::boxed($generator)),+
150 ])
151 };
152}
153
154pub struct OptionalGenerator<G, T> {
156 inner: G,
157 _phantom: PhantomData<fn(T)>,
158}
159
160impl<T, G> Generator<Option<T>> for OptionalGenerator<G, T>
161where
162 G: Generator<T>,
163{
164 fn do_draw(&self, tc: &TestCase) -> Option<T> {
165 if let Some(basic) = self.as_basic() {
166 basic.do_draw(tc)
167 } else {
168 tc.start_span(labels::OPTIONAL);
170 let is_some: bool = super::generate_from_schema(tc, &cbor_map! {"type" => "boolean"});
171 let result = if is_some {
172 Some(self.inner.do_draw(tc))
173 } else {
174 None
175 };
176 tc.stop_span(false);
177 result
178 }
180 }
181
182 fn as_basic(&self) -> Option<BasicGenerator<'_, Option<T>>> {
183 let inner_basic = self.inner.as_basic()?;
184 let inner_schema = inner_basic.schema().clone();
185
186 let null_schema = cbor_map! {"type" => "constant", "value" => Value::Null};
187 let schema =
188 cbor_map! {"type" => "one_of", "generators" => cbor_array![null_schema, inner_schema]};
189
190 Some(BasicGenerator::new(schema, move |raw| {
191 let [idx, value]: [Value; 2] = raw.into_array().unwrap().try_into().unwrap();
194 if i128::from(idx.into_integer().unwrap()) == 0 {
195 None
196 } else {
197 Some(inner_basic.parse_raw(value))
198 }
199 }))
200 }
201}
202
203pub fn optional<T, G: Generator<T>>(inner: G) -> OptionalGenerator<G, T> {
205 OptionalGenerator {
206 inner,
207 _phantom: PhantomData,
208 }
209}