Upgraded to actix-web 4

This commit is contained in:
2024-08-10 22:20:40 +02:00
parent e05159c7d7
commit fd2b4e098b
15 changed files with 452 additions and 505 deletions

View File

@@ -18,31 +18,28 @@ license = "MIT"
include = ["Cargo.toml", "README.md", "LICENSE", "ACKNOWLEDGEMENTS", "src/**/*.rs"] include = ["Cargo.toml", "README.md", "LICENSE", "ACKNOWLEDGEMENTS", "src/**/*.rs"]
#[profile.release]
#lto = true
[features] [features]
static-error-pages = [] static-error-pages = []
[dependencies] [dependencies]
actix-web = "^1.0" actix-web = "4.8"
actix-files = "^0.1" actix-files = "0.6"
actix-service = "^0.4" actix-cors = "0.7"
actix-cors = "^0.1" glob = "0.3"
glob = "^0.3"
measure_time = "^0.6" measure_time = "0.8"
memmap = "^0.7" memmap = "0.7"
mercator_db = "^0.1" mercator_db = "0.1"
mercator_parser = "^0.1" mercator_parser = "0.1"
serde = { version = "^1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "^1.0" serde_json = "1.0"
bincode = "^1.1" bincode = "1.3"
# Logging macros API # Logging macros API
log = { version = "^0.4", features = ["max_level_trace", "release_max_level_trace"] } log = { version = "0.4", features = ["max_level_trace", "release_max_level_trace"] }
pretty_env_logger = "^0.3" # Logger implementation pretty_env_logger = "0.5" # Logger implementation
[dev-dependencies]
# Only for tests
actix-server-config = "^0.1"
actix-http = "^0.2"

View File

@@ -1,2 +1,2 @@
[toolchain] [toolchain]
channel = "1.42.0" channel = "1.80.0"

View File

@@ -50,7 +50,8 @@ fn into_bool(string: &str) -> bool {
} }
*/ */
fn main() { #[actix_web::main]
async fn main() -> std::io::Result<()> {
// If RUST_LOG is unset, set it to INFO, otherwise keep it as-is. // If RUST_LOG is unset, set it to INFO, otherwise keep it as-is.
if std::env::var("RUST_LOG").is_err() { if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info"); std::env::set_var("RUST_LOG", "info");
@@ -78,21 +79,17 @@ fn main() {
std::env::set_var("MERCATOR_DATA", "."); std::env::set_var("MERCATOR_DATA", ".");
} }
let hostname; let hostname = match std::env::var("MERCATOR_HOST") {
let port; Ok(val) => val,
let data;
match std::env::var("MERCATOR_HOST") {
Ok(val) => hostname = val,
Err(val) => { Err(val) => {
error!("Invalid environment {} : `{}`", "MERCATOR_HOST", val); error!("Invalid environment {} : `{}`", "MERCATOR_HOST", val);
exit(1); exit(1);
} }
}; };
match std::env::var("MERCATOR_PORT") { let port = match std::env::var("MERCATOR_PORT") {
Ok(val) => match val.parse::<u16>() { Ok(val) => match val.parse::<u16>() {
Ok(v) => port = v, Ok(v) => v,
Err(e) => { Err(e) => {
error!("Could not convert to u16 {} : `{}`", "MERCATOR_PORT", e); error!("Could not convert to u16 {} : `{}`", "MERCATOR_PORT", e);
exit(1); exit(1);
@@ -104,8 +101,8 @@ fn main() {
} }
}; };
match std::env::var("MERCATOR_DATA") { let data = match std::env::var("MERCATOR_DATA") {
Ok(val) => data = val, Ok(val) => val,
Err(val) => { Err(val) => {
error!("Could not fetch {} : `{}`", "MERCATOR_DATA", val); error!("Could not fetch {} : `{}`", "MERCATOR_DATA", val);
exit(1); exit(1);
@@ -123,9 +120,6 @@ fn main() {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// FIXME: Why do we have to go through a temporary variable?
let datasets = datasets.iter().map(String::as_str).collect::<Vec<_>>();
let db; let db;
// Load a Database: // Load a Database:
{ {
@@ -133,7 +127,7 @@ fn main() {
// those is corrupted / incompatible. // those is corrupted / incompatible.
info_time!("Loading database index"); info_time!("Loading database index");
db = DataBase::load(&datasets) db = DataBase::load(&datasets.iter().map(String::as_str).collect::<Vec<_>>())
.unwrap_or_else(|e| panic!("Error while loading indices: {}", e)); .unwrap_or_else(|e| panic!("Error while loading indices: {}", e));
} }
@@ -141,5 +135,6 @@ fn main() {
&hostname, &hostname,
port, port,
Data::new(RwLock::new(SharedState::new(db))), Data::new(RwLock::new(SharedState::new(db))),
); )
.await
} }

View File

@@ -3,14 +3,15 @@ use std::sync::RwLock;
use serde::Deserialize; use serde::Deserialize;
use super::error_422; use super::error_422;
use super::from_properties_by_spaces;
use super::ok_200; use super::ok_200;
use super::to_spatial_objects;
use super::web; use super::web;
use super::web::Data; use super::web::Data;
use super::web::Json; use super::web::Json;
use super::HandlerResult; use super::HandlerResult;
use super::HttpResponse; use super::HttpResponse;
use super::SharedState; use super::SharedState;
use mercator_db::CoreQueryParameters;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Query { pub struct Query {
@@ -36,11 +37,11 @@ impl Query {
} }
} }
// Also used for the root service. // Also used for the root service.
pub fn health() -> HttpResponse { pub async fn health() -> HttpResponse {
HttpResponse::Ok().finish() HttpResponse::Ok().finish()
} }
fn query((parameters, state): (Json<Query>, Data<RwLock<SharedState>>)) -> HandlerResult { async fn query((parameters, state): (Json<Query>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("POST '{:?}'", parameters); trace!("POST '{:?}'", parameters);
let context = state let context = state
.read() .read()
@@ -50,25 +51,30 @@ fn query((parameters, state): (Json<Query>, Data<RwLock<SharedState>>)) -> Handl
if query.is_empty() { if query.is_empty() {
error_422(format!("Invalid query in '{:?}'", query)) error_422(format!("Invalid query in '{:?}'", query))
} else { } else {
ok_200( let parameters = CoreQueryParameters {
&context db: context.db(),
.db() output_space: None,
.core_keys() threshold_volume: parameters.volume(),
.iter() view_port: &parameters.view_port,
.flat_map(|core| { resolution: parameters.resolution(),
match context.query( };
query,
core, let results = context
parameters.volume(), .db()
&parameters.view_port, .core_keys()
parameters.resolution(), .iter()
) { .filter_map(|core| {
Err(_) => vec![], // FIXME: Return errors here instead!! match context.query(query) {
Ok(objects) => to_spatial_objects(objects), Err(_) => None, // FIXME: Return errors here instead!!
} Ok(tree) => match context.execute(&tree, core, &parameters) {
}) Err(_) => None, // FIXME: Return errors here instead!!
.collect::<Vec<_>>(), Ok(objects) => Some(from_properties_by_spaces(objects).collect::<Vec<_>>()),
) },
}
})
.flatten()
.collect::<Vec<_>>();
ok_200(&results)
} }
} }
@@ -79,30 +85,41 @@ pub fn config(cfg: &mut web::ServiceConfig) {
#[cfg(test)] #[cfg(test)]
mod routing { mod routing {
use super::super::tests_utils::*; use crate::rest_api::tests_utils::*;
use serde_json::json;
#[test] #[actix_web::test]
fn health() { async fn health() {
let ep = &get_path("/health"); let ep = &get_path("/health");
expect_200(Method::GET, ep); expect_200(TestRequest::get(), ep).await;
expect_405(Method::POST, ep); expect_405(TestRequest::post(), ep).await;
expect_405(Method::PUT, ep); expect_405(TestRequest::put(), ep).await;
expect_405(Method::PATCH, ep); expect_405(TestRequest::patch(), ep).await;
expect_405(Method::DELETE, ep); expect_405(TestRequest::delete(), ep).await;
} }
#[test] #[actix_web::test]
fn query() { async fn query() {
let ep = &get_path("/query"); let ep = &get_path("/query");
expect_200(Method::POST, ep); expect_200(
expect_422(Method::POST, ep); TestRequest::post()
.set_json(json!({"query": "json(.,inside(hyperrectangle{[0,0,0],[0,1,1]}))"})),
ep,
)
.await;
expect_405(Method::GET, ep); expect_422(TestRequest::post().set_json(json!({"query": "toto"})), ep).await;
expect_405(Method::PUT, ep); expect_422(TestRequest::post().set_json(json!({"query": ""})), ep).await;
expect_405(Method::PATCH, ep); expect_400(TestRequest::post().set_json(json!({"invalid": true})), ep).await;
expect_405(Method::DELETE, ep); expect_400(TestRequest::post().set_json(json!({})), ep).await;
expect_400(TestRequest::post(), ep).await;
expect_405(TestRequest::get(), ep).await;
expect_405(TestRequest::put(), ep).await;
expect_405(TestRequest::patch(), ep).await;
expect_405(TestRequest::delete(), ep).await;
} }
} }

View File

@@ -10,12 +10,12 @@ use super::Core;
use super::HandlerResult; use super::HandlerResult;
use super::SharedState; use super::SharedState;
fn put(path: Path<String>) -> HandlerResult { async fn put(path: Path<String>) -> HandlerResult {
trace!("PUT Triggered on {}", path); trace!("PUT Triggered on {}", path);
error_400() error_400()
} }
fn get((core, state): (Path<String>, Data<RwLock<SharedState>>)) -> HandlerResult { async fn get((core, state): (Path<String>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("GET '{:?}'", core); trace!("GET '{:?}'", core);
let core = core.to_string(); let core = core.to_string();
let context = state let context = state
@@ -28,12 +28,12 @@ fn get((core, state): (Path<String>, Data<RwLock<SharedState>>)) -> HandlerResul
} }
} }
fn patch(path: Path<String>) -> HandlerResult { async fn patch(path: Path<String>) -> HandlerResult {
trace!("PATCH Triggered on {}", path); trace!("PATCH Triggered on {}", path);
error_400() error_400()
} }
fn delete(path: Path<String>) -> HandlerResult { async fn delete(path: Path<String>) -> HandlerResult {
trace!("DELETE Triggered on {}", path); trace!("DELETE Triggered on {}", path);
error_400() error_400()
} }
@@ -57,35 +57,35 @@ mod routing {
// FIXME: Add Body to request to see difference between (in)valid bodied requests // FIXME: Add Body to request to see difference between (in)valid bodied requests
#[test] #[actix_web::test]
fn put() { async fn put() {
json::expect_200(Method::PUT, &get_core(INSTANCE_EXISTS), "".to_string()); json::expect_200(TestRequest::put(), &get_core(INSTANCE_EXISTS), "".to_string()).await;
json::expect_422(Method::PUT, &get_core(INSTANCE_EXISTS), "".to_string()); json::expect_422(TestRequest::put(), &get_core(INSTANCE_EXISTS), "".to_string()).await;
json::expect_200(Method::PUT, &get_core(INSTANCE_INVALID), "".to_string()); json::expect_200(TestRequest::put(), &get_core(INSTANCE_INVALID), "".to_string()).await;
} }
#[test] #[actix_web::test]
fn patch() { async fn patch() {
json::expect_200(Method::PATCH, &get_core(INSTANCE_EXISTS), "".to_string()); json::expect_200(TestRequest::patch(), &get_core(INSTANCE_EXISTS), "".to_string()).await;
json::expect_422(Method::PATCH, &get_core(INSTANCE_EXISTS), "".to_string()); json::expect_422(TestRequest::patch(), &get_core(INSTANCE_EXISTS), "".to_string()).await;
expect_404(Method::PATCH, &get_core(INSTANCE_INVALID)); expect_404(TestRequest::patch(), &get_core(INSTANCE_INVALID)).await;
} }
#[test] #[actix_web::test]
fn get() { async fn get() {
expect_200(Method::GET, &get_core(INSTANCE_EXISTS)); expect_200(TestRequest::get(), &get_core(INSTANCE_EXISTS)).await;
expect_404(Method::GET, &get_core(INSTANCE_INVALID)); expect_404(TestRequest::get(), &get_core(INSTANCE_INVALID)).await;
} }
#[test] #[actix_web::test]
fn delete() { async fn delete() {
expect_200(Method::DELETE, &get_core(INSTANCE_EXISTS)); expect_200(TestRequest::delete(), &get_core(INSTANCE_EXISTS)).await;
expect_404(Method::DELETE, &get_core(INSTANCE_INVALID)); expect_404(TestRequest::delete(), &get_core(INSTANCE_INVALID)).await;
} }
#[test] #[actix_web::test]
fn post() { async fn post() {
expect_405(Method::POST, &get_core(INSTANCE_EXISTS)); expect_405(TestRequest::post(), &get_core(INSTANCE_EXISTS)).await;
expect_405(Method::POST, &get_core(INSTANCE_INVALID)); expect_405(TestRequest::post(), &get_core(INSTANCE_INVALID)).await;
} }
} }

View File

@@ -8,11 +8,12 @@ use super::web;
use super::web::Data; use super::web::Data;
use super::web::Json; use super::web::Json;
use super::Core; use super::Core;
use super::CoreQueryParameters;
use super::Filters; use super::Filters;
use super::HandlerResult; use super::HandlerResult;
use super::SharedState; use super::SharedState;
fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> HandlerResult { async fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("POST '{:?}'", parameters); trace!("POST '{:?}'", parameters);
let context = state let context = state
.read() .read()
@@ -40,17 +41,23 @@ fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> Hand
} }
} }
Some(filter) => { Some(filter) => {
let core_parameters = CoreQueryParameters {
db,
output_space: space.as_ref().map(String::as_str),
threshold_volume: parameters.volume(),
view_port: &parameters.view_port,
resolution: parameters.resolution(),
};
let tree = match context.filter(filter) {
Err(e) => return error_422(e),
Ok(tree) => tree,
};
// Retrieve the list of core ids. // Retrieve the list of core ids.
let mut results = HashSet::new(); let mut results = HashSet::new();
for core in db.core_keys() { for core in db.core_keys() {
match context.filter( match context.execute(&tree, core, &core_parameters) {
filter,
core,
&space,
parameters.volume(),
&parameters.view_port,
parameters.resolution(),
) {
Err(e) => return error_422(e), Err(e) => return error_422(e),
Ok(objects) => { Ok(objects) => {
// If the list of SpaceObjects is not empty, add // If the list of SpaceObjects is not empty, add
@@ -59,7 +66,7 @@ fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> Hand
results.insert(core.to_string()); results.insert(core.to_string());
} }
} }
} };
} }
// Format the list or the whole core objects. // Format the list or the whole core objects.
@@ -79,17 +86,17 @@ fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> Hand
} }
} }
fn put() -> HandlerResult { async fn put() -> HandlerResult {
trace!("PUT Triggered!"); trace!("PUT Triggered!");
error_400() error_400()
} }
fn patch() -> HandlerResult { async fn patch() -> HandlerResult {
trace!("PATCH Triggered!"); trace!("PATCH Triggered!");
error_400() error_400()
} }
fn delete() -> HandlerResult { async fn delete() -> HandlerResult {
trace!("DELETE Triggered!"); trace!("DELETE Triggered!");
error_400() error_400()
} }
@@ -108,49 +115,47 @@ pub fn config(cfg: &mut web::ServiceConfig) {
mod routing { mod routing {
use super::super::tests_utils::*; use super::super::tests_utils::*;
const COLLECTION: &str = "/cores";
// FIXME: Add Body to request to see difference between (in)valid bodied requests // FIXME: Add Body to request to see difference between (in)valid bodied requests
#[test] #[actix_web::test]
fn post() { async fn post() {
expect_200(Method::POST, &get_core("")); expect_200(TestRequest::post(), &get_core("")).await;
json::expect_200(Method::POST, &get_core(""), "".to_string()); json::expect_200(TestRequest::post(), &get_core(""), "".to_string()).await;
json::expect_422(Method::POST, &get_core(""), "".to_string()); json::expect_422(TestRequest::post(), &get_core(""), "".to_string()).await;
expect_400(Method::POST, &get_core("")); expect_400(TestRequest::post(), &get_core("")).await;
} }
#[test] #[actix_web::test]
fn put() { async fn put() {
json::expect_200(Method::PUT, &get_core(""), "".to_string()); json::expect_200(TestRequest::put(), &get_core(""), "".to_string()).await;
json::expect_422(Method::PUT, &get_core(""), "".to_string()); json::expect_422(TestRequest::put(), &get_core(""), "".to_string()).await;
expect_400(Method::PUT, &get_core("")); expect_400(TestRequest::put(), &get_core("")).await;
} }
#[test] #[actix_web::test]
fn patch() { async fn patch() {
json::expect_200(Method::PATCH, &get_core(""), "".to_string()); json::expect_200(TestRequest::patch(), &get_core(""), "".to_string()).await;
json::expect_422(Method::PATCH, &get_core(""), "".to_string()); json::expect_422(TestRequest::patch(), &get_core(""), "".to_string()).await;
expect_400(Method::PATCH, &get_core("")); expect_400(TestRequest::patch(), &get_core("")).await;
} }
#[test] #[actix_web::test]
fn delete() { async fn delete() {
json::expect_200(Method::DELETE, &get_core(""), "".to_string()); json::expect_200(TestRequest::delete(), &get_core(""), "".to_string()).await;
json::expect_422(Method::DELETE, &get_core(""), "".to_string()); json::expect_422(TestRequest::delete(), &get_core(""), "".to_string()).await;
expect_400(Method::DELETE, &get_core("")); expect_400(TestRequest::delete(), &get_core("")).await;
} }
#[test] #[actix_web::test]
fn get() { async fn get() {
expect_405(Method::GET, &get_core("")); expect_405(TestRequest::get(), &get_core("")).await;
} }
} }

View File

@@ -17,7 +17,7 @@ where
T: Serialize, T: Serialize,
{ {
match serde_json::to_string(data) { match serde_json::to_string(data) {
Ok(response) => Ok(Either::A( Ok(response) => Ok(Either::Left(
HttpResponse::Ok() HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(response), .body(response),
@@ -30,7 +30,7 @@ pub fn error_422<S>(reason: S) -> HandlerResult
where where
S: Debug, S: Debug,
{ {
Ok(Either::A(HttpResponse::UnprocessableEntity().body( Ok(Either::Left(HttpResponse::UnprocessableEntity().body(
format!("422 - Unprocessable Entity:\n{:?}", reason), format!("422 - Unprocessable Entity:\n{:?}", reason),
))) )))
} }
@@ -50,7 +50,7 @@ where
// error_400() // error_400()
//} //}
pub fn page_404() -> HandlerResult { pub async fn page_404() -> HandlerResult {
trace!("404 Triggered!"); trace!("404 Triggered!");
error_404() error_404()
} }
@@ -60,7 +60,7 @@ pub fn page_404() -> HandlerResult {
// error_405() // error_405()
//} //}
pub fn api(path: Path<String>) -> Result<NamedFile, Error> { pub async fn api(path: Path<String>) -> Result<NamedFile, Error> {
trace!("api/{} Triggered!", path); trace!("api/{} Triggered!", path);
match NamedFile::open(format!("static/api/{}", path).as_str()) { match NamedFile::open(format!("static/api/{}", path).as_str()) {
@@ -71,7 +71,7 @@ pub fn api(path: Path<String>) -> Result<NamedFile, Error> {
} }
} }
pub fn static_file(path: Path<String>) -> Result<NamedFile, Error> { pub async fn static_file(path: Path<String>) -> Result<NamedFile, Error> {
trace!("static/{} Triggered!", path); trace!("static/{} Triggered!", path);
match NamedFile::open(format!("static/{}", path).as_str()) { match NamedFile::open(format!("static/{}", path).as_str()) {
@@ -85,10 +85,9 @@ pub fn static_file(path: Path<String>) -> Result<NamedFile, Error> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::tests_utils::*; use super::super::tests_utils::*;
use super::*;
#[test] #[actix_web::test]
fn page_400() { async fn page_400() {
// expect_400(Method::PATCH, get_core(INSTANCE_INVALID)); expect_400(TestRequest::patch(), &get_core(INVALID_CORE)).await;
} }
} }

View File

@@ -7,7 +7,7 @@ use actix_web::HttpResponse;
use super::HandlerResult; use super::HandlerResult;
fn error(code: StatusCode) -> HandlerResult { fn error(code: StatusCode) -> HandlerResult {
Ok(Either::A(HttpResponse::build(code).finish())) Ok(Either::Left(HttpResponse::build(code).finish()))
} }
pub fn error_400() -> HandlerResult { pub fn error_400() -> HandlerResult {

View File

@@ -9,7 +9,7 @@ use super::HandlerResult;
fn error(code: StatusCode) -> HandlerResult { fn error(code: StatusCode) -> HandlerResult {
let path = format!("static/errors/{}.html", u16::from(code)); let path = format!("static/errors/{}.html", u16::from(code));
Ok(Either::B(NamedFile::open(path)?.set_status_code(code))) Ok(Either::Right(NamedFile::open(path)?.set_status_code(code)))
} }
pub fn error_400() -> HandlerResult { pub fn error_400() -> HandlerResult {

View File

@@ -30,7 +30,8 @@ use actix_web::HttpResponse;
use actix_web::HttpServer; use actix_web::HttpServer;
use mercator_db::space::Shape; use mercator_db::space::Shape;
use mercator_db::storage::model; use mercator_db::storage::model;
use mercator_db::storage::model::v2::to_spatial_objects; use mercator_db::storage::model::v2::from_properties_by_spaces;
use mercator_db::storage::model::v2::from_spaces_by_properties;
use mercator_db::CoreQueryParameters; use mercator_db::CoreQueryParameters;
pub use mercator_db::DataBase; pub use mercator_db::DataBase;
use mercator_db::Properties; use mercator_db::Properties;
@@ -64,13 +65,10 @@ impl Filters {
} }
pub fn ids_only(&self) -> bool { pub fn ids_only(&self) -> bool {
match self.ids_only { self.ids_only.unwrap_or(true)
None => true, // Defaults to true
Some(b) => b,
}
} }
pub fn space(&self, db: &mercator_db::DataBase) -> Result<&Option<String>, HandlerResult> { pub fn space(&self, db: &DataBase) -> Result<&Option<String>, HandlerResult> {
if let Some(space_id) = &self.space { if let Some(space_id) = &self.space {
if !db.space_keys().contains(&space_id.to_string()) { if !db.space_keys().contains(&space_id.to_string()) {
return Err(error_422(format!( return Err(error_422(format!(
@@ -87,10 +85,9 @@ impl Filters {
} }
pub fn volume(&self) -> Option<f64> { pub fn volume(&self) -> Option<f64> {
match &self.view_port { self.view_port.as_ref().map(|(low, high)|
None => None, Shape::BoundingBox(low.into(), high.into()).volume()
Some((low, high)) => Some(Shape::BoundingBox(low.into(), high.into()).volume()), )
}
} }
} }
@@ -114,7 +111,7 @@ impl From<&mercator_db::Core> for Core {
} }
// From: https://stackoverflow.com/a/52367953 // From: https://stackoverflow.com/a/52367953
fn into_static<S>(s: S) -> &'static str pub fn into_static<S>(s: S) -> &'static str
where where
S: Into<String>, S: Into<String>,
{ {
@@ -142,10 +139,8 @@ fn config_v1(cfg: &mut web::ServiceConfig) {
} }
pub fn config(cfg: &mut web::ServiceConfig) { pub fn config(cfg: &mut web::ServiceConfig) {
let prefix; let prefix = match std::env::var("MERCATOR_BASE") {
Ok(val) => val,
match std::env::var("MERCATOR_BASE") {
Ok(val) => prefix = val,
Err(val) => { Err(val) => {
error!("Could not fetch {} : `{}`", "MERCATOR_BASE", val); error!("Could not fetch {} : `{}`", "MERCATOR_BASE", val);
exit(1); exit(1);
@@ -160,7 +155,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
pub fn get_cors() -> Cors { pub fn get_cors() -> Cors {
// Setup CORS support. // Setup CORS support.
let mut cors = Cors::new(); let mut cors = Cors::default();
match std::env::var("MERCATOR_ALLOWED_ORIGINS") { match std::env::var("MERCATOR_ALLOWED_ORIGINS") {
Ok(val) => { Ok(val) => {
@@ -189,62 +184,42 @@ pub fn get_cors() -> Cors {
macro_rules! get_app { macro_rules! get_app {
($state:expr) => { ($state:expr) => {
App::new() App::new()
.register_data($state.clone()) .app_data($state.clone())
.wrap(middleware::Logger::new( .wrap(middleware::Logger::new(
r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T[s] %D[ms]"#, r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T[s] %D[ms]"#,
)) ))
.wrap(middleware::Compress::default()) .wrap(middleware::Compress::default())
.wrap(get_cors()) .wrap(get_cors())
.configure(config) .configure(config)
.default_service( .default_service(web::to(page_404))
web::resource("/")
// 404 for GET request
.route(web::to(page_404)),
)
}; };
} }
pub fn run(host: &str, port: u16, state: Data<RwLock<SharedState>>) { pub async fn run(host: &str, port: u16, state: Data<RwLock<SharedState>>) -> std::io::Result<()> {
info!("Starting http server: {}:{}", host, port); info!("Starting http server: {}:{}", host, port);
// Create & run the server. // Create & run the server.
match HttpServer::new(move || get_app!(state)) HttpServer::new(move || get_app!(state))
.bind(format!("{}:{}", host, port)) .bind(format!("{}:{}", host, port))?
.unwrap_or_else(|e| panic!("Failed to bind to `{}:{}`: {}", host, port, e))
.run() .run()
{ .await
Ok(_) => info!("Server Stopped!"),
Err(e) => error!("Error running the server: {}", e),
};
} }
#[cfg(test)] #[cfg(test)]
mod tests_utils { mod tests_utils {
use super::*; use super::*;
//use actix_server_config::ServerConfig;
//use actix_service::IntoNewService;
//use actix_service::NewService;
use actix_service::Service;
//use actix_web::dev::ServiceResponse;
use actix_web::test; use actix_web::test;
//use actix_web::test::TestRequest; pub use actix_web::test::TestRequest;
use mercator_db::DataBase;
pub const CORE_ID: &str = "10k"; pub const CORE_FILE: &str = "10k.index";
pub const CORE_ID: [&str; 1] = ["10k"];
pub const PREFIX: &str = "/spatial-search"; pub const PREFIX: &str = "/spatial-search-test";
pub const CORE: &str = "/10k"; pub const CORE: &str = "/10k";
pub const INVALID_CORE: &str = "/INVALID_CORE";
pub const SPACE: &str = "/std"; pub const SPACE: &str = "/std";
pub const SPATIAL_OBJECT: &str = "/oid0.44050628835072825"; pub const SPATIAL_OBJECT: &str = "/oid0.44050628835072825";
pub enum Method {
GET,
POST,
PUT,
PATCH,
DELETE,
}
pub fn get_path(path: &str) -> String { pub fn get_path(path: &str) -> String {
format!("{}{}", PREFIX, path) format!("{}{}", PREFIX, path)
@@ -262,113 +237,113 @@ mod tests_utils {
format!("{}{}{}", get_core(CORE), "/spatial_objects", name) format!("{}{}{}", get_core(CORE), "/spatial_objects", name)
} }
pub fn expect(method: Method, path: &str, code: http::StatusCode) { macro_rules! expect_code {
std::env::set_var("MERCATOR_BASE", PREFIX); ($request:expr, $path:expr, $code:expr) => {
{
let mut app = test::init_service(get_app!(Data::new(RwLock::new(SharedState::new( std::env::set_var("MERCATOR_BASE", PREFIX);
DataBase::load(CORE_ID).unwrap() let db = DataBase::load(&[CORE_FILE]).unwrap();
))))); let app = test::init_service(
get_app!(Data::new(RwLock::new(SharedState::new(db))))).await;
let request = match method { let request = $request.uri(&$path).to_request();
Method::GET => test::TestRequest::get(), let response = test::call_service(&app, request).await;
Method::POST => test::TestRequest::post(), assert_eq!(response.status(), $code);
Method::PUT => test::TestRequest::put(), // let json = test::read_body(response).await;
Method::PATCH => test::TestRequest::patch(), // println!("BODY: {:?}", json);
Method::DELETE => test::TestRequest::delete(), }
}; };
let request = request.uri(&path).to_request();
let response = test::block_on(app.call(request)).unwrap();
assert_eq!(response.status(), code);
} }
pub fn expect_200(method: Method, path: &str) { /// Checks status code OK
expect(method, path, http::StatusCode::OK); pub async fn expect_200(method: TestRequest, path: &str) {
expect_code!(method, path, StatusCode::OK);
} }
pub fn expect_400(method: Method, path: &str) { /// Checks status code BAD_REQUEST
expect(method, path, http::StatusCode::BAD_REQUEST); pub async fn expect_400(method: TestRequest, path: &str) {
expect_code!(method, path, StatusCode::BAD_REQUEST);
} }
pub fn expect_404(method: Method, path: &str) { /// Checks status code NOT_FOUND
expect(method, path, http::StatusCode::NOT_FOUND); pub async fn expect_404(method: TestRequest, path: &str) {
expect_code!(method, path, StatusCode::NOT_FOUND);
} }
pub fn expect_405(method: Method, path: &str) { /// Checks status code METHOD_NOT_ALLOWED
expect(method, path, http::StatusCode::METHOD_NOT_ALLOWED); pub async fn expect_405(method: TestRequest, path: &str) {
expect_code!(method, path, StatusCode::METHOD_NOT_ALLOWED);
} }
pub fn expect_422(method: Method, path: &str) { /// Checks status code UNPROCESSABLE_ENTITY
expect(method, path, http::StatusCode::UNPROCESSABLE_ENTITY); pub async fn expect_422(method: TestRequest, path: &str) {
expect_code!(method, path, StatusCode::UNPROCESSABLE_ENTITY);
} }
pub mod json { pub mod json {
use super::*; use super::*;
pub fn expect_200(method: Method, path: &str, json: String) { pub async fn expect_200(method: TestRequest, path: &str, _json: String) {
expect(method, path, http::StatusCode::OK); expect_code!(method, path, StatusCode::OK);
} }
pub fn expect_404(method: Method, path: &str, json: String) { pub async fn expect_404(method: TestRequest, path: &str, _json: String) {
expect(method, path, http::StatusCode::NOT_FOUND); expect_code!(method, path, StatusCode::NOT_FOUND);
} }
pub fn expect_422(method: Method, path: &str, json: String) { pub async fn expect_422(method: TestRequest, path: &str, _json: String) {
expect(method, path, http::StatusCode::UNPROCESSABLE_ENTITY); expect_code!(method, path, StatusCode::UNPROCESSABLE_ENTITY);
} }
} }
} }
#[cfg(test)] #[cfg(test)]
mod routing { mod routing {
use super::tests_utils::*;
use std::panic; use std::panic;
use super::tests_utils::*; #[ignore] // Don't know how to make work the catch_unwind in an async context
#[actix_web::test]
#[test] async fn default_no_path() {
fn default_no_path() {
// _FIXME: Currently the string is validated by the URI constructor which // _FIXME: Currently the string is validated by the URI constructor which
// simply unwraps, thus we have to resort to this ugly workaround. // simply unwraps, thus we have to resort to this ugly workaround.
// The goal is to catch if that behavior changes in the future. // The goal is to catch if that behavior changes in the future.
let result = panic::catch_unwind(|| { let result = panic::catch_unwind(|| {
expect_404(Method::GET, ""); //expect_404(TestRequest::get(), "").await;
}); });
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[actix_web::test]
fn default_slash() { async fn default_slash() {
// We have to manually URL-encode spaces. // We have to manually URL-encode spaces.
expect_404(Method::GET, "/"); expect_404(TestRequest::get(), "/").await;
expect_404(Method::GET, "//"); expect_404(TestRequest::get(), "//").await;
expect_404(Method::GET, "/%20/"); expect_404(TestRequest::get(), "/%20/").await;
expect_404(Method::GET, "/%20//"); expect_404(TestRequest::get(), "/%20//").await;
expect_404(Method::GET, "//%20"); expect_404(TestRequest::get(), "//%20").await;
} }
#[test] #[actix_web::test]
fn default_invalid_prefix() { async fn default_invalid_prefix() {
expect_404(Method::GET, "/test"); expect_404(TestRequest::get(), "/test").await;
expect_404(Method::GET, &format!("{}test", PREFIX)); expect_404(TestRequest::get(), &format!("{}test", PREFIX)).await;
} }
#[test] #[actix_web::test]
fn default_prefix_no_slash() { async fn default_prefix_no_slash() {
expect_404(Method::PUT, PREFIX); expect_404(TestRequest::put(), PREFIX).await;
expect_404(Method::GET, PREFIX); expect_404(TestRequest::get(), PREFIX).await;
expect_404(Method::POST, PREFIX); expect_404(TestRequest::post(), PREFIX).await;
expect_404(Method::PATCH, PREFIX); expect_404(TestRequest::patch(), PREFIX).await;
expect_404(Method::DELETE, PREFIX); expect_404(TestRequest::delete(), PREFIX).await;
} }
#[test] #[actix_web::test]
fn default_prefix_final_slash() { async fn default_prefix_final_slash() {
let path = &format!("{}/", PREFIX); let path = &format!("{}/", PREFIX);
expect_404(Method::PUT, path); expect_404(TestRequest::put(), path).await;
expect_404(Method::GET, path); expect_404(TestRequest::get(), path).await;
expect_404(Method::POST, path); expect_404(TestRequest::post(), path).await;
expect_404(Method::PATCH, path); expect_404(TestRequest::patch(), path).await;
expect_404(Method::DELETE, path); expect_404(TestRequest::delete(), path).await;
} }
} }

View File

@@ -10,12 +10,12 @@ use super::web::Path;
use super::HandlerResult; use super::HandlerResult;
use super::SharedState; use super::SharedState;
fn put(path: Path<String>) -> HandlerResult { async fn put(path: Path<String>) -> HandlerResult {
trace!("POST '{:?}'", path); trace!("POST '{:?}'", path);
error_400() error_400()
} }
fn get((path, state): (Path<String>, Data<RwLock<SharedState>>)) -> HandlerResult { async fn get((path, state): (Path<String>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("GET '{:?}'", path); trace!("GET '{:?}'", path);
let name = path.to_string(); let name = path.to_string();
let context = state let context = state
@@ -31,12 +31,12 @@ fn get((path, state): (Path<String>, Data<RwLock<SharedState>>)) -> HandlerResul
} }
} }
fn patch(path: Path<String>) -> HandlerResult { async fn patch(path: Path<String>) -> HandlerResult {
trace!("PATCH Triggered on {}", path); trace!("PATCH Triggered on {}", path);
error_400() error_400()
} }
fn delete(path: Path<String>) -> HandlerResult { async fn delete(path: Path<String>) -> HandlerResult {
trace!("DELETE Triggered on {}", path); trace!("DELETE Triggered on {}", path);
error_400() error_400()
} }
@@ -60,35 +60,35 @@ mod routing {
// FIXME: Add Body to request to see difference between (in)valid bodied requests // FIXME: Add Body to request to see difference between (in)valid bodied requests
#[test] #[actix_web::test]
fn put() { async fn put() {
json::expect_200(Method::PUT, &get_space(INSTANCE_EXISTS), "".to_string()); json::expect_200(TestRequest::put(), &get_space(INSTANCE_EXISTS), "".to_string()).await;
json::expect_422(Method::PUT, &get_space(INSTANCE_EXISTS), "".to_string()); json::expect_422(TestRequest::put(), &get_space(INSTANCE_EXISTS), "".to_string()).await;
json::expect_200(Method::PUT, &get_space(INSTANCE_INVALID), "".to_string()); json::expect_200(TestRequest::put(), &get_space(INSTANCE_INVALID), "".to_string()).await;
} }
#[test] #[actix_web::test]
fn patch() { async fn patch() {
json::expect_200(Method::PATCH, &get_space(INSTANCE_EXISTS), "".to_string()); json::expect_200(TestRequest::patch(), &get_space(INSTANCE_EXISTS), "".to_string()).await;
json::expect_422(Method::PATCH, &get_space(INSTANCE_EXISTS), "".to_string()); json::expect_422(TestRequest::patch(), &get_space(INSTANCE_EXISTS), "".to_string()).await;
expect_400(Method::PATCH, &get_space(INSTANCE_INVALID)); expect_400(TestRequest::patch(), &get_space(INSTANCE_INVALID)).await;
} }
#[test] #[actix_web::test]
fn get() { async fn get() {
expect_200(Method::GET, &get_space(INSTANCE_EXISTS)); expect_200(TestRequest::get(), &get_space(INSTANCE_EXISTS)).await;
expect_404(Method::GET, &get_space(INSTANCE_INVALID)); expect_404(TestRequest::get(), &get_space(INSTANCE_INVALID)).await;
} }
#[test] #[actix_web::test]
fn delete() { async fn delete() {
expect_200(Method::DELETE, &get_space(INSTANCE_EXISTS)); expect_200(TestRequest::delete(), &get_space(INSTANCE_EXISTS)).await;
expect_404(Method::DELETE, &get_space(INSTANCE_INVALID)); expect_404(TestRequest::delete(), &get_space(INSTANCE_INVALID)).await;
} }
#[test] #[actix_web::test]
fn post() { async fn post() {
expect_405(Method::POST, &get_space(INSTANCE_EXISTS)); expect_405(TestRequest::post(), &get_space(INSTANCE_EXISTS)).await;
expect_405(Method::POST, &get_space(INSTANCE_INVALID)); expect_405(TestRequest::post(), &get_space(INSTANCE_INVALID)).await;
} }
} }

View File

@@ -8,11 +8,12 @@ use super::ok_200;
use super::web; use super::web;
use super::web::Data; use super::web::Data;
use super::web::Json; use super::web::Json;
use super::CoreQueryParameters;
use super::Filters; use super::Filters;
use super::HandlerResult; use super::HandlerResult;
use super::SharedState; use super::SharedState;
fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> HandlerResult { async fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("POST '{:?}'", parameters); trace!("POST '{:?}'", parameters);
let context = state let context = state
.read() .read()
@@ -43,15 +44,20 @@ fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> Hand
// Retrieve the list of space ids. // Retrieve the list of space ids.
let mut results = HashSet::new(); let mut results = HashSet::new();
let core_parameters = CoreQueryParameters {
db: context.db(),
output_space: space.as_ref().map(String::as_str),
threshold_volume: parameters.volume(),
view_port: &parameters.view_port,
resolution: parameters.resolution(),
};
let tree = match context.filter(filter) {
Err(e) => return error_422(e),
Ok(bag) => bag,
};
for core in db.core_keys() { for core in db.core_keys() {
match context.filter( match context.execute(&tree, core, &core_parameters) {
filter,
core,
&space,
parameters.volume(),
&parameters.view_port,
parameters.resolution(),
) {
Err(e) => return error_422(e), Err(e) => return error_422(e),
Ok(v) => { Ok(v) => {
// We have a list of SpaceObjects, so extract // We have a list of SpaceObjects, so extract
@@ -70,7 +76,7 @@ fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> Hand
ok_200( ok_200(
&results &results
.drain() .drain()
.map(|id| match db.space(&id) { .map(|id| match db.space(id) {
Err(_) => None, Err(_) => None,
Ok(x) => Some(model::Space::from(x)), Ok(x) => Some(model::Space::from(x)),
}) })
@@ -83,17 +89,17 @@ fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> Hand
} }
} }
fn put() -> HandlerResult { async fn put() -> HandlerResult {
trace!("PUT Triggered!"); trace!("PUT Triggered!");
error_400() error_400()
} }
fn patch() -> HandlerResult { async fn patch() -> HandlerResult {
trace!("PATCH Triggered!"); trace!("PATCH Triggered!");
error_400() error_400()
} }
fn delete() -> HandlerResult { async fn delete() -> HandlerResult {
trace!("DELETE Triggered!"); trace!("DELETE Triggered!");
error_400() error_400()
} }
@@ -114,45 +120,45 @@ mod routing {
// FIXME: Add Body to request to see difference between (in)valid bodied requests // FIXME: Add Body to request to see difference between (in)valid bodied requests
#[test] #[actix_web::test]
fn post() { async fn post() {
expect_200(Method::POST, &get_space("")); expect_200(TestRequest::post(), &get_space("")).await;
json::expect_200(Method::POST, &get_space(""), "".to_string()); json::expect_200(TestRequest::post(), &get_space(""), "".to_string()).await;
json::expect_422(Method::POST, &get_space(""), "".to_string()); json::expect_422(TestRequest::post(), &get_space(""), "".to_string()).await;
expect_400(Method::POST, &get_space("")); expect_400(TestRequest::post(), &get_space("")).await;
} }
#[test] #[actix_web::test]
fn put() { async fn put() {
json::expect_200(Method::PUT, &get_space(""), "".to_string()); json::expect_200(TestRequest::put(), &get_space(""), "".to_string()).await;
json::expect_422(Method::PUT, &get_space(""), "".to_string()); json::expect_422(TestRequest::put(), &get_space(""), "".to_string()).await;
expect_400(Method::PUT, &get_space("")); expect_400(TestRequest::put(), &get_space("")).await;
} }
#[test] #[actix_web::test]
fn patch() { async fn patch() {
json::expect_200(Method::PATCH, &get_space(""), "".to_string()); json::expect_200(TestRequest::patch(), &get_space(""), "".to_string()).await;
json::expect_422(Method::PATCH, &get_space(""), "".to_string()); json::expect_422(TestRequest::patch(), &get_space(""), "".to_string()).await;
expect_400(Method::PATCH, &get_space("")); expect_400(TestRequest::patch(), &get_space("")).await;
} }
#[test] #[actix_web::test]
fn delete() { async fn delete() {
json::expect_200(Method::DELETE, &get_space(""), "".to_string()); json::expect_200(TestRequest::delete(), &get_space(""), "".to_string()).await;
json::expect_422(Method::DELETE, &get_space(""), "".to_string()); json::expect_422(TestRequest::delete(), &get_space(""), "".to_string()).await;
expect_400(Method::DELETE, &get_space("")); expect_400(TestRequest::delete(), &get_space("")).await;
} }
#[test] #[actix_web::test]
fn get() { async fn get() {
expect_405(Method::GET, &get_space("")); expect_405(TestRequest::get(), &get_space("")).await;
} }
} }

View File

@@ -2,8 +2,8 @@ use std::sync::RwLock;
use super::error_400; use super::error_400;
use super::error_404; use super::error_404;
use super::from_properties_by_spaces;
use super::ok_200; use super::ok_200;
use super::to_spatial_objects;
use super::web; use super::web;
use super::web::Data; use super::web::Data;
use super::web::Path; use super::web::Path;
@@ -11,17 +11,16 @@ use super::CoreQueryParameters;
use super::HandlerResult; use super::HandlerResult;
use super::Properties; use super::Properties;
use super::SharedState; use super::SharedState;
use mercator_db::{IterObjects, IterObjectsBySpaces};
fn put(path: Path<String>) -> HandlerResult { async fn put(path: Path<String>) -> HandlerResult {
trace!("PUT '{:?}'", path); trace!("PUT '{:?}'", path);
error_400() error_400()
} }
fn get((path, state): (Path<(String, String)>, Data<RwLock<SharedState>>)) -> HandlerResult { async fn get((path, state): (Path<(String, String)>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("GET '{:?}'", path); trace!("GET '{:?}'", path);
let (core, id) = path.into_inner(); let (core, id) = path.into_inner();
let core = core;
let id = id;
let context = state let context = state
.read() .read()
.unwrap_or_else(|e| panic!("Can't acquire read lock of the database: {}", e)); .unwrap_or_else(|e| panic!("Can't acquire read lock of the database: {}", e));
@@ -39,20 +38,18 @@ fn get((path, state): (Path<(String, String)>, Data<RwLock<SharedState>>)) -> Ha
match db.core(&core) { match db.core(&core) {
Ok(core) => match core.get_by_id(&parameters, &id) { Ok(core) => match core.get_by_id(&parameters, &id) {
Ok(objects) => { Ok(positions_by_spaces) => {
let value = Properties::Feature(id); let value = Properties::Feature(id);
let tmp = objects let tmp: IterObjectsBySpaces = positions_by_spaces
.into_iter() .into_iter()
.map(|(space, positions)| { .map(|(space, positions)| {
let shapes = positions let objects: IterObjects =
.into_iter() Box::new(positions.map(|position| (position, &value)));
.map(|position| (position, &value)) (space, objects)
.collect();
(space, shapes)
}) })
.collect(); .collect();
let results = to_spatial_objects(tmp); let results = from_properties_by_spaces(tmp).collect::<Vec<_>>();
if results.is_empty() { if results.is_empty() {
error_404() error_404()
@@ -66,12 +63,12 @@ fn get((path, state): (Path<(String, String)>, Data<RwLock<SharedState>>)) -> Ha
} }
} }
fn patch(path: Path<String>) -> HandlerResult { async fn patch(path: Path<String>) -> HandlerResult {
trace!("PATCH Triggered on {}", path); trace!("PATCH Triggered on {}", path);
error_400() error_400()
} }
fn delete(path: Path<String>) -> HandlerResult { async fn delete(path: Path<String>) -> HandlerResult {
trace!("DELETE Triggered on {}", path); trace!("DELETE Triggered on {}", path);
error_400() error_400()
} }
@@ -95,35 +92,35 @@ mod routing {
// FIXME: Add Body to request to see difference between (in)valid bodied requests // FIXME: Add Body to request to see difference between (in)valid bodied requests
#[test] #[actix_web::test]
fn put() { async fn put() {
json::expect_200(Method::PUT, &get_objects(INSTANCE_EXISTS), "".to_string()); json::expect_200(TestRequest::put(), &get_objects(INSTANCE_EXISTS), "".to_string()).await;
json::expect_422(Method::PUT, &get_objects(INSTANCE_EXISTS), "".to_string()); json::expect_422(TestRequest::put(), &get_objects(INSTANCE_EXISTS), "".to_string()).await;
json::expect_200(Method::PUT, &get_objects(INSTANCE_INVALID), "".to_string()); json::expect_200(TestRequest::put(), &get_objects(INSTANCE_INVALID), "".to_string()).await;
} }
#[test] #[actix_web::test]
fn patch() { async fn patch() {
json::expect_200(Method::PATCH, &get_objects(INSTANCE_EXISTS), "".to_string()); json::expect_200(TestRequest::patch(), &get_objects(INSTANCE_EXISTS), "".to_string()).await;
json::expect_422(Method::PATCH, &get_objects(INSTANCE_EXISTS), "".to_string()); json::expect_422(TestRequest::patch(), &get_objects(INSTANCE_EXISTS), "".to_string()).await;
expect_400(Method::PATCH, &get_objects(INSTANCE_INVALID)); expect_400(TestRequest::patch(), &get_objects(INSTANCE_INVALID)).await;
} }
#[test] #[actix_web::test]
fn get() { async fn get() {
expect_200(Method::GET, &get_objects(INSTANCE_EXISTS)); expect_200(TestRequest::get(), &get_objects(INSTANCE_EXISTS)).await;
expect_404(Method::GET, &get_objects(INSTANCE_INVALID)); expect_404(TestRequest::get(), &get_objects(INSTANCE_INVALID)).await;
} }
#[test] #[actix_web::test]
fn delete() { async fn delete() {
expect_200(Method::DELETE, &get_objects(INSTANCE_EXISTS)); expect_200(TestRequest::delete(), &get_objects(INSTANCE_EXISTS)).await;
expect_404(Method::DELETE, &get_objects(INSTANCE_INVALID)); expect_404(TestRequest::delete(), &get_objects(INSTANCE_INVALID)).await;
} }
#[test] #[actix_web::test]
fn post() { async fn post() {
expect_405(Method::POST, &get_objects(INSTANCE_EXISTS)); expect_405(TestRequest::post(), &get_objects(INSTANCE_EXISTS)).await;
expect_405(Method::POST, &get_objects(INSTANCE_INVALID)); expect_405(TestRequest::post(), &get_objects(INSTANCE_INVALID)).await;
} }
} }

View File

@@ -1,12 +1,12 @@
use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::RwLock; use std::sync::RwLock;
use super::error_400; use super::error_400;
use super::error_404; use super::error_404;
use super::error_422; use super::error_422;
use super::from_properties_by_spaces;
use super::from_spaces_by_properties;
use super::ok_200; use super::ok_200;
use super::to_spatial_objects;
use super::web; use super::web;
use super::web::Data; use super::web::Data;
use super::web::Json; use super::web::Json;
@@ -16,7 +16,7 @@ use super::Filters;
use super::HandlerResult; use super::HandlerResult;
use super::SharedState; use super::SharedState;
fn post( async fn post(
(core_id, parameters, state): (Path<String>, Json<Filters>, Data<RwLock<SharedState>>), (core_id, parameters, state): (Path<String>, Json<Filters>, Data<RwLock<SharedState>>),
) -> HandlerResult { ) -> HandlerResult {
trace!("POST '{:?}', {:?}", parameters, core_id); trace!("POST '{:?}', {:?}", parameters, core_id);
@@ -32,13 +32,15 @@ fn post(
Err(e) => e, Err(e) => e,
Ok(space) => match parameters.filters() { Ok(space) => match parameters.filters() {
None => { None => {
let mut results = HashMap::new();
for property in core.keys().iter() {
results.insert(property.id(), property);
}
if parameters.ids_only() { if parameters.ids_only() {
ok_200(&results.drain().map(|(k, _)| k).collect::<Vec<_>>()) // keys() contains unique values only.
let ids = core
.keys()
.iter()
.map(|properties| properties.id())
.collect::<Vec<_>>();
ok_200(&ids)
} else { } else {
let core_parameters = CoreQueryParameters { let core_parameters = CoreQueryParameters {
db, db,
@@ -48,40 +50,33 @@ fn post(
resolution: parameters.resolution(), resolution: parameters.resolution(),
}; };
let mut objects = vec![]; let objects_by_spaces =
for (id, properties) in results.drain() { Box::new(core.keys().iter().filter_map(|property| {
match core.get_by_id(&core_parameters, id) { match core.get_by_id(&core_parameters, property.id()) {
Err(_) => (), // FIXME: Return error ? Err(_) => None, // FIXME: Return error ?
Ok(r) => { Ok(positions_by_spaces) => {
let mut tmp = r Some((property, positions_by_spaces))
.into_iter() }
.map(|(space, positions)| {
let shapes = positions
.into_iter()
.map(|position| (position, properties))
.collect();
(space, shapes)
})
.collect();
objects.append(&mut tmp);
} }
} }));
} ok_200(&from_spaces_by_properties(objects_by_spaces).collect::<Vec<_>>())
let objects = to_spatial_objects(objects);
ok_200(&objects)
} }
} }
Some(filter) => { Some(filter) => {
match context.filter( let core_parameters = CoreQueryParameters {
filter, db,
&core_id, output_space: space.as_ref().map(String::as_str),
space, threshold_volume: parameters.volume(),
parameters.volume(), view_port: &parameters.view_port,
&parameters.view_port, resolution: parameters.resolution(),
parameters.resolution(), };
) {
let tree = match context.filter(filter) {
Err(e) => return error_422(e),
Ok(bag) => bag,
};
let r = match context.execute(&tree, &core_id, &core_parameters) {
Err(e) => error_422(e), Err(e) => error_422(e),
Ok(objects) => { Ok(objects) => {
if parameters.ids_only() { if parameters.ids_only() {
@@ -94,29 +89,29 @@ fn post(
ok_200(&uniques.drain().collect::<Vec<_>>()) ok_200(&uniques.drain().collect::<Vec<_>>())
} else { } else {
let objects = to_spatial_objects(objects); ok_200(&from_properties_by_spaces(objects).collect::<Vec<_>>())
ok_200(&objects)
} }
} }
} };
r
} }
}, },
}, },
} }
} }
fn put() -> HandlerResult { async fn put() -> HandlerResult {
trace!("PUT Triggered!"); trace!("PUT Triggered!");
error_400() error_400()
} }
fn patch() -> HandlerResult { async fn patch() -> HandlerResult {
trace!("PATCH Triggered!"); trace!("PATCH Triggered!");
error_400() error_400()
} }
fn delete() -> HandlerResult { async fn delete() -> HandlerResult {
trace!("DELETE Triggered!"); trace!("DELETE Triggered!");
error_400() error_400()
} }
@@ -137,45 +132,45 @@ mod routing {
// FIXME: Add Body to request to see difference between (in)valid bodied requests // FIXME: Add Body to request to see difference between (in)valid bodied requests
#[test] #[actix_web::test]
fn post() { async fn post() {
expect_200(Method::POST, &get_objects("")); expect_200(TestRequest::post(), &get_objects("")).await;
json::expect_200(Method::POST, &get_objects(""), "".to_string()); json::expect_200(TestRequest::post(), &get_objects(""), "".to_string()).await;
json::expect_422(Method::POST, &get_objects(""), "".to_string()); json::expect_422(TestRequest::post(), &get_objects(""), "".to_string()).await;
expect_400(Method::POST, &get_objects("")); expect_400(TestRequest::post(), &get_objects("")).await;
} }
#[test] #[actix_web::test]
fn put() { async fn put() {
json::expect_200(Method::PUT, &get_objects(""), "".to_string()); json::expect_200(TestRequest::put(), &get_objects(""), "".to_string()).await;
json::expect_422(Method::PUT, &get_objects(""), "".to_string()); json::expect_422(TestRequest::put(), &get_objects(""), "".to_string()).await;
expect_400(Method::PUT, &get_objects("")); expect_400(TestRequest::put(), &get_objects("")).await;
} }
#[test] #[actix_web::test]
fn patch() { async fn patch() {
json::expect_200(Method::PATCH, &get_objects(""), "".to_string()); json::expect_200(TestRequest::patch(), &get_objects(""), "".to_string()).await;
json::expect_422(Method::PATCH, &get_objects(""), "".to_string()); json::expect_422(TestRequest::patch(), &get_objects(""), "".to_string()).await;
expect_400(Method::PATCH, &get_objects("")); expect_400(TestRequest::patch(), &get_objects("")).await;
} }
#[test] #[actix_web::test]
fn delete() { async fn delete() {
json::expect_200(Method::DELETE, &get_objects(""), "".to_string()); json::expect_200(TestRequest::delete(), &get_objects(""), "".to_string()).await;
json::expect_422(Method::DELETE, &get_objects(""), "".to_string()); json::expect_422(TestRequest::delete(), &get_objects(""), "".to_string()).await;
expect_400(Method::DELETE, &get_objects("")); expect_400(TestRequest::delete(), &get_objects("")).await;
} }
#[test] #[actix_web::test]
fn get() { async fn get() {
expect_405(Method::GET, &get_objects("")); expect_405(TestRequest::get(), &get_objects("")).await;
} }
} }

View File

@@ -1,7 +1,9 @@
use mercator_db::CoreQueryParameters; use mercator_db::CoreQueryParameters;
use mercator_db::DataBase; use mercator_db::DataBase;
use mercator_parser::Bag;
use mercator_parser::Executor; use mercator_parser::Executor;
use mercator_parser::FiltersParser; use mercator_parser::FiltersParser;
use mercator_parser::Projection;
use mercator_parser::QueryParser; use mercator_parser::QueryParser;
use mercator_parser::Validator; use mercator_parser::Validator;
@@ -32,24 +34,35 @@ impl SharedState {
&self.query_parser &self.query_parser
} }
pub fn filter<'q>( pub fn execute<'e, T>(
&'q self, &'e self,
filter: &'q str, tree: &'e T, //&'e Bag,
core: &'q str, core: &'e str,
output_space: &'q Option<String>, parameters: &'e CoreQueryParameters<'e>,
volume: Option<f64>, ) -> mercator_db::ResultSet<'e>
view_port: &'q Option<(Vec<f64>, Vec<f64>)>, where
resolution: &'q Option<Vec<u32>>, T: Executor<'e, ResultSet = mercator_db::ResultSet<'e>>,
) -> mercator_db::ResultSet<'q> { {
// Execute filter.
let execution = {
info_time!("Execution");
// _FIXME: Output space is defined as part of the projection
// and is ignored by projections operators.
tree.execute(core, parameters)
};
match execution {
Err(e) => {
debug!("Execution failed: \n{:?}", e);
Err(e)
}
results @ Ok(_) => results,
}
}
pub fn filter<'q>(&'q self, filter: &'q str) -> Result<Bag, String> {
let parser = self.filter_parser(); let parser = self.filter_parser();
let parse; let parse;
let parameters = CoreQueryParameters {
db: self.db(),
output_space: output_space.as_ref().map(String::as_str),
threshold_volume: volume,
view_port,
resolution,
};
// Parse Input // Parse Input
{ {
@@ -63,52 +76,20 @@ impl SharedState {
Err(format!("{}", e)) Err(format!("{}", e))
} }
Ok(tree) => { Ok(tree) => {
let validation;
let execution;
// Check type coherence & validate tree // Check type coherence & validate tree
{ {
debug_time!("Type check"); debug_time!("Type check");
validation = tree.validate(); let _ = tree.validate()?;
}
if validation.is_err() {
debug!("Type check failed");
return Err("Type check failed".to_string());
} }
// Execute filter. Ok(tree)
{
info_time!("Execution");
execution = tree.execute(core, &parameters);
}
match execution {
Err(e) => {
debug!("Parsing failed: \n{:?}", e);
Err(e)
}
results @ Ok(_) => results,
}
} }
} }
} }
pub fn query<'q>( pub fn query(&self, query: &str) -> Result<Projection, String> {
&'q self,
query: &str,
core: &str,
volume: Option<f64>,
view_port: &'q Option<(Vec<f64>, Vec<f64>)>,
resolution: &'q Option<Vec<u32>>,
) -> mercator_db::ResultSet<'q> {
let parser = self.query_parser(); let parser = self.query_parser();
let parse; let parse;
let parameters = CoreQueryParameters {
db: self.db(),
output_space: None,
threshold_volume: volume,
view_port,
resolution,
};
// Parse Input // Parse Input
{ {
@@ -120,35 +101,15 @@ impl SharedState {
debug!("Parsing failed: \n{:?}", e); debug!("Parsing failed: \n{:?}", e);
Err(e.to_string()) Err(e.to_string())
} }
Ok(None) => Ok(vec![]), Ok(None) => Err("Query is empty!".to_string()),
Ok(Some(tree)) => { Ok(Some(tree)) => {
let validation;
let execution;
// Check type coherence & validate tree // Check type coherence & validate tree
{ {
debug_time!("Type check"); debug_time!("Type check");
validation = tree.validate(); let _ = tree.validate()?;
}
if validation.is_err() {
debug!("Type check failed");
return Err("Type check failed".to_string());
} }
// Execute filter. Ok(tree)
{
info_time!("Execution");
// _FIXME: Output space is defined as part of the projection
// and is ignored by projections operators.
execution = tree.execute(core, &parameters);
}
match execution {
Err(e) => {
debug!("Parsing failed: \n{:?}", e);
Err(e)
}
results @ Ok(_) => results,
}
} }
} }
} }