Skip to content

Commit 42a7062

Browse files
committed
Add arbitrary support
This adds optional support for https://github.com/rust-fuzz/arbitrary/ behind the `arbitrary` feature flag (off by default). This will be used in a future pull request to enable fuzzing using `cargo-fuzz`
1 parent 4862915 commit 42a7062

File tree

9 files changed

+465
-4
lines changed

9 files changed

+465
-4
lines changed

juniper/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ default = [
2626
"url",
2727
"uuid",
2828
]
29+
arbitrary = ["dep:arbitrary", "smartstring/arbitrary"]
2930
expose-test-schema = ["anyhow", "serde_json"]
3031
graphql-parser-integration = ["graphql-parser"]
3132
scalar-naivetime = []
@@ -35,6 +36,7 @@ schema-language = ["graphql-parser-integration"]
3536
juniper_codegen = { version = "0.16.0-dev", path = "../juniper_codegen" }
3637

3738
anyhow = { version = "1.0.32", optional = true, default-features = false }
39+
arbitrary = { version = "1.1", optional = true, features = ["derive"] }
3840
async-trait = "0.1.39"
3941
bson = { version = "2.0", features = ["chrono-0_4"], optional = true }
4042
chrono = { version = "0.4", default-features = false, optional = true }

juniper/src/ast.rs

+158-4
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,36 @@ pub enum Type<'a> {
2828
NonNullList(Box<Type<'a>>, Option<usize>),
2929
}
3030

31+
#[cfg(feature = "arbitrary")]
32+
impl<'a> arbitrary::Arbitrary<'a> for Type<'a> {
33+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
34+
let num_choices = 4;
35+
36+
let ty = match u.int_in_range::<u8>(1..=num_choices)? {
37+
1 => Type::Named(u.arbitrary::<Cow<'a, str>>()?),
38+
2 => Type::List(
39+
u.arbitrary::<Box<Type<'a>>>()?,
40+
u.arbitrary::<Option<usize>>()?,
41+
),
42+
3 => Type::NonNullNamed(u.arbitrary::<Cow<'a, str>>()?),
43+
4 => Type::NonNullList(
44+
u.arbitrary::<Box<Type<'a>>>()?,
45+
u.arbitrary::<Option<usize>>()?,
46+
),
47+
_ => unreachable!(),
48+
};
49+
Ok(ty)
50+
}
51+
}
52+
3153
/// A JSON-like value that can be passed into the query execution, either
3254
/// out-of-band, or in-band as default variable values. These are _not_ constant
3355
/// and might contain variables.
3456
///
3557
/// Lists and objects variants are _spanned_, i.e. they contain a reference to
3658
/// their position in the source file, if available.
3759
#[derive(Clone, Debug, PartialEq)]
38-
#[allow(missing_docs)]
60+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
3961
pub enum InputValue<S = DefaultScalarValue> {
4062
Null,
4163
Scalar(S),
@@ -46,23 +68,67 @@ pub enum InputValue<S = DefaultScalarValue> {
4668
}
4769

4870
#[derive(Clone, PartialEq, Debug)]
71+
#[allow(missing_docs)]
4972
pub struct VariableDefinition<'a, S> {
5073
pub var_type: Spanning<Type<'a>>,
5174
pub default_value: Option<Spanning<InputValue<S>>>,
5275
pub directives: Option<Vec<Spanning<Directive<'a, S>>>>,
5376
}
5477

78+
#[cfg(feature = "arbitrary")]
79+
impl<'a, S> arbitrary::Arbitrary<'a> for VariableDefinition<'a, S>
80+
where
81+
S: arbitrary::Arbitrary<'a>,
82+
{
83+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
84+
let var_type: Spanning<Type<'a>> = u.arbitrary()?;
85+
let default_value: Option<Spanning<InputValue<S>>> = u.arbitrary()?;
86+
let directives: Option<Vec<Spanning<Directive<'a, S>>>> = u.arbitrary()?;
87+
88+
Ok(Self {
89+
var_type,
90+
default_value,
91+
directives,
92+
})
93+
}
94+
}
95+
5596
#[derive(Clone, PartialEq, Debug)]
97+
#[allow(missing_docs)]
5698
pub struct Arguments<'a, S> {
5799
pub items: Vec<(Spanning<&'a str>, Spanning<InputValue<S>>)>,
58100
}
59101

