Skip to content

Commit

Permalink
TPF test, collections feature #17
Browse files Browse the repository at this point in the history
  • Loading branch information
joepio committed Sep 23, 2020
1 parent 27ef597 commit 5747000
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 12 deletions.
9 changes: 9 additions & 0 deletions lib/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,14 @@ pub fn post_delta(endpoint: &str, delta: Delta) -> AtomicResult<()> {
.set("Accept", crate::parse::AD3_MIME)
.timeout_read(500)
.call();
// So what happens next?
// If we'd only have deltalines, serialization could be a simple json array with some strings.
// However, now it becomes a bit more complicated.
// We could create an empty store, create a Resource from the Delta, serialize it as .AD3.
// However, what to do with the deltalines?
// One (ugly) solution is to serialize it to JSON arrays... But this feels wrong.
// Another one is to create nested Resources for every deltaline.
// I think having JSON compatibility should be top priority.
todo!();
Ok(())
}
57 changes: 57 additions & 0 deletions lib/src/collections.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#[derive(Debug)]
pub struct TPFQuery {
pub subject: Option<String>,
pub property: Option<String>,
pub value: Option<String>,
}

/// Dynamic resource used for ordering, filtering and querying content.
/// Features pagination.
#[derive(Debug)]
pub struct Collection {
// The set of triples that form the basis of the data
pub tpf: TPFQuery,
// List of all the pages.
pub pages: Vec<Page>,
// URL of the value to sort by
pub sort_by: String,
// Sorts ascending by default
pub sort_desc: bool,
// How many items per page
pub page_size: u8,
// Current page number, defaults to 0 (firs page)
pub current_page: u8,
// Total number of items
pub total_items: u8,
// Total number of pages
pub total_pages: u8,
}

/// A single page of a Collection
#[derive(Debug)]
pub struct Page {
// partOf: Collection,
// The individual items in the page
pub members: Vec<String>,
}

#[cfg(test)]
mod test {
use super::*;
use crate::Storelike;
use crate::urls;

#[test]
fn create_collection() {
let mut store = crate::Store::init();
store.populate().unwrap();
let tpf = TPFQuery {
subject: None,
property: Some(urls::IS_A.into()),
value: Some(urls::CLASS.into()),
};
// Get all Classes, sorted by shortname
let collection = store.get_collection(tpf, urls::SHORTNAME.into(), false, 1, 1).unwrap();
assert!(collection.pages[0].members.contains(&urls::PROPERTY.into()));
}
}
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ assert!(found_atoms.len() == 1);

pub mod atoms;
pub mod client;
pub mod collections;
#[cfg(feature = "db")]
pub mod db;
pub mod delta;
Expand Down
3 changes: 1 addition & 2 deletions lib/src/serialize.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Serialization / formatting / encoding (JSON, RDF, N-Triples, AD3)
use crate::{datatype::DataType, errors::AtomicResult};
use crate::{Atom, Storelike};
use crate::{Atom, Storelike, datatype::DataType, errors::AtomicResult};

