diff --git a/src/rest_api/actions.rs b/src/rest_api/actions.rs index 2b67f11..056d476 100644 --- a/src/rest_api/actions.rs +++ b/src/rest_api/actions.rs @@ -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>, // None means automatic selection, based on ViewPort + view_port: Option<(Vec, Vec)>, } +impl Query { + pub fn query(&self) -> &String { + &self.query + } + + pub fn resolution(&self) -> Option> { + self.resolution.clone() + } + + pub fn volume(&self) -> Option { + 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, Data>)) -> HandlerResult { - trace!("query Triggered!"); + trace!("POST '{:?}'", parameters); let context = state.read().unwrap(); - let query = ¶meters.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::>(); - r.sort_unstable(); - r.dedup(); - r - } - }) - .collect::>(); - - 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::>(), + ) } } diff --git a/src/rest_api/core.rs b/src/rest_api/core.rs index 9dde030..d23d6b5 100644 --- a/src/rest_api/core.rs +++ b/src/rest_api/core.rs @@ -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>, -} - fn put(path: Path) -> HandlerResult { trace!("PUT Triggered on {}", path); error_400() } fn get((core, state): (Path, Data>)) -> 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(), } } diff --git a/src/rest_api/cores.rs b/src/rest_api/cores.rs index cd6f9ce..ae186b4 100644 --- a/src/rest_api/cores.rs +++ b/src/rest_api/cores.rs @@ -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>, Data>)) -> HandlerResult { - trace!("POST Triggered!"); +fn post((parameters, state): (Json, Data>)) -> 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::>(); - 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::>()) + } else { + ok_200( + &results + .drain() + .map(|x| Core::from(db.core(x).unwrap())) + .collect::>(), + ) + } + } + } + } + } } fn put() -> HandlerResult { diff --git a/src/rest_api/mod.rs b/src/rest_api/mod.rs index a749165..34bbf0d 100644 --- a/src/rest_api/mod.rs +++ b/src/rest_api/mod.rs @@ -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, Error>; -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct Filters { filters: Option, ids_only: Option, - // resolution: Option>, // None means automatic selection, based on ViewPort - // view_port: Option, + space: Option, // Output space, None, means each object in its own original space + resolution: Option>, // None means automatic selection, based on ViewPort + view_port: Option<(Vec, Vec)>, } impl Filters { - pub fn get(parameters: Option>) -> Self { - trace!("PARAMETERS {:#?}", parameters); + pub fn filters(&self) -> &Option { + &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, 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> { + self.resolution.clone() + } + + pub fn volume(&self) -> Option { + 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>, +} + +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(|| { diff --git a/src/rest_api/space.rs b/src/rest_api/space.rs index 1a9d3dc..65a8e63 100644 --- a/src/rest_api/space.rs +++ b/src/rest_api/space.rs @@ -13,12 +13,12 @@ use super::ok_200; use super::HandlerResult; fn put(path: Path) -> HandlerResult { - trace!("PUT Triggered on {}", path); + trace!("POST '{:?}'", path); error_400() } fn get((path, state): (Path, Data>)) -> HandlerResult { - trace!("GET Triggered on '{}'", path); + trace!("GET '{:?}'", path); let name = path.to_string(); let context = state.read().unwrap(); diff --git a/src/rest_api/spaces.rs b/src/rest_api/spaces.rs index 22b8056..4e1130b 100644 --- a/src/rest_api/spaces.rs +++ b/src/rest_api/spaces.rs @@ -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>, Data>)) -> HandlerResult { - trace!("POST Triggered!"); +fn post((parameters, state): (Json, Data>)) -> 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::>(); - 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::>(); + + 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::>()) + } else { + ok_200( + &results + .drain() + .map(|id| match db.space(id) { + Err(_) => None, + Ok(x) => Some(model::Space::from(x)), + }) + .collect::>(), + ) + } + } + } + } + } } fn put() -> HandlerResult { diff --git a/src/rest_api/spatial_object.rs b/src/rest_api/spatial_object.rs index 38e1342..de25a1d 100644 --- a/src/rest_api/spatial_object.rs +++ b/src/rest_api/spatial_object.rs @@ -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) -> HandlerResult { - trace!("PUT Triggered on {}", path); + trace!("PUT '{:?}'", path); error_400() } fn get((path, state): (Path<(String, String)>, Data>)) -> 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(¶meters, &id) { Ok(objects) => { let results = to_spatial_objects(db, objects); if results.is_empty() { diff --git a/src/rest_api/spatial_objects.rs b/src/rest_api/spatial_objects.rs index 33e965d..370a816 100644 --- a/src/rest_api/spatial_objects.rs +++ b/src/rest_api/spatial_objects.rs @@ -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, - Option>, - Data>, - ), + (core_id, parameters, state): (Path, Json, Data>), ) -> 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::>()) + } 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::>(); + 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::>()) + } else { + let objects = to_spatial_objects(db, objects); + + ok_200(&objects) + } + } + } + } + }, + }, } } diff --git a/src/shared_state.rs b/src/shared_state.rs index 15fa8c1..4139166 100644 --- a/src/shared_state.rs +++ b/src/shared_state.rs @@ -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, + output_space: Option, + volume: Option, + resolution: Option>, ) -> 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, ¶meters); } match execution { Err(e) => { @@ -85,11 +94,17 @@ impl SharedState { &self, input: &str, core: &str, - output_space: Option<&str>, - threshold_volume: Option, + volume: Option, + resolution: Option>, ) -> 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, ¶meters); } match execution { Err(e) => { diff --git a/static/api/v0.2.yaml b/static/api/v0.2.yaml index 4c4dd2e..de4d360 100644 --- a/static/api/v0.2.yaml +++ b/static/api/v0.2.yaml @@ -667,7 +667,7 @@ parameters: Filters: name: filters in: body - required: false + required: true description: > Filter string to use to select the data.