Skip to main content

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}