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
use thiserror::Error;
use crate::types::*;
/// Helper to request users with filters like gender, nationalities, etc.
pub struct UserGeneratorBuilder {
req: reqwest::RequestBuilder
}
impl UserGeneratorBuilder {
pub(crate) fn new(req: reqwest::RequestBuilder) -> Self {
Self { req }
}
/// Request a specific gender
pub fn gender(self, gender: Gender) -> Self {
Self::new(self.req.query(&[("gender", serde_json::to_value(gender).unwrap().as_str())]))
}
/// Request a specific nationality
pub fn nationality(self, nationality: Nationality) -> Self {
Self::new(self.req.query(&[("nat", serde_json::to_value(nationality).unwrap().as_str())]))
}
/// Request specific nationalities, picked at random between each user
pub fn nationalities(self, nationalities: &[Nationality]) -> Self {
let mut nats = String::new();
for nat in nationalities {
nats += serde_json::to_value(nat).unwrap().as_str().unwrap();
nats.push(',');
}
nats.pop();
Self::new(self.req.query(&[("nat", nats)]))
}
/// Request with a specified seed, allow to always generate the same users
///
/// ### Warning:
/// May discard other filters
pub fn seed(self, seed: &str) -> Self {
Self::new(self.req.query(&[("seed", seed)]))
}
/// Request a user with specific password rules
///
/// Format, without spaces:
/// `CHARSETS,MIN_LENGTH-MAX_LENGTH`
/// or
/// `CHARSETS,MAX_LENGTH`
///
/// You can mix and match the charsets below for the CHARSETS option above:
/// ```md
/// special !"#$%&'()*+,- ./:;<=>?@[\]^_`{|}~
/// upper ABCDEFGHIJKLMNOPQRSTUVWXYZ
/// lower abcdefghijklmnopqrstuvwxyz
/// number 0123456789
/// ```
/// `MIN_LENGTH` and `MAX_LENGTH` are the min/max length of the passwords that you want to generate.
/// By default, passwords will be between 8 - 64 characters long.
///
/// ## Example:
/// ```
/// // Get a user with a password composed with 8 upper and/or lower characters
/// let user = generator.get().password("upper,lower,8").fetch_one().await?
/// ```
pub fn password(self, charset: &str) -> Self {
Self::new(self.req.query(&[("password", charset)]))
}
/// Generate users with the api informations
pub async fn fetch_with_info(self, count: usize) -> Result<RandomUserResult> {
self.count(count).request().await
}
/// Generate users
pub async fn fetch(self, count: usize) -> Result<Vec<RandomUser>> {
Ok(self.count(count).request().await?.results)
}
/// Generate 1 user
pub async fn fetch_one(self) -> Result<RandomUser> {
Ok(self.fetch(1).await?.remove(0))
}
fn count(self, count: usize) -> Self {
Self::new(self.req.query(&[("results", count)]))
}
async fn request(self) -> Result<RandomUserResult> {
let api_rsp = self.req.send().await?;
let rsp = Self::parse_response(api_rsp).await?;
match rsp {
RandomUserResponse::Error(e) => Err(RandomUserError::Api(e)),
RandomUserResponse::Result(res) => Ok(res),
}
}
async fn parse_response(response: reqwest::Response) -> Result<RandomUserResponse> {
let content_type = response
.headers()
.get("content-type")
.ok_or(RandomUserError::BadFormat)?
.to_str()
.map_err(|_| RandomUserError::BadFormat)?
.to_owned();
let text = response.text().await?;
match content_type {
ct if ct.contains("text/plain") => Ok(RandomUserResponse::Error(text)),
ct if ct.contains("application/json") => serde_json::from_str::<RandomUserResponse>(&text).map_err(|_| RandomUserError::BadFormat),
_ => Err(RandomUserError::BadFormat),
}
}
}
/// Random user generator
///
/// ## Example:
/// ```
/// let generator = UserGenerator::new();
///
/// let user = generator.fetch_one().await?
///
/// println!("{:#?}", user);
/// ```
pub struct UserGenerator {
client: reqwest::Client,
}
impl UserGenerator {
const API_URL: &str = "https://randomuser.me/api/1.4/";
pub fn new() -> UserGenerator {
UserGenerator { client: reqwest::Client::new() }
}
/// Start the request to easily apply filters
pub fn get(&self) -> UserGeneratorBuilder {
UserGeneratorBuilder::new(
self.client.get(Self::API_URL)
)
}
/// Generate users with the api informations
///
/// ## Example:
/// ```
/// // Fetch 5 random users with api info
/// let users = generator.fetch_with_info(5).await?
///
/// println!("{:?}", users.info);
/// for user in users.results {
/// prinln!("{user:?}");
/// }
///
/// ```
pub async fn fetch_with_info(&self, count: usize) -> Result<RandomUserResult> {
self.get().fetch_with_info(count).await
}
/// Generate users
///
/// ## Example:
/// ```
/// // Fetch 5 random users
/// let users = generator.fetch(5).await?
///
/// for user in users {
/// prinln!("{user:?}");
/// }
///
/// ```
pub async fn fetch(&self, count: usize) -> Result<Vec<RandomUser>> {
self.get().fetch(count).await
}
/// Generate a user
///
/// ## Example:
/// ```
/// let user = generator.fetch_one().await?
/// println("{user:?}");
/// ```
pub async fn fetch_one(&self) -> Result<RandomUser> {
self.get().fetch_one().await
}
}
impl Default for UserGenerator {
fn default() -> Self {
Self::new()
}
}
type Result<T> = std::result::Result<T, RandomUserError>;
#[derive(Debug, Error)]
pub enum RandomUserError {
#[error("Reqwest error: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("Api error: {0}")]
Api(String),
#[error("Bad format")]
BadFormat,
}