About
The goal of apollo-smith is to generate valid GraphQL documents by sampling
from all available possibilities of GraphQL grammar.
We've written apollo-smith to use in fuzzing, but you may wish to use it for
anything that requires GraphQL document generation.
apollo-smith is inspired by bytecodealliance's wasm-smith crate, and the
article written by Nick Fitzgerald on writing test case generators in Rust.
This is still a work in progress, for outstanding issues, checkout out the apollo-smith label in our issue tracker.
Rust versions
apollo-smith is tested on the latest stable version of Rust.
Older version may or may not be compatible.
Using apollo-smith with cargo fuzz
Define a new target with cargo fuzz,
$ cargo fuzz add my_apollo_smith_fuzz_target
and add apollo-smith to your Cargo.toml:
## fuzz/Cargo.toml
[]
= "0.16.0"
It can then be used in a fuzz_target along with the arbitrary crate,
// fuzz/fuzz_targets/my_apollo_smith_fuzz_target.rs
use fuzz_target;
use Unstructured;
use DocumentBuilder;
fuzz_target!;
and fuzzed with the following command:
$ cargo +nightly fuzz run my_apollo_smith_fuzz_target
Using apollo-smith with apollo-parser
You can use apollo-parser to generate valid operations in apollo-smith.
use fs;
use Parser;
use ;
use ;
/// This generate an arbitrary valid GraphQL operation
Generating responses using apollo-smith with apollo-compiler
If you have a GraphQL operation in the form of an ExecutableDocument and its
accompanying Schema, you can generate a response matching the shape of the
operation with apollo_smith::ResponseBuilder.
ResponseBuilder is generic over its randomness source via the RandomProvider
trait. This allows it to be used with arbitrary::Unstructured for fuzz testing,
with RandProvider for standard random generation, or with any custom implementation.
Using Unstructured (for fuzz testing)
use Valid;
use ExecutableDocument;
use Schema;
use ;
use Unstructured;
use RngExt as _;
use Value;
Using RandProvider
Use RandProvider to wrap any rand::Rng:
use ;
let mut rng = RandProvider;
let response = new
.with_min_list_size
.with_max_list_size
.with_null_ratio
.build?;
Configuring generation per type
Use with_generator to register a [Generator] for any named GraphQL type —
scalars, objects, interfaces, or unions. The same trait powers both leaf and
composite generation; the fields argument is empty for scalar types and
contains the requested selection (flattened across fragments and grouped by
response key) for composite types.
To swap one of the built-in scalar defaults:
use ;
use Name;
let response = new
.with_generator
.build?;
Or with completely custom generation logic:
use ;
use Field;
use ;
use IndexMap;
use Value;
let response = new
.with_generator
.build;
For composite types, the generator's return value is used as-is — the builder does not recurse into it. The generator receives the requested fields already flattened across fragments and grouped by response key, so it can return only what the caller asked for:
use ;
use Field;
use ;
use IndexMap;
use ;
/// Generator for the federation `_Service` type that returns the real schema SDL.
let response = new
.with_generator
.build?;
Accessing the default generators
The generators argument passed to every Generator::generate call is the
same registry the builder is using. It starts from Generators::default(),
which pre-registers a generator for each of the five standard GraphQL scalars:
| Type | Generator | Default range |
|---|---|---|
Boolean |
BooleanGenerator |
true or false |
Int |
IntGenerator |
0..=100 |
Float |
FloatGenerator |
-1.0..=1.0 |
String |
StringGenerator |
1–10 alphanumeric characters |
ID |
IdGenerator |
0..=100, serialized as a string |
Each per-type struct is public, so a custom composite generator can:
- delegate a single field back to the registry via
generators.generate_scalar(type_name, rng)— uses whatever is registered for that scalar (default or user-supplied), falling back to aStringGeneratorif nothing is registered; - dispatch to any registered type by name with
generators.try_generate(type_name, rng, fields)— returnsNoneif no generator is registered for that type; - construct one of the built-in per-type generators directly
(e.g.
IntGenerator { min: -10, max: 10 }.generate(rng, generators, &fields)) when you want a tuned instance without registering it.
The example below uses generate_scalar so each field is filled by whichever
generator is registered for its scalar type — including any overrides the
caller has installed via with_generator:
Limitations
- Recursive object type not yet supported (example :
myType { inner: myType })
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.