Compare commits
2 Commits
1e5b5a8685
...
fd2b4e098b
| Author | SHA1 | Date | |
|---|---|---|---|
| fd2b4e098b | |||
| e05159c7d7 |
35
Cargo.toml
35
Cargo.toml
@@ -18,31 +18,28 @@ license = "MIT"
|
||||
|
||||
include = ["Cargo.toml", "README.md", "LICENSE", "ACKNOWLEDGEMENTS", "src/**/*.rs"]
|
||||
|
||||
#[profile.release]
|
||||
#lto = true
|
||||
|
||||
[features]
|
||||
static-error-pages = []
|
||||
|
||||
[dependencies]
|
||||
actix-web = "^1.0"
|
||||
actix-files = "^0.1"
|
||||
actix-service = "^0.4"
|
||||
actix-cors = "^0.1"
|
||||
glob = "^0.3"
|
||||
actix-web = "4.8"
|
||||
actix-files = "0.6"
|
||||
actix-cors = "0.7"
|
||||
glob = "0.3"
|
||||
|
||||
measure_time = "^0.6"
|
||||
memmap = "^0.7"
|
||||
measure_time = "0.8"
|
||||
memmap = "0.7"
|
||||
|
||||
mercator_db = "^0.1"
|
||||
mercator_parser = "^0.1"
|
||||
mercator_db = "0.1"
|
||||
mercator_parser = "0.1"
|
||||
|
||||
serde = { version = "^1.0", features = ["derive"] }
|
||||
serde_json = "^1.0"
|
||||
bincode = "^1.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
bincode = "1.3"
|
||||
|
||||
# Logging macros API
|
||||
log = { version = "^0.4", features = ["max_level_trace", "release_max_level_trace"] }
|
||||
pretty_env_logger = "^0.3" # Logger implementation
|
||||
|
||||
[dev-dependencies]
|
||||
# Only for tests
|
||||
actix-server-config = "^0.1"
|
||||
actix-http = "^0.2"
|
||||
log = { version = "0.4", features = ["max_level_trace", "release_max_level_trace"] }
|
||||
pretty_env_logger = "0.5" # Logger implementation
|
||||
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.80.0"
|
||||
27
src/main.rs
27
src/main.rs
@@ -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 std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
@@ -78,21 +79,17 @@ fn main() {
|
||||
std::env::set_var("MERCATOR_DATA", ".");
|
||||
}
|
||||
|
||||
let hostname;
|
||||
let port;
|
||||
let data;
|
||||
|
||||
match std::env::var("MERCATOR_HOST") {
|
||||
Ok(val) => hostname = val,
|
||||
let hostname = match std::env::var("MERCATOR_HOST") {
|
||||
Ok(val) => val,
|
||||
Err(val) => {
|
||||
error!("Invalid environment {} : `{}`", "MERCATOR_HOST", val);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
match std::env::var("MERCATOR_PORT") {
|
||||
let port = match std::env::var("MERCATOR_PORT") {
|
||||
Ok(val) => match val.parse::<u16>() {
|
||||
Ok(v) => port = v,
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("Could not convert to u16 {} : `{}`", "MERCATOR_PORT", e);
|
||||
exit(1);
|
||||
@@ -104,8 +101,8 @@ fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
match std::env::var("MERCATOR_DATA") {
|
||||
Ok(val) => data = val,
|
||||
let data = match std::env::var("MERCATOR_DATA") {
|
||||
Ok(val) => val,
|
||||
Err(val) => {
|
||||
error!("Could not fetch {} : `{}`", "MERCATOR_DATA", val);
|
||||
exit(1);
|
||||
@@ -123,9 +120,6 @@ fn main() {
|
||||
})
|
||||
.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;
|
||||
// Load a Database:
|
||||
{
|
||||
@@ -133,7 +127,7 @@ fn main() {
|
||||
// those is corrupted / incompatible.
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -141,5 +135,6 @@ fn main() {
|
||||
&hostname,
|
||||
port,
|
||||
Data::new(RwLock::new(SharedState::new(db))),
|
||||
);
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -3,14 +3,15 @@ use std::sync::RwLock;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::error_422;
|
||||
use super::from_properties_by_spaces;
|
||||
use super::ok_200;
|
||||
use super::to_spatial_objects;
|
||||
use super::web;
|
||||
use super::web::Data;
|
||||
use super::web::Json;
|
||||
use super::HandlerResult;
|
||||
use super::HttpResponse;
|
||||
use super::SharedState;
|
||||
use mercator_db::CoreQueryParameters;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Query {
|
||||
@@ -36,11 +37,11 @@ impl Query {
|
||||
}
|
||||
}
|
||||
// Also used for the root service.
|
||||
pub fn health() -> HttpResponse {
|
||||
pub async fn health() -> HttpResponse {
|
||||
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);
|
||||
let context = state
|
||||
.read()
|
||||
@@ -50,25 +51,30 @@ fn query((parameters, state): (Json<Query>, Data<RwLock<SharedState>>)) -> Handl
|
||||
if query.is_empty() {
|
||||
error_422(format!("Invalid query in '{:?}'", query))
|
||||
} else {
|
||||
ok_200(
|
||||
&context
|
||||
.db()
|
||||
.core_keys()
|
||||
.iter()
|
||||
.flat_map(|core| {
|
||||
match context.query(
|
||||
query,
|
||||
core,
|
||||
parameters.volume(),
|
||||
¶meters.view_port,
|
||||
parameters.resolution(),
|
||||
) {
|
||||
Err(_) => vec![], // FIXME: Return errors here instead!!
|
||||
Ok(objects) => to_spatial_objects(objects),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
let parameters = CoreQueryParameters {
|
||||
db: context.db(),
|
||||
output_space: None,
|
||||
threshold_volume: parameters.volume(),
|
||||
view_port: ¶meters.view_port,
|
||||
resolution: parameters.resolution(),
|
||||
};
|
||||
|
||||
let results = context
|
||||
.db()
|
||||
.core_keys()
|
||||
.iter()
|
||||
.filter_map(|core| {
|
||||
match context.query(query) {
|
||||
Err(_) => None, // FIXME: Return errors here instead!!
|
||||
Ok(tree) => match context.execute(&tree, core, ¶meters) {
|
||||
Err(_) => None, // FIXME: Return errors here instead!!
|
||||
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)]
|
||||
mod routing {
|
||||
use super::super::tests_utils::*;
|
||||
use crate::rest_api::tests_utils::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn health() {
|
||||
#[actix_web::test]
|
||||
async fn health() {
|
||||
let ep = &get_path("/health");
|
||||
|
||||
expect_200(Method::GET, ep);
|
||||
expect_200(TestRequest::get(), ep).await;
|
||||
|
||||
expect_405(Method::POST, ep);
|
||||
expect_405(Method::PUT, ep);
|
||||
expect_405(Method::PATCH, ep);
|
||||
expect_405(Method::DELETE, ep);
|
||||
expect_405(TestRequest::post(), ep).await;
|
||||
expect_405(TestRequest::put(), ep).await;
|
||||
expect_405(TestRequest::patch(), ep).await;
|
||||
expect_405(TestRequest::delete(), ep).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query() {
|
||||
#[actix_web::test]
|
||||
async fn query() {
|
||||
let ep = &get_path("/query");
|
||||
|
||||
expect_200(Method::POST, ep);
|
||||
expect_422(Method::POST, ep);
|
||||
expect_200(
|
||||
TestRequest::post()
|
||||
.set_json(json!({"query": "json(.,inside(hyperrectangle{[0,0,0],[0,1,1]}))"})),
|
||||
ep,
|
||||
)
|
||||
.await;
|
||||
|
||||
expect_405(Method::GET, ep);
|
||||
expect_405(Method::PUT, ep);
|
||||
expect_405(Method::PATCH, ep);
|
||||
expect_405(Method::DELETE, ep);
|
||||
expect_422(TestRequest::post().set_json(json!({"query": "toto"})), ep).await;
|
||||
expect_422(TestRequest::post().set_json(json!({"query": ""})), ep).await;
|
||||
expect_400(TestRequest::post().set_json(json!({"invalid": true})), ep).await;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ use super::Core;
|
||||
use super::HandlerResult;
|
||||
use super::SharedState;
|
||||
|
||||
fn put(path: Path<String>) -> HandlerResult {
|
||||
async fn put(path: Path<String>) -> HandlerResult {
|
||||
trace!("PUT Triggered on {}", path);
|
||||
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);
|
||||
let core = core.to_string();
|
||||
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);
|
||||
error_400()
|
||||
}
|
||||
|
||||
fn delete(path: Path<String>) -> HandlerResult {
|
||||
async fn delete(path: Path<String>) -> HandlerResult {
|
||||
trace!("DELETE Triggered on {}", path);
|
||||
error_400()
|
||||
}
|
||||
@@ -57,35 +57,35 @@ mod routing {
|
||||
|
||||
// FIXME: Add Body to request to see difference between (in)valid bodied requests
|
||||
|
||||
#[test]
|
||||
fn put() {
|
||||
json::expect_200(Method::PUT, &get_core(INSTANCE_EXISTS), "".to_string());
|
||||
json::expect_422(Method::PUT, &get_core(INSTANCE_EXISTS), "".to_string());
|
||||
json::expect_200(Method::PUT, &get_core(INSTANCE_INVALID), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn put() {
|
||||
json::expect_200(TestRequest::put(), &get_core(INSTANCE_EXISTS), "".to_string()).await;
|
||||
json::expect_422(TestRequest::put(), &get_core(INSTANCE_EXISTS), "".to_string()).await;
|
||||
json::expect_200(TestRequest::put(), &get_core(INSTANCE_INVALID), "".to_string()).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn patch() {
|
||||
json::expect_200(Method::PATCH, &get_core(INSTANCE_EXISTS), "".to_string());
|
||||
json::expect_422(Method::PATCH, &get_core(INSTANCE_EXISTS), "".to_string());
|
||||
expect_404(Method::PATCH, &get_core(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn patch() {
|
||||
json::expect_200(TestRequest::patch(), &get_core(INSTANCE_EXISTS), "".to_string()).await;
|
||||
json::expect_422(TestRequest::patch(), &get_core(INSTANCE_EXISTS), "".to_string()).await;
|
||||
expect_404(TestRequest::patch(), &get_core(INSTANCE_INVALID)).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get() {
|
||||
expect_200(Method::GET, &get_core(INSTANCE_EXISTS));
|
||||
expect_404(Method::GET, &get_core(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn get() {
|
||||
expect_200(TestRequest::get(), &get_core(INSTANCE_EXISTS)).await;
|
||||
expect_404(TestRequest::get(), &get_core(INSTANCE_INVALID)).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete() {
|
||||
expect_200(Method::DELETE, &get_core(INSTANCE_EXISTS));
|
||||
expect_404(Method::DELETE, &get_core(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn delete() {
|
||||
expect_200(TestRequest::delete(), &get_core(INSTANCE_EXISTS)).await;
|
||||
expect_404(TestRequest::delete(), &get_core(INSTANCE_INVALID)).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn post() {
|
||||
expect_405(Method::POST, &get_core(INSTANCE_EXISTS));
|
||||
expect_405(Method::POST, &get_core(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn post() {
|
||||
expect_405(TestRequest::post(), &get_core(INSTANCE_EXISTS)).await;
|
||||
expect_405(TestRequest::post(), &get_core(INSTANCE_INVALID)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@ use super::web;
|
||||
use super::web::Data;
|
||||
use super::web::Json;
|
||||
use super::Core;
|
||||
use super::CoreQueryParameters;
|
||||
use super::Filters;
|
||||
use super::HandlerResult;
|
||||
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);
|
||||
let context = state
|
||||
.read()
|
||||
@@ -40,17 +41,23 @@ fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> Hand
|
||||
}
|
||||
}
|
||||
Some(filter) => {
|
||||
let core_parameters = CoreQueryParameters {
|
||||
db,
|
||||
output_space: space.as_ref().map(String::as_str),
|
||||
threshold_volume: parameters.volume(),
|
||||
view_port: ¶meters.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.
|
||||
let mut results = HashSet::new();
|
||||
for core in db.core_keys() {
|
||||
match context.filter(
|
||||
filter,
|
||||
core,
|
||||
&space,
|
||||
parameters.volume(),
|
||||
¶meters.view_port,
|
||||
parameters.resolution(),
|
||||
) {
|
||||
match context.execute(&tree, core, &core_parameters) {
|
||||
Err(e) => return error_422(e),
|
||||
Ok(objects) => {
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 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!");
|
||||
error_400()
|
||||
}
|
||||
|
||||
fn patch() -> HandlerResult {
|
||||
async fn patch() -> HandlerResult {
|
||||
trace!("PATCH Triggered!");
|
||||
error_400()
|
||||
}
|
||||
|
||||
fn delete() -> HandlerResult {
|
||||
async fn delete() -> HandlerResult {
|
||||
trace!("DELETE Triggered!");
|
||||
error_400()
|
||||
}
|
||||
@@ -108,49 +115,47 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
mod routing {
|
||||
use super::super::tests_utils::*;
|
||||
|
||||
const COLLECTION: &str = "/cores";
|
||||
|
||||
// FIXME: Add Body to request to see difference between (in)valid bodied requests
|
||||
|
||||
#[test]
|
||||
fn post() {
|
||||
expect_200(Method::POST, &get_core(""));
|
||||
json::expect_200(Method::POST, &get_core(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn post() {
|
||||
expect_200(TestRequest::post(), &get_core("")).await;
|
||||
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]
|
||||
fn put() {
|
||||
json::expect_200(Method::PUT, &get_core(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn put() {
|
||||
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]
|
||||
fn patch() {
|
||||
json::expect_200(Method::PATCH, &get_core(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn patch() {
|
||||
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]
|
||||
fn delete() {
|
||||
json::expect_200(Method::DELETE, &get_core(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn delete() {
|
||||
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]
|
||||
fn get() {
|
||||
expect_405(Method::GET, &get_core(""));
|
||||
#[actix_web::test]
|
||||
async fn get() {
|
||||
expect_405(TestRequest::get(), &get_core("")).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ where
|
||||
T: Serialize,
|
||||
{
|
||||
match serde_json::to_string(data) {
|
||||
Ok(response) => Ok(Either::A(
|
||||
Ok(response) => Ok(Either::Left(
|
||||
HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(response),
|
||||
@@ -30,7 +30,7 @@ pub fn error_422<S>(reason: S) -> HandlerResult
|
||||
where
|
||||
S: Debug,
|
||||
{
|
||||
Ok(Either::A(HttpResponse::UnprocessableEntity().body(
|
||||
Ok(Either::Left(HttpResponse::UnprocessableEntity().body(
|
||||
format!("422 - Unprocessable Entity:\n{:?}", reason),
|
||||
)))
|
||||
}
|
||||
@@ -50,7 +50,7 @@ where
|
||||
// error_400()
|
||||
//}
|
||||
|
||||
pub fn page_404() -> HandlerResult {
|
||||
pub async fn page_404() -> HandlerResult {
|
||||
trace!("404 Triggered!");
|
||||
error_404()
|
||||
}
|
||||
@@ -60,7 +60,7 @@ pub fn page_404() -> HandlerResult {
|
||||
// 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);
|
||||
|
||||
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);
|
||||
|
||||
match NamedFile::open(format!("static/{}", path).as_str()) {
|
||||
@@ -85,10 +85,9 @@ pub fn static_file(path: Path<String>) -> Result<NamedFile, Error> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::tests_utils::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn page_400() {
|
||||
// expect_400(Method::PATCH, get_core(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn page_400() {
|
||||
expect_400(TestRequest::patch(), &get_core(INVALID_CORE)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use actix_web::HttpResponse;
|
||||
use super::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 {
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::HandlerResult;
|
||||
fn error(code: StatusCode) -> HandlerResult {
|
||||
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 {
|
||||
|
||||
@@ -30,7 +30,8 @@ use actix_web::HttpResponse;
|
||||
use actix_web::HttpServer;
|
||||
use mercator_db::space::Shape;
|
||||
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;
|
||||
pub use mercator_db::DataBase;
|
||||
use mercator_db::Properties;
|
||||
@@ -64,13 +65,10 @@ impl Filters {
|
||||
}
|
||||
|
||||
pub fn ids_only(&self) -> bool {
|
||||
match self.ids_only {
|
||||
None => true, // Defaults to true
|
||||
Some(b) => b,
|
||||
}
|
||||
self.ids_only.unwrap_or(true)
|
||||
}
|
||||
|
||||
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 !db.space_keys().contains(&space_id.to_string()) {
|
||||
return Err(error_422(format!(
|
||||
@@ -87,10 +85,9 @@ impl Filters {
|
||||
}
|
||||
|
||||
pub fn volume(&self) -> Option<f64> {
|
||||
match &self.view_port {
|
||||
None => None,
|
||||
Some((low, high)) => Some(Shape::BoundingBox(low.into(), high.into()).volume()),
|
||||
}
|
||||
self.view_port.as_ref().map(|(low, high)|
|
||||
Shape::BoundingBox(low.into(), high.into()).volume()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +111,7 @@ impl From<&mercator_db::Core> for Core {
|
||||
}
|
||||
|
||||
// From: https://stackoverflow.com/a/52367953
|
||||
fn into_static<S>(s: S) -> &'static str
|
||||
pub fn into_static<S>(s: S) -> &'static str
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
@@ -142,10 +139,8 @@ fn config_v1(cfg: &mut web::ServiceConfig) {
|
||||
}
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
let prefix;
|
||||
|
||||
match std::env::var("MERCATOR_BASE") {
|
||||
Ok(val) => prefix = val,
|
||||
let prefix = match std::env::var("MERCATOR_BASE") {
|
||||
Ok(val) => val,
|
||||
Err(val) => {
|
||||
error!("Could not fetch {} : `{}`", "MERCATOR_BASE", val);
|
||||
exit(1);
|
||||
@@ -160,7 +155,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
|
||||
pub fn get_cors() -> Cors {
|
||||
// Setup CORS support.
|
||||
let mut cors = Cors::new();
|
||||
let mut cors = Cors::default();
|
||||
|
||||
match std::env::var("MERCATOR_ALLOWED_ORIGINS") {
|
||||
Ok(val) => {
|
||||
@@ -189,62 +184,42 @@ pub fn get_cors() -> Cors {
|
||||
macro_rules! get_app {
|
||||
($state:expr) => {
|
||||
App::new()
|
||||
.register_data($state.clone())
|
||||
.app_data($state.clone())
|
||||
.wrap(middleware::Logger::new(
|
||||
r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T[s] %D[ms]"#,
|
||||
))
|
||||
.wrap(middleware::Compress::default())
|
||||
.wrap(get_cors())
|
||||
.configure(config)
|
||||
.default_service(
|
||||
web::resource("/")
|
||||
// 404 for GET request
|
||||
.route(web::to(page_404)),
|
||||
)
|
||||
.default_service(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);
|
||||
|
||||
// Create & run the server.
|
||||
match HttpServer::new(move || get_app!(state))
|
||||
.bind(format!("{}:{}", host, port))
|
||||
.unwrap_or_else(|e| panic!("Failed to bind to `{}:{}`: {}", host, port, e))
|
||||
HttpServer::new(move || get_app!(state))
|
||||
.bind(format!("{}:{}", host, port))?
|
||||
.run()
|
||||
{
|
||||
Ok(_) => info!("Server Stopped!"),
|
||||
Err(e) => error!("Error running the server: {}", e),
|
||||
};
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_utils {
|
||||
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::TestRequest;
|
||||
use mercator_db::DataBase;
|
||||
pub use actix_web::test::TestRequest;
|
||||
|
||||
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 INVALID_CORE: &str = "/INVALID_CORE";
|
||||
pub const SPACE: &str = "/std";
|
||||
pub const SPATIAL_OBJECT: &str = "/oid0.44050628835072825";
|
||||
|
||||
pub enum Method {
|
||||
GET,
|
||||
POST,
|
||||
PUT,
|
||||
PATCH,
|
||||
DELETE,
|
||||
}
|
||||
|
||||
pub fn get_path(path: &str) -> String {
|
||||
format!("{}{}", PREFIX, path)
|
||||
@@ -262,113 +237,113 @@ mod tests_utils {
|
||||
format!("{}{}{}", get_core(CORE), "/spatial_objects", name)
|
||||
}
|
||||
|
||||
pub fn expect(method: Method, path: &str, code: http::StatusCode) {
|
||||
std::env::set_var("MERCATOR_BASE", PREFIX);
|
||||
|
||||
let mut app = test::init_service(get_app!(Data::new(RwLock::new(SharedState::new(
|
||||
DataBase::load(CORE_ID).unwrap()
|
||||
)))));
|
||||
|
||||
let request = match method {
|
||||
Method::GET => test::TestRequest::get(),
|
||||
Method::POST => test::TestRequest::post(),
|
||||
Method::PUT => test::TestRequest::put(),
|
||||
Method::PATCH => test::TestRequest::patch(),
|
||||
Method::DELETE => test::TestRequest::delete(),
|
||||
macro_rules! expect_code {
|
||||
($request:expr, $path:expr, $code:expr) => {
|
||||
{
|
||||
std::env::set_var("MERCATOR_BASE", PREFIX);
|
||||
let db = DataBase::load(&[CORE_FILE]).unwrap();
|
||||
let app = test::init_service(
|
||||
get_app!(Data::new(RwLock::new(SharedState::new(db))))).await;
|
||||
let request = $request.uri(&$path).to_request();
|
||||
let response = test::call_service(&app, request).await;
|
||||
assert_eq!(response.status(), $code);
|
||||
// let json = test::read_body(response).await;
|
||||
// println!("BODY: {:?}", json);
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
expect(method, path, http::StatusCode::OK);
|
||||
/// Checks status code OK
|
||||
pub async fn expect_200(method: TestRequest, path: &str) {
|
||||
expect_code!(method, path, StatusCode::OK);
|
||||
}
|
||||
|
||||
pub fn expect_400(method: Method, path: &str) {
|
||||
expect(method, path, http::StatusCode::BAD_REQUEST);
|
||||
/// Checks status code 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) {
|
||||
expect(method, path, http::StatusCode::NOT_FOUND);
|
||||
/// Checks status code 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) {
|
||||
expect(method, path, http::StatusCode::METHOD_NOT_ALLOWED);
|
||||
/// Checks status code 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) {
|
||||
expect(method, path, http::StatusCode::UNPROCESSABLE_ENTITY);
|
||||
/// Checks status code UNPROCESSABLE_ENTITY
|
||||
pub async fn expect_422(method: TestRequest, path: &str) {
|
||||
expect_code!(method, path, StatusCode::UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
pub mod json {
|
||||
use super::*;
|
||||
|
||||
pub fn expect_200(method: Method, path: &str, json: String) {
|
||||
expect(method, path, http::StatusCode::OK);
|
||||
pub async fn expect_200(method: TestRequest, path: &str, _json: String) {
|
||||
expect_code!(method, path, StatusCode::OK);
|
||||
}
|
||||
|
||||
pub fn expect_404(method: Method, path: &str, json: String) {
|
||||
expect(method, path, http::StatusCode::NOT_FOUND);
|
||||
pub async fn expect_404(method: TestRequest, path: &str, _json: String) {
|
||||
expect_code!(method, path, StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
pub fn expect_422(method: Method, path: &str, json: String) {
|
||||
expect(method, path, http::StatusCode::UNPROCESSABLE_ENTITY);
|
||||
pub async fn expect_422(method: TestRequest, path: &str, _json: String) {
|
||||
expect_code!(method, path, StatusCode::UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod routing {
|
||||
use super::tests_utils::*;
|
||||
use std::panic;
|
||||
|
||||
use super::tests_utils::*;
|
||||
|
||||
#[test]
|
||||
fn default_no_path() {
|
||||
#[ignore] // Don't know how to make work the catch_unwind in an async context
|
||||
#[actix_web::test]
|
||||
async fn default_no_path() {
|
||||
// _FIXME: Currently the string is validated by the URI constructor which
|
||||
// simply unwraps, thus we have to resort to this ugly workaround.
|
||||
// The goal is to catch if that behavior changes in the future.
|
||||
let result = panic::catch_unwind(|| {
|
||||
expect_404(Method::GET, "");
|
||||
//expect_404(TestRequest::get(), "").await;
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_slash() {
|
||||
#[actix_web::test]
|
||||
async fn default_slash() {
|
||||
// We have to manually URL-encode spaces.
|
||||
expect_404(Method::GET, "/");
|
||||
expect_404(Method::GET, "//");
|
||||
expect_404(Method::GET, "/%20/");
|
||||
expect_404(Method::GET, "/%20//");
|
||||
expect_404(Method::GET, "//%20");
|
||||
expect_404(TestRequest::get(), "/").await;
|
||||
expect_404(TestRequest::get(), "//").await;
|
||||
expect_404(TestRequest::get(), "/%20/").await;
|
||||
expect_404(TestRequest::get(), "/%20//").await;
|
||||
expect_404(TestRequest::get(), "//%20").await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_invalid_prefix() {
|
||||
expect_404(Method::GET, "/test");
|
||||
expect_404(Method::GET, &format!("{}test", PREFIX));
|
||||
#[actix_web::test]
|
||||
async fn default_invalid_prefix() {
|
||||
expect_404(TestRequest::get(), "/test").await;
|
||||
expect_404(TestRequest::get(), &format!("{}test", PREFIX)).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_prefix_no_slash() {
|
||||
expect_404(Method::PUT, PREFIX);
|
||||
expect_404(Method::GET, PREFIX);
|
||||
expect_404(Method::POST, PREFIX);
|
||||
expect_404(Method::PATCH, PREFIX);
|
||||
expect_404(Method::DELETE, PREFIX);
|
||||
#[actix_web::test]
|
||||
async fn default_prefix_no_slash() {
|
||||
expect_404(TestRequest::put(), PREFIX).await;
|
||||
expect_404(TestRequest::get(), PREFIX).await;
|
||||
expect_404(TestRequest::post(), PREFIX).await;
|
||||
expect_404(TestRequest::patch(), PREFIX).await;
|
||||
expect_404(TestRequest::delete(), PREFIX).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_prefix_final_slash() {
|
||||
#[actix_web::test]
|
||||
async fn default_prefix_final_slash() {
|
||||
let path = &format!("{}/", PREFIX);
|
||||
expect_404(Method::PUT, path);
|
||||
expect_404(Method::GET, path);
|
||||
expect_404(Method::POST, path);
|
||||
expect_404(Method::PATCH, path);
|
||||
expect_404(Method::DELETE, path);
|
||||
expect_404(TestRequest::put(), path).await;
|
||||
expect_404(TestRequest::get(), path).await;
|
||||
expect_404(TestRequest::post(), path).await;
|
||||
expect_404(TestRequest::patch(), path).await;
|
||||
expect_404(TestRequest::delete(), path).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ use super::web::Path;
|
||||
use super::HandlerResult;
|
||||
use super::SharedState;
|
||||
|
||||
fn put(path: Path<String>) -> HandlerResult {
|
||||
async fn put(path: Path<String>) -> HandlerResult {
|
||||
trace!("POST '{:?}'", path);
|
||||
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);
|
||||
let name = path.to_string();
|
||||
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);
|
||||
error_400()
|
||||
}
|
||||
|
||||
fn delete(path: Path<String>) -> HandlerResult {
|
||||
async fn delete(path: Path<String>) -> HandlerResult {
|
||||
trace!("DELETE Triggered on {}", path);
|
||||
error_400()
|
||||
}
|
||||
@@ -60,35 +60,35 @@ mod routing {
|
||||
|
||||
// FIXME: Add Body to request to see difference between (in)valid bodied requests
|
||||
|
||||
#[test]
|
||||
fn put() {
|
||||
json::expect_200(Method::PUT, &get_space(INSTANCE_EXISTS), "".to_string());
|
||||
json::expect_422(Method::PUT, &get_space(INSTANCE_EXISTS), "".to_string());
|
||||
json::expect_200(Method::PUT, &get_space(INSTANCE_INVALID), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn put() {
|
||||
json::expect_200(TestRequest::put(), &get_space(INSTANCE_EXISTS), "".to_string()).await;
|
||||
json::expect_422(TestRequest::put(), &get_space(INSTANCE_EXISTS), "".to_string()).await;
|
||||
json::expect_200(TestRequest::put(), &get_space(INSTANCE_INVALID), "".to_string()).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn patch() {
|
||||
json::expect_200(Method::PATCH, &get_space(INSTANCE_EXISTS), "".to_string());
|
||||
json::expect_422(Method::PATCH, &get_space(INSTANCE_EXISTS), "".to_string());
|
||||
expect_400(Method::PATCH, &get_space(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn patch() {
|
||||
json::expect_200(TestRequest::patch(), &get_space(INSTANCE_EXISTS), "".to_string()).await;
|
||||
json::expect_422(TestRequest::patch(), &get_space(INSTANCE_EXISTS), "".to_string()).await;
|
||||
expect_400(TestRequest::patch(), &get_space(INSTANCE_INVALID)).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get() {
|
||||
expect_200(Method::GET, &get_space(INSTANCE_EXISTS));
|
||||
expect_404(Method::GET, &get_space(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn get() {
|
||||
expect_200(TestRequest::get(), &get_space(INSTANCE_EXISTS)).await;
|
||||
expect_404(TestRequest::get(), &get_space(INSTANCE_INVALID)).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete() {
|
||||
expect_200(Method::DELETE, &get_space(INSTANCE_EXISTS));
|
||||
expect_404(Method::DELETE, &get_space(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn delete() {
|
||||
expect_200(TestRequest::delete(), &get_space(INSTANCE_EXISTS)).await;
|
||||
expect_404(TestRequest::delete(), &get_space(INSTANCE_INVALID)).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn post() {
|
||||
expect_405(Method::POST, &get_space(INSTANCE_EXISTS));
|
||||
expect_405(Method::POST, &get_space(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn post() {
|
||||
expect_405(TestRequest::post(), &get_space(INSTANCE_EXISTS)).await;
|
||||
expect_405(TestRequest::post(), &get_space(INSTANCE_INVALID)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@ use super::ok_200;
|
||||
use super::web;
|
||||
use super::web::Data;
|
||||
use super::web::Json;
|
||||
use super::CoreQueryParameters;
|
||||
use super::Filters;
|
||||
use super::HandlerResult;
|
||||
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);
|
||||
let context = state
|
||||
.read()
|
||||
@@ -43,15 +44,20 @@ fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> Hand
|
||||
// Retrieve the list of space ids.
|
||||
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: ¶meters.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() {
|
||||
match context.filter(
|
||||
filter,
|
||||
core,
|
||||
&space,
|
||||
parameters.volume(),
|
||||
¶meters.view_port,
|
||||
parameters.resolution(),
|
||||
) {
|
||||
match context.execute(&tree, core, &core_parameters) {
|
||||
Err(e) => return error_422(e),
|
||||
Ok(v) => {
|
||||
// We have a list of SpaceObjects, so extract
|
||||
@@ -70,7 +76,7 @@ fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> Hand
|
||||
ok_200(
|
||||
&results
|
||||
.drain()
|
||||
.map(|id| match db.space(&id) {
|
||||
.map(|id| match db.space(id) {
|
||||
Err(_) => None,
|
||||
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!");
|
||||
error_400()
|
||||
}
|
||||
|
||||
fn patch() -> HandlerResult {
|
||||
async fn patch() -> HandlerResult {
|
||||
trace!("PATCH Triggered!");
|
||||
error_400()
|
||||
}
|
||||
|
||||
fn delete() -> HandlerResult {
|
||||
async fn delete() -> HandlerResult {
|
||||
trace!("DELETE Triggered!");
|
||||
error_400()
|
||||
}
|
||||
@@ -114,45 +120,45 @@ mod routing {
|
||||
|
||||
// FIXME: Add Body to request to see difference between (in)valid bodied requests
|
||||
|
||||
#[test]
|
||||
fn post() {
|
||||
expect_200(Method::POST, &get_space(""));
|
||||
json::expect_200(Method::POST, &get_space(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn post() {
|
||||
expect_200(TestRequest::post(), &get_space("")).await;
|
||||
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]
|
||||
fn put() {
|
||||
json::expect_200(Method::PUT, &get_space(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn put() {
|
||||
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]
|
||||
fn patch() {
|
||||
json::expect_200(Method::PATCH, &get_space(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn patch() {
|
||||
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]
|
||||
fn delete() {
|
||||
json::expect_200(Method::DELETE, &get_space(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn delete() {
|
||||
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]
|
||||
fn get() {
|
||||
expect_405(Method::GET, &get_space(""));
|
||||
#[actix_web::test]
|
||||
async fn get() {
|
||||
expect_405(TestRequest::get(), &get_space("")).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::sync::RwLock;
|
||||
|
||||
use super::error_400;
|
||||
use super::error_404;
|
||||
use super::from_properties_by_spaces;
|
||||
use super::ok_200;
|
||||
use super::to_spatial_objects;
|
||||
use super::web;
|
||||
use super::web::Data;
|
||||
use super::web::Path;
|
||||
@@ -11,17 +11,16 @@ use super::CoreQueryParameters;
|
||||
use super::HandlerResult;
|
||||
use super::Properties;
|
||||
use super::SharedState;
|
||||
use mercator_db::{IterObjects, IterObjectsBySpaces};
|
||||
|
||||
fn put(path: Path<String>) -> HandlerResult {
|
||||
async fn put(path: Path<String>) -> HandlerResult {
|
||||
trace!("PUT '{:?}'", path);
|
||||
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);
|
||||
let (core, id) = path.into_inner();
|
||||
let core = core;
|
||||
let id = id;
|
||||
let context = state
|
||||
.read()
|
||||
.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) {
|
||||
Ok(core) => match core.get_by_id(¶meters, &id) {
|
||||
Ok(objects) => {
|
||||
Ok(positions_by_spaces) => {
|
||||
let value = Properties::Feature(id);
|
||||
let tmp = objects
|
||||
let tmp: IterObjectsBySpaces = positions_by_spaces
|
||||
.into_iter()
|
||||
.map(|(space, positions)| {
|
||||
let shapes = positions
|
||||
.into_iter()
|
||||
.map(|position| (position, &value))
|
||||
.collect();
|
||||
(space, shapes)
|
||||
let objects: IterObjects =
|
||||
Box::new(positions.map(|position| (position, &value)));
|
||||
(space, objects)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let results = to_spatial_objects(tmp);
|
||||
let results = from_properties_by_spaces(tmp).collect::<Vec<_>>();
|
||||
|
||||
if results.is_empty() {
|
||||
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);
|
||||
error_400()
|
||||
}
|
||||
|
||||
fn delete(path: Path<String>) -> HandlerResult {
|
||||
async fn delete(path: Path<String>) -> HandlerResult {
|
||||
trace!("DELETE Triggered on {}", path);
|
||||
error_400()
|
||||
}
|
||||
@@ -95,35 +92,35 @@ mod routing {
|
||||
|
||||
// FIXME: Add Body to request to see difference between (in)valid bodied requests
|
||||
|
||||
#[test]
|
||||
fn put() {
|
||||
json::expect_200(Method::PUT, &get_objects(INSTANCE_EXISTS), "".to_string());
|
||||
json::expect_422(Method::PUT, &get_objects(INSTANCE_EXISTS), "".to_string());
|
||||
json::expect_200(Method::PUT, &get_objects(INSTANCE_INVALID), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn put() {
|
||||
json::expect_200(TestRequest::put(), &get_objects(INSTANCE_EXISTS), "".to_string()).await;
|
||||
json::expect_422(TestRequest::put(), &get_objects(INSTANCE_EXISTS), "".to_string()).await;
|
||||
json::expect_200(TestRequest::put(), &get_objects(INSTANCE_INVALID), "".to_string()).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn patch() {
|
||||
json::expect_200(Method::PATCH, &get_objects(INSTANCE_EXISTS), "".to_string());
|
||||
json::expect_422(Method::PATCH, &get_objects(INSTANCE_EXISTS), "".to_string());
|
||||
expect_400(Method::PATCH, &get_objects(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn patch() {
|
||||
json::expect_200(TestRequest::patch(), &get_objects(INSTANCE_EXISTS), "".to_string()).await;
|
||||
json::expect_422(TestRequest::patch(), &get_objects(INSTANCE_EXISTS), "".to_string()).await;
|
||||
expect_400(TestRequest::patch(), &get_objects(INSTANCE_INVALID)).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get() {
|
||||
expect_200(Method::GET, &get_objects(INSTANCE_EXISTS));
|
||||
expect_404(Method::GET, &get_objects(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn get() {
|
||||
expect_200(TestRequest::get(), &get_objects(INSTANCE_EXISTS)).await;
|
||||
expect_404(TestRequest::get(), &get_objects(INSTANCE_INVALID)).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete() {
|
||||
expect_200(Method::DELETE, &get_objects(INSTANCE_EXISTS));
|
||||
expect_404(Method::DELETE, &get_objects(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn delete() {
|
||||
expect_200(TestRequest::delete(), &get_objects(INSTANCE_EXISTS)).await;
|
||||
expect_404(TestRequest::delete(), &get_objects(INSTANCE_INVALID)).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn post() {
|
||||
expect_405(Method::POST, &get_objects(INSTANCE_EXISTS));
|
||||
expect_405(Method::POST, &get_objects(INSTANCE_INVALID));
|
||||
#[actix_web::test]
|
||||
async fn post() {
|
||||
expect_405(TestRequest::post(), &get_objects(INSTANCE_EXISTS)).await;
|
||||
expect_405(TestRequest::post(), &get_objects(INSTANCE_INVALID)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use super::error_400;
|
||||
use super::error_404;
|
||||
use super::error_422;
|
||||
use super::from_properties_by_spaces;
|
||||
use super::from_spaces_by_properties;
|
||||
use super::ok_200;
|
||||
use super::to_spatial_objects;
|
||||
use super::web;
|
||||
use super::web::Data;
|
||||
use super::web::Json;
|
||||
@@ -16,7 +16,7 @@ use super::Filters;
|
||||
use super::HandlerResult;
|
||||
use super::SharedState;
|
||||
|
||||
fn post(
|
||||
async fn post(
|
||||
(core_id, parameters, state): (Path<String>, Json<Filters>, Data<RwLock<SharedState>>),
|
||||
) -> HandlerResult {
|
||||
trace!("POST '{:?}', {:?}", parameters, core_id);
|
||||
@@ -32,13 +32,15 @@ fn post(
|
||||
Err(e) => e,
|
||||
Ok(space) => match parameters.filters() {
|
||||
None => {
|
||||
let mut results = HashMap::new();
|
||||
for property in core.keys().iter() {
|
||||
results.insert(property.id(), property);
|
||||
}
|
||||
|
||||
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 {
|
||||
let core_parameters = CoreQueryParameters {
|
||||
db,
|
||||
@@ -48,40 +50,33 @@ fn post(
|
||||
resolution: parameters.resolution(),
|
||||
};
|
||||
|
||||
let mut objects = vec![];
|
||||
for (id, properties) in results.drain() {
|
||||
match core.get_by_id(&core_parameters, id) {
|
||||
Err(_) => (), // FIXME: Return error ?
|
||||
Ok(r) => {
|
||||
let mut tmp = r
|
||||
.into_iter()
|
||||
.map(|(space, positions)| {
|
||||
let shapes = positions
|
||||
.into_iter()
|
||||
.map(|position| (position, properties))
|
||||
.collect();
|
||||
(space, shapes)
|
||||
})
|
||||
.collect();
|
||||
objects.append(&mut tmp);
|
||||
let objects_by_spaces =
|
||||
Box::new(core.keys().iter().filter_map(|property| {
|
||||
match core.get_by_id(&core_parameters, property.id()) {
|
||||
Err(_) => None, // FIXME: Return error ?
|
||||
Ok(positions_by_spaces) => {
|
||||
Some((property, positions_by_spaces))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let objects = to_spatial_objects(objects);
|
||||
|
||||
ok_200(&objects)
|
||||
}));
|
||||
ok_200(&from_spaces_by_properties(objects_by_spaces).collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
Some(filter) => {
|
||||
match context.filter(
|
||||
filter,
|
||||
&core_id,
|
||||
space,
|
||||
parameters.volume(),
|
||||
¶meters.view_port,
|
||||
parameters.resolution(),
|
||||
) {
|
||||
let core_parameters = CoreQueryParameters {
|
||||
db,
|
||||
output_space: space.as_ref().map(String::as_str),
|
||||
threshold_volume: parameters.volume(),
|
||||
view_port: ¶meters.view_port,
|
||||
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),
|
||||
Ok(objects) => {
|
||||
if parameters.ids_only() {
|
||||
@@ -94,29 +89,29 @@ fn post(
|
||||
|
||||
ok_200(&uniques.drain().collect::<Vec<_>>())
|
||||
} else {
|
||||
let objects = to_spatial_objects(objects);
|
||||
|
||||
ok_200(&objects)
|
||||
ok_200(&from_properties_by_spaces(objects).collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
r
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn put() -> HandlerResult {
|
||||
async fn put() -> HandlerResult {
|
||||
trace!("PUT Triggered!");
|
||||
error_400()
|
||||
}
|
||||
|
||||
fn patch() -> HandlerResult {
|
||||
async fn patch() -> HandlerResult {
|
||||
trace!("PATCH Triggered!");
|
||||
error_400()
|
||||
}
|
||||
|
||||
fn delete() -> HandlerResult {
|
||||
async fn delete() -> HandlerResult {
|
||||
trace!("DELETE Triggered!");
|
||||
error_400()
|
||||
}
|
||||
@@ -137,45 +132,45 @@ mod routing {
|
||||
|
||||
// FIXME: Add Body to request to see difference between (in)valid bodied requests
|
||||
|
||||
#[test]
|
||||
fn post() {
|
||||
expect_200(Method::POST, &get_objects(""));
|
||||
json::expect_200(Method::POST, &get_objects(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn post() {
|
||||
expect_200(TestRequest::post(), &get_objects("")).await;
|
||||
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]
|
||||
fn put() {
|
||||
json::expect_200(Method::PUT, &get_objects(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn put() {
|
||||
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]
|
||||
fn patch() {
|
||||
json::expect_200(Method::PATCH, &get_objects(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn patch() {
|
||||
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]
|
||||
fn delete() {
|
||||
json::expect_200(Method::DELETE, &get_objects(""), "".to_string());
|
||||
#[actix_web::test]
|
||||
async fn delete() {
|
||||
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]
|
||||
fn get() {
|
||||
expect_405(Method::GET, &get_objects(""));
|
||||
#[actix_web::test]
|
||||
async fn get() {
|
||||
expect_405(TestRequest::get(), &get_objects("")).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use mercator_db::CoreQueryParameters;
|
||||
use mercator_db::DataBase;
|
||||
use mercator_parser::Bag;
|
||||
use mercator_parser::Executor;
|
||||
use mercator_parser::FiltersParser;
|
||||
use mercator_parser::Projection;
|
||||
use mercator_parser::QueryParser;
|
||||
use mercator_parser::Validator;
|
||||
|
||||
@@ -32,24 +34,35 @@ impl SharedState {
|
||||
&self.query_parser
|
||||
}
|
||||
|
||||
pub fn filter<'q>(
|
||||
&'q self,
|
||||
filter: &'q str,
|
||||
core: &'q str,
|
||||
output_space: &'q Option<String>,
|
||||
volume: Option<f64>,
|
||||
view_port: &'q Option<(Vec<f64>, Vec<f64>)>,
|
||||
resolution: &'q Option<Vec<u32>>,
|
||||
) -> mercator_db::ResultSet<'q> {
|
||||
pub fn execute<'e, T>(
|
||||
&'e self,
|
||||
tree: &'e T, //&'e Bag,
|
||||
core: &'e str,
|
||||
parameters: &'e CoreQueryParameters<'e>,
|
||||
) -> mercator_db::ResultSet<'e>
|
||||
where
|
||||
T: Executor<'e, ResultSet = mercator_db::ResultSet<'e>>,
|
||||
{
|
||||
// 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 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
|
||||
{
|
||||
@@ -63,52 +76,20 @@ impl SharedState {
|
||||
Err(format!("{}", e))
|
||||
}
|
||||
Ok(tree) => {
|
||||
let validation;
|
||||
let execution;
|
||||
|
||||
// Check type coherence & validate tree
|
||||
{
|
||||
debug_time!("Type check");
|
||||
validation = tree.validate();
|
||||
}
|
||||
if validation.is_err() {
|
||||
debug!("Type check failed");
|
||||
return Err("Type check failed".to_string());
|
||||
let _ = tree.validate()?;
|
||||
}
|
||||
|
||||
// Execute filter.
|
||||
{
|
||||
info_time!("Execution");
|
||||
execution = tree.execute(core, ¶meters);
|
||||
}
|
||||
match execution {
|
||||
Err(e) => {
|
||||
debug!("Parsing failed: \n{:?}", e);
|
||||
Err(e)
|
||||
}
|
||||
results @ Ok(_) => results,
|
||||
}
|
||||
Ok(tree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query<'q>(
|
||||
&'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> {
|
||||
pub fn query(&self, query: &str) -> Result<Projection, String> {
|
||||
let parser = self.query_parser();
|
||||
let parse;
|
||||
let parameters = CoreQueryParameters {
|
||||
db: self.db(),
|
||||
output_space: None,
|
||||
threshold_volume: volume,
|
||||
view_port,
|
||||
resolution,
|
||||
};
|
||||
|
||||
// Parse Input
|
||||
{
|
||||
@@ -120,35 +101,15 @@ impl SharedState {
|
||||
debug!("Parsing failed: \n{:?}", e);
|
||||
Err(e.to_string())
|
||||
}
|
||||
Ok(None) => Ok(vec![]),
|
||||
Ok(None) => Err("Query is empty!".to_string()),
|
||||
Ok(Some(tree)) => {
|
||||
let validation;
|
||||
let execution;
|
||||
|
||||
// Check type coherence & validate tree
|
||||
{
|
||||
debug_time!("Type check");
|
||||
validation = tree.validate();
|
||||
}
|
||||
if validation.is_err() {
|
||||
debug!("Type check failed");
|
||||
return Err("Type check failed".to_string());
|
||||
let _ = tree.validate()?;
|
||||
}
|
||||
|
||||
// Execute filter.
|
||||
{
|
||||
info_time!("Execution");
|
||||
// _FIXME: Output space is defined as part of the projection
|
||||
// and is ignored by projections operators.
|
||||
execution = tree.execute(core, ¶meters);
|
||||
}
|
||||
match execution {
|
||||
Err(e) => {
|
||||
debug!("Parsing failed: \n{:?}", e);
|
||||
Err(e)
|
||||
}
|
||||
results @ Ok(_) => results,
|
||||
}
|
||||
Ok(tree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user