use proptest::prelude::*;
use proptest::strategy::BoxedStrategy;
use crate::tests::property_tests::generators::names::arb_directive_name;
use crate::tests::property_tests::generators::names::arb_field_name;
use crate::tests::property_tests::generators::type_annotations::arb_type_annotation;
use crate::tests::property_tests::generators::values::arb_const_value;
use crate::tests::property_tests::generators::values::arb_value;
use crate::tests::property_tests::generators::whitespace::arb_separator;
use crate::tests::property_tests::generators::whitespace::join_items;
pub fn arb_directive_annotation(depth: usize) -> BoxedStrategy<String> {
(
arb_directive_name(),
prop::option::of(arb_directive_arguments(depth)),
)
.prop_map(|(name, args)| match args {
Some(a) => format!("@{name}({a})"),
None => format!("@{name}"),
})
.boxed()
}
pub fn arb_const_directive_annotation(depth: usize) -> BoxedStrategy<String> {
(
arb_directive_name(),
prop::option::of(arb_const_directive_arguments(depth)),
)
.prop_map(|(name, args)| match args {
Some(a) => format!("@{name}({a})"),
None => format!("@{name}"),
})
.boxed()
}
pub fn arb_directives(depth: usize) -> BoxedStrategy<String> {
prop::collection::vec((arb_directive_annotation(depth), arb_separator()), 0..3)
.prop_map(|pairs| {
if pairs.is_empty() {
String::new()
} else {
format!(" {}", join_items(&pairs))
}
})
.boxed()
}
pub fn arb_const_directives(depth: usize) -> BoxedStrategy<String> {
prop::collection::vec(
(arb_const_directive_annotation(depth), arb_separator()),
0..3,
)
.prop_map(|pairs| {
if pairs.is_empty() {
String::new()
} else {
format!(" {}", join_items(&pairs))
}
})
.boxed()
}
pub fn arb_directives_non_empty(depth: usize) -> BoxedStrategy<String> {
prop::collection::vec(
(arb_const_directive_annotation(depth), arb_separator()),
1..4,
)
.prop_map(|pairs| format!(" {}", join_items(&pairs)))
.boxed()
}
fn arb_directive_arguments(depth: usize) -> BoxedStrategy<String> {
let arb_arg = (arb_field_name(), arb_value(depth))
.prop_map(|(name, val)| format!("{name}: {val}"));
prop::collection::vec((arb_arg, arb_separator()), 1..4)
.prop_map(|pairs| join_items(&pairs))
.boxed()
}
fn arb_const_directive_arguments(depth: usize) -> BoxedStrategy<String> {
let arb_arg = (arb_field_name(), arb_const_value(depth))
.prop_map(|(name, val)| format!("{name}: {val}"));
prop::collection::vec((arb_arg, arb_separator()), 1..4)
.prop_map(|pairs| join_items(&pairs))
.boxed()
}
const EXECUTABLE_DIRECTIVE_LOCATIONS: &[&str] = &[
"FIELD",
"FIELD_DEFINITION",
"FRAGMENT_DEFINITION",
"FRAGMENT_SPREAD",
"INLINE_FRAGMENT",
"MUTATION",
"QUERY",
"SUBSCRIPTION",
];
const TYPE_SYSTEM_DIRECTIVE_LOCATIONS: &[&str] = &[
"ARGUMENT_DEFINITION",
"ENUM",
"ENUM_VALUE",
"INPUT_FIELD_DEFINITION",
"INPUT_OBJECT",
"INTERFACE",
"OBJECT",
"SCALAR",
"SCHEMA",
"UNION",
"VARIABLE_DEFINITION",
];
pub fn arb_directive_definition(depth: usize) -> BoxedStrategy<String> {
(
arb_optional_description(),
arb_directive_name(),
prop::option::of(arb_input_value_definitions(depth)),
prop::bool::ANY,
arb_directive_locations(),
)
.prop_map(|(desc, name, args, repeatable, locations)| {
let desc_str = desc.map_or(String::new(), |d| format!("{d}\n"));
let args_str = args.map_or(String::new(), |a| format!("({a})"));
let rep_str = if repeatable { " repeatable" } else { "" };
format!("{desc_str}directive @{name}{args_str}{rep_str} on {locations}")
})
.boxed()
}
fn arb_directive_locations() -> BoxedStrategy<String> {
let all_locations: Vec<&str> = EXECUTABLE_DIRECTIVE_LOCATIONS
.iter()
.chain(TYPE_SYSTEM_DIRECTIVE_LOCATIONS.iter())
.copied()
.collect();
prop::sample::subsequence(all_locations.clone(), 1..=4)
.prop_map(|locs| {
locs.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(" | ")
})
.boxed()
}
fn arb_optional_description() -> BoxedStrategy<Option<String>> {
prop::option::weighted(
0.3,
prop_oneof![
"[a-zA-Z0-9 .,!?-]{1,40}".prop_map(|s| format!("\"{s}\"")),
"[a-zA-Z0-9 .,!?-]{1,60}".prop_map(|s| format!("\"\"\"{s}\"\"\"")),
],
)
.boxed()
}
fn arb_input_value_definitions(depth: usize) -> BoxedStrategy<String> {
prop::collection::vec((arb_input_value_definition(depth), arb_separator()), 1..4)
.prop_map(|pairs| join_items(&pairs))
.boxed()
}
fn arb_input_value_definition(depth: usize) -> BoxedStrategy<String> {
(
arb_field_name(),
arb_type_annotation(2),
prop::option::weighted(0.3, arb_const_value(depth)),
arb_const_directives(depth),
)
.prop_map(|(name, ty, default, dirs)| {
let default_str = default.map_or(String::new(), |d| format!(" = {d}"));
format!("{name}: {ty}{default_str}{dirs}")
})
.boxed()
}