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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
use fake::faker;
use fake::{Fake, Faker};
use rand::Rng;
use serde_derive::{Deserialize, Serialize};
use std::ops::Add;
use uuid;

/// An Enum that represents all of the possible random data generation types.
/// Where the type is a struct it should be written in a specification as a nested map type,
/// where the outer map contains a single property correlating to the struct variant
/// with value should be a map containing the struct values.
///
/// # Examples
///
/// ## Basic Model (JSON)
///
/// To model a company identification card that contains basic employee information using simple
/// types:
///
/// ```json
/// {
///     "information_card": {
///         "name": "FullName",
///         "contact_email": "Email",
///         "contact_number": "PhoneNumber",
///         "address": "FullAddress"
///     }
/// }
/// ```
///
/// ## Model With Struct Types (JSON)
///
/// To use complex data types that take arguments, you need to write the property values as
/// nested map types. This example models an Under-25 Travelcard for a train company, where
/// the age of the person needs to be between 18 and 25. The `NumberBetween` type takes two
/// arguments:
///
/// ```json
/// {
///     "travelcard": {
///         "name": "FullName",
///         "address": "FullAddress",
///         "age": {
///             "NumberBetween": {
///                 "min": 18,
///                 "max": 25
///             }
///         }
///     }
/// }
/// ```
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum RandomData {
	/// Generates a latin first name
	FirstName,
	/// Generates a latin surname
	LastName,
	/// Generates a combination of a `FirstName` and `LastName`, combined with a space between them
	FullName,
	/// Generates a safe email address
	Email,
	/// Generates an integer with the given number of digits.
	///
	/// ## Examples
	///
	/// `Number: { "digits": 3 }` will generate a number between 100 and 999, inclusive
	Number {
		/// The number of digits the generated number should have, to get a number with an appropriate
		/// order of magnitude
		digits: usize,
	},
	/// Generate a random number between the minimum and maximum boundaries
	///
	/// ## Examples
	///
	/// `NumberBetween: { "min": 23, "max": 50 }` will generate a number between 23 and 49, inclusive
	NumberBetween {
		/// The minimum boundary for the generated number. This boundary is inclusive
		min: usize,
		/// The maximum boundary for the generated number. This boundary is exclusive
		max: usize,
	},
	/// Generate a number of paragraphs containing raw lorem ipsum text (No formatting)
	Paragraphs {
		/// The number of paragraphs to generate. Defaults to 1
		amount: Option<usize>,
	},
	/// Generate a single paragraph of lorem ipsum text. This is a convenience for specifying
	/// `Paragraphs` as a simple type without options
	Paragraph,
	/// Generate a number of sentences containing raw lorem ipsum text (No formatting)
	Sentences {
		/// The number of sentences to generate. Defaults to 1
		amount: Option<usize>,
	},
	/// Generate a single sentence of lorem ipsum text. This is a convenience for specifying
	/// `Sentences` as a simple type without options
	Sentence,
	/// Generates the name of a local company - usually an amalgamation of name-parts
	Company,
	/// Generates the name of a city
	City,
	/// Generates a sensible street address (including house number and street name components)
	StreetAddress,
	/// Generates a valid latitude component
	Latitude,
	/// Generates a valid longitude component
	Longitude,
	/// Generates a coordinate pair, formatted as a JSON array of (Latitude, Longitude)
	LatLong,
	/// Generates a coordinate pair, formatted as a JSON array of (Longitude, Latitude)
	LongLat,
	/// Generates a GeoJson point object using the WKT formatting for postgis recognition and insertion
	GeoPoint,
	/// Generates a valid postcode
	Postcode,
	/// Generates an address with `StreetAddress`, `City` and `PostCode` components, separated by commas
	FullAddress,
	/// Generates a valid V4 UUID, generally useful for object IDs
	UUID4,
	/// Generates a valid phone number
	PhoneNumber,
	/// Generates a URL to a random picture with the given dimensions and optional greyscale mode.
	/// Where one of the size values is absent, the image will be a square as dictated by the
	/// other value that is present. Where both are absent, the image will be a 200x200 pixel square.
	LoremPicsum {
		width: Option<usize>,
		height: Option<usize>,
		grayscale: Option<bool>,
	},
	NullValue,
	String {
		content: String,
	},
	Reference {
		model: String,
		field: String,
	},
}

impl RandomData {
	/// Consumes the `RandomData` instance and turns it into a random piece of data, corresponding to
	/// its type
	pub fn into_data(self) -> String {
		generate_fake_data(self)
	}
}

impl std::string::ToString for RandomData {
	fn to_string(&self) -> String {
		self.clone().into_data()
	}
}