pub fn serialize_json_array(items: &[String]) -> AtomicResult<String> {
let string = serde_json::to_string(items)?;
Expand Down
32 changes: 32 additions & 0 deletions lib/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,38 @@ mod test {
.unwrap();
}

#[test]
fn tpf() {
let mut store = init_store();
// All atoms
let atoms = store
.tpf(None, None, None)
.unwrap();
assert!(atoms.len() > 10);
// Find by subject
let atoms = store
.tpf(Some(urls::CLASS), None, None)
.unwrap();
assert!(atoms.len() == 5);
// Find by value
let atoms = store
.tpf(None, None, Some("class"))
.unwrap();
assert!(atoms[0].subject == urls::CLASS);
assert!(atoms.len() == 1);
// Find by property and value
let atoms = store
.tpf(None, Some(urls::SHORTNAME), Some("class"))
.unwrap();
assert!(atoms[0].subject == urls::CLASS);
assert!(atoms.len() == 1);
// Find item in array
let atoms = store
.tpf(None, Some(urls::IS_A), Some(urls::CLASS))
.unwrap();
assert!(atoms.len() > 3);
}

#[test]
fn path() {
let mut store = init_store();
Expand Down
76 changes: 70 additions & 6 deletions lib/src/storelike.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Trait for all stores to use
use crate::{errors::AtomicResult, delta::Delta};
use crate::urls;
use crate::{
collections::Collection, collections::Page, collections::TPFQuery, delta::Delta,
errors::AtomicResult,
};
use crate::{
datatype::{match_datatype, DataType},
mapping::Mapping,
Expand Down Expand Up @@ -156,6 +159,45 @@ pub trait Storelike {
Ok(classes)
}

/// Constructs a Collection, which is a paginated list of items with some sorting applied.
fn get_collection(
&mut self,
tpf: TPFQuery,
sort_by: String,
sort_desc: bool,
_page_nr: u8,
_page_size: u8,
) -> AtomicResult<Collection> {
// Execute the TPF query, get all the subjects.
let atoms = self.tpf(None, tpf.property.as_deref(), tpf.value.as_deref())?;
// Iterate over the fetched resources
let subjects: Vec<String> = atoms.iter().map(|atom| atom.subject.clone()).collect();
let mut resources: Vec<ResourceString> = Vec::new();
for sub in subjects.clone() {
resources.push(self.get_resource_string(&sub)?);
}
// Sort the resources (TODO), use sortBy and sortDesc
let sorted_subjects: Vec<String> = subjects.clone();
// Construct the pages (TODO), use pageSize
let mut pages: Vec<Page> = Vec::new();
// Construct the requested page (TODO)
let page = Page {
members: sorted_subjects,
};
pages.push(page);
let collection = Collection {
tpf,
total_pages: pages.len() as u8,
pages,
sort_by,
sort_desc,
current_page: 0,
total_items: subjects.len() as u8,
page_size: subjects.len() as u8,
};
Ok(collection)
}

/// Fetches a property by URL, returns a Property instance
fn get_property(&mut self, url: &str) -> AtomicResult<Property> {
let property_resource = self.get_resource_string(url)?;
Expand Down Expand Up @@ -218,7 +260,8 @@ pub trait Storelike {
.data_type;
Value::new(&deltaline.value, &datatype)?;
updated_resources.push(delta.subject.clone());
let atom = Atom::new(delta.subject.clone(), deltaline.property, deltaline.value);
let atom =
Atom::new(delta.subject.clone(), deltaline.property, deltaline.value);
self.add_atom(atom)?;
}
urls::DELETE | "delete" => {
Expand All @@ -228,7 +271,8 @@ pub trait Storelike {
};
}
Ok(())
} /// Finds the URL of a shortname used in the context of a specific Resource.
}
/// Finds the URL of a shortname used in the context of a specific Resource.
/// The Class, Properties and Shortnames of the Resource are used to find this URL
fn property_shortname_to_url(
&mut self,
Expand Down Expand Up @@ -415,18 +459,34 @@ pub trait Storelike {
return Ok(vec);
}

// If the value is a resourcearray, check if it is inside
let val_equals = |val: &str| {
let q = q_value.unwrap();
val == q || {
if val.starts_with('[') {
match crate::parse::parse_json_array(val) {
Ok(vec) => {
return vec.contains(&q.into())
}
Err(_) => return false
}
}
false
}
};

// Find atoms matching the TPF query in a single resource
let mut find_in_resource = |subj: &str, resource: &ResourceString| {
for (prop, val) in resource.iter() {
if hasprop && q_property.as_ref().unwrap() == prop {
if hasval {
if val == q_value.as_ref().unwrap() {
if val_equals(val) {
vec.push(Atom::new(subj.into(), prop.into(), val.into()))
}
} else {
vec.push(Atom::new(subj.into(), prop.into(), val.into()))
}
} else if hasval && q_value.as_ref().unwrap() == val {
} else if hasval && val_equals(val) {
vec.push(Atom::new(subj.into(), prop.into(), val.into()))
}
}
Expand Down Expand Up @@ -457,7 +517,11 @@ pub trait Storelike {
/// E.g. `https://example.com description` or `thing isa 0`
/// https://docs.atomicdata.dev/core/paths.html
// Todo: return something more useful, give more context.
fn get_path(&mut self, atomic_path: &str, mapping: Option<&Mapping>) -> AtomicResult<PathReturn> {
fn get_path(
&mut self,
atomic_path: &str,
mapping: Option<&Mapping>,
) -> AtomicResult<PathReturn> {
// The first item of the path represents the starting Resource, the following ones are traversing the graph / selecting properties.
let path_items: Vec<&str> = atomic_path.split(' ').collect();
let first_item = String::from(path_items[0]);
Expand Down
12 changes: 8 additions & 4 deletions server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use actix_web::{middleware, web, App, HttpServer};
use std::{io, sync::Mutex};
// use actix_web_middleware_redirect_https::RedirectHTTPS;
mod appstate;
mod config;
mod errors;
Expand All @@ -17,21 +16,26 @@ async fn main() -> io::Result<()> {
env_logger::init();

let config = config::init();
let https = config.https;
let appstate = appstate::init(config.clone()).expect("Failed to build appstate.");

let server = HttpServer::new(move || {
let data = web::Data::new(Mutex::new(appstate.clone()));
App::new()
let app = App::new()
.app_data(data)
.wrap(middleware::Logger::default())
.wrap(middleware::Compress::default())
// .wrap(actix_web_middleware_redirect_https::RedirectHTTPS::default())
.service(actix_files::Files::new("/static", "static/").show_files_listing())
.service(actix_files::Files::new("/.well-known", "static/well-known/").show_files_listing())
.service(web::scope("/tpf").service(web::resource("").route(web::get().to(handlers::tpf::tpf))))
.service(web::scope("/path").service(web::resource("").route(web::get().to(handlers::path::path))))
.service(web::scope("/{path:[^{}]+}").service(web::resource("").route(web::get().to(handlers::resource::get_resource))))
.service(web::scope("/").service(web::resource("").route(web::get().to(handlers::home::home))))
.service(web::scope("/").service(web::resource("").route(web::get().to(handlers::home::home))));
if https {
// Needs upate
// app.wrap(actix_web_middleware_redirect_https::RedirectHTTPS::default())
}
app
});

if config.https {
Expand Down

0 comments on commit 5747000

Please sign in to comment.