apollo_smith/generators.rs
1//! Generator trait and registry used by [`ResponseBuilder`][crate::ResponseBuilder]
2//! to produce values for GraphQL types, plus the built-in generators for the
3//! standard GraphQL scalars.
4//!
5//! # Built-in defaults
6//!
7//! [`Generators::default`] pre-registers a generator for each of the five
8//! standard GraphQL scalars:
9//!
10//! | Type | Generator | Default range |
11//! |-----------|-------------------------------------------------|------------------------|
12//! | `Boolean` | [`BooleanGenerator`] | `true` or `false` |
13//! | `Int` | [`IntGenerator`] | `0..=100` |
14//! | `Float` | [`FloatGenerator`] | `-1.0..=1.0` |
15//! | `String` | [`StringGenerator`] | 1–10 alphanumeric chars|
16//! | `ID` | [`IdGenerator`] | `0..=100`, stringified |
17//!
18//! Each per-type struct is public, so a custom generator can construct one
19//! directly (for example, to call a configured [`IntGenerator`] inline) or
20//! register a tuned instance via
21//! [`ResponseBuilder::with_generator`][crate::ResponseBuilder::with_generator]
22//! to override the default range.
23//!
24//! # Accessing defaults from a custom generator
25//!
26//! Every [`Generator::generate`] call receives a `&mut Generators<R>` — the
27//! same registry the builder is using, including any defaults from
28//! [`Generators::default`] and any overrides registered with
29//! [`ResponseBuilder::with_generator`][crate::ResponseBuilder::with_generator].
30//! A custom generator can delegate back to the registry in two ways:
31//!
32//! - [`Generators::generate_scalar`] — fill a leaf field by named scalar type,
33//! falling back to a default [`StringGenerator`] if nothing is registered.
34//! This is the common path for composite generators that only want to
35//! customize a few fields and let defaults handle the rest.
36//! - [`Generators::try_generate`] — dispatch to any registered generator
37//! (composite or leaf) by type name, returning `None` if none is registered.
38//! Useful when a custom generator wants to compose results from other
39//! registered types.
40
41use crate::random::RandomProvider;
42use crate::random::ResponseError;
43use apollo_compiler::executable::Field;
44use apollo_compiler::Name;
45use apollo_compiler::Node;
46use indexmap::IndexMap;
47use serde_json_bytes::serde_json::Number;
48use serde_json_bytes::Value;
49use std::collections::HashMap;
50
51/// A pluggable generator for the value of a named GraphQL type.
52///
53/// Generators are registered on a
54/// [`ResponseBuilder`][crate::ResponseBuilder] under a type name via
55/// [`ResponseBuilder::with_generator`][crate::ResponseBuilder::with_generator] and
56/// invoked when the builder is about to produce a value of that type.
57///
58/// The `fields` argument is the requested selection grouped by response key (alias
59/// if present, else field name); fragment spreads and inline fragments are
60/// pre-flattened against the concrete type. Leaf-type generators (scalars, enums)
61/// may ignore `fields`. The `generators` argument exposes the full registry so an
62/// implementation can delegate to other registered generators — most often to fill
63/// a leaf field via [`Generators::generate_scalar`].
64pub trait Generator<R: RandomProvider> {
65 fn generate(
66 &mut self,
67 rng: &mut R,
68 generators: &mut Generators<R>,
69 fields: &IndexMap<String, Vec<Node<Field>>>,
70 ) -> Result<Value, ResponseError>;
71
72 /// Move this generator into a `Box<dyn Generator<R>>` for registration with
73 /// [`ResponseBuilder::with_generator`][crate::ResponseBuilder::with_generator].
74 fn boxed(self) -> Box<dyn Generator<R>>
75 where
76 Self: Sized + 'static,
77 {
78 Box::new(self)
79 }
80}
81
82/// Registry of [`Generator`]s keyed by GraphQL type name.
83///
84/// [`Generators::default`] pre-registers a generator for each of the five
85/// standard GraphQL scalars: [`BooleanGenerator`], [`IntGenerator`],
86/// [`FloatGenerator`], [`StringGenerator`], and [`IdGenerator`]. The
87/// [`ResponseBuilder`][crate::ResponseBuilder] starts from this set and layers
88/// any overrides supplied via
89/// [`ResponseBuilder::with_generator`][crate::ResponseBuilder::with_generator]
90/// on top.
91///
92/// Custom generators receive a `&mut Generators<R>` and can delegate back to
93/// it via [`Self::generate_scalar`] (for leaf fields) or [`Self::try_generate`]
94/// (for any registered type) — see the [module-level docs][self] for the full
95/// picture.
96pub struct Generators<R: RandomProvider> {
97 map: HashMap<Name, Box<dyn Generator<R>>>,
98}
99
100impl<R: RandomProvider> Generators<R> {
101 pub(crate) fn insert(&mut self, name: Name, generator: Box<dyn Generator<R>>) {
102 self.map.insert(name, generator);
103 }
104
105 /// Dispatch to the generator registered for `type_name`, if any.
106 ///
107 /// Returns `None` if no generator is registered. The caller decides what to do
108 /// in that case (the [`ResponseBuilder`][crate::ResponseBuilder] falls back to
109 /// default field-by-field object generation for composite types, and to a
110 /// default scalar generator for leaf types via [`Self::generate_scalar`]).
111 ///
112 /// While the dispatched generator runs, its entry is temporarily removed from
113 /// the registry. A generator that recursively asks for its own registered type
114 /// will see `None` on the inner call; generators for other types remain
115 /// reachable as normal.
116 pub fn try_generate(
117 &mut self,
118 type_name: &Name,
119 rng: &mut R,
120 fields: &IndexMap<String, Vec<Node<Field>>>,
121 ) -> Option<Result<Value, ResponseError>> {
122 let mut generator = self.map.remove(type_name)?;
123 let result = generator.generate(rng, self, fields);
124 self.map.insert(type_name.clone(), generator);
125 Some(result)
126 }
127
128 /// Generate a leaf value for a named scalar type using the registered
129 /// generator, falling back to an alphanumeric string of length 1–10 if none
130 /// is registered.
131 ///
132 /// Object generators typically call this to fill scalar fields without
133 /// hand-rolling generation logic for each leaf.
134 pub fn generate_scalar(
135 &mut self,
136 type_name: &Name,
137 rng: &mut R,
138 ) -> Result<Value, ResponseError> {
139 let empty = IndexMap::new();
140 if let Some(result) = self.try_generate(type_name, rng, &empty) {
141 return result;
142 }
143 let mut fallback = StringGenerator {
144 min_len: 1,
145 max_len: 10,
146 };
147 fallback.generate(rng, self, &empty)
148 }
149}
150
151/// Generates a random boolean.
152#[derive(Debug, Default, Clone)]
153pub struct BooleanGenerator;
154
155impl<R: RandomProvider> Generator<R> for BooleanGenerator {
156 fn generate(
157 &mut self,
158 rng: &mut R,
159 _generators: &mut Generators<R>,
160 _fields: &IndexMap<String, Vec<Node<Field>>>,
161 ) -> Result<Value, ResponseError> {
162 Ok(Value::Bool(rng.gen_bool()?))
163 }
164}
165
166/// Generates a random integer in the given inclusive range.
167#[derive(Debug, Clone)]
168pub struct IntGenerator {
169 pub min: i32,
170 pub max: i32,
171}
172
173impl<R: RandomProvider> Generator<R> for IntGenerator {
174 fn generate(
175 &mut self,
176 rng: &mut R,
177 _generators: &mut Generators<R>,
178 _fields: &IndexMap<String, Vec<Node<Field>>>,
179 ) -> Result<Value, ResponseError> {
180 Ok(Value::Number(rng.gen_i32_range(self.min, self.max)?.into()))
181 }
182}
183
184impl Default for IntGenerator {
185 fn default() -> Self {
186 Self { min: 0, max: 100 }
187 }
188}
189
190/// Generates a random float in the given inclusive range.
191#[derive(Debug, Clone)]
192pub struct FloatGenerator {
193 pub min: f64,
194 pub max: f64,
195}
196
197impl<R: RandomProvider> Generator<R> for FloatGenerator {
198 fn generate(
199 &mut self,
200 rng: &mut R,
201 _generators: &mut Generators<R>,
202 _fields: &IndexMap<String, Vec<Node<Field>>>,
203 ) -> Result<Value, ResponseError> {
204 let f = rng.gen_f64_range(self.min, self.max)?;
205 let num = Number::from_f64(f)
206 .ok_or_else(|| ResponseError::InvalidFormat("generated non-finite float".into()))?;
207 Ok(Value::Number(num))
208 }
209}
210
211impl Default for FloatGenerator {
212 fn default() -> Self {
213 Self {
214 min: -1.0,
215 max: 1.0,
216 }
217 }
218}
219
220/// Generates a random alphanumeric string with length in the given inclusive range.
221#[derive(Debug, Clone)]
222pub struct StringGenerator {
223 pub min_len: usize,
224 pub max_len: usize,
225}
226
227impl<R: RandomProvider> Generator<R> for StringGenerator {
228 fn generate(
229 &mut self,
230 rng: &mut R,
231 _generators: &mut Generators<R>,
232 _fields: &IndexMap<String, Vec<Node<Field>>>,
233 ) -> Result<Value, ResponseError> {
234 let len = rng.gen_usize_range(self.min_len, self.max_len)?;
235 let s: Result<std::string::String, _> =
236 (0..len).map(|_| rng.gen_alphanumeric_char()).collect();
237 Ok(Value::String(s?.into()))
238 }
239}
240
241impl Default for StringGenerator {
242 fn default() -> Self {
243 Self {
244 min_len: 1,
245 max_len: 10,
246 }
247 }
248}
249
250/// Generates a random integer ID in the given inclusive range, serialized as a string.
251#[derive(Debug, Clone)]
252pub struct IdGenerator {
253 pub min: i32,
254 pub max: i32,
255}
256
257impl<R: RandomProvider> Generator<R> for IdGenerator {
258 fn generate(
259 &mut self,
260 rng: &mut R,
261 _generators: &mut Generators<R>,
262 _fields: &IndexMap<String, Vec<Node<Field>>>,
263 ) -> Result<Value, ResponseError> {
264 Ok(Value::String(
265 rng.gen_i32_range(self.min, self.max)?.to_string().into(),
266 ))
267 }
268}
269
270impl Default for IdGenerator {
271 fn default() -> Self {
272 Self { min: 0, max: 100 }
273 }
274}
275
276impl<R: RandomProvider> Default for Generators<R> {
277 fn default() -> Self {
278 let map: HashMap<Name, Box<dyn Generator<R>>> = [
279 (Name::new_unchecked("Boolean"), BooleanGenerator.boxed()),
280 (Name::new_unchecked("Int"), IntGenerator::default().boxed()),
281 (Name::new_unchecked("ID"), IdGenerator::default().boxed()),
282 (
283 Name::new_unchecked("Float"),
284 FloatGenerator::default().boxed(),
285 ),
286 (
287 Name::new_unchecked("String"),
288 StringGenerator::default().boxed(),
289 ),
290 ]
291 .into_iter()
292 .collect();
293 Self { map }
294 }
295}