Skip to content

Commit 87de8f0

Browse files
committed
feat(generator): create a seeded DataGenerator
1 parent 603af06 commit 87de8f0

8 files changed

Lines changed: 81 additions & 53 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ chrono = { version = "0.4.41", features = [] }
2121
clap = { version = "4.5.42", features = ["derive"] }
2222
dialoguer = { version = "0.12.0", features = ["fuzzy-select"] }
2323
rand = "0.9.2"
24+
rand_chacha = "0.9.0"
2425
random-data = "0.1.1"
2526
serde_json = "1.0.142"

src/data_generator.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//! Wrapper around [`DataGenerator`] for internal usage.
2+
3+
use rand::rngs::ThreadRng;
4+
use rand_chacha::ChaCha20Rng;
5+
use random_data::{DataGenerator, DataType};
6+
7+
/// Wrapper around [`DataGenerator`] to hide a different random generator,
8+
/// depending on whether we wand to generate with a seed or with a non
9+
/// determinastic generator.
10+
pub enum RandomDataGenerator {
11+
/// Non-deterministic generator
12+
NonDeterministic(DataGenerator<ThreadRng>),
13+
/// Determinastic with a seed
14+
Seeded(Box<DataGenerator<ChaCha20Rng>>),
15+
}
16+
17+
impl RandomDataGenerator {
18+
/// Creates a new [`RandomDataGenerator`] with a seed or not.
19+
pub fn new(seed: Option<u64>) -> Self {
20+
seed.map_or_else(
21+
|| Self::NonDeterministic(DataGenerator::default()),
22+
|inner| Self::Seeded(Box::new(DataGenerator::new_with_seed(inner))),
23+
)
24+
}
25+
26+
/// Generates a value randomly from the given [`DataType`]
27+
pub fn random_value(&mut self, data_type: DataType) -> String {
28+
match self {
29+
Self::NonDeterministic(data_generator) => data_type.random(data_generator),
30+
Self::Seeded(data_generator) => data_type.random(data_generator),
31+
}
32+
}
33+
}

src/dialog/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use dialoguer::FuzzySelect;
66
use dialoguer::theme::ColorfulTheme as ColourfulTheme;
7-
use rand::RngCore;
87

