auditor/domain/
component.rs1use super::{Score, ScoreTest, ValidAmount, ValidName};
9use anyhow::{Context, Error};
10use fake::{Dummy, Fake, Faker, StringFaker};
11use rand::Rng;
12use serde::{Deserialize, Serialize};
13use sqlx::{
14 Postgres, Type,
15 postgres::{PgHasArrayType, PgTypeInfo},
16};
17
18#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::Encode, Clone, PartialOrd, Ord)]
37#[sqlx(type_name = "component")]
38pub struct Component {
39 pub name: ValidName,
41 pub amount: ValidAmount,
43 pub scores: Vec<Score>,
45}
46
47impl Component {
48 pub fn new<T: AsRef<str>>(name: T, amount: i64) -> Result<Self, Error> {
55 Ok(Component {
56 name: ValidName::parse(name.as_ref().to_string())
57 .context("Failed to parse component name.")?,
58 amount: ValidAmount::parse(amount).context("Failed to parse component amount.")?,
59 scores: vec![],
60 })
61 }
62
63 pub fn with_score(mut self, score: Score) -> Self {
65 self.scores.push(score);
66 self
67 }
68
69 pub fn with_scores(mut self, mut scores: Vec<Score>) -> Self {
71 self.scores.append(&mut scores);
72 self
73 }
74}
75
76impl sqlx::decode::Decode<'_, sqlx::Postgres> for Component {
80 fn decode(
81 value: sqlx::postgres::PgValueRef<'_>,
82 ) -> Result<Self, std::boxed::Box<dyn std::error::Error + 'static + Send + Sync>> {
83 let mut decoder = sqlx::postgres::types::PgRecordDecoder::new(value)?;
84 let name = decoder.try_decode::<ValidName>()?;
85 let amount = decoder.try_decode::<ValidAmount>()?;
86 let scores = decoder.try_decode::<Vec<Score>>()?;
87 Ok(Component {
88 name,
89 amount,
90 scores,
91 })
92 }
93}
94
95impl Type<Postgres> for Component {
96 fn type_info() -> PgTypeInfo {
97 PgTypeInfo::with_name("component")
98 }
99}
100
101impl PgHasArrayType for Component {
102 fn array_type_info() -> PgTypeInfo {
103 PgTypeInfo::with_name("_component")
104 }
105}
106
107impl TryFrom<ComponentTest> for Component {
108 type Error = Error;
109
110 fn try_from(value: ComponentTest) -> Result<Self, Self::Error> {
111 Ok(Component {
112 name: ValidName::parse(value.name.ok_or_else(|| anyhow::anyhow!("name is None"))?)?,
113 amount: ValidAmount::parse(
114 value
115 .amount
116 .ok_or_else(|| anyhow::anyhow!("amount is None"))?,
117 )?,
118 scores: value
119 .scores
120 .into_iter()
121 .map(Score::try_from)
122 .collect::<Result<_, Self::Error>>()?,
123 })
124 }
125}
126
127#[derive(Debug, Serialize, Deserialize, Clone)]
128pub struct ComponentTest {
129 pub name: Option<String>,
130 pub amount: Option<i64>,
131 pub scores: Vec<ScoreTest>,
133}
134
135impl PartialEq<Component> for ComponentTest {
136 fn eq(&self, other: &Component) -> bool {
137 let ComponentTest {
138 name: s_name,
139 amount: s_amount,
140 scores: s_scores,
141 } = self;
142 let Component {
143 name: o_name,
144 amount: o_amount,
145 scores: o_scores,
146 } = other;
147
148 if s_name.is_none() || s_amount.is_none() {
150 return false;
151 }
152
153 let mut s_scores = s_scores.clone();
154 let mut o_scores = o_scores.clone();
155
156 s_scores.sort();
157 o_scores.sort();
158
159 s_name.as_ref().unwrap() == o_name.as_ref()
160 && s_amount.as_ref().unwrap() == o_amount.as_ref()
161 && s_scores
162 .into_iter()
163 .zip(o_scores)
164 .fold(true, |acc, (a, b)| acc && a == b)
165 }
166}
167
168impl PartialEq<ComponentTest> for Component {
169 fn eq(&self, other: &ComponentTest) -> bool {
170 other.eq(self)
171 }
172}
173
174impl Dummy<Faker> for ComponentTest {
175 fn dummy_with_rng<R: Rng + ?Sized>(_: &Faker, rng: &mut R) -> ComponentTest {
176 let name = StringFaker::with(
177 String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*&^%$#@!~")
178 .into_bytes(),
179 1..256,
180 )
181 .fake_with_rng(rng);
182 ComponentTest {
183 name: Some(name),
184 amount: Some((0..i64::MAX).fake_with_rng(rng)),
185 scores: (0..(0..10u64).fake_with_rng(rng))
186 .map(|_| Faker.fake_with_rng::<ScoreTest, _>(rng))
187 .collect(),
188 }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use claim::assert_ok;
196
197 impl quickcheck::Arbitrary for ComponentTest {
198 fn arbitrary(_g: &mut quickcheck::Gen) -> Self {
199 Faker.fake()
200 }
201 }
202
203 #[quickcheck]
204 fn a_valid_name_is_parsed_successfully(component: ComponentTest) {
205 assert_ok!(Component::try_from(component));
206 }
207}