-
Notifications
You must be signed in to change notification settings - Fork 0
/
api.rs
99 lines (80 loc) · 2.6 KB
/
api.rs
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
use crate::{
trie::Trie,
utils::{current_time_ms, parse_words},
};
use actix_web::{get, http::StatusCode, web, HttpResponse, Responder};
use lazy_static::lazy_static;
use regex::Regex;
use serde::Deserialize;
use serde_json::json;
use std::fs;
// -------- Constants --------------
lazy_static! {
static ref VALID_WORD_PATTERN: Regex = Regex::new("^[a-zA-Z-]+$").unwrap();
}
// ----------- Struct --------------
pub struct RouteState {
pub trie: Trie,
pub max_word_len: usize,
}
#[derive(Clone)]
pub struct WordsState {
pub words: Vec<String>,
pub max_len: usize,
}
// ---------- Routes ------------
#[derive(Deserialize)]
struct UnscrambleQuery {
word: Option<String>,
}
#[get("/unscramble")]
pub async fn unscramble(
state: web::Data<RouteState>,
queries: web::Query<UnscrambleQuery>,
) -> impl Responder {
let word = match validate_query(queries.word.clone(), state.max_word_len) {
Ok(word) => word,
Err(message) => {
return HttpResponse::BadRequest().json(json!({"message": message }));
}
};
let start_time_ms = current_time_ms();
let results = state.trie.search_words(&word);
let end_time_ms = current_time_ms();
println!("request -> unscramble: '{}'", word);
HttpResponse::build(StatusCode::OK)
.insert_header(("Access-Control-Allow-Origin", "http://localhost:5173"))
.insert_header(("Access-Control-Allow-Methods", "GET, POST, OPTIONS"))
.json(json!({
"query": &word,
"possibleWords": results,
"searchTime": end_time_ms - start_time_ms
}))
}
// ---------- Utils ------------
pub fn get_words_state(words_file_path: &str) -> WordsState {
let words = {
let file_content = fs::read_to_string(words_file_path).unwrap();
parse_words(&file_content)
};
let max_len = words.iter().map(|word| word.len()).max().unwrap_or(0);
WordsState { words, max_len }
}
fn validate_query(query: Option<String>, max_query_len: usize) -> Result<String, String> {
if query.is_none() {
return Err(r#"Missing query "word". e.g., "/unscramble?word=test""#.to_string());
}
let query = query.unwrap();
if query.len() < 2 {
return Err("Query must be at least 2 characters long.".to_string());
}
if query.len() > max_query_len {
return Err(
format!("The query cannot be longer than {max_query_len} characters!").to_string(),
);
}
if !VALID_WORD_PATTERN.is_match(&query) {
return Err("The query must only contain alpha (a-z) and '-' characters.".to_string());
}
Ok(query)
}