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_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
nom = "7.1.3"
|
nom = "7.1.3"
|
||||||
|
actix-web = { version = "4.3.0", default_features = false, features = ["macros"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
test-case = "2.2.2"
|
test-case = "2.2.2"
|
||||||
|
@ -40,10 +40,10 @@ impl Display for Card {
|
|||||||
write!(f, "{attr}/")?;
|
write!(f, "{attr}/")?;
|
||||||
}
|
}
|
||||||
write!(f, "{} {})", self.r#type, self.card_type)?;
|
write!(f, "{} {})", self.r#type, self.card_type)?;
|
||||||
f.write_str("\n")?;
|
f.write_str("<br/>")?;
|
||||||
f.write_str(&self.text)?;
|
f.write_str(&self.text)?;
|
||||||
if self.card_type.contains(&String::from("Monster")) {
|
if self.card_type.contains(&String::from("Monster")) {
|
||||||
f.write_str("\n")?;
|
f.write_str("<br/>")?;
|
||||||
match (self.atk, self.def) {
|
match (self.atk, self.def) {
|
||||||
(Some(atk), Some(def)) => write!(f, "{atk} ATK / {def} DEF")?,
|
(Some(atk), Some(def)) => write!(f, "{atk} ATK / {def} DEF")?,
|
||||||
(Some(atk), None) if self.link_rating.is_some() => write!(f, "{atk} ATK")?,
|
(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::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::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::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::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)),
|
(Field::Name, Operator::Equals, Value::String(s)) => Box::new(move |card| card.name.contains(&s)),
|
||||||
|
85
src/main.rs
85
src/main.rs
@ -1,28 +1,73 @@
|
|||||||
#![feature(option_result_contains)]
|
#![feature(option_result_contains, once_cell)]
|
||||||
use std::{collections::HashMap, time::Instant};
|
use actix_web::{get, http::header, web, App, Either, HttpResponse, HttpServer};
|
||||||
|
use data::{Card, CardInfo};
|
||||||
use data::CardInfo;
|
|
||||||
use filter::SearchCard;
|
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 data;
|
||||||
mod filter;
|
mod filter;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
static CARDS: LazyLock<Vec<Card>> = LazyLock::new(|| {
|
||||||
let cards = serde_json::from_reader::<_, CardInfo>(std::io::BufReader::new(std::fs::File::open("cards.json")?))?.data;
|
serde_json::from_reader::<_, CardInfo>(BufReader::new(File::open("cards.json").expect("cards.json not found")))
|
||||||
let search_cards: Vec<_> = cards.iter().map(SearchCard::from).collect();
|
.expect("Could not deserialize cards")
|
||||||
let cards_by_id: HashMap<_, _> = cards.into_iter().map(|c| (c.id, c)).collect();
|
.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 now = Instant::now();
|
||||||
let raw_query = std::env::args().nth(1).unwrap();
|
println!("Starting server");
|
||||||
let query = parser::parse_filters(&raw_query)?;
|
// tap these so they’re initialized
|
||||||
let query_time = now.elapsed();
|
let num_cards = (CARDS_BY_ID.len() + SEARCH_CARDS.len()) / 2;
|
||||||
let now = Instant::now();
|
println!("Read {num_cards} cards in {:?}", now.elapsed());
|
||||||
let matches: Vec<_> = search_cards.iter().filter(|card| query.iter().all(|q| q(card))).collect();
|
HttpServer::new(|| App::new().service(search)).bind((Ipv4Addr::from([127, 0, 0, 1]), 8080))?.run().await
|
||||||
let filter_time = now.elapsed();
|
}
|
||||||
for c in &matches {
|
|
||||||
println!("{}\n", cards_by_id.get(&c.id).unwrap());
|
#[derive(Debug, Deserialize)]
|
||||||
}
|
struct Query {
|
||||||
println!("Parsed query in {:?}", query_time);
|
q: String,
|
||||||
println!("Searched {} cards in {:?} ({} hits)", search_cards.len(), filter_time, matches.len());
|
}
|
||||||
Ok(())
|
|
||||||
|
#[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<&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/>");
|
||||||
|
}
|
||||||
|
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