wordle_cli/db/
db_dictionary.rs

1use chrono::NaiveDate;
2use diesel::dsl::sql;
3use diesel::{Connection, ExpressionMethods, OptionalExtension, SqliteConnection, QueryDsl, RunQueryDsl};
4use diesel::result::Error;
5
6use crate::db::model::{DbDictionaryEntry, NewDbDictionaryEntry};
7use crate::db::schema::dictionary;
8use crate::dictionary::{Dictionary, DictionaryEntry};
9
10pub struct DbDictionary{
11    conn: SqliteConnection,
12    lang: String
13}
14
15impl DbDictionary {
16    pub fn new(db_url: String, lang: &str) -> DbDictionary {
17        DbDictionary {
18            conn: SqliteConnection::establish(&db_url)
19                .expect(&format!("Error connecting to database {}", db_url)),
20            lang: String::from(lang)
21        }
22    }
23
24    /// Issue an update statement on a specific dictionary entry.
25    fn update_entry(&self, entry: &DbDictionaryEntry) {
26        match diesel::update(dictionary::dsl::dictionary
27            .filter(dictionary::id.eq(entry.id)))
28            .set((dictionary::used_at.eq(entry.used_at), dictionary::guessed.eq(entry.guessed)))
29            .execute(&self.conn) {
30                Ok(affected_rows) => if affected_rows <= 0 { println!("No rows were affected when updating {}", entry.id) },
31                Err(error) => println!("Error when updating entry with id {}:\n{}", entry.id, error)
32        }
33    }
34
35    /// Returns either an entry with matching [used_at] or randomly selects a unused entry.
36    fn get_word_of_today(&self, current_day: NaiveDate) -> Result<Option<DbDictionaryEntry>, Error> {
37        match dictionary::dsl::dictionary
38            .filter(dictionary::used_at.eq(current_day))
39            .filter(dictionary::language.eq(&self.lang))
40            .limit(1)
41            .get_result::<DbDictionaryEntry>(&self.conn)
42            .optional() {
43                Err(error) => Err(error),
44                Ok(result) => match result {
45                    Some(entry) => Ok(Some(entry)),
46                    None => dictionary::dsl::dictionary
47                        .filter(dictionary::used_at.is_null())
48                        .filter(dictionary::language.eq(&self.lang))
49                        .order(sql::<()>("RANDOM()"))
50                        .limit(1)
51                        .get_result::<DbDictionaryEntry>(&self.conn)
52                        .optional()
53            }
54        }
55    }
56}
57
58impl Dictionary for DbDictionary {
59    /// Return a randomly selected word that has not been used before.
60    /// If found, the database entry will be updated with a [chrono::NaiveDate] matching today.
61    fn get_random_word(&self) -> Option<DictionaryEntry> {
62        let current_day = chrono::Utc::now();
63        let current_day: NaiveDate = current_day.naive_utc().date();
64
65        match self.get_word_of_today(current_day) {
66            Ok(result) => match result {
67                None => None,
68                Some(mut entry) => {
69                    entry.used_at = Some(current_day);
70
71                    self.update_entry(&entry);
72                    Some(DictionaryEntry{
73                        word: entry.word,
74                        guessed: entry.guessed
75                    })
76                }
77            }
78            Err(error) => {
79                println!("Error when getting today's word.\n{}", error);
80
81                None
82            }
83        }
84    }
85
86    /// Find dictionary entry with matching [text]. Language is implicitly set in DbDictionary.
87    fn find_word(&self, text: &str) -> Option<DictionaryEntry> {
88        let db_result = dictionary::dsl::dictionary
89            .filter(dictionary::word.eq(text))
90            .filter(dictionary::language.eq(&self.lang))
91            .get_result::<DbDictionaryEntry>(&self.conn)
92            .optional();
93
94        match db_result {
95            Ok(db_word) => match db_word {
96                Some(entry) => Some(DictionaryEntry {
97                    word: entry.word,
98                    guessed: entry.guessed
99                }),
100                None => None
101            },
102            Err(error) => {
103                println!("Error when looking for '{}' in the database:\n{}", text, error);
104
105                None
106            }
107        }
108    }
109
110    /// Add new dictionary entry
111    fn create_word(&self, word_entry: DictionaryEntry) -> Option<DictionaryEntry> {
112        match self.find_word(&word_entry.word) {
113            None => {
114                let new_word = NewDbDictionaryEntry {
115                    word: String::from(&word_entry.word),
116                    language: String::from(&self.lang)
117                };
118
119                let db_result = diesel::insert_into(dictionary::table)
120                    .values(&new_word)
121                    .execute(&self.conn);
122
123                match db_result {
124                    Ok(_) => {
125                        Some(word_entry)
126                    },
127                    Err(e) => {
128                        println!("Error when writing '{}' to the database:\n{}", &new_word.word, e);
129                        None
130                    }
131                }
132            },
133            Some (_) => None
134        }
135    }
136
137    /// Mark word as guessed in the database when the player won
138    fn guessed_word(&self, word_entry: DictionaryEntry) {
139        match diesel::update(dictionary::dsl::dictionary
140            .filter(dictionary::word.eq(word_entry.word)))
141            .filter(dictionary::language.eq(&self.lang))
142            .set(dictionary::guessed.eq(true))
143            .execute(&self.conn) {
144                Ok(_) => {},
145                Err(error) => { println!("Error updating the solution.\n{}", error) }
146        }
147    }
148}