use super::{BasicGenerator, Generator, TestCase};
use crate::cbor_utils::{cbor_array, cbor_map, map_extend, map_insert};
use ciborium::Value;
const SURROGATE_CATEGORIES: &[&str] = &["Cs", "C"];
struct CharacterFields {
codec: Option<String>,
min_codepoint: Option<u32>,
max_codepoint: Option<u32>,
categories: Option<Vec<String>>,
exclude_categories: Option<Vec<String>>,
include_characters: Option<String>,
exclude_characters: Option<String>,
}
impl CharacterFields {
fn new() -> Self {
CharacterFields {
codec: None,
min_codepoint: None,
max_codepoint: None,
categories: None,
exclude_categories: None,
include_characters: None,
exclude_characters: None,
}
}
fn to_schema(&self) -> Value {
let mut schema = cbor_map! {};
if let Some(ref codec) = self.codec {
map_insert(&mut schema, "codec", codec.as_str());
}
if let Some(min_cp) = self.min_codepoint {
map_insert(&mut schema, "min_codepoint", min_cp as u64);
}
if let Some(max_cp) = self.max_codepoint {
map_insert(&mut schema, "max_codepoint", max_cp as u64);
}
if let Some(ref cats) = self.categories {
for cat in cats {
assert!(
!SURROGATE_CATEGORIES.contains(&cat.as_str()),
"Category \"{cat}\" includes surrogate codepoints (Cs), \
which Rust strings cannot represent."
);
}
let arr = Value::Array(cats.iter().map(|c| Value::from(c.as_str())).collect());
map_insert(&mut schema, "categories", arr);
} else {
let mut excl = self.exclude_categories.clone().unwrap_or_default();
if !excl.iter().any(|c| c == "Cs") {
excl.push("Cs".to_string());
}
let arr = Value::Array(excl.iter().map(|c| Value::from(c.as_str())).collect());
map_insert(&mut schema, "exclude_categories", arr);
}
if let Some(ref incl) = self.include_characters {
map_insert(&mut schema, "include_characters", incl.as_str());
}
if let Some(ref excl) = self.exclude_characters {
map_insert(&mut schema, "exclude_characters", excl.as_str());
}
schema
}
}
pub struct TextGenerator {
min_size: usize,
max_size: Option<usize>,
char_fields: CharacterFields,
alphabet_called: bool,
char_param_called: bool,
}
impl TextGenerator {
pub fn min_size(mut self, min_size: usize) -> Self {
self.min_size = min_size;
self
}
pub fn max_size(mut self, max_size: usize) -> Self {
self.max_size = Some(max_size);
self
}
pub fn alphabet(mut self, chars: &str) -> Self {
self.char_fields = CharacterFields {
codec: None,
min_codepoint: None,
max_codepoint: None,
categories: Some(vec![]),
exclude_categories: None,
include_characters: Some(chars.to_string()),
exclude_characters: None,
};
self.alphabet_called = true;
self
}
pub fn codec(mut self, codec: &str) -> Self {
self.char_param_called = true;
self.char_fields.codec = Some(codec.to_string());
self
}
pub fn min_codepoint(mut self, min_codepoint: u32) -> Self {
self.char_param_called = true;
self.char_fields.min_codepoint = Some(min_codepoint);
self
}
pub fn max_codepoint(mut self, max_codepoint: u32) -> Self {
self.char_param_called = true;
self.char_fields.max_codepoint = Some(max_codepoint);
self
}
pub fn categories(mut self, categories: &[&str]) -> Self {
self.char_param_called = true;
self.char_fields.categories = Some(categories.iter().map(|s| s.to_string()).collect());
self
}
pub fn exclude_categories(mut self, exclude_categories: &[&str]) -> Self {
self.char_param_called = true;
self.char_fields.exclude_categories =
Some(exclude_categories.iter().map(|s| s.to_string()).collect());
self
}
pub fn include_characters(mut self, include_characters: &str) -> Self {
self.char_param_called = true;
self.char_fields.include_characters = Some(include_characters.to_string());
self
}
pub fn exclude_characters(mut self, exclude_characters: &str) -> Self {
self.char_param_called = true;
self.char_fields.exclude_characters = Some(exclude_characters.to_string());
self
}
fn build_schema(&self) -> Value {
assert!(
!(self.alphabet_called && self.char_param_called),
"Cannot combine .alphabet() with character methods."
);
if let Some(max) = self.max_size {
assert!(self.min_size <= max, "Cannot have max_size < min_size");
}
let mut schema = cbor_map! {
"type" => "string",
"min_size" => self.min_size as u64
};
if let Some(max) = self.max_size {
map_insert(&mut schema, "max_size", max as u64);
}
map_extend(&mut schema, self.char_fields.to_schema());
schema
}
}
impl Generator<String> for TextGenerator {
fn do_draw(&self, tc: &TestCase) -> String {
super::generate_from_schema(tc, &self.build_schema())
}
fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
Some(BasicGenerator::new(
self.build_schema(),
super::deserialize_value,
))
}
}
pub fn text() -> TextGenerator {
TextGenerator {
min_size: 0,
max_size: None,
char_fields: CharacterFields::new(),
alphabet_called: false,
char_param_called: false,
}
}
pub struct CharactersGenerator {
char_fields: CharacterFields,
}
impl CharactersGenerator {
pub fn codec(mut self, codec: &str) -> Self {
self.char_fields.codec = Some(codec.to_string());
self
}
pub fn min_codepoint(mut self, min_codepoint: u32) -> Self {
self.char_fields.min_codepoint = Some(min_codepoint);
self
}
pub fn max_codepoint(mut self, max_codepoint: u32) -> Self {
self.char_fields.max_codepoint = Some(max_codepoint);
self
}
pub fn categories(mut self, categories: &[&str]) -> Self {
self.char_fields.categories = Some(categories.iter().map(|s| s.to_string()).collect());
self
}
pub fn exclude_categories(mut self, exclude_categories: &[&str]) -> Self {
self.char_fields.exclude_categories =
Some(exclude_categories.iter().map(|s| s.to_string()).collect());
self
}
pub fn include_characters(mut self, include_characters: &str) -> Self {
self.char_fields.include_characters = Some(include_characters.to_string());
self
}
pub fn exclude_characters(mut self, exclude_characters: &str) -> Self {
self.char_fields.exclude_characters = Some(exclude_characters.to_string());
self
}
fn build_schema(&self) -> Value {
let mut schema = cbor_map! {
"type" => "string",
"min_size" => 1u64,
"max_size" => 1u64
};
map_extend(&mut schema, self.char_fields.to_schema());
schema
}
pub(super) fn build_alphabet_schema(&self) -> Value {
self.char_fields.to_schema()
}
}
fn parse_char(raw: Value) -> char {
let s: String = super::deserialize_value(raw);
let mut chars = s.chars();
let c = chars
.next()
.expect("expected a single character, got empty string");
assert!(
chars.next().is_none(),
"expected a single character, got multiple"
);
c
}
impl Generator<char> for CharactersGenerator {
fn do_draw(&self, tc: &TestCase) -> char {
parse_char(super::generate_raw(tc, &self.build_schema()))
}
fn as_basic(&self) -> Option<BasicGenerator<'_, char>> {
Some(BasicGenerator::new(self.build_schema(), parse_char))
}
}
pub fn characters() -> CharactersGenerator {
CharactersGenerator {
char_fields: CharacterFields::new(),
}
}
pub struct RegexGenerator {
pattern: String,
fullmatch: bool,
alphabet: Option<CharactersGenerator>,
}
impl RegexGenerator {
pub fn fullmatch(mut self, fullmatch: bool) -> Self {
self.fullmatch = fullmatch;
self
}
pub fn alphabet(mut self, alphabet: CharactersGenerator) -> Self {
self.alphabet = Some(alphabet);
self
}
fn build_schema(&self) -> Value {
let mut schema = cbor_map! {
"type" => "regex",
"pattern" => self.pattern.as_str(),
"fullmatch" => self.fullmatch
};
if let Some(ref alphabet) = self.alphabet {
map_insert(&mut schema, "alphabet", alphabet.build_alphabet_schema());
}
schema
}
}
impl Generator<String> for RegexGenerator {
fn do_draw(&self, tc: &TestCase) -> String {
super::generate_from_schema(tc, &self.build_schema())
}
fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
Some(BasicGenerator::new(
self.build_schema(),
super::deserialize_value,
))
}
}
pub fn from_regex(pattern: &str) -> RegexGenerator {
RegexGenerator {
pattern: pattern.to_string(),
fullmatch: false,
alphabet: None,
}
}
pub struct BinaryGenerator {
min_size: usize,
max_size: Option<usize>,
}
impl BinaryGenerator {
pub fn min_size(mut self, min_size: usize) -> Self {
self.min_size = min_size;
self
}
pub fn max_size(mut self, max_size: usize) -> Self {
self.max_size = Some(max_size);
self
}
fn build_schema(&self) -> Value {
if let Some(max) = self.max_size {
assert!(self.min_size <= max, "Cannot have max_size < min_size");
}
let mut schema = cbor_map! {
"type" => "binary",
"min_size" => self.min_size as u64
};
if let Some(max) = self.max_size {
map_insert(&mut schema, "max_size", max as u64);
}
schema
}
}
fn parse_binary(raw: Value) -> Vec<u8> {
match raw {
Value::Bytes(bytes) => bytes,
_ => panic!("expected Value::Bytes, got {:?}", raw), }
}
impl Generator<Vec<u8>> for BinaryGenerator {
fn do_draw(&self, tc: &TestCase) -> Vec<u8> {
parse_binary(super::generate_raw(tc, &self.build_schema()))
}
fn as_basic(&self) -> Option<BasicGenerator<'_, Vec<u8>>> {
Some(BasicGenerator::new(self.build_schema(), parse_binary))
}
}
pub fn binary() -> BinaryGenerator {
BinaryGenerator {
min_size: 0,
max_size: None,
}
}
pub struct EmailGenerator;
impl Generator<String> for EmailGenerator {
fn do_draw(&self, tc: &TestCase) -> String {
super::generate_from_schema(tc, &cbor_map! {"type" => "email"})
}
fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
Some(BasicGenerator::new(cbor_map! {"type" => "email"}, |raw| {
super::deserialize_value(raw)
}))
}
}
pub fn emails() -> EmailGenerator {
EmailGenerator
}
pub struct UrlGenerator;
impl Generator<String> for UrlGenerator {
fn do_draw(&self, tc: &TestCase) -> String {
super::generate_from_schema(tc, &cbor_map! {"type" => "url"})
}
fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
Some(BasicGenerator::new(cbor_map! {"type" => "url"}, |raw| {
super::deserialize_value(raw)
}))
}
}
pub fn urls() -> UrlGenerator {
UrlGenerator
}
pub struct DomainGenerator {
max_length: usize,
}
impl DomainGenerator {
pub fn max_length(mut self, max_length: usize) -> Self {
self.max_length = max_length;
self
}
fn build_schema(&self) -> Value {
assert!(
self.max_length >= 4 && self.max_length <= 255,
"max_length must be between 4 and 255"
);
cbor_map! { "type" => "domain",
"max_length" => self.max_length as u64 }
}
}
impl Generator<String> for DomainGenerator {
fn do_draw(&self, tc: &TestCase) -> String {
super::generate_from_schema(tc, &self.build_schema())
}
fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
Some(BasicGenerator::new(self.build_schema(), |raw| {
super::deserialize_value(raw) }))
}
}
pub fn domains() -> DomainGenerator {
DomainGenerator { max_length: 255 }
}
#[derive(Clone, Copy)]
pub enum IpVersion {
V4,
V6,
}
pub struct IpAddressGenerator {
version: Option<IpVersion>,
}
impl IpAddressGenerator {
pub fn v4(mut self) -> Self {
self.version = Some(IpVersion::V4);
self
}
pub fn v6(mut self) -> Self {
self.version = Some(IpVersion::V6);
self
}
fn build_schema(&self) -> Value {
match self.version {
Some(IpVersion::V4) => cbor_map! {"type" => "ipv4"},
Some(IpVersion::V6) => cbor_map! {"type" => "ipv6"},
None => cbor_map! {
"one_of" => cbor_array![
cbor_map!{"type" => "ipv4"},
cbor_map!{"type" => "ipv6"}
]
},
}
}
}
impl Generator<String> for IpAddressGenerator {
fn do_draw(&self, tc: &TestCase) -> String {
super::generate_from_schema(tc, &self.build_schema())
}
fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
Some(BasicGenerator::new(self.build_schema(), |raw| {
super::deserialize_value(raw)
}))
}
}
pub fn ip_addresses() -> IpAddressGenerator {
IpAddressGenerator { version: None }
}
pub struct DateGenerator;
impl Generator<String> for DateGenerator {
fn do_draw(&self, tc: &TestCase) -> String {
super::generate_from_schema(tc, &cbor_map! {"type" => "date"})
}
fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
Some(BasicGenerator::new(cbor_map! {"type" => "date"}, |raw| {
super::deserialize_value(raw)
}))
}
}
pub fn dates() -> DateGenerator {
DateGenerator
}
pub struct TimeGenerator;
impl Generator<String> for TimeGenerator {
fn do_draw(&self, tc: &TestCase) -> String {
super::generate_from_schema(tc, &cbor_map! {"type" => "time"})
}
fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
Some(BasicGenerator::new(cbor_map! {"type" => "time"}, |raw| {
super::deserialize_value(raw)
}))
}
}
pub fn times() -> TimeGenerator {
TimeGenerator
}
pub struct DateTimeGenerator;
impl Generator<String> for DateTimeGenerator {
fn do_draw(&self, tc: &TestCase) -> String {
super::generate_from_schema(tc, &cbor_map! {"type" => "datetime"})
}
fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
Some(BasicGenerator::new(
cbor_map! {"type" => "datetime"},
super::deserialize_value,
))
}
}
pub fn datetimes() -> DateTimeGenerator {
DateTimeGenerator
}