use super::{Score, ScoreTest, ValidAmount, ValidName};
use anyhow::{Context, Error};
use fake::{Dummy, Fake, Faker, StringFaker};
use rand::Rng;
use serde::{Deserialize, Serialize};
use sqlx::{
Postgres, Type,
postgres::{PgHasArrayType, PgTypeInfo},
};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::Encode, Clone, PartialOrd, Ord)]
#[sqlx(type_name = "component")]
pub struct Component {
pub name: ValidName,
pub amount: ValidAmount,
pub scores: Vec<Score>,
}
impl Component {
pub fn new<T: AsRef<str>>(name: T, amount: i64) -> Result<Self, Error> {
Ok(Component {
name: ValidName::parse(name.as_ref().to_string())
.context("Failed to parse component name.")?,
amount: ValidAmount::parse(amount).context("Failed to parse component amount.")?,
scores: vec![],
})
}
pub fn with_score(mut self, score: Score) -> Self {
self.scores.push(score);
self
}
pub fn with_scores(mut self, mut scores: Vec<Score>) -> Self {
self.scores.append(&mut scores);
self
}
}
impl sqlx::decode::Decode<'_, sqlx::Postgres> for Component {
fn decode(
value: sqlx::postgres::PgValueRef<'_>,
) -> Result<Self, std::boxed::Box<dyn std::error::Error + 'static + Send + Sync>> {
let mut decoder = sqlx::postgres::types::PgRecordDecoder::new(value)?;
let name = decoder.try_decode::<ValidName>()?;
let amount = decoder.try_decode::<ValidAmount>()?;
let scores = decoder.try_decode::<Vec<Score>>()?;
Ok(Component {
name,
amount,
scores,
})
}
}
impl Type<Postgres> for Component {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_name("component")
}
}
impl PgHasArrayType for Component {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::with_name("_component")
}
}
impl TryFrom<ComponentTest> for Component {
type Error = Error;
fn try_from(value: ComponentTest) -> Result<Self, Self::Error> {
Ok(Component {
name: ValidName::parse(value.name.ok_or_else(|| anyhow::anyhow!("name is None"))?)?,
amount: ValidAmount::parse(
value
.amount
.ok_or_else(|| anyhow::anyhow!("amount is None"))?,
)?,
scores: value
.scores
.into_iter()
.map(Score::try_from)
.collect::<Result<_, Self::Error>>()?,
})
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ComponentTest {
pub name: Option<String>,
pub amount: Option<i64>,
pub scores: Vec<ScoreTest>,
}
impl PartialEq<Component> for ComponentTest {
fn eq(&self, other: &Component) -> bool {
let ComponentTest {
name: s_name,
amount: s_amount,
scores: s_scores,
} = self;
let Component {
name: o_name,
amount: o_amount,
scores: o_scores,
} = other;
if s_name.is_none() || s_amount.is_none() {
return false;
}
let mut s_scores = s_scores.clone();
let mut o_scores = o_scores.clone();
s_scores.sort();
o_scores.sort();
s_name.as_ref().unwrap() == o_name.as_ref()
&& s_amount.as_ref().unwrap() == o_amount.as_ref()
&& s_scores
.into_iter()
.zip(o_scores)
.fold(true, |acc, (a, b)| acc && a == b)
}
}
impl PartialEq<ComponentTest> for Component {
fn eq(&self, other: &ComponentTest) -> bool {
other.eq(self)
}
}
impl Dummy<Faker> for ComponentTest {
fn dummy_with_rng<R: Rng + ?Sized>(_: &Faker, rng: &mut R) -> ComponentTest {
let name = StringFaker::with(
String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*&^%$#@!~")
.into_bytes(),
1..256,
)
.fake_with_rng(rng);
ComponentTest {
name: Some(name),
amount: Some((0..i64::MAX).fake_with_rng(rng)),
scores: (0..(0..10u64).fake_with_rng(rng))
.map(|_| Faker.fake_with_rng::<ScoreTest, _>(rng))
.collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use claim::assert_ok;
impl quickcheck::Arbitrary for ComponentTest {
fn arbitrary(_g: &mut quickcheck::Gen) -> Self {
Faker.fake()
}
}
#[quickcheck]
fn a_valid_name_is_parsed_successfully(component: ComponentTest) {
assert_ok!(Component::try_from(component));
}
}