Skip to content

Commit

Permalink
#502 refactor to AtomicUrl
Browse files Browse the repository at this point in the history
WIP Path URL

#502 URLs
  • Loading branch information
joepio committed Oct 12, 2022
1 parent fd71f8f commit e48ecff
Show file tree
Hide file tree
Showing 28 changed files with 511 additions and 239 deletions.
32 changes: 32 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[build]
rustc-wrapper = '/Users/joep/.cargo/bin/sccache'
[target.x86_64-unknown-linux-gnu]
rustflags = [
'-Clink-arg=-fuse-ld=lld',
'-Zshare-generics=y',
]
linker = '/usr/bin/clang'

[target.x86_64-pc-windows-msvc]
rustflags = ['-Zshare-generics=y']
linker = 'rust-lld.exe'

[target.x86_64-apple-darwin]
rustflags = [
'-C',
'link-arg=-fuse-ld=/usr/local/bin/zld',
'-Zshare-generics=y',
'-Csplit-debuginfo=unpacked',
]
[profile.dev]
opt-level = 0
debug = 2
incremental = true
codegen-units = 512

[profile.release]
opt-level = 3
debug = 0
incremental = false
codegen-units = 256
split-debuginfo = '...'
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jobs:
with:
command: nextest
args: run --all-features --retries 3
# https://github.com/nextest-rs/nextest/issues/16
- run: cargo test --doc

coverage:
name: Code coverage
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

