Init repo

This commit is contained in:
Jonas Zeunert
2024-04-22 21:37:32 +02:00
parent 8063acde00
commit f6d6fd43d2
8 changed files with 2009 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

1755
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

16
Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[package]
name = "awesm"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
env_logger = "0.11.3"
lazy_static = "1.4.0"
log = "0.4.21"
regex = "1.10.4"
reqwest = { version = "0.12.4", features = ["blocking"] }
rouille = "3.6.2"
serde = { version = "1.0.198", features = ["derive"] }
serde_json = { version = "1.0.116", features = ["raw_value"] }

67
src/elastic_query.rs Normal file
View File

@@ -0,0 +1,67 @@
use std::error::Error;
use log::debug;
use regex::Regex;
use reqwest::Url;
use serde::Serialize;
use crate::elastic_response::ElasticResponse;
#[derive(Debug, Serialize)]
pub struct ElasticQuery {
_source: Source,
query: Query,
}
#[derive(Debug, Serialize)]
pub struct Source {
includes: Vec<String>,
}
#[derive(Debug, Serialize)]
pub struct Query {
query_string: QueryString,
}
#[derive(Debug, Serialize)]
pub struct QueryString {
query: String,
}
impl ElasticQuery {
pub fn new(fields: Vec<String>, query: String) -> ElasticQuery {
let regex = Regex::new(r"[^[\w\s]]").unwrap();
let escaped = regex.replace_all(&query, "").to_string();
Self {
_source: Source { includes: fields },
query: Query {
query_string: QueryString { query: escaped },
},
}
}
pub fn send(&self, url: Url) -> Result<ElasticResponse, Box<dyn Error>> {
debug!("Url: {:?}\nQuery: {:?}", url, self);
let query = serde_json::to_string(self).unwrap();
let client = reqwest::blocking::Client::new();
let response = client.get(url).body(query).send()?.text()?;
Ok(serde_json::from_str(&response)?)
}
}
#[cfg(test)]
mod test {
use super::*;
mod new {
use super::*;
#[test]
fn should_escape() {
let fields = vec![];
let query = "!@#$%^&*()+]}[{|\\'\"/?.>,<~` some";
let elastic_query = ElasticQuery::new(fields, query.to_string());
assert_eq!(elastic_query.query.query_string.query, " some");
}
}
}

27
src/elastic_response.rs Normal file
View File

@@ -0,0 +1,27 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct ElasticResponse {
pub hits: ElasticHits,
}
#[derive(Debug, Deserialize)]
pub struct ElasticHits {
pub hits: Vec<Hits>,
}
#[derive(Debug, Deserialize)]
pub struct Hits {
#[serde(alias = "_source")]
pub source: Source,
}
#[derive(Debug, Deserialize)]
pub struct Source {
pub file: File,
}
#[derive(Debug, Deserialize)]
pub struct File {
pub file_name: String,
}

14
src/main.rs Normal file
View File

@@ -0,0 +1,14 @@
use log::info;
mod options;
mod server;
mod elastic_query;
mod elastic_response;
fn main() {
env_logger::init();
info!("Starting server...");
rouille::start_server("0.0.0.0:80", move |request| server::serve_request(request));
}

14
src/options.rs Normal file
View File

@@ -0,0 +1,14 @@
pub mod elastic_search {
use lazy_static::lazy_static;
use reqwest::Url;
lazy_static! {
pub static ref URL: Url =
Url::parse("http://127.0.0.1:9200/_search?filter_path=hits.hits._source").unwrap();
pub static ref FIELDS: Vec<String> = vec!["file.filename".to_string()];
}
}
pub mod query {
pub const MAX_LENGTH: usize = 100;
pub const MIN_LENGTH: usize = 3;
}

115
src/server.rs Normal file
View File

@@ -0,0 +1,115 @@
use log::{error, info};
use rouille::router;
use rouille::Request;
use rouille::Response;
use std::error::Error;
use crate::elastic_query::ElasticQuery;
use crate::options;
pub fn serve_request(req: &Request) -> Response {
router!(req,
(GET) (/search) => { query(req) },
_ => Response::empty_404()
)
}
fn query(req: &Request) -> Response {
info!("Handling request: {:?}", req);
let Some(query) = req.get_param("q") else {
return Response::empty_400();
};
match query.len() {
0..=options::query::MIN_LENGTH => {
return Response::text(format!(
"Min query length: {:?}",
options::query::MIN_LENGTH
))
.with_status_code(400);
}
options::query::MAX_LENGTH..=usize::MAX => {
return Response::text(format!(
"Max query length: {:?}",
options::query::MAX_LENGTH
))
.with_status_code(400);
}
_ => (),
}
let response = query_elastic_search(query);
if let Err(e) = response {
error!("Error searching: {}", e);
return Response::empty_404();
}
let response = response.unwrap();
let Some(name) = response.split('.').next() else {
error!("Error getting name from response!");
return Response::empty_404();
};
Response::redirect_303(format!("/{}", name))
}
fn query_elastic_search(query: String) -> Result<String, Box<dyn Error>> {
let query = ElasticQuery::new(options::elastic_search::FIELDS.clone(), query);
let res = query.send(options::elastic_search::URL.clone())?;
Ok(res.hits.hits[0].source.file.file_name.clone())
}
#[cfg(test)]
mod test {
const SEARCH_URL: &str = "/search";
use super::*;
mod serve_request {
use super::*;
#[test]
fn test_query_route_exists() {
let req = Request::fake_http("GET", SEARCH_URL, vec![], vec![]);
let res = serve_request(&req);
assert_ne!(res.status_code, 404)
}
#[test]
fn test_non_existant_route() {
let req = Request::fake_http("GET", "/test", vec![], vec![]);
let res = serve_request(&req);
assert_eq!(res.status_code, 404)
}
}
mod query {
use super::*;
#[test]
fn no_query_parameter() {
let req = Request::fake_http("GET", SEARCH_URL, vec![], vec![]);
let res = query(&req);
assert_eq!(res.status_code, 400)
}
#[test]
fn query_too_long() {
let q: String = std::iter::repeat("a")
.take(options::query::MAX_LENGTH + 1)
.collect();
let req = Request::fake_http("GET", format!("/{}?q={}", SEARCH_URL, q), vec![], vec![]);
let res = query(&req);
let mut body = String::new();
let _ = res.data.into_reader_and_size().0.read_to_string(&mut body);
assert_eq!(body, "Max query length: 100");
assert_eq!(res.status_code, 400)
}
#[test]
fn query_too_short() {
let req = Request::fake_http("GET", format!("/{}?q=", SEARCH_URL), vec![], vec![]);
let res = query(&req);
assert_eq!(res.status_code, 400);
let mut body = String::new();
let _ = res.data.into_reader_and_size().0.read_to_string(&mut body);
assert_eq!(body, "Min query length: 3")
}
}
}