98
use crate::errors::Res;
109
use crate::generator::{Data, Generator as _};
@@ -15,7 +14,7 @@ pub struct Dialog;
1514
impl Dialog {
1615
/// Generate data from a dialogue selection.
1716
#[expect(clippy::indexing_slicing, reason = "can't be out of bounds")]
18-
pub fn generate<Rng: RngCore>(mut data: Data<Rng>) -> Res<String> {
17+
pub fn generate(mut data: Data) -> Res<String> {
1918
let data_list = data.list();
2019

2120
let selection = FuzzySelect::with_theme(&ColourfulTheme::default())

src/generator.rs

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,32 @@ use std::collections::{HashMap, HashSet};
66

77
use rand::rngs::ThreadRng;
88
use rand::seq::IndexedRandom as _;
9-
use rand::{Rng as _, RngCore, rng};
10-
use random_data::{DataGenerator, DataType};
9+
use rand::{Rng as _, rng};
10+
use random_data::DataType;
1111
use serde_json::{Number, Value};
1212

13+
use crate::data_generator::RandomDataGenerator;
1314
use crate::errors::{Error, Res};
1415
///
1516
/// Generate random data of the given type.
1617
pub trait Generator<T>: Sized {
1718
/// Generate random data of the given type.
18-
fn generate<Rng: RngCore>(&self, data: &mut Data<Rng>) -> Res<T>;
19+
fn generate(&self, data: &mut Data) -> Res<T>;
1920
}
2021

2122
/// Generate random data of the given type, but with a nullable type.
2223
pub trait NullableGenerator<T>: Sized {
2324
/// Generate random data of the given type, but with a nullable type.
2425
///
2526
/// This can sometimes returns None.
26-
fn generate_nullable<Rng: RngCore>(&self, data: &mut Data<Rng>) -> Res<Option<T>>;
27+
fn generate_nullable(&self, data: &mut Data) -> Res<Option<T>>;
2728
}
2829

2930
/// Contains the list of data types and the random generator to apply
3031
/// generators.
31-
pub struct Data<Rng: RngCore> {
32+
pub struct Data {
3233
/// Radnom data generator
33-
random_data_generator: DataGenerator<Rng>,
34+
random_data_generator: RandomDataGenerator,
3435
/// Pseudo-random refs
3536
///
3637
/// This represents data that is randomly generated once, then used in
@@ -44,7 +45,7 @@ pub struct Data<Rng: RngCore> {
4445
user_defined: HashMap<String, Vec<String>>,
4546
}
4647

47-
impl<Rng: RngCore> Data<Rng> {
48+
impl Data {
4849
/// Generate non-nullable data of the provided data type.
4950
fn generate(&mut self, data_type: &str) -> Res<OutputData> {
5051
if let Some(parsed) = data_type.strip_suffix(']')
@@ -77,9 +78,10 @@ impl<Rng: RngCore> Data<Rng> {
7778
OutputData::Float(self.rng.random_range(0.0f64..=f64::MAX))
7879
} else {
7980
OutputData::String(
80-
DataType::try_from(data_type)
81-
.map_err(|()| Error::InvalidDataType(data_type.to_owned()))?
82-
.random(&mut self.random_data_generator),
81+
self.random_data_generator.random_value(
82+
DataType::try_from(data_type)
83+
.map_err(|()| Error::InvalidDataType(data_type.to_owned()))?,
84+
),
8385
)
8486
};
8587

@@ -199,6 +201,27 @@ impl<Rng: RngCore> Data<Rng> {
199201
list
200202
}
201203

204+
/// Build the [`Data`] handler from user inputs
205+
pub fn new(input_data: Vec<String>, seed: Option<u64>) -> Res<Self> {
206+
let mut user_defined = HashMap::new();
207+
208+
for data_type in input_data {
209+
let (name, values) = Self::parse_user_defined(&data_type)?;
210+
211+
if user_defined.insert(name, values).is_some() {
212+
return Err(Error::DuplicateDataType(data_type));
213+
}
214+
}
215+
216+
Ok(Self {
217+
random_data_generator: RandomDataGenerator::new(seed),
218+
user_defined,
219+
rng: rng(),
220+
refs: HashMap::new(),
221+
uniq_types: HashMap::new(),
222+
})
223+
}
224+
202225
/// Parse a user-defined data-type, with the format
203226
/// `Name:Value1|Value2|Value3`.
204227
fn parse_user_defined(user_input: &str) -> Res<(String, Vec<String>)> {
@@ -242,37 +265,14 @@ impl<Rng: RngCore> Data<Rng> {
242265
}
243266
}
244267

245-
impl Data<ThreadRng> {
246-
/// Build the [`Data`] handler from user inputs
247-
pub fn new(input_data: Vec<String>, _seed: Option<u64>) -> Res<Self> {
248-
let mut user_defined = HashMap::new();
249-
250-
for data_type in input_data {
251-
let (name, values) = Self::parse_user_defined(&data_type)?;
252-
253-
if user_defined.insert(name, values).is_some() {
254-
return Err(Error::DuplicateDataType(data_type));
255-
}
256-
}
257-
258-
Ok(Self {
259-
random_data_generator: DataGenerator::default(),
260-
user_defined,
261-
rng: rng(),
262-
refs: HashMap::new(),
263-
uniq_types: HashMap::new(),
264-
})
265-
}
266-
}
267-
268268
impl Generator<OutputData> for String {
269-
fn generate<Rng: RngCore>(&self, data: &mut Data<Rng>) -> Res<OutputData> {
269+
fn generate(&self, data: &mut Data) -> Res<OutputData> {
270270
data.generate(self)
271271
}
272272
}
273273

274274
impl NullableGenerator<OutputData> for String {
275-
fn generate_nullable<Rng: RngCore>(&self, data: &mut Data<Rng>) -> Res<Option<OutputData>> {
275+
fn generate_nullable(&self, data: &mut Data) -> Res<Option<OutputData>> {
276276
data.generate_nullable(self)
277277
}
278278
}

src/json/generator.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
33
use core::iter::repeat_with;
44

5-
use rand::{Rng as _, RngCore};
5+
use rand::Rng as _;
66
use serde_json::{Map, Value};
77

88
use crate::errors::{Error, Res};
99
use crate::generator::{Data, Generator, NullableGenerator};
1010

1111
impl Generator<Value> for Map<String, Value> {
12-
fn generate<Rng: RngCore>(&self, data: &mut Data<Rng>) -> Res<Value> {
12+
fn generate(&self, data: &mut Data) -> Res<Value> {
1313
let mut new_map = Self::with_capacity(self.len());
1414
for (key, json_value) in self {
1515
if let Some(parsed_key) = key.strip_suffix('!') {
@@ -45,7 +45,7 @@ impl Generator<Value> for Vec<Value> {
4545
/// ["FreeEmail"] // produce a random number of emails
4646
/// ["FirstName", 1] // produce 1 first name
4747
/// ["LicencePlate", 1, 10] // produce between 1 and 9 licence plates
48-
fn generate<Rng: RngCore>(&self, data: &mut Data<Rng>) -> Res<Value> {
48+
fn generate(&self, data: &mut Data) -> Res<Value> {
4949
let mut iter = self.iter();
5050

5151
let array_item_type = iter.next().ok_or(Error::ArrayMissingDataType)?;
@@ -67,7 +67,7 @@ impl Generator<Value> for Vec<Value> {
6767
}
6868

6969
impl Generator<Self> for Value {
70-
fn generate<Rng: RngCore>(&self, data: &mut Data<Rng>) -> Res<Self> {
70+
fn generate(&self, data: &mut Data) -> Res<Self> {
7171
match self {
7272
Self::Null | Self::Bool(_) | Self::Number(_) =>
7373
Err(Error::InvalidSchemaType(format!("{self:?}"))),
@@ -79,7 +79,7 @@ impl Generator<Self> for Value {
7979
}
8080

8181
impl NullableGenerator<Self> for Value {
82-
fn generate_nullable<Rng: RngCore>(&self, data: &mut Data<Rng>) -> Res<Option<Self>> {
82+
fn generate_nullable(&self, data: &mut Data) -> Res<Option<Self>> {
8383
let generated_json = match self {
8484
Self::Null | Self::Bool(_) | Self::Number(_) =>
8585
return Err(Error::InvalidSchemaType(format!("{self:?}"))),

src/json/mod.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,26 @@ mod generator;
44

55
use core::fmt::Write as _;
66

7-
use rand::RngCore;
87
use serde_json::Value;
98

109
use crate::errors::{Error, Res};
1110
use crate::generator::{Data, NullableGenerator as _};
1211

1312
/// Arguments for generating JSON data based on a schema file.
14-
pub struct JsonArgs<Rng: RngCore> {
13+
pub struct JsonArgs {
1514
/// String to print after every data generation of the JSON schema.
1615
after: String,
1716
/// String to print before every data generation of the JSON schema.
1817
before: String,
1918
/// Number of times to repeat the JSON generation.
2019
count: u32,
2120
/// Data generator
22-
data: Data<Rng>,
21+
data: Data,
2322
/// JSON schema content
2423
json: String,
2524
}
2625

27-
impl<Rng: RngCore> JsonArgs<Rng> {
26+
impl JsonArgs {
2827
/// Generate the JSON data based on the schema file and the provided
2928
/// parameters.
3029
pub fn generate(mut self) -> Res<String> {
@@ -43,13 +42,7 @@ impl<Rng: RngCore> JsonArgs<Rng> {
4342
}
4443

4544
/// Create a new instance of `JsonArgs` with the provided parameters.
46-
pub const fn new(
47-
before: String,
48-
after: String,
49-
count: u32,
50-
json: String,
51-
data: Data<Rng>,
52-
) -> Self {
45+
pub const fn new(before: String, after: String, count: u32, json: String, data: Data) -> Self {
5346
Self { after, before, count, data, json }
5447
}
5548
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#![allow(clippy::unwrap_in_result, reason = "unwrap_used is active")]
4343

4444
mod clap;
45+
mod data_generator;
4546
mod dialog;
4647
mod errors;
4748
mod generator;

0 commit comments

Comments
 (0)