add basic web server
This commit is contained in:
parent
fee7dcc62a
commit
8fcb2824de
922
Cargo.lock
generated
922
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,4 +7,7 @@ edition = "2021"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
nom = "7.1.3"
|
||||
actix-web = { version = "4.3.0", default_features = false, features = ["macros"] }
|
||||
|
||||
[dev-dependencies]
|
||||
test-case = "2.2.2"
|
||||
|
@ -40,10 +40,10 @@ impl Display for Card {
|
||||
write!(f, "{attr}/")?;
|
||||
}
|
||||
write!(f, "{} {})", self.r#type, self.card_type)?;
|
||||
f.write_str("\n")?;
|
||||
f.write_str("<br/>")?;
|
||||
f.write_str(&self.text)?;
|
||||
if self.card_type.contains(&String::from("Monster")) {
|
||||
f.write_str("\n")?;
|
||||
f.write_str("<br/>")?;
|
||||
match (self.atk, self.def) {
|
||||
(Some(atk), Some(def)) => write!(f, "{atk} ATK / {def} DEF")?,
|
||||
(Some(atk), None) if self.link_rating.is_some() => write!(f, "{atk} ATK")?,
|
||||
|
@ -61,6 +61,7 @@ pub fn build_filter(query: RawCardFilter) -> Result<CardFilter, String> {
|
||||
}
|
||||
(Field::Level, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.level, n)),
|
||||
(Field::Type, Operator::Equals, Value::String(s)) => Box::new(move |card| card.r#type == s),
|
||||
(Field::Attribute, Operator::Equals, Value::String(s)) => Box::new(move |card| card.attribute.contains(&s)),
|
||||
(Field::Class, Operator::Equals, Value::String(s)) => Box::new(move |card| card.card_type.contains(&s)),
|
||||
(Field::Text, Operator::Equals, Value::String(s)) => Box::new(move |card| card.text.contains(&s)),
|
||||
(Field::Name, Operator::Equals, Value::String(s)) => Box::new(move |card| card.name.contains(&s)),
|
||||
|
81
src/main.rs
81
src/main.rs
@ -1,28 +1,73 @@
|
||||
#![feature(option_result_contains)]
|
||||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
use data::CardInfo;
|
||||
#![feature(option_result_contains, once_cell)]
|
||||
use actix_web::{get, http::header, web, App, Either, HttpResponse, HttpServer};
|
||||
use data::{Card, CardInfo};
|
||||
use filter::SearchCard;
|
||||
use serde::Deserialize;
|
||||
use std::{collections::HashMap, fmt::Write, fs::File, io::BufReader, net::Ipv4Addr, sync::LazyLock, time::Instant};
|
||||
|
||||
mod data;
|
||||
mod filter;
|
||||
mod parser;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cards = serde_json::from_reader::<_, CardInfo>(std::io::BufReader::new(std::fs::File::open("cards.json")?))?.data;
|
||||
let search_cards: Vec<_> = cards.iter().map(SearchCard::from).collect();
|
||||
let cards_by_id: HashMap<_, _> = cards.into_iter().map(|c| (c.id, c)).collect();
|
||||
static CARDS: LazyLock<Vec<Card>> = LazyLock::new(|| {
|
||||
serde_json::from_reader::<_, CardInfo>(BufReader::new(File::open("cards.json").expect("cards.json not found")))
|
||||
.expect("Could not deserialize cards")
|
||||
.data
|
||||
});
|
||||
static CARDS_BY_ID: LazyLock<HashMap<usize, Card>> =
|
||||
LazyLock::new(|| CARDS.iter().map(|c| (c.id, Card { text: c.text.replace("\r", "").replace('\n', "<br/>"), ..c.clone() })).collect());
|
||||
static SEARCH_CARDS: LazyLock<Vec<SearchCard>> = LazyLock::new(|| CARDS.iter().map(SearchCard::from).collect());
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let now = Instant::now();
|
||||
let raw_query = std::env::args().nth(1).unwrap();
|
||||
let query = parser::parse_filters(&raw_query)?;
|
||||
let query_time = now.elapsed();
|
||||
println!("Starting server");
|
||||
// tap these so they’re initialized
|
||||
let num_cards = (CARDS_BY_ID.len() + SEARCH_CARDS.len()) / 2;
|
||||
println!("Read {num_cards} cards in {:?}", now.elapsed());
|
||||
HttpServer::new(|| App::new().service(search)).bind((Ipv4Addr::from([127, 0, 0, 1]), 8080))?.run().await
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Query {
|
||||
q: String,
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn search(q: Option<Either<web::Query<Query>, web::Form<Query>>>) -> Result<HttpResponse, Box<dyn std::error::Error>> {
|
||||
let q = match q {
|
||||
Some(Either::Left(web::Query(Query { q }))) => Some(q),
|
||||
Some(Either::Right(web::Form(Query { q }))) => Some(q),
|
||||
None => None,
|
||||
};
|
||||
let mut res = String::with_capacity(10_000);
|
||||
write!(
|
||||
res,
|
||||
r#"
|
||||
<html><body>
|
||||
<form action="/">
|
||||
<label for="fname">Search query:</label><br>
|
||||
<input type="text" name="q" value="{}"><br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>"#,
|
||||
match &q {
|
||||
Some(q) => q,
|
||||
None => "",
|
||||
}
|
||||
)?;
|
||||
if let Some(q) = q {
|
||||
let query = parser::parse_filters(&q)?;
|
||||
let now = Instant::now();
|
||||
let matches: Vec<_> = search_cards.iter().filter(|card| query.iter().all(|q| q(card))).collect();
|
||||
let filter_time = now.elapsed();
|
||||
for c in &matches {
|
||||
println!("{}\n", cards_by_id.get(&c.id).unwrap());
|
||||
let matches: Vec<&Card> =
|
||||
SEARCH_CARDS.iter().filter(|card| query.iter().all(|q| q(card))).map(|c| CARDS_BY_ID.get(&c.id).unwrap()).collect();
|
||||
write!(res, "Showing {} results (took {:?})<br/><br/>", matches.len(), now.elapsed())?;
|
||||
for card in matches {
|
||||
res.push_str(&card.to_string());
|
||||
res.push_str("<br/><br/>");
|
||||
}
|
||||
println!("Parsed query in {:?}", query_time);
|
||||
println!("Searched {} cards in {:?} ({} hits)", search_cards.len(), filter_time, matches.len());
|
||||
Ok(())
|
||||
write!(res, "</body></html>")?;
|
||||
} else {
|
||||
res.write_str("Enter a query above to search")?;
|
||||
}
|
||||
Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user