/// Create a number of a certain length. The value is returned as a string for display,
/// and can therefore represent a number of arbitrary length at the expense of higher memory
/// consumption.
///
/// The first digit will be in the range 1-9, whilst the following digits will be between 0-9.
///
/// # Examples
///
/// ```rust
/// let three_digit_number = number_with_length(3);
/// println!("{}", three_digit_number);
/// ```
fn number_with_length(length: usize) -> String {
	let mut random = rand::thread_rng();
	let mut buffer = String::with_capacity(length);
	buffer = buffer + &format!("{}", random.gen_range(1, 10));

	for _ in 0..length - 1 {
		buffer = buffer + &format!("{}", random.gen_range(0, 10));
	}

	buffer
}

#[test]
fn generate_number_format_of_correct_length() {
	assert_eq!(number_with_length(1).len(), 1);
	assert_eq!(number_with_length(2).len(), 2);
	assert_eq!(number_with_length(10).len(), 10);
	assert_eq!(number_with_length(1000).len(), 1000);
}

/// Use a `RandomData` definition to generate a random string of data
///
/// # Examples
///
/// ```rust
/// use mockery::datatypes::{RandomData, generate_fake_data};
/// println!(
///     "Hello {}, your new email address is {}",
///     generate_fake_data(RandomData::FullName),
///     generate_fake_data(RandomData::Email)
/// )
/// ```
pub fn generate_fake_data(spec: RandomData) -> String {
	match spec {
		RandomData::FirstName => format!("{}", faker::name::en::FirstName().fake::<String>()),
		RandomData::LastName => format!("{}", faker::name::en::LastName().fake::<String>()),
		RandomData::FullName => format!("{}", faker::name::en::Name().fake::<String>()),
		RandomData::Email => format!("{}", faker::internet::en::SafeEmail().fake::<String>()),
		RandomData::Number { digits } => format!("{}", number_with_length(digits)),
		RandomData::NumberBetween { min, max } => {
			format!("{}", rand::thread_rng().gen_range(min, max))
		}
		RandomData::Paragraph => faker::lorem::en::Paragraph(1..2).fake::<String>(),
		RandomData::Paragraphs { amount } => {
			let val = amount.unwrap_or(1usize);
			format!(
				"{}",
				faker::lorem::en::Paragraph(val..val + 1).fake::<String>()
			)
		}
		RandomData::Sentence => faker::lorem::en::Sentence(1..2).fake::<String>(),
		RandomData::Sentences { amount } => {
			let val = amount.unwrap_or(1usize);
			format!(
				"{}",
				faker::lorem::en::Sentence(val..val + 1).fake::<String>()
			)
		}
		RandomData::Company => format!("{}", faker::company::en::CompanyName().fake::<String>()),
		RandomData::City => format!("{}", faker::address::en::CityName().fake::<String>()),
		RandomData::StreetAddress => {
			format!("{}", faker::address::en::StreetName().fake::<String>())
		}
		RandomData::Latitude => format!("{}", faker::address::en::Latitude().fake::<String>()),
		RandomData::Longitude => format!("{}", faker::address::en::Longitude().fake::<String>()),
		RandomData::LatLong => format!(
			r#"[{}, {}]"#,
			faker::address::en::Latitude().fake::<String>(),
			faker::address::en::Longitude().fake::<String>()
		),
		RandomData::LongLat => format!(
			r#"[{}, {}]"#,
			faker::address::en::Longitude().fake::<String>(),
			faker::address::en::Latitude().fake::<String>()
		),
		RandomData::GeoPoint => format!(
			r#"POINT({} {})"#,
			faker::address::en::Longitude().fake::<String>(),
			faker::address::en::Latitude().fake::<String>()
		),
		RandomData::Postcode => format!("{}", faker::address::en::PostCode().fake::<String>()),
		RandomData::FullAddress => format!(
			"{}, {}, {}",
			faker::address::en::StreetName().fake::<String>(),
			faker::address::en::CityName().fake::<String>(),
			faker::address::en::PostCode().fake::<String>()
		),
		RandomData::UUID4 => format!("{}", uuid::Uuid::new_v4()),
		RandomData::PhoneNumber => format!(
			"{}",
			faker::phone_number::en::PhoneNumber().fake::<String>()
		),
		RandomData::LoremPicsum {
			width,
			height,
			grayscale,
		} => format!(
			"https://picusm.photos/{}{}/{}",
			if grayscale.unwrap_or(false) { "g/" } else { "" },
			width.unwrap_or(200),
			height.unwrap_or(200)
		),
		RandomData::NullValue => format!("null"),
		RandomData::String { content } => content.clone(),
		RandomData::Reference { .. } => format!("null"),
	}
}