JSON request parameters definition

+ JSON Filter parameters:
   * Add filters
   * Add resolution
   * Add view_port
   * Add space, for output space normalization into a single space
   * Add ids_only

 + JSON Query parameters:
   * Add query
   * Add resolution
   * Add view_port
This commit is contained in:
2019-10-01 19:50:35 +02:00
parent 66d19f7a2d
commit 0239b15fa3
10 changed files with 324 additions and 128 deletions

View File

@@ -5,6 +5,7 @@ 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 super::error_422;
@@ -14,39 +15,52 @@ use super::HandlerResult;
#[derive(Debug, Deserialize)]
pub struct Query {
query: String,
resolution: Option<Vec<u64>>, // None means automatic selection, based on ViewPort
view_port: Option<(Vec<f64>, Vec<f64>)>,
}
impl Query {
pub fn query(&self) -> &String {
&self.query
}
pub fn resolution(&self) -> Option<Vec<u64>> {
self.resolution.clone()
}
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.
}
}
}
// Also used for the root service.
pub fn health() -> HttpResponse {
HttpResponse::Ok().finish()
}
fn query((parameters, state): (Json<Query>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("query Triggered!");
trace!("POST '{:?}'", parameters);
let context = state.read().unwrap();
let query = &parameters.query;
let query = parameters.query();
if query.is_empty() {
error_422(format!("Invalid query in '{:?}'", query))
} else {
// FIXME: MANAGE PROJECTIONS
let results = context
.db()
.core_keys()
.iter()
// FIXME: Specify from json output space + threshold volume
.flat_map(|core| match context.query(query, core, None, None) {
Err(_) => vec![], // FIXME: Return errors here instead!!
Ok(r) => {
let mut r = r.into_iter().map(|o| o.space_id).collect::<Vec<_>>();
r.sort_unstable();
r.dedup();
r
}
})
.collect::<Vec<_>>();
ok_200(&results)
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<_>>(),
)
}
}

View File

