protest_extras/
lib.rs

1#![allow(clippy::module_inception)]
2
3//! # Protest Extras
4//!
5//! Additional generators for the Protest property testing library.
6//!
7//! This crate provides extra generators for:
8//! - **Network**: IP addresses, URLs, email addresses
9//! - **DateTime**: Unix timestamps, durations, system time ranges
10//! - **Text**: Alphabetic, alphanumeric, identifiers, sentences
11//! - **Collections**: Non-empty, sorted, unique collections
12//! - **Numeric**: Positive integers, even numbers, primes, percentages
13//! - **Domain**: UUIDs, Base64, hex strings, file paths
14//!
15//! All features use **std library only** by default, with optional external dependencies
16//! available via feature flags.
17//!
18//! ## No External Dependencies
19//!
20//! This crate uses **std library only** (plus `rand` which you already have from `protest`).
21//! All generators including UUID v4 are implemented without external dependencies.
22//!
23//! ## Quick Start
24//!
25//! ```rust
26//! use protest_extras::prelude::*;
27//! use protest::Generator;
28//! use rand::thread_rng;
29//!
30//! # fn main() {
31//! let mut rng = thread_rng();
32//! let config = protest::GeneratorConfig::default();
33//!
34//! // Generate an IPv4 address
35//! let ip_gen = IpAddressGenerator::ipv4();
36//! let ip = ip_gen.generate(&mut rng, &config);
37//! assert!(ip.contains('.'));
38//!
39//! // Generate an email address
40//! let email_gen = EmailGenerator::new();
41//! let email = email_gen.generate(&mut rng, &config);
42//! assert!(email.contains('@'));
43//!
44//! // Generate a UUID
45//! let uuid_gen = UuidV4Generator::new();
46//! let uuid = uuid_gen.generate(&mut rng, &config);
47//! assert_eq!(uuid.len(), 36); // UUID format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
48//! # }
49//! ```
50//!
51//! ## Examples by Category
52//!
53//! ### Network Generators
54//!
55//! ```rust
56//! use protest_extras::prelude::*;
57//! use protest::Generator;
58//! use rand::thread_rng;
59//!
60//! # fn main() {
61//! let mut rng = thread_rng();
62//! let config = protest::GeneratorConfig::default();
63//!
64//! // Generate IPv4 addresses
65//! let generator = IpAddressGenerator::ipv4();
66//! let ip = generator.generate(&mut rng, &config);
67//! let parts: Vec<&str> = ip.split('.').collect();
68//! assert_eq!(parts.len(), 4);
69//!
70//! // Generate IPv6 addresses
71//! let generator = IpAddressGenerator::ipv6();
72//! let ip = generator.generate(&mut rng, &config);
73//! assert!(ip.contains(':'));
74//!
75//! // Generate email addresses
76//! let generator = EmailGenerator::new();
77//! let email = generator.generate(&mut rng, &config);
78//! assert_eq!(email.matches('@').count(), 1);
79//!
80//! // Generate URLs
81//! let generator = UrlGenerator::new();
82//! let url = generator.generate(&mut rng, &config);
83//! assert!(url.starts_with("http://") || url.starts_with("https://"));
84//! # }
85//! ```
86//!
87//! ### DateTime Generators
88//!
89//! ```rust
90//! use protest_extras::prelude::*;
91//! use protest::Generator;
92//! use rand::thread_rng;
93//! use std::time::{SystemTime, UNIX_EPOCH};
94//!
95//! # fn main() {
96//! let mut rng = thread_rng();
97//! let config = protest::GeneratorConfig::default();
98//!
99//! // Generate recent Unix timestamps
100//! let generator = TimestampGenerator::recent();
101//! let timestamp = generator.generate(&mut rng, &config);
102//! let now = SystemTime::now()
103//!     .duration_since(UNIX_EPOCH)
104//!     .unwrap()
105//!     .as_secs() as i64;
106//! assert!(timestamp <= now);
107//!
108//! // Generate durations
109//! let generator = DurationGenerator::seconds();
110//! let duration = generator.generate(&mut rng, &config);
111//! assert!(duration.as_secs() <= 60);
112//!
113//! // Generate system times around now
114//! let generator = SystemTimeGenerator::around_now();
115//! let time = generator.generate(&mut rng, &config);
116//! // Time should be valid
117//! assert!(time.duration_since(UNIX_EPOCH).is_ok());
118//! # }
119//! ```
120//!
121//! ### Text Generators
122//!
123//! ```rust
124//! use protest_extras::prelude::*;
125//! use protest::Generator;
126//! use rand::thread_rng;
127//!
128//! # fn main() {
129//! let mut rng = thread_rng();
130//! let config = protest::GeneratorConfig::default();
131//!
132//! // Generate lowercase alphabetic strings
133//! let generator = AlphabeticGenerator::lowercase(5, 10);
134//! let text = generator.generate(&mut rng, &config);
135//! assert!(text.len() >= 5 && text.len() <= 10);
136//! assert!(text.chars().all(|c| c.is_lowercase()));
137//!
138//! // Generate valid identifiers
139//! let generator = IdentifierGenerator::new(3, 15);
140//! let id = generator.generate(&mut rng, &config);
141//! assert!(id.chars().next().unwrap().is_alphabetic() || id.chars().next().unwrap() == '_');
142//! assert!(id.chars().all(|c| c.is_alphanumeric() || c == '_'));
143//!
144//! // Generate sentences
145//! let generator = SentenceGenerator::new(3, 10);
146//! let sentence = generator.generate(&mut rng, &config);
147//! assert!(sentence.ends_with('.'));
148//! # }
149//! ```
150//!
151//! ### Collection Generators
152//!
153//! ```rust
154//! use protest_extras::prelude::*;
155//! use protest::{Generator, IntGenerator};
156//! use rand::thread_rng;
157//!
158//! # fn main() {
159//! let mut rng = thread_rng();
160//! let config = protest::GeneratorConfig::default();
161//!
162//! // Generate non-empty vectors
163//! let generator = NonEmptyVecGenerator::new(IntGenerator::new(0, 100), 1, 10);
164//! let vec = generator.generate(&mut rng, &config);
165//! assert!(!vec.is_empty());
166//! assert!(vec.len() <= 10);
167//!
168//! // Generate sorted vectors
169//! let generator = SortedVecGenerator::new(IntGenerator::new(0, 100), 0, 20);
170//! let vec = generator.generate(&mut rng, &config);
171//! for window in vec.windows(2) {
172//!     assert!(window[0] <= window[1]);
173//! }
174//!
175//! // Generate vectors with unique elements
176//! let generator = UniqueVecGenerator::new(IntGenerator::new(0, 1000), 5, 20);
177//! let vec = generator.generate(&mut rng, &config);
178//! let unique_count = vec.iter().collect::<std::collections::HashSet<_>>().len();
179//! assert_eq!(unique_count, vec.len());
180//! # }
181//! ```
182//!
183//! ### Numeric Generators
184//!
185//! ```rust
186//! use protest_extras::prelude::*;
187//! use protest::Generator;
188//! use rand::thread_rng;
189//!
190//! # fn main() {
191//! let mut rng = thread_rng();
192//! let config = protest::GeneratorConfig::default();
193//!
194//! // Generate positive integers
195//! let generator = PositiveIntGenerator::<u32>::new(1, 1000);
196//! let n = generator.generate(&mut rng, &config);
197//! assert!(n >= 1 && n <= 1000);
198//!
199//! // Generate even numbers
200//! let generator = EvenNumberGenerator::new(0i32, 100i32);
201//! let n = generator.generate(&mut rng, &config);
202//! assert_eq!(n % 2, 0);
203//!
204//! // Generate prime numbers
205//! let generator = PrimeNumberGenerator::new(2, 100);
206//! let n = generator.generate(&mut rng, &config);
207//! assert!(n >= 2);
208//!
209//! // Generate percentages
210//! let generator = PercentageGenerator::new();
211//! let p = generator.generate(&mut rng, &config);
212//! assert!(p >= 0.0 && p <= 100.0);
213//! # }
214//! ```
215//!
216//! ### Domain Generators
217//!
218//! ```rust
219//! use protest_extras::prelude::*;
220//! use protest::Generator;
221//! use rand::thread_rng;
222//!
223//! # fn main() {
224//! let mut rng = thread_rng();
225//! let config = protest::GeneratorConfig::default();
226//!
227//! // Generate hexadecimal strings
228//! let generator = HexGenerator::new(8, 16);
229//! let hex = generator.generate(&mut rng, &config);
230//! assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
231//!
232//! // Generate Base64 strings
233//! let generator = Base64Generator::new(6, 32);
234//! let b64 = generator.generate(&mut rng, &config);
235//! assert!(!b64.is_empty());
236//!
237//! // Generate file paths
238//! let generator = PathGenerator::new(1, 4);
239//! let path = generator.generate(&mut rng, &config);
240//! assert!(path.components().count() >= 1);
241//!
242//! // Generate UUID v4
243//! let generator = UuidV4Generator::new();
244//! let uuid = generator.generate(&mut rng, &config);
245//! let parts: Vec<&str> = uuid.split('-').collect();
246//! assert_eq!(parts.len(), 5);
247//! assert_eq!(parts[2].chars().next().unwrap(), '4'); // Version 4
248//! # }
249//! ```
250
251// Re-export protest for convenience
252pub use protest;
253
254// Generators module
255pub mod generators;
256
257// Shrinking strategies module
258pub mod shrinking;
259
260// Re-export commonly used items
261pub mod prelude {
262    //! Convenient re-exports of commonly used generators
263
264    // Network generators
265    pub use crate::generators::network::{EmailGenerator, IpAddressGenerator, UrlGenerator};
266
267    // DateTime generators
268    pub use crate::generators::datetime::{
269        DurationGenerator, SystemTimeGenerator, TimestampGenerator,
270    };
271
272    // Text generators
273    pub use crate::generators::text::{
274        AlphabeticGenerator, AlphanumericGenerator, IdentifierGenerator, ParagraphGenerator,
275        SentenceGenerator,
276    };
277
278    // Collection generators
279    pub use crate::generators::collections::{
280        BoundedMapGenerator, NonEmptyVecGenerator, SortedVecGenerator, UniqueVecGenerator,
281    };
282
283    // Numeric generators
284    pub use crate::generators::numeric::{
285        EvenNumberGenerator, PercentageGenerator, PositiveIntGenerator, PrimeNumberGenerator,
286    };
287
288    // Domain generators
289    pub use crate::generators::domain::{
290        Base64Generator, HexGenerator, PathGenerator, UuidV4Generator,
291    };
292
293    // Shrinking strategies
294    pub use crate::shrinking::{
295        CascadingShrinker, ConfigurableShrinker, DeltaDebugShrinker, GuidedShrinker,
296        ShrinkStrategy, SmartShrink, TargetedShrinker,
297    };
298}