Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ Powers `atomic-cli` and `atomic-server`.

I've been working with Linked Data for a couple of years, and I believe it has some incredible merits.
URLs are great identifiers, and using them for keys makes sense as well.
However, using the RDF data model has [some characteristics](https://docs.atomicdata.dev/interoperability/rdf.html) that make it difficult for many developers, and that limits adoption.
It has the potential to help a more democratic and decentralized web, where people control their own data.
However, the RDF data model has [some characteristics](https://docs.atomicdata.dev/interoperability/rdf.html) that make it difficult for many developers, and I think that limits adoption.
That's why I've been working on a new way to think about linked data: [Atomic Data](https://docs.atomicdata.dev/).
Atomic Data is heavily inspired by RDF (and converts nicely into RDF, as it is a strict subset), but introduces some new concepts that aim to make it easier to use for developers.

This repository serves the following purposes:

- Test some of the core ideas of Atomic Data ([Atomic Schema](https://docs.atomicdata.dev/schema/intro.html), [Paths](https://docs.atomicdata.dev/core/paths.html), [AD3 Serialization](https://docs.atomicdata.dev/core/serialization.html))
- Learn how Rust works (it's a cool language, and this is my first Rust project - keep that in mind while traversing the code!)
- Serve the first Atomic Data (now available on [atomicdata.dev](https://atomicdata.dev)), which is referred to by the constantly evolving [Atomic Data Docs](https://docs.atomicdata.dev/)
- Test and experiment with some of the core ideas of Atomic Data, such as [Atomic Schema](https://docs.atomicdata.dev/schema/intro.html) (share models and data types), [Paths](https://docs.atomicdata.dev/core/paths.html) (traversing data), [AD3 Serialization](https://docs.atomicdata.dev/core/serialization.html) and [Atomic Commits](https://docs.atomicdata.dev/commits/intro.html) (storing signed state changes).
- Serve the first Atomic Data, including the core schema (now available on [atomicdata.dev](https://atomicdata.dev)), which is referred to by the constantly evolving [docs](https://docs.atomicdata.dev/)
- Provide developers with tools and inspiration to use Atomic Data in their own projects.

## Contribute
Expand Down
4 changes: 2 additions & 2 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "atomic-cli"
version = "0.14.0"
version = "0.15.0"
authors = ["Joep Meindertsma <joep@argu.co>"]
edition = "2018"
license = "MIT"
Expand All @@ -11,9 +11,9 @@ repository = "https://github.com/joepio/atomic"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
atomic_lib = { version = "0.15.0", path = "../lib", features = ["db", "rdf"] }
promptly = "0.3.0"
clap = "2.33.1"
colored = "1.9.3"
atomic_lib = { version = "0.14.0", path = "../lib", features = ["db", "rdf"] }
dirs = "3.0.1"
regex = "1.3.9"
2 changes: 1 addition & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ atomic new class
- [x] Basic JSON Serialization
- [x] RDF (Turtle / N-Triples / RDF/XML) Serialization
- [x] Fetch data from the interwebs with `get` commands
- [ ] Works with [`atomic-server`](../server) (fetches from there, stores there, uses domain etc.)
- [ ] Works with [`atomic-server`](../server) (fetches from there, stores there, uses domain etc.) [#6](https://github.com/joepio/atomic/issues/6)
- [x] A `delta` command for manipulating existing resources
- [ ] Tests for the cli
- [ ] A `map` command for creating a bookmark and storing a copy
Expand Down
2 changes: 1 addition & 1 deletion cli/wapm.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "atomic"
version = "0.14.0"
version = "0.15.0"
description = "Create, share, fetch and model linked Atomic Data!"
license = "MIT"
repository = "https://github.com/joepio/atomic"
Expand Down
3 changes: 2 additions & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "atomic_lib"
version = "0.14.0"
version = "0.15.0"
authors = ["Joep Meindertsma <joep@argu.co>"]
edition = "2018"
license = "MIT"
Expand All @@ -20,6 +20,7 @@ rio_api = { version = "0.5.0", optional = true}
rand = "0.7.3"
ring = "0.16.15"
base64 = "0.13.0"
url = "2.1.1"

[features]
db = ["sled", "bincode"]
Expand Down
51 changes: 51 additions & 0 deletions lib/defaults/default_store.ad3
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
["https://atomicdata.dev/classes/Agent","https://atomicdata.dev/properties/recommends","[\"https://atomicdata.dev/properties/description\",\"https://atomicdata.dev/properties/remove\",\"https://atomicdata.dev/properties/destroy\"]"]
["https://atomicdata.dev/classes/Agent","https://atomicdata.dev/properties/description","An Agent is a user that can create or modify data. It has two keys: a private and a public one. The private key should be kept secret. The publik key is for proving that the "]
["https://atomicdata.dev/classes/Agent","https://atomicdata.dev/properties/shortname","agent"]
["https://atomicdata.dev/classes/Collection","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Class\"]"]
["https://atomicdata.dev/classes/Collection","https://atomicdata.dev/properties/recommends","[\"https://atomicdata.dev/properties/collection/property\"]"]
["https://atomicdata.dev/classes/Collection","https://atomicdata.dev/properties/description","A paginated set of resources that can be sorted."]
["https://atomicdata.dev/classes/Collection","https://atomicdata.dev/properties/shortname","collection"]
# Datatypes
["https://atomicdata.dev/datatypes/string","https://atomicdata.dev/properties/shortname","string"]
["https://atomicdata.dev/datatypes/string","https://atomicdata.dev/properties/description","A UTF-8 string. Allows newlines with `\n`. This is a generic string datatype - don't use this for things like [markdown](https://atomicdata.dev/datatypes/markdown) or html."]
Expand Down Expand Up @@ -122,3 +126,50 @@
["https://atomicdata.dev/properties/publicKey","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/string"]
["https://atomicdata.dev/properties/publicKey","https://atomicdata.dev/properties/shortname","publickey"]
["https://atomicdata.dev/properties/publicKey","https://atomicdata.dev/properties/description","The publicKey of an Agent. Is a base64 serialized Ed25519 key."]
["https://atomicdata.dev/properties/collection/subject","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/collection/subject","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/atomicURL"]
["https://atomicdata.dev/properties/collection/subject","https://atomicdata.dev/properties/shortname","subject"]
["https://atomicdata.dev/properties/collection/subject","https://atomicdata.dev/properties/description","The value is the first field of an atom. Similar to `subject` in RDF. In this context, it is used as a filter in a collection."]
["https://atomicdata.dev/properties/collection/property","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/collection/property","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/atomicURL"]
["https://atomicdata.dev/properties/collection/property","https://atomicdata.dev/properties/shortname","property"]
["https://atomicdata.dev/properties/collection/property","https://atomicdata.dev/properties/description","The property is the second field of an atom. Similar to `predicate` in RDF. In this context, it is used as a filter in a collection."]
["https://atomicdata.dev/properties/collection/value","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/collection/value","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/atomicURL"]
["https://atomicdata.dev/properties/collection/value","https://atomicdata.dev/properties/shortname","value"]
["https://atomicdata.dev/properties/collection/value","https://atomicdata.dev/properties/description","The property is the third field of an atom. Similar to `object` in RDF. In this context, it is used as a filter in a collection."]
["https://atomicdata.dev/properties/collection/members","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/collection/members","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/resourceArray"]
["https://atomicdata.dev/properties/collection/members","https://atomicdata.dev/properties/shortname","members"]
["https://atomicdata.dev/properties/collection/members","https://atomicdata.dev/properties/description","The members are the list of resources in a collection."]
["https://atomicdata.dev/properties/collection/itemCount","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/collection/itemCount","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/integer"]
["https://atomicdata.dev/properties/collection/itemCount","https://atomicdata.dev/properties/shortname","item-count"]
["https://atomicdata.dev/properties/collection/itemCount","https://atomicdata.dev/properties/description","The total number of items in the collection."]
["https://atomicdata.dev/properties/collection/totalPages","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/collection/totalPages","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/integer"]
["https://atomicdata.dev/properties/collection/totalPages","https://atomicdata.dev/properties/shortname","total-pages"]
["https://atomicdata.dev/properties/collection/totalPages","https://atomicdata.dev/properties/description","The total number of pages in the collection."]
["https://atomicdata.dev/properties/collection/currentPage","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/collection/currentPage","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/integer"]
["https://atomicdata.dev/properties/collection/currentPage","https://atomicdata.dev/properties/shortname","current-page"]
["https://atomicdata.dev/properties/collection/currentPage","https://atomicdata.dev/properties/description","The curent page number of the collection. Defaults to 0."]
# Collections
["https://atomicdata.dev/classes","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Collection\"]"]
["https://atomicdata.dev/classes","https://atomicdata.dev/properties/collection/property","https://atomicdata.dev/properties/isA"]
["https://atomicdata.dev/classes","https://atomicdata.dev/properties/collection/value","https://atomicdata.dev/classes/Class"]
["https://atomicdata.dev/properties","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Collection\"]"]
["https://atomicdata.dev/properties","https://atomicdata.dev/properties/collection/property","https://atomicdata.dev/properties/isA"]
["https://atomicdata.dev/properties","https://atomicdata.dev/properties/collection/value","https://atomicdata.dev/classes/Property"]
["https://atomicdata.dev/commits","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Collection\"]"]
["https://atomicdata.dev/commits","https://atomicdata.dev/properties/collection/property","https://atomicdata.dev/properties/isA"]
["https://atomicdata.dev/commits","https://atomicdata.dev/properties/collection/value","https://atomicdata.dev/classes/Commit"]
["https://atomicdata.dev/datatypes","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Collection\"]"]
["https://atomicdata.dev/datatypes","https://atomicdata.dev/properties/collection/property","https://atomicdata.dev/properties/isA"]
["https://atomicdata.dev/datatypes","https://atomicdata.dev/properties/collection/value","https://atomicdata.dev/classes/Datatype"]
["https://atomicdata.dev/agents","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Collection\"]"]
["https://atomicdata.dev/agents","https://atomicdata.dev/properties/collection/property","https://atomicdata.dev/properties/isA"]
["https://atomicdata.dev/agents","https://atomicdata.dev/properties/collection/value","https://atomicdata.dev/classes/Agent"]
["https://atomicdata.dev/collections","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Collection\"]"]
["https://atomicdata.dev/collections","https://atomicdata.dev/properties/collection/property","https://atomicdata.dev/properties/isA"]
["https://atomicdata.dev/collections","https://atomicdata.dev/properties/collection/value","https://atomicdata.dev/classes/Collection"]
91 changes: 68 additions & 23 deletions lib/src/collections.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,69 @@
//! Collections are dynamic resources that refer to multiple resources.
//! They are constructed using a TPF query

use crate::errors::AtomicResult;

#[derive(Debug)]
pub struct TPFQuery {
pub subject: Option<String>,
pub property: Option<String>,
pub value: Option<String>,
}

pub struct CollectionBuilder {
pub subject: String,
pub property: Option<String>,
pub value: Option<String>,
pub sort_by: Option<String>,
pub sort_desc: bool,
pub current_page: usize,
pub page_size: usize,
}

/// 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>,
// Full Subject URL of the resource, including query parameters
pub subject: String,
/// The TPF property which the results are to be filtered by
pub property: Option<String>,
/// The TPF value which the results are to be filtered by
pub value: Option<String>,
// The actual items that you're interested in. List the member subjects of the current page.
pub members: Vec<String>,
// URL of the value to sort by
pub sort_by: String,
pub sort_by: Option<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,
pub page_size: usize,
// Current page number, defaults to 0 (first page)
pub current_page: usize,
// Total number of items
pub total_items: u8,
pub total_items: usize,
// Total number of pages
pub total_pages: u8,
pub total_pages: usize,
}

/// A single page of a Collection
#[derive(Debug)]
pub struct Page {
// partOf: Collection,
// The individual items in the page
pub members: Vec<String>,
impl Collection {
pub fn to_resource<'a>(&self, store: &'a dyn crate::Storelike) -> AtomicResult<crate::Resource<'a>> {
// TODO: Should not persist, because now it is spammimg the store!
// let mut resource = crate::Resource::new_instance(crate::urls::COLLECTION, store)?;
let mut resource = crate::Resource::new(self.subject.clone(), store);
resource.set_propval(crate::urls::MEMBERS.into(), self.members.clone().into())?;
if let Some(prop) = self.property.clone() {
resource.set_propval(crate::urls::COLLECTION_PROPERTY.into(), prop.into())?;
}
if let Some(prop) = self.value.clone() {
resource.set_propval(crate::urls::COLLECTION_VALUE.into(), prop.into())?;
}
resource.set_propval(crate::urls::COLLECTION_ITEM_COUNT.into(), self.total_items.clone().into())?;
resource.set_propval(crate::urls::COLLECTION_TOTAL_PAGES.into(), self.total_pages.clone().into())?;
resource.set_propval(crate::urls::COLLECTION_CURRENT_PAGE.into(), self.current_page.clone().into())?;
// Maybe include items directly
Ok(resource)
}
}

#[cfg(test)]
Expand All @@ -45,13 +76,27 @@ mod test {
fn create_collection() {
let 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()));
let collection_builder = CollectionBuilder {
subject: "test_subject".into(),
property: Some(urls::IS_A.into()),
value: Some(urls::CLASS.into()),
sort_by: None,
sort_desc: false,
page_size: 1000,
current_page: 0,
};
let collection = store.new_collection(collection_builder).unwrap();
assert!(collection.members.contains(&urls::PROPERTY.into()));
}

#[test]
fn get_collection() {
let store = crate::Store::init();
store.populate().unwrap();
let collection = store.get_resource_extended("https://atomicdata.dev/classes").unwrap();
assert!(collection.get(urls::COLLECTION_PROPERTY).unwrap().to_string() == urls::IS_A);
println!("Count is {}", collection.get(urls::COLLECTION_ITEM_COUNT).unwrap().to_string());
assert!(collection.get(urls::COLLECTION_ITEM_COUNT).unwrap().to_string() == "6");
}
}
2 changes: 2 additions & 0 deletions lib/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ mod test {
new_property
.set_by_shortname("description", "the age of a person")
.unwrap();
// Changes are only applied to the store after calling `.save()`
new_property.save().unwrap();
// The modified resource is saved to the store after this

// A subject URL has been created automatically.
Expand Down
Loading