use proptest::prelude::*;
use proptest::strategy::BoxedStrategy;
use crate::tests::property_tests::generators::names::arb_enum_value_name;
use crate::tests::property_tests::generators::names::arb_field_name;
use crate::tests::property_tests::generators::names::arb_name;
use crate::tests::property_tests::generators::whitespace::arb_separator;
use crate::tests::property_tests::generators::whitespace::join_items;
pub fn arb_int_value() -> BoxedStrategy<String> {
prop_oneof![
Just("0".to_string()),
(-999_999i32..999_999i32).prop_map(|n| n.to_string()),
]
.boxed()
}
pub fn arb_float_value() -> BoxedStrategy<String> {
prop_oneof![
(-999i32..999, 0u32..999_999).prop_map(|(int, frac)| {
format!("{int}.{frac}")
}),
(-999i32..999, -10i32..10).prop_map(|(int, exp)| {
format!("{int}e{exp}")
}),
(-99i32..99, 0u32..9999, -5i32..5).prop_map(|(int, frac, exp)| {
format!("{int}.{frac}e{exp}")
}),
]
.boxed()
}
pub fn arb_string_value() -> BoxedStrategy<String> {
prop_oneof![
"[a-zA-Z0-9 _.,!?:;@#$%^&*()+=/<>-]{0,30}"
.prop_map(|s| format!("\"{}\"", escape_string_chars(&s))),
Just("\"\"".to_string()),
prop::collection::vec(arb_string_char(), 1..10)
.prop_map(|chars| format!("\"{}\"", chars.join(""))),
]
.boxed()
}
fn arb_string_char() -> BoxedStrategy<String> {
prop_oneof![
4 => "[a-zA-Z0-9 ]".prop_map(|s| s.to_string()),
1 => Just("\\n".to_string()),
1 => Just("\\t".to_string()),
1 => Just("\\\\".to_string()),
1 => Just("\\\"".to_string()),
1 => Just("\\/".to_string()),
]
.boxed()
}
fn escape_string_chars(s: &str) -> String {
let mut result = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\t' => result.push_str("\\t"),
_ => result.push(ch),
}
}
result
}
pub fn arb_block_string_value() -> BoxedStrategy<String> {
"[a-zA-Z0-9 _.,!?:;@#$%^&*()+=/<>\\n-]{0,50}"
.prop_filter("no triple quotes", |s| !s.contains("\"\"\""))
.prop_map(|s| format!("\"\"\"{s}\"\"\""))
.boxed()
}
pub fn arb_boolean_value() -> BoxedStrategy<String> {
prop_oneof![Just("true".to_string()), Just("false".to_string()),].boxed()
}
pub fn arb_null_value() -> BoxedStrategy<String> {
Just("null".to_string()).boxed()
}
pub fn arb_enum_value() -> BoxedStrategy<String> {
arb_enum_value_name()
}
pub fn arb_variable_value() -> BoxedStrategy<String> {
arb_name().prop_map(|n| format!("${n}")).boxed()
}
pub fn arb_value(depth: usize) -> BoxedStrategy<String> {
if depth == 0 {
arb_scalar_value_or_variable()
} else {
prop_oneof![
4 => arb_scalar_value_or_variable(),
1 => arb_list_value(depth - 1),
1 => arb_object_value(depth - 1),
]
.boxed()
}
}
pub fn arb_const_value(depth: usize) -> BoxedStrategy<String> {
if depth == 0 {
arb_scalar_value()
} else {
prop_oneof![
4 => arb_scalar_value(),
1 => arb_const_list_value(depth - 1),
1 => arb_const_object_value(depth - 1),
]
.boxed()
}
}
fn arb_scalar_value_or_variable() -> BoxedStrategy<String> {
prop_oneof![
2 => arb_int_value(),
2 => arb_float_value(),
2 => arb_string_value(),
1 => arb_block_string_value(),
1 => arb_boolean_value(),
1 => arb_null_value(),
2 => arb_enum_value(),
1 => arb_variable_value(),
]
.boxed()
}
fn arb_scalar_value() -> BoxedStrategy<String> {
prop_oneof![
2 => arb_int_value(),
2 => arb_float_value(),
2 => arb_string_value(),
1 => arb_block_string_value(),
1 => arb_boolean_value(),
1 => arb_null_value(),
2 => arb_enum_value(),
]
.boxed()
}
fn arb_list_value(depth: usize) -> BoxedStrategy<String> {
prop::collection::vec((arb_value(depth), arb_separator()), 0..4)
.prop_map(|pairs| format!("[{}]", join_items(&pairs)))
.boxed()
}
fn arb_object_value(depth: usize) -> BoxedStrategy<String> {
let arb_entry = (arb_field_name(), arb_value(depth))
.prop_map(|(name, val)| format!("{name}: {val}"));
prop::collection::vec((arb_entry, arb_separator()), 0..4)
.prop_map(|pairs| format!("{{{}}}", join_items(&pairs)))
.boxed()
}
fn arb_const_list_value(depth: usize) -> BoxedStrategy<String> {
prop::collection::vec((arb_const_value(depth), arb_separator()), 0..4)
.prop_map(|pairs| format!("[{}]", join_items(&pairs)))
.boxed()
}
fn arb_const_object_value(depth: usize) -> BoxedStrategy<String> {
let arb_entry = (arb_field_name(), arb_const_value(depth))
.prop_map(|(name, val)| format!("{name}: {val}"));
prop::collection::vec((arb_entry, arb_separator()), 0..4)
.prop_map(|pairs| format!("{{{}}}", join_items(&pairs)))
.boxed()
}