@@ -9,33 +9,21 @@ use crate::shared_state::SharedState;
use super::error_400;
use super::error_404;
use super::ok_200;
use super::Core;
use super::HandlerResult;
#[derive(Clone, Deserialize, Serialize)]
pub struct Core {
name: String,
version: String,
scales: Vec<Vec<i32>>,
}
fn put(path: Path<String>) -> HandlerResult {
trace!("PUT Triggered on {}", path);
error_400()
}
fn get((core, state): (Path<String>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("GET Triggered!");
trace!("GET '{:?}'", core);
let core = core.to_string();
let context = state.read().unwrap();
match context.db().core(core) {
Ok(core) => ok_200(&Core {
name: core.name().clone(),
version: core.version().clone(),
scales: vec![vec![0, 0, 0]],
// FIXME: Report the actual values. Might need to change the format
// to per reference space.
}),
Ok(core) => ok_200(&Core::from(core)),
Err(_) => error_404(),
}
}

View File

@@ -1,3 +1,4 @@
use std::collections::HashSet;
use std::sync::RwLock;
use actix_web::web;
@@ -7,33 +8,75 @@ use actix_web::web::Json;
use crate::shared_state::SharedState;
use super::error_400;
use super::error_422;
use super::ok_200;
use super::Core;
use super::Filters;
use super::HandlerResult;
fn post((parameters, state): (Option<Json<Filters>>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("POST Triggered!");
fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("POST '{:?}'", parameters);
let context = state.read().unwrap();
let parameters = Filters::get(parameters);
let db = context.db();
let mut results = match parameters.filters {
None => context.db().core_keys().clone(),
Some(filter) => context
.db()
.core_keys()
.iter()
//FIXME: Specify from json output space + threshold volume
.filter_map(|core| match context.filter(&filter, core, None, None) {
Err(_) => None, // FIXME: Return errors here instead!!
Ok(_) => Some(core.to_string()),
})
.collect(),
};
match parameters.space(db) {
Err(e) => e,
Ok(space) => {
match parameters.filters() {
None => {
if parameters.ids_only() {
ok_200(db.core_keys())
} else {
let cores = db
.core_keys()
.iter()
.filter_map(|id| match db.core(id) {
Err(_) => None, // FIXME: Return error ?
Ok(x) => Some(Core::from(x)),
})
.collect::<Vec<_>>();
results.sort_unstable();
results.dedup();
ok_200(&cores)
}
}
Some(filter) => {
// Retrieve the list of core ids.
let mut results = HashSet::new();
ok_200(&results)
for core in db.core_keys() {
match context.filter(
filter,
core,
space.clone(),
parameters.volume(),
parameters.resolution(),
) {
Err(e) => return error_422(e),
Ok(objects) => {
// If the list of SpaceObjects is not empty, add
// the current core to the list.
if !objects.is_empty() {
results.insert(core.to_string());
}
}
}
}
// Format the list or the whole core objects.
if parameters.ids_only() {
ok_200(&results.drain().collect::<Vec<_>>())
} else {
ok_200(
&results
.drain()
.map(|x| Core::from(db.core(x).unwrap()))
.collect::<Vec<_>>(),
)
}
}
}
}
}
}
fn put() -> HandlerResult {

View File

@@ -24,7 +24,6 @@ use actix_web::http::StatusCode;
use actix_web::middleware;
use actix_web::web;
use actix_web::web::Data;
use actix_web::web::Json;
use actix_web::web::Path;
use actix_web::App;
use actix_web::Either;
@@ -43,25 +42,66 @@ use crate::SharedState;
pub type HandlerResult = Result<Either<HttpResponse, NamedFile>, Error>;
#[derive(Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize)]
pub struct Filters {
filters: Option<String>,
ids_only: Option<bool>,
// resolution: Option<Vec<u64>>, // None means automatic selection, based on ViewPort
// view_port: Option<u64>,
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
view_port: Option<(Vec<f64>, Vec<f64>)>,
}
impl Filters {
pub fn get(parameters: Option<Json<Filters>>) -> Self {
trace!("PARAMETERS {:#?}", parameters);
pub fn filters(&self) -> &Option<String> {
&self.filters
}
match parameters {
None => Filters {
filters: None,
ids_only: Some(true),
//resolution: None,
},
Some(p) => p.0,
pub fn ids_only(&self) -> bool {
match self.ids_only {
None => true, // Defaults to true
Some(b) => b,
}
}
pub fn space(&self, db: &mercator_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!(
"Invalid reference space id in '{:?}'",
self
)));
}
}
Ok(self.space.clone())
}
pub fn resolution(&self) -> Option<Vec<u64>> {
self.resolution.clone()
}
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.
}
}
}
#[derive(Clone, Deserialize, Serialize)]
pub struct Core {
name: String,
version: String,
scales: Vec<Vec<i32>>,
}
impl From<&mercator_db::Core> for Core {
fn from(core: &mercator_db::Core) -> Self {
Core {
name: core.name().clone(),
version: core.version().clone(),
scales: vec![vec![0, 0, 0]],
// FIXME: Report the actual values. Might need to change the format
// to per reference space.
}
}
}
@@ -77,6 +117,7 @@ where
fn config_v1(cfg: &mut web::ServiceConfig) {
// Warning: Order matters, as a more generic path would catch calls for a
// more specific one when registered first.
space::config(cfg);
spaces::config(cfg);
@@ -279,7 +320,7 @@ mod routing {
#[test]
fn default_no_path() {
// FIXME: Currently the string is validated by the URI constructor which
// _FIXME: Currently the string is validated by the URI constructor which
// simply unwraps, thus we have to resort to this ugly workaround.
// The goal is to catch if that behavior changes in the future.
let result = panic::catch_unwind(|| {

View File

@@ -13,12 +13,12 @@ use super::ok_200;
use super::HandlerResult;
fn put(path: Path<String>) -> HandlerResult {
trace!("PUT Triggered on {}", path);
trace!("POST '{:?}'", path);
error_400()
}
fn get((path, state): (Path<String>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("GET Triggered on '{}'", path);
trace!("GET '{:?}'", path);
let name = path.to_string();
let context = state.read().unwrap();

View File

@@ -1,43 +1,85 @@
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::ok_200;
use super::Filters;
use super::HandlerResult;
fn post((parameters, state): (Option<Json<Filters>>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("POST Triggered!");
fn post((parameters, state): (Json<Filters>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("POST '{:?}'", parameters);
let context = state.read().unwrap();
let parameters = Filters::get(parameters);
let db = context.db();
let mut results = match parameters.filters {
None => context.db().space_keys().clone(),
Some(filter) => context
.db()
.core_keys()
.iter()
// FIXME: Specify from json output space + threshold volume
.flat_map(|core| match context.filter(&filter, core, None, None) {
Err(_) => vec![], // FIXME: Return errors here instead!!
Ok(r) => {
let mut r = r.into_iter().map(|o| o.space_id).collect::<Vec<_>>();
r.sort_unstable();
r.dedup();
r
match parameters.space(db) {
Err(e) => e,
Ok(space) => {
match parameters.filters() {
None => {
if parameters.ids_only() {
ok_200(db.space_keys())
} else {
let spaces = db
.space_keys()
.iter()
.filter_map(|id| match db.space(id) {
Err(_) => None, // FIXME: Return error ?
Ok(x) => Some(model::Space::from(x)),
})
.collect::<Vec<_>>();
ok_200(&spaces)
}
}
})
.collect(),
};
results.sort_unstable();
results.dedup();
Some(filter) => {
// Retrieve the list of space ids.
let mut results = HashSet::new();
ok_200(&results)
for core in db.core_keys() {
match context.filter(
filter,
core,
space.clone(),
parameters.volume(),
parameters.resolution(),
) {
Err(e) => return error_422(e),
Ok(objects) => {
// We have a list of SpaceObjects, so extract
// the space Ids
for o in objects {
results.insert(o.space_id);
}
}
}
}
// Format the list or the whole space objects.
if parameters.ids_only() {
ok_200(&results.drain().collect::<Vec<_>>())
} else {
ok_200(
&results
.drain()
.map(|id| match db.space(id) {
Err(_) => None,
Ok(x) => Some(model::Space::from(x)),
})
.collect::<Vec<_>>(),
)
}
}
}
}
}
}
fn put() -> HandlerResult {

View File

@@ -3,6 +3,7 @@ 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;
@@ -13,20 +14,28 @@ use super::ok_200;
use super::HandlerResult;
fn put(path: Path<String>) -> HandlerResult {
trace!("PUT Triggered on {}", path);
trace!("PUT '{:?}'", path);
error_400()
}
fn get((path, state): (Path<(String, String)>, Data<RwLock<SharedState>>)) -> HandlerResult {
trace!("GET Triggered!");
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 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,
};
match db.core(core) {
Ok(core) => match core.get_by_id(db, &id, None, 0.0) {
Ok(core) => match core.get_by_id(&parameters, &id) {
Ok(objects) => {
let results = to_spatial_objects(db, objects);
if results.is_empty() {

View File

@@ -1,48 +1,90 @@
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::ok_200;
use super::Filters;
use super::HandlerResult;
fn post(
(core_id, parameters, state): (
Path<String>,
Option<Json<Filters>>,
Data<RwLock<SharedState>>,
),
(core_id, parameters, state): (Path<String>, Json<Filters>, Data<RwLock<SharedState>>),
) -> HandlerResult {
trace!("POST Triggered!");
let core = core_id.to_string();
trace!("POST '{:?}', {:?}", parameters, core_id);
let core_id = core_id.to_string();
let context = state.read().unwrap();
let db = context.db();
match context.db().core(core) {
Ok(core) => {
let parameters = Filters::get(parameters);
// Generate a list of oid.
let mut results = match parameters.filters {
None => core.keys().iter().map(|o| o.id().clone()).collect(),
// FIXME: Specify from json output space + threshold volume
Some(filter) => match context.filter(&filter, &core_id, None, None) {
Err(_) => vec![], // FIXME: Return errors here instead!!
Ok(objects) => objects.iter().map(|o| o.value.id().clone()).collect(),
},
};
results.sort_unstable();
results.dedup();
ok_200(&results)
}
match db.core(core_id.clone()) {
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<_>>())
} else {
let core_parameters = CoreQueryParameters {
db,
output_space: space.as_ref().map(String::as_str),
threshold_volume: parameters.volume(),
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)
}
}
Some(filter) => {
match context.filter(
filter,
&core_id,
space,
parameters.volume(),
parameters.resolution(),
) {
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());
}
ok_200(&uniques.drain().collect::<Vec<_>>())
} else {
let objects = to_spatial_objects(db, objects);
ok_200(&objects)
}
}
}
}
},
},
}
}

View File

@@ -1,3 +1,4 @@
use mercator_db::CoreQueryParameters;
use mercator_db::DataBase;
use parser::Executor;
use parser::FiltersParser;
@@ -35,12 +36,20 @@ impl SharedState {
&self,
input: &str,
core: &str,
output_space: Option<&str>,
threshold_volume: Option<f64>,
output_space: Option<String>,
volume: Option<f64>,
resolution: Option<Vec<u64>>,
) -> mercator_db::ResultSet {
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");
@@ -68,7 +77,7 @@ impl SharedState {
// Execute filter.
{
info_time!("Execution");
execution = tree.execute(self.db(), core, output_space, threshold_volume);
execution = tree.execute(core, &parameters);
}
match execution {
Err(e) => {
@@ -85,11 +94,17 @@ impl SharedState {
&self,
input: &str,
core: &str,
output_space: Option<&str>,
threshold_volume: Option<f64>,
volume: Option<f64>,
resolution: Option<Vec<u64>>,
) -> mercator_db::ResultSet {
let parser = self.query_parser();
let parse;
let parameters = CoreQueryParameters {
db: self.db(),
output_space: None,
threshold_volume: volume,
resolution,
};
// Parse Input
{
@@ -119,7 +134,9 @@ impl SharedState {
// Execute filter.
{
info_time!("Execution");
execution = tree.execute(self.db(), core, output_space, threshold_volume);
// _FIXME: Output space is defined as part of the projection
// and is ignored by projections operators.
execution = tree.execute(core, &parameters);
}
match execution {
Err(e) => {

View File

@@ -667,7 +667,7 @@ parameters:
Filters:
name: filters
in: body
required: false
required: true
description: >
Filter string to use to select the data.