9 changes: 7 additions & 2 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use atomic_lib::atomic_url::Routes;
use atomic_lib::{agents::generate_public_key, mapping::Mapping};
use atomic_lib::{agents::Agent, config::Config};
use atomic_lib::{errors::AtomicResult, Storelike};
Expand Down Expand Up @@ -55,7 +56,7 @@ fn set_agent_config() -> CLIResult<Config> {
"No config found at {:?}. Let's create one!",
&agent_config_path
);
let server = promptly::prompt("What's the base url of your Atomic Server?")?;
let server: String = promptly::prompt("What's the base url of your Atomic Server?")?;
let agent = promptly::prompt("What's the URL of your Agent?")?;
let private_key = promptly::prompt("What's the private key of this Agent?")?;
let config = atomic_lib::config::Config {
Expand Down Expand Up @@ -297,7 +298,11 @@ fn tpf(context: &Context) -> AtomicResult<()> {
let subject = tpf_value(subcommand_matches.value_of("subject").unwrap());
let property = tpf_value(subcommand_matches.value_of("property").unwrap());
let value = tpf_value(subcommand_matches.value_of("value").unwrap());
let endpoint = format!("{}/tpf", &context.get_write_context().server);
let endpoint = context
.store
.get_server_url()
.set_route(Routes::Tpf)
.to_string();
let resources =
atomic_lib::client::fetch_tpf(&endpoint, subject, property, value, &context.store)?;
for r in resources {
Expand Down
18 changes: 12 additions & 6 deletions lib/src/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,29 @@ impl Agent {
pub fn new(name: Option<&str>, store: &impl Storelike) -> AtomicResult<Agent> {
let keypair = generate_keypair()?;

Ok(Agent::new_from_private_key(name, store, &keypair.private))
Agent::new_from_private_key(name, store, &keypair.private)
}

pub fn new_from_private_key(
name: Option<&str>,
store: &impl Storelike,
private_key: &str,
) -> Agent {
) -> AtomicResult<Agent> {
let keypair = generate_public_key(private_key);
println!("server url: {}", store.get_server_url());
let subject = store
.get_server_url()
.url()
.join(&format!("agents/{}", &keypair.public))?
.to_string();

Agent {
Ok(Agent {
private_key: Some(keypair.private),
public_key: keypair.public.clone(),
subject: format!("{}/agents/{}", store.get_server_url(), keypair.public),
public_key: keypair.public,
subject,
name: name.map(|x| x.to_owned()),
created_at: crate::utils::now(),
}
})
}

pub fn new_from_public_key(store: &impl Storelike, public_key: &str) -> AtomicResult<Agent> {
Expand Down
195 changes: 195 additions & 0 deletions lib/src/atomic_url.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
use serde::{Deserialize, Serialize, Serializer};
use url::Url;

use crate::{errors::AtomicResult, utils::random_string};

pub enum Routes {
Agents,
AllVersions,
Collections,
Commits,
CommitsUnsigned,
Endpoints,
Import,
Tpf,
Version,
Setup,
}

#[derive(Debug, Clone, PartialEq, Eq)]
/// Wrapper for URLs / subjects.
/// Has a bunch of methods for finding or creating commonly used paths.
pub struct AtomicUrl {
url: Url,
}

impl AtomicUrl {
pub fn new(url: Url) -> Self {
Self { url }
}

pub fn as_str(&self) -> &str {
self.url.as_str()
}

/// Returns the route to some common Endpoint
pub fn set_route(&self, route: Routes) -> Self {
let path = match route {
Routes::AllVersions => "/all-versions".to_string(),
Routes::Agents => "/agents".to_string(),
Routes::Collections => "/collections".to_string(),
Routes::Commits => "/commits".to_string(),
Routes::CommitsUnsigned => "/commits-unsigned".to_string(),
Routes::Endpoints => "/endpoints".to_string(),
Routes::Import => "/import".to_string(),
Routes::Tpf => "/tpf".to_string(),
Routes::Version => "/version".to_string(),
Routes::Setup => "/setup".to_string(),
};
let mut new = self.url.clone();
new.set_path(&path);
Self::new(new)
}

/// Returns a new URL generated from the provided path_shortname and a random string.
/// ```
/// let url = atomic_lib::AtomicUrl::try_from("https://example.com").unwrap();
/// let generated = url.generate_random("my-type");
/// assert!(generated.to_string().starts_with("https://example.com/my-type/"));
/// ```
pub fn generate_random(&self, path_shortname: &str) -> Self {
let mut url = self.url.clone();
let path = format!("{path_shortname}/{}", random_string(10));
url.set_path(&path);
Self { url }
}

/// Adds a sub-path to a URL.
/// Adds a slash to the existing URL, followed by the passed path.
///
/// ```
/// use atomic_lib::AtomicUrl;
/// let start = "http://localhost";
/// let mut url = AtomicUrl::try_from(start).unwrap();
/// assert_eq!(url.to_string(), "http://localhost/");
/// url.append("/");
/// assert_eq!(url.to_string(), "http://localhost/");
/// url.append("someUrl/123");
/// assert_eq!(url.to_string(), "http://localhost/someUrl/123");
/// url.append("/345");
/// assert_eq!(url.to_string(), "http://localhost/someUrl/123/345");
/// ```
pub fn append(&mut self, path: &str) -> &Self {
let mut new_path = self.url.path().to_string();
match (new_path.ends_with('/'), path.starts_with('/')) {
(true, true) => {
new_path.pop();
}
(false, false) => new_path.push('/'),
_other => {}
};

// Remove first slash if it exists
if new_path.starts_with('/') {
new_path.remove(0);
}

new_path.push_str(path);

self.url.set_path(&new_path);
self
}

/// Sets the subdomain of the URL.
/// Removes an existing subdomain if the subdomain is None
pub fn set_subdomain(&mut self, subdomain: Option<&str>) -> AtomicResult<&Self> {
let mut host = self.url.host_str().unwrap().to_string();
if let Some(subdomain) = subdomain {
host = format!("{}.{}", subdomain, host);
}
self.url.set_host(Some(host.as_str()))?;
Ok(self)
}

pub fn set_path(&mut self, path: &str) -> &Self {
self.url.set_path(path);
self
}

pub fn subdomain(&self) -> Option<String> {
let url = self.url.clone();
let host = url.host_str().unwrap();
let parts: Vec<&str> = host.split('.').collect();
if parts.len() > 2 {
Some(parts[0].to_string())
} else {
None
}
}

/// Returns the inner {url::Url} struct that has a bunch of regular URL methods
/// Useful if you need the host or something.
pub fn url(&self) -> Url {
self.url.clone()
}
}

impl TryFrom<&str> for AtomicUrl {
type Error = url::ParseError;

fn try_from(value: &str) -> Result<Self, Self::Error> {
let url = Url::parse(value)?;
Ok(Self { url })
}
}

impl Serialize for AtomicUrl {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.url.as_str())
}
}

impl<'de> Deserialize<'de> for AtomicUrl {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let url = Url::parse(&s).map_err(serde::de::Error::custom)?;
Ok(Self { url })
}
}

impl std::fmt::Display for AtomicUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.url)
}
}

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

#[test]
fn test_url() {
let _should_fail = AtomicUrl::try_from("nonsense").unwrap_err();
let _should_succeed = AtomicUrl::try_from("http://localhost/someUrl").unwrap();
}