102+
#[cfg(feature = "arbitrary")]
103+
impl<'a, S> arbitrary::Arbitrary<'a> for Arguments<'a, S>
104+
where
105+
S: arbitrary::Arbitrary<'a>,
106+
{
107+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
108+
let items: Vec<(Spanning<&'a str>, Spanning<InputValue<S>>)> = u.arbitrary()?;
109+
Ok(Self { items })
110+
}
111+
}
112+
60113
#[derive(Clone, PartialEq, Debug)]
114+
#[allow(missing_docs)]
61115
pub struct VariableDefinitions<'a, S> {
62116
pub items: Vec<(Spanning<&'a str>, VariableDefinition<'a, S>)>,
63117
}
64118

119+
#[cfg(feature = "arbitrary")]
120+
impl<'a, S> arbitrary::Arbitrary<'a> for VariableDefinitions<'a, S>
121+
where
122+
S: arbitrary::Arbitrary<'a>,
123+
{
124+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
125+
let items: Vec<(Spanning<&'a str>, VariableDefinition<'a, S>)> = u.arbitrary()?;
126+
Ok(Self { items })
127+
}
128+
}
129+
65130
#[derive(Clone, PartialEq, Debug)]
131+
#[allow(missing_docs)]
66132
pub struct Field<'a, S> {
67133
pub alias: Option<Spanning<&'a str>>,
68134
pub name: Spanning<&'a str>,
@@ -71,19 +137,74 @@ pub struct Field<'a, S> {
71137
pub selection_set: Option<Vec<Selection<'a, S>>>,
72138
}
73139

140+
#[cfg(feature = "arbitrary")]
141+
impl<'a, S> arbitrary::Arbitrary<'a> for Field<'a, S>
142+
where
143+
S: arbitrary::Arbitrary<'a>,
144+
{
145+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
146+
let alias: Option<Spanning<&'a str>> = u.arbitrary()?;
147+
let name: Spanning<&'a str> = u.arbitrary()?;
148+
let arguments: Option<Spanning<Arguments<'a, S>>> = u.arbitrary()?;
149+
let directives: Option<Vec<Spanning<Directive<'a, S>>>> = u.arbitrary()?;
150+
let selection_set: Option<Vec<Selection<'a, S>>> = u.arbitrary()?;
151+
152+
Ok(Self {
153+
alias,
154+
name,
155+
arguments,
156+
directives,
157+
selection_set,
158+
})
159+
}
160+
}
161+
74162
#[derive(Clone, PartialEq, Debug)]
163+
#[allow(missing_docs)]
75164
pub struct FragmentSpread<'a, S> {
76165
pub name: Spanning<&'a str>,
77166
pub directives: Option<Vec<Spanning<Directive<'a, S>>>>,
78167
}
79168

169+
#[cfg(feature = "arbitrary")]
170+
impl<'a, S> arbitrary::Arbitrary<'a> for FragmentSpread<'a, S>
171+
where
172+
S: arbitrary::Arbitrary<'a>,
173+
{
174+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
175+
let name: Spanning<&'a str> = u.arbitrary()?;
176+
let directives: Option<Vec<Spanning<Directive<'a, S>>>> = u.arbitrary()?;
177+
178+
Ok(Self { name, directives })
179+
}
180+
}
181+
80182
#[derive(Clone, PartialEq, Debug)]
183+
#[allow(missing_docs)]
81184
pub struct InlineFragment<'a, S> {
82185
pub type_condition: Option<Spanning<&'a str>>,
83186
pub directives: Option<Vec<Spanning<Directive<'a, S>>>>,
84187
pub selection_set: Vec<Selection<'a, S>>,
85188
}
86189

190+
#[cfg(feature = "arbitrary")]
191+
impl<'a, S> arbitrary::Arbitrary<'a> for InlineFragment<'a, S>
192+
where
193+
S: arbitrary::Arbitrary<'a>,
194+
{
195+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
196+
let type_condition: Option<Spanning<&'a str>> = u.arbitrary()?;
197+
let directives: Option<Vec<Spanning<Directive<'a, S>>>> = u.arbitrary()?;
198+
let selection_set: Vec<Selection<'a, S>> = u.arbitrary()?;
199+
200+
Ok(Self {
201+
type_condition,
202+
directives,
203+
selection_set,
204+
})
205+
}
206+
}
207+
87208
/// Entry in a GraphQL selection set
88209
///
89210
/// This enum represents one of the three variants of a selection that exists
@@ -107,22 +228,54 @@ pub enum Selection<'a, S = DefaultScalarValue> {
107228
InlineFragment(Spanning<InlineFragment<'a, S>>),
108229
}
109230

