Compare commits
17 Commits
75b3529eb9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fd2b4e098b | |||
| e05159c7d7 | |||
| 1e5b5a8685 | |||
| 72e5296a7d | |||
| 1da288b826 | |||
| 478563f820 | |||
| 5dc9f13a78 | |||
| 6532bd5a0d | |||
| fc84eee48b | |||
| eba485519c | |||
| 16ac1ea7d1 | |||
| d1dffdf890 | |||
| e8e50bca8f | |||
| a6865c6cbd | |||
| 7c54dd4fc9 | |||
| bba41629f4 | |||
| 5cbd2a0b69 |
14
.idea/mercator_service.iml
generated
14
.idea/mercator_service.iml
generated
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="CPP_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/mercator_service.iml" filepath="$PROJECT_DIR$/.idea/mercator_service.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
36
Cargo.toml
36
Cargo.toml
@@ -18,32 +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 = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
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
|
||||
|
||||
115
README.md
115
README.md
@@ -18,18 +18,10 @@ This enables the index implementations to be agnostic from the underlying data s
|
||||
|
||||
## Requirements
|
||||
|
||||
### Hardware
|
||||
|
||||
* **Processor:** XGHz CPU
|
||||
* **RAM:** Y MB per MB of indexed data
|
||||
* **Available storage space:** X MB per MB of indexed data
|
||||
|
||||
### Software
|
||||
|
||||
* Rust: https://www.rust-lang.org
|
||||
|
||||
## Quick start
|
||||
|
||||
## Building from sources
|
||||
|
||||
To build this project, you will need to run the following:
|
||||
@@ -38,44 +30,107 @@ To build this project, you will need to run the following:
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
### Installation
|
||||
## Installation
|
||||
|
||||
To install the software on the system you can use:
|
||||
To install the software on the system, after checking out the
|
||||
dependencies you can use:
|
||||
|
||||
```sh
|
||||
cargo install --release
|
||||
cargo install --path .
|
||||
```
|
||||
|
||||
### Usage
|
||||
## Usage
|
||||
|
||||
In order to configure the behavior of the service, there is couple of environment variables:
|
||||
In order to configure the behavior of the service, there is couple of
|
||||
environment variables, in bold their default values:
|
||||
|
||||
* `RUST_LOG`: Set the level of logging, for example (**error**, **warn**, **info**, **debug**, **trace**). This can be controlled per subsystem of the service, or globally by specifying th subsystem and the level in a list, or omitting the subsystem part. For example:
|
||||
```sh
|
||||
RUST_LOG="actix_web=debug,mercator_service=trace" # Set actix_web to debug, mercator_service to trace
|
||||
RUST_LOG="trace" # Set everything to trace
|
||||
```
|
||||
* `RUST_LOG` = `info`:
|
||||
|
||||
* `MERCATOR_HOST`: Name or IP address to bind to.
|
||||
* `MERCATOR_PORT`: Port on which to listen.
|
||||
* `MERCATOR_BASE`: Prefix du service web.
|
||||
* `MERCATOR_ALLOWED_ORIGINS`: Allowed origins for CORS requests:
|
||||
```sh
|
||||
MERCATOR_ALLOWED_ORIGINS="http://localhost:3200,http://localhost:3201, http://localhost:3202"
|
||||
```
|
||||
Set the level of logging, for example (**error**, **warn**, **info**,
|
||||
**debug**, **trace**). This can be controlled per subsystem of the
|
||||
service, or globally by specifying th subsystem and the level in a
|
||||
list, or omitting the subsystem part.
|
||||
|
||||
* `MERCATOR_IMPORT_DATA`: Provide the data set to expose.
|
||||
For example:
|
||||
|
||||
```sh
|
||||
# Set actix_web to debug, mercator_service to trace,
|
||||
# fall back to info for everything else:
|
||||
RUST_LOG="info,actix_web=debug,mercator_service=trace"
|
||||
|
||||
# Set everything to trace
|
||||
RUST_LOG="trace"
|
||||
```
|
||||
|
||||
[More details](https://epfl-dias.github.io/mercator_service/env_logger/index.html)
|
||||
on how to control the logs.
|
||||
|
||||
* `MERCATOR_HOST` = **0.0.0.0** :
|
||||
|
||||
Name or IP address to bind to.
|
||||
|
||||
* `MERCATOR_PORT` = **8888** :
|
||||
|
||||
Port on which to listen.
|
||||
|
||||
* `MERCATOR_BASE` = **/spatial-search** :
|
||||
|
||||
Web service URL prefix.
|
||||
|
||||
* `MERCATOR_ALLOWED_ORIGINS` = **http://localhost:3200** :
|
||||
|
||||
Allowed origins for CORS requests.
|
||||
|
||||
* `MERCATOR_DATA` = **.**:
|
||||
|
||||
Provide the root folder of the data sets to expose.
|
||||
|
||||
### Example
|
||||
|
||||
Complete example of a run:
|
||||
```sh
|
||||
RUST_LOG="warn,actix_web=info,mercator_service=trace" MERCATOR_IMPORT_DATA="1000k" MERCATOR_ALLOWED_ORIGINS="http://localhost:3200,http://localhost:3201, http://localhost:3202" cargo run --release
|
||||
RUST_LOG="warn,actix_web=info,mercator_service=trace" \
|
||||
MERCATOR_HOST="mercator.example.org" \
|
||||
MERCATOR_PORT="1234" \
|
||||
MERCATOR_BASE="/" \
|
||||
MERCATOR_DATA="../mercator_indexer" \
|
||||
MERCATOR_ALLOWED_ORIGINS="http://localhost:3200,http://localhost:3201, http://localhost:3202" \
|
||||
mercator_service
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For more information, please refer to the [documentation](https://epfl-dias.github.io/mercator_service/).
|
||||
### User documentation
|
||||
|
||||
If you want to build the documentation and access it locally, you can use:
|
||||
By this, we mean the REST API documentation. To access it and be able
|
||||
to test it live you can use the following procedure:
|
||||
|
||||
1. Install [docker](https://www.docker.com).
|
||||
2. Start mercator_service, making sure there is the `static` folder,
|
||||
or a symlink to it, in the current working directory.
|
||||
3. Start swagger to have access to the live documentation:
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
--rm \
|
||||
-p 3200:8080 \
|
||||
-e API_URL='http://127.0.0.1:8888/spatial-search/static/api/v1.0.yaml' \
|
||||
swaggerapi/swagger-ui
|
||||
```
|
||||
|
||||
4. Using your web navigator, got to [http://localhost:3200](http://localhost:3200).
|
||||
5. You have the whole user documentation accessible, and you can
|
||||
trigger actions on your live mercator_service instance, running
|
||||
queries on your data.
|
||||
|
||||
Otherwise you can read `static/api/v1.0.yaml` directly.
|
||||
|
||||
### Developer documentation
|
||||
|
||||
For people looking at the internal structure, you can use the
|
||||
[developer documentation](https://epfl-dias.github.io/mercator_service/).
|
||||
|
||||
If you want to build this documentation and access it locally, you can
|
||||
use:
|
||||
|
||||
```sh
|
||||
cargo doc --open
|
||||
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.80.0"
|
||||
69
src/main.rs
69
src/main.rs
@@ -1,8 +1,36 @@
|
||||
#[macro_use]
|
||||
extern crate measure_time;
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! # Mercator Service
|
||||
//!
|
||||
//! REST-based HTTP service for Mercator.
|
||||
//!
|
||||
//! ## Mercator: Spatial Index
|
||||
//!
|
||||
//! **Mercator** is a spatial *volumetric* index for the
|
||||
//! [Human Brain Project]. It is a component of the [Knowledge Graph]
|
||||
//! service, which provides the spatial anchoring for the metadata
|
||||
//! registered as well as processes the volumetric queries.
|
||||
//!
|
||||
//! It is build on top of the Iron Sea database toolkit.
|
||||
//!
|
||||
//! ## Iron Sea: Database Toolkit
|
||||
//! **Iron Sea** provides a set of database engine bricks, which can be
|
||||
//! combined and applied on arbitrary data structures.
|
||||
//!
|
||||
//! Unlike a traditional database, it does not assume a specific
|
||||
//! physical structure for the tables nor the records, but relies on the
|
||||
//! developer to provide a set of extractor functions which are used by
|
||||
//! the specific indices provided.
|
||||
//!
|
||||
//! This enables the index implementations to be agnostic from the
|
||||
//! underlying data structure, and re-used.
|
||||
//!
|
||||
//! [Human Brain Project]: http://www.humanbrainproject.eu
|
||||
//! [Knowledge Graph]: http://www.humanbrainproject.eu/en/explore-the-brain/search/
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate measure_time;
|
||||
|
||||
mod rest_api;
|
||||
mod shared_state;
|
||||
@@ -10,11 +38,10 @@ mod shared_state;
|
||||
use std::process::exit;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use actix_web::web::Data;
|
||||
use glob::glob;
|
||||
use mercator_db::json::model;
|
||||
use mercator_db::DataBase;
|
||||
|
||||
use rest_api::Data;
|
||||
use rest_api::DataBase;
|
||||
use shared_state::SharedState;
|
||||
|
||||
/*
|
||||
@@ -23,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");
|
||||
@@ -51,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);
|
||||
@@ -77,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);
|
||||
@@ -96,20 +120,21 @@ 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:
|
||||
{
|
||||
// Load all the index contained in the folder, and fail if anyone of
|
||||
// those is corrupted / incompatible.
|
||||
info_time!("Loading database index");
|
||||
|
||||
db = DataBase::load(&datasets).unwrap();
|
||||
db = DataBase::load(&datasets.iter().map(String::as_str).collect::<Vec<_>>())
|
||||
.unwrap_or_else(|e| panic!("Error while loading indices: {}", e));
|
||||
}
|
||||
|
||||
rest_api::run(
|
||||
&hostname,
|
||||
port,
|
||||
Data::new(RwLock::new(SharedState::new(db))),
|
||||
);
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
use std::sync::RwLock;
|
||||
|
||||
use actix_web::web;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::web::Json;
|
||||
use actix_web::HttpResponse;
|
||||
|
||||
use crate::model::to_spatial_objects;
|
||||
use crate::shared_state::SharedState;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::error_422;
|
||||
use super::from_properties_by_spaces;
|
||||
use super::ok_200;
|
||||
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 {
|
||||
query: String,
|
||||
resolution: Option<Vec<u64>>, // None means automatic selection, based on ViewPort
|
||||
resolution: Option<Vec<u32>>, // None means automatic selection, based on ViewPort
|
||||
view_port: Option<(Vec<f64>, Vec<f64>)>,
|
||||
}
|
||||
|
||||
@@ -24,8 +25,8 @@ impl Query {
|
||||
&self.query
|
||||
}
|
||||
|
||||
pub fn resolution(&self) -> Option<Vec<u64>> {
|
||||
self.resolution.clone()
|
||||
pub fn resolution(&self) -> &Option<Vec<u32>> {
|
||||
&self.resolution
|
||||
}
|
||||
|
||||
pub fn volume(&self) -> Option<f64> {
|
||||
@@ -36,31 +37,44 @@ 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().unwrap();
|
||||
let context = state
|
||||
.read()
|
||||
.unwrap_or_else(|e| panic!("Can't acquire read lock of the database: {}", e));
|
||||
let query = parameters.query();
|
||||
|
||||
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(), parameters.resolution()) {
|
||||
Err(_) => vec![], // FIXME: Return errors here instead!!
|
||||
Ok(objects) => to_spatial_objects(context.db(), 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
use std::sync::RwLock;
|
||||
|
||||
use actix_web::web;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::web::Path;
|
||||
|
||||
use crate::shared_state::SharedState;
|
||||
|
||||
use super::error_400;
|
||||
use super::error_404;
|
||||
use super::ok_200;
|
||||
use super::web;
|
||||
use super::web::Data;
|
||||
use super::web::Path;
|
||||
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.read().unwrap();
|
||||
let context = state
|
||||
.read()
|
||||
.unwrap_or_else(|e| panic!("Can't acquire read lock of the database: {}", e));
|
||||
|
||||
match context.db().core(core) {
|
||||
match context.db().core(&core) {
|
||||
Ok(core) => ok_200(&Core::from(core)),
|
||||
Err(_) => error_404(),
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use actix_web::web;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::web::Json;
|
||||
|
||||
use crate::shared_state::SharedState;
|
||||
|
||||
use super::error_400;
|
||||
use super::error_422;
|
||||
use super::ok_200;
|
||||
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().unwrap();
|
||||
let context = state
|
||||
.read()
|
||||
.unwrap_or_else(|e| panic!("Can't acquire read lock of the database: {}", e));
|
||||
let db = context.db();
|
||||
|
||||
match parameters.space(db) {
|
||||
@@ -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.clone(),
|
||||
parameters.volume(),
|
||||
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.
|
||||
@@ -69,7 +76,7 @@ fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> Hand
|
||||
ok_200(
|
||||
&results
|
||||
.drain()
|
||||
.map(|x| Core::from(db.core(x).unwrap()))
|
||||
.map(|x| Core::from(db.core(&x).unwrap()))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,26 @@ use std::fmt::Debug;
|
||||
use std::io::Error;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use actix_files::NamedFile;
|
||||
use actix_web::Either;
|
||||
use actix_web::HttpResponse;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::error_404;
|
||||
use super::web::Path;
|
||||
use super::Either;
|
||||
use super::HandlerResult;
|
||||
use super::*;
|
||||
use super::HttpResponse;
|
||||
use super::NamedFile;
|
||||
use super::StatusCode;
|
||||
|
||||
pub fn ok_200<T>(data: &T) -> HandlerResult
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
match serde_json::to_string(data) {
|
||||
Ok(response) => Ok(Either::A(HttpResponse::Ok().body(response))),
|
||||
Ok(response) => Ok(Either::Left(
|
||||
HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(response),
|
||||
)),
|
||||
Err(e) => error_500(e),
|
||||
}
|
||||
}
|
||||
@@ -24,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),
|
||||
)))
|
||||
}
|
||||
@@ -44,7 +50,7 @@ where
|
||||
// error_400()
|
||||
//}
|
||||
|
||||
pub fn page_404() -> HandlerResult {
|
||||
pub async fn page_404() -> HandlerResult {
|
||||
trace!("404 Triggered!");
|
||||
error_404()
|
||||
}
|
||||
@@ -54,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()) {
|
||||
@@ -65,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()) {
|
||||
@@ -79,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 {
|
||||
|
||||
@@ -23,22 +23,30 @@ use actix_web::http;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::middleware;
|
||||
use actix_web::web;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::web::Path;
|
||||
pub use actix_web::web::Data;
|
||||
use actix_web::App;
|
||||
use actix_web::Either;
|
||||
use actix_web::HttpResponse;
|
||||
use actix_web::HttpServer;
|
||||
use mercator_db::space::Shape;
|
||||
use mercator_db::storage::model;
|
||||
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;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg(feature = "static-error-pages")]
|
||||
pub use helpers_static_pages::*;
|
||||
use crate::SharedState;
|
||||
|
||||
pub use helpers::*;
|
||||
|
||||
#[cfg(not(feature = "static-error-pages"))]
|
||||
pub use helpers_dynamic_pages::*;
|
||||
|
||||
pub use helpers::*;
|
||||
|
||||
use crate::SharedState;
|
||||
#[cfg(feature = "static-error-pages")]
|
||||
pub use helpers_static_pages::*;
|
||||
|
||||
pub type HandlerResult = Result<Either<HttpResponse, NamedFile>, Error>;
|
||||
|
||||
@@ -47,7 +55,7 @@ pub struct Filters {
|
||||
filters: Option<String>,
|
||||
ids_only: Option<bool>,
|
||||
space: Option<String>, // Output space, None, means each object in its own original space
|
||||
resolution: Option<Vec<u64>>, // None means automatic selection, based on ViewPort
|
||||
resolution: Option<Vec<u32>>, // None means automatic selection, based on ViewPort
|
||||
view_port: Option<(Vec<f64>, Vec<f64>)>,
|
||||
}
|
||||
|
||||
@@ -57,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!(
|
||||
@@ -72,18 +77,17 @@ impl Filters {
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(self.space.clone())
|
||||
Ok(&self.space)
|
||||
}
|
||||
|
||||
pub fn resolution(&self) -> Option<Vec<u64>> {
|
||||
self.resolution.clone()
|
||||
pub fn resolution(&self) -> &Option<Vec<u32>> {
|
||||
&self.resolution
|
||||
}
|
||||
|
||||
pub fn volume(&self) -> Option<f64> {
|
||||
match &self.view_port {
|
||||
None => None,
|
||||
Some(_view) => None, // FIXME: Need to move the Volume functions from mercator_parser to mercator_db.
|
||||
}
|
||||
self.view_port.as_ref().map(|(low, high)|
|
||||
Shape::BoundingBox(low.into(), high.into()).volume()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,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>,
|
||||
{
|
||||
@@ -135,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);
|
||||
@@ -153,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) => {
|
||||
@@ -182,61 +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()
|
||||
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)
|
||||
@@ -254,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
use std::sync::RwLock;
|
||||
|
||||
use actix_web::web;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::web::Path;
|
||||
|
||||
use crate::model;
|
||||
use crate::shared_state::SharedState;
|
||||
|
||||
use super::error_400;
|
||||
use super::error_404;
|
||||
use super::model;
|
||||
use super::ok_200;
|
||||
use super::web;
|
||||
use super::web::Data;
|
||||
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.read().unwrap();
|
||||
let context = state
|
||||
.read()
|
||||
.unwrap_or_else(|e| panic!("Can't acquire read lock of the database: {}", e));
|
||||
|
||||
match context.db().space(name) {
|
||||
match context.db().space(&name) {
|
||||
Ok(space) => {
|
||||
let space: model::Space = space.into();
|
||||
ok_200(&space)
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use actix_web::web;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::web::Json;
|
||||
|
||||
use crate::model;
|
||||
use crate::shared_state::SharedState;
|
||||
|
||||
use super::error_400;
|
||||
use super::error_422;
|
||||
use super::model;
|
||||
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().unwrap();
|
||||
let context = state
|
||||
.read()
|
||||
.unwrap_or_else(|e| panic!("Can't acquire read lock of the database: {}", e));
|
||||
let db = context.db();
|
||||
|
||||
match parameters.space(db) {
|
||||
@@ -43,20 +44,26 @@ 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.clone(),
|
||||
parameters.volume(),
|
||||
parameters.resolution(),
|
||||
) {
|
||||
match context.execute(&tree, core, &core_parameters) {
|
||||
Err(e) => return error_422(e),
|
||||
Ok(objects) => {
|
||||
Ok(v) => {
|
||||
// We have a list of SpaceObjects, so extract
|
||||
// the space Ids
|
||||
for o in objects {
|
||||
results.insert(o.space_id);
|
||||
for (space_id, _) in v {
|
||||
results.insert(space_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,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()
|
||||
}
|
||||
@@ -113,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,56 @@
|
||||
use std::sync::RwLock;
|
||||
|
||||
use actix_web::web;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::web::Path;
|
||||
use mercator_db::CoreQueryParameters;
|
||||
|
||||
use crate::model::to_spatial_objects;
|
||||
use crate::shared_state::SharedState;
|
||||
|
||||
use super::error_400;
|
||||
use super::error_404;
|
||||
use super::from_properties_by_spaces;
|
||||
use super::ok_200;
|
||||
use super::web;
|
||||
use super::web::Data;
|
||||
use super::web::Path;
|
||||
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.to_string();
|
||||
let id = id.to_string();
|
||||
let context = state.read().unwrap();
|
||||
let context = state
|
||||
.read()
|
||||
.unwrap_or_else(|e| panic!("Can't acquire read lock of the database: {}", e));
|
||||
let db = context.db();
|
||||
|
||||
// FIXME: Should we allow setting the resolution/threshold_volume?
|
||||
let parameters = CoreQueryParameters {
|
||||
db,
|
||||
output_space: None,
|
||||
threshold_volume: Some(0.0), // Empty volume => Highest resolution possible
|
||||
resolution: None,
|
||||
// Enforce highest resolution index.
|
||||
threshold_volume: None,
|
||||
view_port: &None,
|
||||
resolution: &Some(vec![0]),
|
||||
};
|
||||
|
||||
match db.core(core) {
|
||||
match db.core(&core) {
|
||||
Ok(core) => match core.get_by_id(¶meters, &id) {
|
||||
Ok(objects) => {
|
||||
let results = to_spatial_objects(db, objects);
|
||||
Ok(positions_by_spaces) => {
|
||||
let value = Properties::Feature(id);
|
||||
let tmp: IterObjectsBySpaces = positions_by_spaces
|
||||
.into_iter()
|
||||
.map(|(space, positions)| {
|
||||
let objects: IterObjects =
|
||||
Box::new(positions.map(|position| (position, &value)));
|
||||
(space, objects)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let results = from_properties_by_spaces(tmp).collect::<Vec<_>>();
|
||||
|
||||
if results.is_empty() {
|
||||
error_404()
|
||||
} else {
|
||||
@@ -50,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()
|
||||
}
|
||||
@@ -79,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,104 +1,117 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use actix_web::web;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::web::Json;
|
||||
use actix_web::web::Path;
|
||||
use mercator_db::CoreQueryParameters;
|
||||
|
||||
use crate::model::to_spatial_objects;
|
||||
use crate::shared_state::SharedState;
|
||||
|
||||
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::web;
|
||||
use super::web::Data;
|
||||
use super::web::Json;
|
||||
use super::web::Path;
|
||||
use super::CoreQueryParameters;
|
||||
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);
|
||||
let core_id = core_id.to_string();
|
||||
let context = state.read().unwrap();
|
||||
let context = state
|
||||
.read()
|
||||
.unwrap_or_else(|e| panic!("Can't acquire read lock of the database: {}", e));
|
||||
let db = context.db();
|
||||
|
||||
match db.core(core_id.clone()) {
|
||||
match db.core(&core_id) {
|
||||
Err(_) => error_404(),
|
||||
Ok(core) => match parameters.space(db) {
|
||||
Err(e) => e,
|
||||
Ok(space) => match parameters.filters() {
|
||||
None => {
|
||||
let mut results = HashSet::new();
|
||||
for property in core.keys().iter() {
|
||||
results.insert(property.id().clone());
|
||||
}
|
||||
|
||||
if parameters.ids_only() {
|
||||
ok_200(&results.drain().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,
|
||||
output_space: space.as_ref().map(String::as_str),
|
||||
threshold_volume: parameters.volume(),
|
||||
view_port: ¶meters.view_port,
|
||||
resolution: parameters.resolution(),
|
||||
};
|
||||
|
||||
let objects = results
|
||||
.drain()
|
||||
.flat_map(|id| match core.get_by_id(&core_parameters, id) {
|
||||
Err(_) => vec![], // FIXME: Return error ?
|
||||
Ok(r) => r,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let objects = to_spatial_objects(db, objects);
|
||||
|
||||
ok_200(&objects)
|
||||
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))
|
||||
}
|
||||
}
|
||||
}));
|
||||
ok_200(&from_spaces_by_properties(objects_by_spaces).collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
Some(filter) => {
|
||||
match context.filter(
|
||||
filter,
|
||||
&core_id,
|
||||
space,
|
||||
parameters.volume(),
|
||||
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() {
|
||||
let mut uniques = HashSet::new();
|
||||
for o in objects.iter() {
|
||||
uniques.insert(o.value.id().clone());
|
||||
for (_, v) in objects {
|
||||
for (_, properties) in v {
|
||||
uniques.insert(properties.id());
|
||||
}
|
||||
}
|
||||
|
||||
ok_200(&uniques.drain().collect::<Vec<_>>())
|
||||
} else {
|
||||
let objects = to_spatial_objects(db, 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()
|
||||
}
|
||||
@@ -119,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,9 +1,11 @@
|
||||
use mercator_db::CoreQueryParameters;
|
||||
use mercator_db::DataBase;
|
||||
use parser::Executor;
|
||||
use parser::FiltersParser;
|
||||
use parser::QueryParser;
|
||||
use parser::Validator;
|
||||
use mercator_parser::Bag;
|
||||
use mercator_parser::Executor;
|
||||
use mercator_parser::FiltersParser;
|
||||
use mercator_parser::Projection;
|
||||
use mercator_parser::QueryParser;
|
||||
use mercator_parser::Validator;
|
||||
|
||||
pub struct SharedState {
|
||||
db: DataBase,
|
||||
@@ -32,119 +34,82 @@ impl SharedState {
|
||||
&self.query_parser
|
||||
}
|
||||
|
||||
pub fn filter(
|
||||
&self,
|
||||
input: &str,
|
||||
core: &str,
|
||||
output_space: Option<String>,
|
||||
volume: Option<f64>,
|
||||
resolution: Option<Vec<u64>>,
|
||||
) -> mercator_db::ResultSet {
|
||||
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,
|
||||
resolution,
|
||||
};
|
||||
|
||||
// Parse Input
|
||||
{
|
||||
info_time!("Parsing");
|
||||
parse = parser.parse(input);
|
||||
debug_time!("Parsing");
|
||||
parse = parser.parse(filter);
|
||||
}
|
||||
|
||||
match parse {
|
||||
Err(e) => {
|
||||
debug!("Parsing failed: \n{:?}", e);
|
||||
Err(format!("{}", e))
|
||||
}
|
||||
Ok(tree) => {
|
||||
let validation;
|
||||
let execution;
|
||||
|
||||
// Check type coherence & validate tree
|
||||
{
|
||||
info_time!("Type check");
|
||||
validation = tree.validate();
|
||||
}
|
||||
if validation.is_err() {
|
||||
debug!("Type check failed");
|
||||
return Err("Type check failed".to_string());
|
||||
debug_time!("Type check");
|
||||
let _ = tree.validate()?;
|
||||
}
|
||||
|
||||
// Execute filter.
|
||||
{
|
||||
info_time!("Execution");
|
||||
execution = tree.execute(core, ¶meters);
|
||||
}
|
||||
match execution {
|
||||
Err(e) => {
|
||||
debug!("Parsing failed: \n{:?}", e);
|
||||
Err(e.to_string())
|
||||
}
|
||||
results @ Ok(_) => results,
|
||||
}
|
||||
Ok(tree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query(
|
||||
&self,
|
||||
input: &str,
|
||||
core: &str,
|
||||
volume: Option<f64>,
|
||||
resolution: Option<Vec<u64>>,
|
||||
) -> mercator_db::ResultSet {
|
||||
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,
|
||||
resolution,
|
||||
};
|
||||
|
||||
// Parse Input
|
||||
{
|
||||
info_time!("Parsing");
|
||||
parse = parser.parse(input);
|
||||
debug_time!("Parsing");
|
||||
parse = parser.parse(query);
|
||||
}
|
||||
match parse {
|
||||
Err(e) => {
|
||||
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
|
||||
{
|
||||
info_time!("Type check");
|
||||
validation = tree.validate();
|
||||
}
|
||||
if validation.is_err() {
|
||||
debug!("Type check failed");
|
||||
return Err("Type check failed".to_string());
|
||||
debug_time!("Type check");
|
||||
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.to_string())
|
||||
}
|
||||
results @ Ok(_) => results,
|
||||
}
|
||||
Ok(tree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ paths:
|
||||
delete:
|
||||
tags: [Spaces]
|
||||
summary: >
|
||||
Delete mulltiple spaces at a time.
|
||||
Delete multiple spaces at a time.
|
||||
|
||||
Each reference space can only be removed if and only if there
|
||||
are no spatial objects left referencing it.
|
||||
@@ -249,7 +249,7 @@ paths:
|
||||
delete:
|
||||
tags: [Cores]
|
||||
summary: >
|
||||
Delete mulltiple Cores at a time. This also removes all the
|
||||
Delete multiple Cores at a time. This also removes all the
|
||||
Spatial Objects tied to these cores.
|
||||
operationId: delete_cores
|
||||
parameters:
|
||||
@@ -401,7 +401,7 @@ paths:
|
||||
delete:
|
||||
tags: [Spatial Objects]
|
||||
summary: >
|
||||
Delete mulltiple spatial objects at a time.
|
||||
Delete multiple spatial objects at a time.
|
||||
operationId: delete_spatial_objects
|
||||
parameters:
|
||||
- $ref: '#/parameters/SpatialObjectIds'
|
||||
@@ -682,6 +682,20 @@ parameters:
|
||||
ids_only:
|
||||
type: boolean
|
||||
default: false
|
||||
space:
|
||||
type: string
|
||||
resolution:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
minimum: 0
|
||||
format: int32
|
||||
view_port:
|
||||
type: array
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
|
||||
Query:
|
||||
name: query
|
||||
@@ -690,7 +704,22 @@ parameters:
|
||||
description: >
|
||||
For more about the query syntax, please refer to `FIXME: URL` http://repo/queries.g4.
|
||||
schema:
|
||||
type: string
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
resolution:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
minimum: 0
|
||||
format: int32
|
||||
view_port:
|
||||
type: array
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
|
||||
responses:
|
||||
Space200:
|
||||
|
||||
503
static/api/v1.0.yaml
Normal file
503
static/api/v1.0.yaml
Normal file
@@ -0,0 +1,503 @@
|
||||
openapi: "3.0.0"
|
||||
info:
|
||||
title: Spatial Search Backend API Documentation
|
||||
description: API Documentation for the spatial search backend.
|
||||
termsOfService: "" #urn:tos FIXME: Describe ToS?
|
||||
license:
|
||||
name: The MIT License
|
||||
url: https://opensource.org/licenses/MIT
|
||||
version: "1.0"
|
||||
|
||||
servers:
|
||||
- url: http://127.0.0.1:8888/spatial-search
|
||||
|
||||
tags:
|
||||
- name: Actions
|
||||
description: General Database actions.
|
||||
- name: Spaces
|
||||
description: Operations on Reference Spaces.
|
||||
- name: Cores
|
||||
description: Operations on Cores.
|
||||
- name: Spatial Objects
|
||||
description: Operations on Spatial Objects.
|
||||
|
||||
paths:
|
||||
#--------------------------------------------------------------------
|
||||
# GENERAL ACTIONS ON THE SYSTEM
|
||||
#--------------------------------------------------------------------
|
||||
/health:
|
||||
get:
|
||||
tags: [Actions]
|
||||
summary: >
|
||||
Health check of the service.
|
||||
description: >
|
||||
Please note that making anything but a **GET** call is a bad request, and will return a 405.
|
||||
operationId: get_health_check
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/Standard200'
|
||||
default:
|
||||
$ref: '#/components/responses/Standard405'
|
||||
|
||||
/query:
|
||||
post:
|
||||
tags: [Actions]
|
||||
summary: >
|
||||
Execute an arbitrary query.
|
||||
#description: >
|
||||
# This is a POST operation, as it "creates" and execute a query, and it is not idempotent as the same query re-run multiple times might have different results, depending on the state of the database.
|
||||
operationId: query
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/Query'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/Query200'
|
||||
'422':
|
||||
$ref: '#/components/responses/Standard422'
|
||||
default:
|
||||
$ref: '#/components/responses/Standard405'
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# SPACES QUERIES
|
||||
#--------------------------------------------------------------------
|
||||
/spaces:
|
||||
|
||||
post:
|
||||
tags: [Spaces]
|
||||
summary: >
|
||||
Retrieve a list of space definition names.
|
||||
operationId: post_spaces
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/Filters'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/ArrayOfStrings'
|
||||
'422':
|
||||
$ref: '#/components/responses/Standard422'
|
||||
default:
|
||||
$ref: '#/components/responses/Standard400'
|
||||
|
||||
/spaces/{name}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/SpaceName'
|
||||
|
||||
get:
|
||||
tags: [Spaces]
|
||||
summary: >
|
||||
Retrieve the space `name`.
|
||||
operationId: get_space
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/Space200'
|
||||
'404':
|
||||
$ref: '#/components/responses/Standard404'
|
||||
default:
|
||||
$ref: '#/components/responses/Standard400'
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# CORE QUERIES
|
||||
#--------------------------------------------------------------------
|
||||
/cores:
|
||||
|
||||
post:
|
||||
tags: [Cores]
|
||||
summary: >
|
||||
Retrieve a list of core names.
|
||||
operationId: post_cores
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/Filters'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/ArrayOfStrings'
|
||||
'422':
|
||||
$ref: '#/components/responses/Standard422'
|
||||
default:
|
||||
$ref: '#/components/responses/Standard400'
|
||||
|
||||
/cores/{name}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/CoreName'
|
||||
|
||||
get:
|
||||
tags: [Cores]
|
||||
summary: >
|
||||
Retrieve the core `name` properties. This does not include
|
||||
the SpatialObjects contained in this Core.
|
||||
operationId: get_core
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/Core200'
|
||||
'404':
|
||||
$ref: '#/components/responses/Standard404'
|
||||
default:
|
||||
$ref: '#/components/responses/Standard400'
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# SPATIAL_OBJECTS QUERIES
|
||||
#--------------------------------------------------------------------
|
||||
/cores/{name}/spatial_objects:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/CoreName'
|
||||
|
||||
post:
|
||||
tags: [Spatial Objects]
|
||||
summary: >
|
||||
Retrieve a list of spatial object.
|
||||
operationId: post_spatial_objects
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/Filters'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/ArrayOfStrings'
|
||||
'422':
|
||||
$ref: '#/components/responses/Standard422'
|
||||
default:
|
||||
$ref: '#/components/responses/Standard400'
|
||||
|
||||
/cores/{name}/spatial_objects/{id}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/CoreName'
|
||||
- $ref: '#/components/parameters/SpatialObjectId'
|
||||
|
||||
get:
|
||||
tags: [Spatial Objects]
|
||||
summary: >
|
||||
Retrieve the spatial object `id` of the core `name`.
|
||||
operationId: get_spatial_object
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/SpatialObject200'
|
||||
'404':
|
||||
$ref: '#/components/responses/Standard404'
|
||||
default:
|
||||
$ref: '#/components/responses/Standard400'
|
||||
|
||||
components:
|
||||
requestBodies:
|
||||
Filters:
|
||||
description: >
|
||||
Filter string to use to select the data.
|
||||
|
||||
For more about the filter syntax, please refer to [filter grammar](https://epfl-dias.github.io/mercator_parser/book/filters.html).
|
||||
|
||||
If **ids_only** is true, then a list of **unique identifiers** is returned, instead of the whole, distinct, objects for the selected objects.
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
filters:
|
||||
type: string
|
||||
ids_only:
|
||||
type: boolean
|
||||
default: false
|
||||
space:
|
||||
type: string
|
||||
resolution:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
minimum: 0
|
||||
format: int32
|
||||
view_port:
|
||||
type: array
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
|
||||
Query:
|
||||
description: >
|
||||
For more about the query syntax, please refer to the [query grammar](https://epfl-dias.github.io/mercator_parser/book/queries.html).
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
resolution:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
minimum: 0
|
||||
format: int32
|
||||
view_port:
|
||||
type: array
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
|
||||
parameters:
|
||||
SpaceName:
|
||||
name: name
|
||||
in: path
|
||||
required: true
|
||||
description: >
|
||||
Name of the reference space
|
||||
type: string
|
||||
|
||||
CoreName:
|
||||
name: name
|
||||
in: path
|
||||
required: true
|
||||
description: >
|
||||
Name of the core
|
||||
type: string
|
||||
|
||||
SpatialObjectId:
|
||||
name: id
|
||||
in: path
|
||||
required: true
|
||||
description: >
|
||||
Id of the spatial object
|
||||
type: string
|
||||
|
||||
responses:
|
||||
Space200:
|
||||
description: >
|
||||
Reference space definition.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Space'
|
||||
|
||||
SpatialObject200:
|
||||
description: >
|
||||
Spatial object.
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpatialObject'
|
||||
|
||||
Core200:
|
||||
description: >
|
||||
Core properties
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Core'
|
||||
|
||||
Query200:
|
||||
description: Arbitrary query.
|
||||
|
||||
ArrayOfStrings:
|
||||
description: >
|
||||
Array of strings, usually identifiers.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
Standard422:
|
||||
description: >
|
||||
Unprocessable Entity
|
||||
Standard405:
|
||||
description: >
|
||||
Invalid Method
|
||||
Standard404:
|
||||
description: >
|
||||
Object not found
|
||||
Standard400:
|
||||
description: >
|
||||
Invalid or malformed request
|
||||
Standard200:
|
||||
description: OK
|
||||
|
||||
schemas:
|
||||
#--------------------------------------------------------------------
|
||||
# Types returned / accepted by the API
|
||||
#--------------------------------------------------------------------
|
||||
Space:
|
||||
title: Reference Space
|
||||
description: >
|
||||
Definition of a space, in which objects are described.
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: >
|
||||
Unique Id for the space, which can also be used to generate a link to the user documentation describing the space, explaining the semantic meaning of the values stored, as well as the definitions of the axes.
|
||||
type: string
|
||||
origin:
|
||||
description: >
|
||||
Translation vector between the Universe origin to the origin of this reference space. This is expressed in Universe coordinates.
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
axes:
|
||||
description: >
|
||||
The order of the axes matter and MUST be kept, as this is also linked to the definition found in the documentation.
|
||||
|
||||
|
||||
Coordinate of a point MUST always be expressed using the same order as defined here.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Axis'
|
||||
|
||||
SpatialObject:
|
||||
title: Spatial Object
|
||||
description: >
|
||||
Collection of positions in a space, which share a common set of properties.
|
||||
type: object
|
||||
properties:
|
||||
properties:
|
||||
description: >
|
||||
Properties tied to a shape, in other words properties valid for the whole content of the shape.
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
description: >
|
||||
Label defining the kind of the spatial object.
|
||||
type: string
|
||||
id:
|
||||
description: >
|
||||
Identifier of this spatial object.
|
||||
type: string
|
||||
volumes:
|
||||
description: >
|
||||
List of volumes, overlapping or not, which define the whole space covered by this spatial object.
|
||||
|
||||
The overall volume described here is the **union** of the volumes.
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
space:
|
||||
description: >
|
||||
Name of the reference space the associated shapes are defined in.
|
||||
type: string
|
||||
shapes:
|
||||
description: >
|
||||
List of shapes.
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
description: >
|
||||
One of the following fields per instance.
|
||||
properties:
|
||||
points:
|
||||
description: >
|
||||
List of points.
|
||||
example: [[1, 2], [2, 3], [6, 1]]
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Point'
|
||||
boundingboxes:
|
||||
description: >
|
||||
List of bounding boxes.
|
||||
example: [
|
||||
[ [1, 2], [2, 3] ],
|
||||
[ [-1, 0], [20, 10] ],
|
||||
[ [0, 0], [4, 9] ],
|
||||
]
|
||||
type: array
|
||||
items:
|
||||
type: array
|
||||
minItems: 2
|
||||
maxitems: 2
|
||||
items:
|
||||
$ref: '#/components/schemas/Point'
|
||||
hyperspheres:
|
||||
description: >
|
||||
List of hyperspheres, each hypersphere is described as tuple (position, radius)
|
||||
example: [
|
||||
[ [1, 2], 3 ],
|
||||
[ [-1, 0], 4 ],
|
||||
[ [0, 0], 23 ],
|
||||
]
|
||||
type: array
|
||||
items:
|
||||
type: array
|
||||
minItems: 2
|
||||
maxitems: 2
|
||||
items:
|
||||
type: {}
|
||||
|
||||
Core:
|
||||
title: Core
|
||||
description: >
|
||||
Collection of Spatial Objects, stored in one or more Reference Spaces.
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
version:
|
||||
type: string
|
||||
scales:
|
||||
title: Scale Vectors
|
||||
description: >
|
||||
Scale factors used to generate less precise, coarser indexes in order to speed up queries over large volumes of the space.
|
||||
|
||||
|
||||
Values are expressed as powers of two, in the range [0;n]. For each scale, a whole vector providing values for each axis MUST be provided.
|
||||
|
||||
|
||||
Values, which are equal, and whose coordinates gets merged are merged as well, to reduce the number of results.
|
||||
|
||||
|
||||
Distinct values whose coordinates are merged are recorded, thus allowing the user to move from one scale factor to another, with a finer resolution smoothly.
|
||||
type: array
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
minimum: 0
|
||||
format: int32
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Helper types
|
||||
#--------------------------------------------------------------------
|
||||
Point:
|
||||
description: >
|
||||
One valid value for each axes of the reference space this point
|
||||
is used in.
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
|
||||
Axis:
|
||||
title: Coordinate Axis
|
||||
description: >
|
||||
Defines the properties of an axis. The origin and unit vectors or defined within the universe space, but this does NOT imply a linear conversion is possible, this only provide anchoring of the axis as well as its absolute direction.
|
||||
type: object
|
||||
properties:
|
||||
measurement_unit:
|
||||
description: >
|
||||
Length unit, as in SI Unit, for the `1.0` value on this axis.
|
||||
|
||||
For example [mm], [s], [um].
|
||||
type: string
|
||||
graduation:
|
||||
description: >
|
||||
Definition of the valid coordinate values which can be used on this axis.
|
||||
type: object
|
||||
properties:
|
||||
set:
|
||||
description: >
|
||||
Valid numbers as defined by the usual mathematical sets, for example
|
||||
* N: Natural numbers,
|
||||
* Z: Integers,
|
||||
* Q: Rational numbers,
|
||||
* R: Real numbers.
|
||||
type: string
|
||||
# Decision: For now we leave it at strictly numbers, no
|
||||
# categories until actually needed.
|
||||
enum: [N, Z, Q, R]
|
||||
minimum:
|
||||
type: number
|
||||
format: float
|
||||
maximum:
|
||||
type: number
|
||||
format: float
|
||||
steps:
|
||||
type: number
|
||||
format: integer
|
||||
unit_vector:
|
||||
description: >
|
||||
Direction vector, with a norm of 1.0. This is expressed in Universe coordinates and provides the orientation of this axis in the Universe.
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
Reference in New Issue
Block a user