use std::string::FromUtf8Error;
mod constants;
pub mod helpers;
pub use constants::*;
#[derive(Debug, Default, Clone)]
pub struct RandomBlock {
inner: Vec<u8>,
}
impl RandomBlock {
pub fn inner(&self) -> &Vec<u8> {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut Vec<u8> {
&mut self.inner
}
}
impl From<Vec<u8>> for RandomBlock {
fn from(v: Vec<u8>) -> Self {
RandomBlock { inner: v }
}
}
impl From<&Vec<u8>> for RandomBlock {
fn from(v: &Vec<u8>) -> Self {
RandomBlock { inner: v.clone() }
}
}
impl From<RandomBlock> for Result<String, Error> {
fn from(random_block: RandomBlock) -> Result<String, Error> {
String::from_utf8(random_block.inner).map_err(Error::from)
}
}
impl From<RandomBlock> for Result<Vec<u8>, Error> {
fn from(r: RandomBlock) -> Self {
Ok(r.inner)
}
}
impl From<RandomBlock> for Vec<u8> {
fn from(r: RandomBlock) -> Self {
r.inner
}
}
pub enum Charset {
String(String),
StringSets(Vec<String>),
Bytes(Vec<u8>),
ByteSets(Vec<Vec<u8>>),
AnyByte,
AnyString,
}
impl Default for Charset {
fn default() -> Self {
Charset::AnyByte
}
}
impl From<String> for Charset {
fn from(s: String) -> Self {
Charset::String(s)
}
}
impl From<Vec<String>> for Charset {
fn from(v: Vec<String>) -> Self {
Charset::StringSets(v)
}
}
impl From<Vec<&str>> for Charset {
fn from(v: Vec<&str>) -> Self {
Charset::StringSets(v.into_iter().map(|t| t.to_string()).collect())
}
}
impl From<&str> for Charset {
fn from(s: &str) -> Self {
Charset::String(s.to_string())
}
}
impl From<&[u8]> for Charset {
fn from(u: &[u8]) -> Self {
Charset::Bytes(u.to_vec())
}
}
#[derive(PartialEq)]
pub enum StrictLimit {
ByteLimit,
StringLimit,
None,
}
pub struct Randomizer {
length: usize,
charset: Charset,
separator: Option<Vec<u8>>,
strict_limit: StrictLimit,
}
impl Randomizer {
pub fn new<C: Into<Charset>>(length: usize, charset: Option<C>) -> Randomizer {
Randomizer {
length,
charset: charset.map(|t| t.into()).unwrap_or_default(),
separator: None,
strict_limit: StrictLimit::None,
}
}
pub fn new_with_separator<C: Into<Charset>, B: Into<Vec<u8>>>(
length: usize,
charset: Option<C>,
sep: B,
) -> Randomizer {
Randomizer {
length,
charset: charset.map(|t| t.into()).unwrap_or_default(),
separator: Some(sep).map(|t| t.into()),
strict_limit: StrictLimit::None,
}
}
pub fn string(&self) -> Result<String, Error> {
self.rand().and_then(|r| r.into())
}
pub fn bytes(&self) -> Result<Vec<u8>, Error> {
self.rand().and_then(|r| r.into())
}
pub fn rand(&self) -> Result<RandomBlock, Error> {
match &self.charset {
Charset::String(s) => self.rand_string(&s),
Charset::Bytes(b) => {
self.rand_byte(Some(b.iter().map(|t| vec![*t]).collect::<Vec<Vec<u8>>>()))
}
Charset::AnyByte => self.rand_byte(None),
Charset::AnyString => self.rand_string(constants::UTF8),
Charset::ByteSets(v) => self.rand_byte(Some(v.clone())),
Charset::StringSets(v) => self.rand_string_set(v),
}
}
helpers::local_macros::automated_impl! {
(ALPHANUMERIC);
(ALPHANUMERIC_LOWER);
(ALPHANUMERIC_UPPER);
(ALPHABETICAL);
(ALPHABETICAL_UPPER);
(ALPHABETICAL_LOWER);
(NUMERICAL);
(UTF8);
}
}
impl Randomizer {
fn rand_string(&self, charset: &str) -> Result<RandomBlock, Error> {
let chars: Vec<Vec<u8>> = charset
.chars()
.map(|c| c.to_string().as_bytes().to_vec())
.collect();
self.rand_byte(Some(chars))
}
fn rand_string_set(&self, charset: &Vec<String>) -> Result<RandomBlock, Error> {
let byte_sets: Vec<Vec<u8>> = charset
.into_iter()
.map(|c| c.to_string().as_bytes().to_vec())
.collect();
self.rand_byte(Some(byte_sets))
}
fn rand_byte(&self, set_opt: Option<Vec<Vec<u8>>>) -> Result<RandomBlock, Error> {
let mut vec = Vec::new();
let set = set_opt.unwrap_or_else(|| (0..255u8).into_iter().map(|t| vec![t]).collect());
let mut idx = 0;
while idx < self.length {
if let Some(v) = set.get(fastrand::usize(0..set.len())) {
vec = vec![vec, v.clone()].concat();
idx = self.index_add(idx, &vec)?;
if let Some(sep) = &self.separator {
if idx != self.length {
vec = vec![vec, sep.clone()].concat();
if self.strict_limit != StrictLimit::None {
idx = self.index_add(idx, &vec)?;
}
}
}
} else {
return Err(Error::OverflowUnderflow(
"Getting string cause overflow/underflow".to_string(),
));
}
}
Ok(vec.into())
}
fn index_add(&self, idx: usize, data: &Vec<u8>) -> Result<usize, Error> {
match self.strict_limit {
StrictLimit::ByteLimit => Ok(data.len()),
StrictLimit::StringLimit => String::from_utf8(data.clone())
.map_err(Error::from)
.map(|t| t.chars().count()),
StrictLimit::None => Ok(idx + 1),
}
}
}
#[derive(Debug)]
pub enum Error {
OverflowUnderflow(String),
ByteConversionFailedForUTF8(FromUtf8Error),
CharsetMismatch(String),
}
impl From<FromUtf8Error> for Error {
fn from(e: FromUtf8Error) -> Self {
Error::ByteConversionFailedForUTF8(e)
}
}
#[cfg(test)]
pub mod test {
use super::*;
#[test]
pub fn test_ascii_generation() {
let string = Randomizer::ALPHABETICAL(6).string().unwrap();
println!("{}", string);
assert_eq!(string.chars().count(), 6);
}
#[test]
pub fn test_unicode_generation() {
let charset = "ó❤⚙";
let string = Randomizer::new(6, Some(charset)).string().unwrap();
println!("{}", string);
assert_eq!(string.chars().count(), 6);
}
#[test]
pub fn test_unicode_generation_single() {
let charset = "⚙";
println!("{:?}", charset.as_bytes());
let charset = "1⚙2";
println!("{:?}", charset.as_bytes());
let string = Randomizer::new(6, Some(charset)).string().unwrap();
println!("{}", string);
assert_eq!(string.chars().count(), 6);
}
#[test]
pub fn test_unicode_generation_large() {
let charset = "ó❤⚙";
let string = Randomizer::new(12, Some(charset)).string().unwrap();
println!("'{}'", string);
assert_eq!(string.chars().count(), 12);
}
#[test]
pub fn test_new_with_separator() {
let string = Randomizer::new_with_separator(6, Some("u"), " ")
.string()
.unwrap();
println!("{:?}", string);
assert_eq!(string.chars().count(), 11);
assert_eq!(string, "u u u u u u");
}
#[test]
pub fn test_rand() {
let bytes = Randomizer::new(6, Some(Charset::AnyByte)).rand().unwrap();
println!("{:?}", bytes);
assert_eq!(bytes.inner().len(), 6);
}
#[test]
pub fn test_randomblock_to_result() {
let foo_vec = vec![100u8, 100u8, 100u8];
let random_block: RandomBlock = RandomBlock::from(&foo_vec);
let string_res: Result<String, Error> = random_block.into();
assert_eq!(string_res.unwrap(), "ddd")
}
}