231+
#[cfg(feature = "arbitrary")]
232+
impl<'a, S> arbitrary::Arbitrary<'a> for Selection<'a, S>
233+
where
234+
S: arbitrary::Arbitrary<'a>,
235+
{
236+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
237+
let num_choices = 3;
238+
239+
let ty = match u.int_in_range::<u8>(1..=num_choices)? {
240+
1 => Selection::Field(u.arbitrary::<Spanning<Field<'a, S>>>()?),
241+
2 => Selection::FragmentSpread(u.arbitrary::<Spanning<FragmentSpread<'a, S>>>()?),
242+
3 => Selection::InlineFragment(u.arbitrary::<Spanning<InlineFragment<'a, S>>>()?),
243+
_ => unreachable!(),
244+
};
245+
Ok(ty)
246+
}
247+
}
248+
110249
#[derive(Clone, PartialEq, Debug)]
250+
#[allow(missing_docs)]
111251
pub struct Directive<'a, S> {
112252
pub name: Spanning<&'a str>,
113253
pub arguments: Option<Spanning<Arguments<'a, S>>>,
114254
}
115255

116-
#[allow(missing_docs)]
256+
#[cfg(feature = "arbitrary")]
257+
impl<'a, S> arbitrary::Arbitrary<'a> for Directive<'a, S>
258+
where
259+
S: arbitrary::Arbitrary<'a>,
260+
{
261+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
262+
let name: Spanning<&'a str> = u.arbitrary()?;
263+
let arguments: Option<Spanning<Arguments<'a, S>>> = u.arbitrary()?;
264+
Ok(Self { name, arguments })
265+
}
266+
}
267+
117268
#[derive(Clone, PartialEq, Debug)]
269+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
270+
#[allow(missing_docs)]
118271
pub enum OperationType {
119272
Query,
120273
Mutation,
121274
Subscription,
122275
}
123276

124-
#[allow(missing_docs)]
125277
#[derive(Clone, PartialEq, Debug)]
278+
#[allow(missing_docs)]
126279
pub struct Operation<'a, S> {
127280
pub operation_type: OperationType,
128281
pub name: Option<Spanning<&'a str>>,
@@ -132,15 +285,16 @@ pub struct Operation<'a, S> {
132285
}
133286

134287
#[derive(Clone, PartialEq, Debug)]
288+
#[allow(missing_docs)]
135289
pub struct Fragment<'a, S> {
136290
pub name: Spanning<&'a str>,
137291
pub type_condition: Spanning<&'a str>,
138292
pub directives: Option<Vec<Spanning<Directive<'a, S>>>>,
139293
pub selection_set: Vec<Selection<'a, S>>,
140294
}
141295

142-
#[doc(hidden)]
143296
#[derive(Clone, PartialEq, Debug)]
297+
#[allow(missing_docs)]
144298
pub enum Definition<'a, S> {
145299
Operation(Spanning<Operation<'a, S>>),
146300
Fragment(Spanning<Fragment<'a, S>>),

juniper/src/parser/lexer.rs

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub struct Lexer<'a> {
2121
///
2222
/// This is only used for tagging how the lexer has interpreted a value literal
2323
#[derive(Debug, PartialEq, Clone, Copy)]
24+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2425
#[allow(missing_docs)]
2526
pub enum ScalarToken<'a> {
2627
String(&'a str),
@@ -30,6 +31,7 @@ pub enum ScalarToken<'a> {
3031

3132
/// A single token in the input source
3233
#[derive(Debug, PartialEq, Clone, Copy)]
34+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
3335
#[allow(missing_docs)]
3436
pub enum Token<'a> {
3537
Name(&'a str),

juniper/src/parser/utils.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::fmt;
22

33
/// A reference to a line and column in an input source file
44
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
5+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
56
pub struct SourcePosition {
67
index: usize,
78
line: usize,
@@ -14,6 +15,7 @@ pub struct SourcePosition {
1415
/// character pointed by the `start` field and ending just before the `end`
1516
/// marker.
1617
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
18+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1719
pub struct Spanning<T> {
1820
/// The wrapped item
1921
pub item: T,

0 commit comments

Comments
 (0)