1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use crate::{
    alloc_generators::{CollectionGenerator, DEFAULT_LEN_RANGE},
    Driver, TypeGenerator, TypeGeneratorWithParams, TypeValueGenerator, ValueGenerator,
};
use alloc::string::String;
use core::ops::RangeInclusive;

pub struct StringGenerator<C, L> {
    chars: C,
    len: L,
}

impl<C, L> StringGenerator<C, L> {
    pub fn chars<Gen: ValueGenerator<Output = char>>(self, chars: Gen) -> StringGenerator<Gen, L> {
        StringGenerator {
            chars,
            len: self.len,
        }
    }

    pub fn map_chars<Gen: ValueGenerator<Output = char>, F: Fn(C) -> Gen>(
        self,
        map: F,
    ) -> StringGenerator<Gen, L> {
        StringGenerator {
            chars: map(self.chars),
            len: self.len,
        }
    }

    pub fn len<Gen: ValueGenerator<Output = Len>, Len: Into<usize>>(
        self,
        len: Gen,
    ) -> StringGenerator<C, Gen> {
        StringGenerator {
            chars: self.chars,
            len,
        }
    }

    pub fn map_len<Gen: ValueGenerator<Output = Len>, F: Fn(L) -> Gen, Len: Into<usize>>(
        self,
        map: F,
    ) -> StringGenerator<C, Gen> {
        StringGenerator {
            chars: self.chars,
            len: map(self.len),
        }
    }
}

impl<G: ValueGenerator<Output = char>, L: ValueGenerator<Output = Len>, Len: Into<usize>>
    ValueGenerator for StringGenerator<G, L>
{
    type Output = String;

    fn generate<D: Driver>(&self, driver: &mut D) -> Option<Self::Output> {
        let len = ValueGenerator::generate(&self.len, driver)?.into();

        Iterator::map(0..len, |_| ValueGenerator::generate(&self.chars, driver)).collect()
    }

    fn mutate<D: Driver>(&self, driver: &mut D, value: &mut Self::Output) -> Option<()> {
        let len = ValueGenerator::generate(&self.len, driver)?.into();
        CollectionGenerator::mutate_collection(value, driver, len, &self.chars)
    }
}

impl CollectionGenerator for String {
    type Item = char;

    fn mutate_collection<D: Driver, G>(
        &mut self,
        driver: &mut D,
        new_len: usize,
        item_gen: &G,
    ) -> Option<()>
    where
        G: ValueGenerator<Output = Self::Item>,
    {
        let prev = core::mem::take(self);

        let to_mutate = self.len().min(new_len);
        let to_append = new_len.saturating_sub(to_mutate);

        for mut c in prev.chars().take(to_mutate) {
            item_gen.mutate(driver, &mut c)?;
            self.push(c);
        }

        for _ in 0..to_append {
            self.push(item_gen.generate(driver)?);
        }

        // make sure the char count is correct
        #[cfg(test)]
        assert_eq!(self.chars().count(), new_len);

        Some(())
    }
}

impl TypeGenerator for String {
    fn generate<D: Driver>(driver: &mut D) -> Option<Self> {
        String::gen_with().generate(driver)
    }
}

impl TypeGeneratorWithParams for String {
    type Output = StringGenerator<TypeValueGenerator<char>, RangeInclusive<usize>>;

    fn gen_with() -> Self::Output {
        StringGenerator {
            chars: Default::default(),
            len: DEFAULT_LEN_RANGE,
        }
    }
}

impl ValueGenerator for String {
    type Output = Self;

    fn generate<D: Driver>(&self, _driver: &mut D) -> Option<Self> {
        Some(self.clone())
    }
}

#[test]
fn string_type_test() {
    let _ = generator_test!(gen::<String>());
}

#[test]
fn string_with_test() {
    for _ in 0..100 {
        if let Some(string) = generator_test!(gen::<String>().with().len(32usize)) {
            assert_eq!(string.chars().count(), 32usize);
            return;
        }
    }

    panic!("failed to generate a valid string");
}