vocab/vocab_store/
translation.rs1use diesel::{Insertable, Queryable};
2
3use crate::schema::translations;
4use crate::VocabStoreError;
5
6#[derive(Debug, Default, Insertable, Queryable, PartialEq)]
7pub struct Translation {
8 pub local: String,
9 pub foreign: String,
10 pub guesses_local_total: i32,
11 pub guesses_local_correct: i32,
12 pub guesses_foreign_total: i32,
13 pub guesses_foreign_correct: i32,
14}
15
16fn normalised_percent(numerator: i32, denominator: i32) -> f64 {
17 if denominator > 0 {
18 numerator as f64 / denominator as f64
19 } else {
20 0.0
21 }
22}
23
24impl Translation {
25 pub fn new(local: &str, foreign: &str) -> Translation {
26 Translation {
27 local: local.to_lowercase(),
28 foreign: foreign.to_lowercase(),
29 ..Default::default()
30 }
31 }
32
33 pub fn get_total_percent(&self) -> f64 {
34 normalised_percent(
35 self.guesses_local_correct + self.guesses_foreign_correct,
36 self.guesses_local_total + self.guesses_foreign_total,
37 )
38 }
39
40 pub fn guess_local(&mut self, guess: &str) -> bool {
41 self.guesses_local_total += 1;
42 if self.local.to_lowercase() == guess.to_lowercase() {
43 self.guesses_local_correct += 1;
44 true
45 } else {
46 false
47 }
48 }
49
50 pub fn guess_foreign(&mut self, guess: &str) -> bool {
51 self.guesses_foreign_total += 1;
52 if self.foreign.to_lowercase() == guess.to_lowercase() {
53 self.guesses_foreign_correct += 1;
54 true
55 } else {
56 false
57 }
58 }
59
60 pub fn reconcile(self, other: Translation) -> Result<Translation, VocabStoreError> {
61 if self.local != other.local || self.foreign != other.foreign {
63 return Err(VocabStoreError::ReconciliationError);
64 }
65
66 let more_local = self.guesses_local_total > other.guesses_local_total;
68 let more_foreign = self.guesses_foreign_total > other.guesses_foreign_total;
69
70 let (guesses_local_total, guesses_local_correct) = if more_local {
71 (self.guesses_local_total, self.guesses_local_correct)
72 } else {
73 (other.guesses_local_total, other.guesses_local_correct)
74 };
75 let (guesses_foreign_total, guesses_foreign_correct) = if more_foreign {
76 (self.guesses_foreign_total, self.guesses_foreign_correct)
77 } else {
78 (other.guesses_foreign_total, other.guesses_foreign_correct)
79 };
80
81 Ok(Translation {
82 local: self.local,
83 foreign: self.foreign,
84 guesses_local_total,
85 guesses_local_correct,
86 guesses_foreign_total,
87 guesses_foreign_correct,
88 })
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::Translation;
95 use crate::VocabStoreError;
96
97 #[test]
98 fn test_guess_local() {
99 let mut translation = Translation::new("yes", "はい");
100 assert_eq!(translation.guess_local("yEs"), true);
101 assert_eq!(translation.guesses_local_total, 1);
102 assert_eq!(translation.guesses_local_correct, 1);
103 assert_eq!(translation.guesses_foreign_total, 0);
104 assert_eq!(translation.guesses_foreign_correct, 0);
105 assert_eq!(translation.get_total_percent(), 1.0);
106
107 assert_eq!(translation.guess_local("no"), false);
108 assert_eq!(translation.guesses_local_total, 2);
109 assert_eq!(translation.guesses_local_correct, 1);
110 assert_eq!(translation.guesses_foreign_total, 0);
111 assert_eq!(translation.guesses_foreign_correct, 0);
112 assert_eq!(translation.get_total_percent(), 0.5);
113 }
114
115 #[test]
116 fn test_guess_foreign() {
117 let mut translation = Translation::new("yes", "はい");
118 assert_eq!(translation.guess_foreign("はい"), true);
119 assert_eq!(translation.guesses_local_total, 0);
120 assert_eq!(translation.guesses_local_correct, 0);
121 assert_eq!(translation.guesses_foreign_total, 1);
122 assert_eq!(translation.guesses_foreign_correct, 1);
123 assert_eq!(translation.get_total_percent(), 1.0);
124
125 assert_eq!(translation.guess_foreign("いいえ"), false);
126 assert_eq!(translation.guesses_local_total, 0);
127 assert_eq!(translation.guesses_local_correct, 0);
128 assert_eq!(translation.guesses_foreign_total, 2);
129 assert_eq!(translation.guesses_foreign_correct, 1);
130 assert_eq!(translation.get_total_percent(), 0.5);
131 }
132
133 #[test]
134 fn test_reconcile() {
135 let mut old_translation = Translation::new("yes", "はい");
136 old_translation.guesses_local_correct = 4;
137 old_translation.guesses_local_total = 5;
138 old_translation.guesses_foreign_correct = 3;
139 old_translation.guesses_foreign_total = 5;
140 let mut new_translation = Translation::new("yes", "はい");
141 new_translation.guesses_local_correct = 4;
142 new_translation.guesses_local_total = 4;
143 new_translation.guesses_foreign_correct = 6;
144 new_translation.guesses_foreign_total = 6;
145 let reconciled_translation = old_translation.reconcile(new_translation).unwrap();
146
147 assert_eq!(reconciled_translation.local, "yes");
148 assert_eq!(reconciled_translation.foreign, "はい");
149 assert_eq!(reconciled_translation.guesses_local_correct, 4);
150 assert_eq!(reconciled_translation.guesses_local_total, 5);
151 assert_eq!(reconciled_translation.guesses_foreign_correct, 6);
152 assert_eq!(reconciled_translation.guesses_foreign_total, 6);
153
154 let mut old_translation = Translation::new("no", "いいえ");
155 let mut new_translation = Translation::new("japan", "日本");
156 match old_translation.reconcile(new_translation) {
157 Err(VocabStoreError::ReconciliationError) => {}
158 _ => assert!(false, "VocabStore did not return ReconciliationError error"),
159 }
160 }
161}