#[test]
fn subdomain() {
let sub = "http://test.example.com";
assert_eq!(
AtomicUrl::try_from(sub).unwrap().subdomain(),
Some("test".to_string())
);
let mut no_sub = AtomicUrl::try_from("http://example.com").unwrap();
assert_eq!(no_sub.subdomain(), None);

let to_sub = no_sub.set_subdomain(Some("mysub")).unwrap();
assert_eq!(to_sub.subdomain(), Some("mysub".to_string()));
}
}
8 changes: 5 additions & 3 deletions lib/src/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl CollectionBuilder {
store: &impl Storelike,
) -> CollectionBuilder {
CollectionBuilder {
subject: format!("{}/{}", store.get_server_url(), path),
subject: store.get_server_url().clone().set_path(path).to_string(),
property: Some(urls::IS_A.into()),
value: Some(class_url.into()),
sort_by: None,
Expand Down Expand Up @@ -429,7 +429,9 @@ pub fn create_collection_resource_for_class(
let parent = if class.subject == urls::COLLECTION {
drive.to_string()
} else {
format!("{}/collections", drive)
drive
.set_route(crate::atomic_url::Routes::Collections)
.to_string()
};

collection_resource.set_propval_string(urls::PARENT.into(), &parent, store)?;
Expand Down Expand Up @@ -533,7 +535,7 @@ mod test {
println!("{:?}", subjects);
let collections_collection = store
.get_resource_extended(
&format!("{}/collections", store.get_server_url()),
&format!("{}collections", store.get_server_url()),
false,
None,
)
Expand Down
25 changes: 17 additions & 8 deletions lib/src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use std::collections::{HashMap, HashSet};
use urls::{SET, SIGNER};

use crate::{
datatype::DataType, errors::AtomicResult, hierarchy, resources::PropVals, urls,
values::SubResource, Atom, Resource, Storelike, Value,
atomic_url::Routes, datatype::DataType, errors::AtomicResult, hierarchy, resources::PropVals,
urls, values::SubResource, Atom, Resource, Storelike, Value,
};

/// The `resource_new`, `resource_old` and `commit_resource` fields are only created if the Commit is persisted.
Expand Down Expand Up @@ -425,12 +425,21 @@ impl Commit {
#[tracing::instrument(skip(store))]
pub fn into_resource(self, store: &impl Storelike) -> AtomicResult<Resource> {
let commit_subject = match self.signature.as_ref() {
Some(sig) => format!("{}/commits/{}", store.get_server_url(), sig),
Some(sig) => store
.get_server_url()
.set_route(Routes::Commits)
.append(sig)
.to_string(),
None => {
let now = crate::utils::now();
format!("{}/commitsUnsigned/{}", store.get_server_url(), now)
store
.get_server_url()
.set_route(Routes::CommitsUnsigned)
.append(&now.to_string())
.to_string()
}
};
println!("commit subject: {}", commit_subject);
let mut resource = Resource::new_instance(urls::COMMIT, store)?;
resource.set_subject(commit_subject);
resource.set_propval_unsafe(
Expand Down Expand Up @@ -752,10 +761,10 @@ mod test {
let private_key = "CapMWIhFUT+w7ANv9oCPqrHrwZpkP2JhzF9JnyT6WcI=";
let store = crate::Store::init().unwrap();
store.populate().unwrap();
let agent = Agent::new_from_private_key(None, &store, private_key);
let agent = Agent::new_from_private_key(None, &store, private_key).unwrap();
assert_eq!(
&agent.subject,
"local:store/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U="
"http://noresolve.localhost/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U="
);
store.add_resource(&agent.to_resource().unwrap()).unwrap();
let subject = "https://localhost/new_thing";
Expand All @@ -770,8 +779,8 @@ mod test {
let signature = commit.signature.clone().unwrap();
let serialized = commit.serialize_deterministically_json_ad(&store).unwrap();

assert_eq!(serialized, "{\"https://atomicdata.dev/properties/createdAt\":0,\"https://atomicdata.dev/properties/isA\":[\"https://atomicdata.dev/classes/Commit\"],\"https://atomicdata.dev/properties/set\":{\"https://atomicdata.dev/properties/description\":\"Some value\",\"https://atomicdata.dev/properties/shortname\":\"someval\"},\"https://atomicdata.dev/properties/signer\":\"local:store/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U=\",\"https://atomicdata.dev/properties/subject\":\"https://localhost/new_thing\"}");
assert_eq!(signature, "JOGRyp1NCulc0RNuuNozgIagQPRoZy0Y5+mbSpHY2DKiN3vqUNYLjXbAPYT6Cga6vSG9zztEIa/ZcbQPo7wgBg==");
assert_eq!(serialized, "{\"https://atomicdata.dev/properties/createdAt\":0,\"https://atomicdata.dev/properties/isA\":[\"https://atomicdata.dev/classes/Commit\"],\"https://atomicdata.dev/properties/set\":{\"https://atomicdata.dev/properties/description\":\"Some value\",\"https://atomicdata.dev/properties/shortname\":\"someval\"},\"https://atomicdata.dev/properties/signer\":\"http://noresolve.localhost/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U=\",\"https://atomicdata.dev/properties/subject\":\"https://localhost/new_thing\"}");
assert_eq!(signature, "CZbjUJW/tokEKSZTCFjEHWbWqGW+jyhZWYs82K9wt0SArxu9xGg+D3IniAlygQp0F3KcI4Z876th3/X3fJIVAQ==");
}

#[test]
Expand Down
Loading

0 comments on commit e48ecff

Please sign in to comment.