Skip to content

Commit e48ecff

Browse files
committed
#502 refactor to AtomicUrl
WIP Path URL #502 URLs
1 parent fd71f8f commit e48ecff

28 files changed

+511
-239
lines changed

.cargo/config.toml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[build]
2+
rustc-wrapper = '/Users/joep/.cargo/bin/sccache'
3+
[target.x86_64-unknown-linux-gnu]
4+
rustflags = [
5+
'-Clink-arg=-fuse-ld=lld',
6+
'-Zshare-generics=y',
7+
]
8+
linker = '/usr/bin/clang'
9+
10+
[target.x86_64-pc-windows-msvc]
11+
rustflags = ['-Zshare-generics=y']
12+
linker = 'rust-lld.exe'
13+
14+
[target.x86_64-apple-darwin]
15+
rustflags = [
16+
'-C',
17+
'link-arg=-fuse-ld=/usr/local/bin/zld',
18+
'-Zshare-generics=y',
19+
'-Csplit-debuginfo=unpacked',
20+
]
21+
[profile.dev]
22+
opt-level = 0
23+
debug = 2
24+
incremental = true
25+
codegen-units = 512
26+
27+
[profile.release]
28+
opt-level = 3
29+
debug = 0
30+
incremental = false
31+
codegen-units = 256
32+
split-debuginfo = '...'

.github/workflows/main.yml

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ jobs:
4040
with:
4141
command: nextest
4242
args: run --all-features --retries 3
43+
# https://github.com/nextest-rs/nextest/issues/16
44+
- run: cargo test --doc
4345

4446
coverage:
4547
name: Code coverage

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/main.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use atomic_lib::atomic_url::Routes;
12
use atomic_lib::{agents::generate_public_key, mapping::Mapping};
23
use atomic_lib::{agents::Agent, config::Config};
34
use atomic_lib::{errors::AtomicResult, Storelike};
@@ -55,7 +56,7 @@ fn set_agent_config() -> CLIResult<Config> {
5556
"No config found at {:?}. Let's create one!",
5657
&agent_config_path
5758
);
58-
let server = promptly::prompt("What's the base url of your Atomic Server?")?;
59+
let server: String = promptly::prompt("What's the base url of your Atomic Server?")?;
5960
let agent = promptly::prompt("What's the URL of your Agent?")?;
6061
let private_key = promptly::prompt("What's the private key of this Agent?")?;
6162
let config = atomic_lib::config::Config {
@@ -297,7 +298,11 @@ fn tpf(context: &Context) -> AtomicResult<()> {
297298
let subject = tpf_value(subcommand_matches.value_of("subject").unwrap());
298299
let property = tpf_value(subcommand_matches.value_of("property").unwrap());
299300
let value = tpf_value(subcommand_matches.value_of("value").unwrap());
300-
let endpoint = format!("{}/tpf", &context.get_write_context().server);
301+
let endpoint = context
302+
.store
303+
.get_server_url()
304+
.set_route(Routes::Tpf)
305+
.to_string();
301306
let resources =
302307
atomic_lib::client::fetch_tpf(&endpoint, subject, property, value, &context.store)?;
303308
for r in resources {

lib/src/agents.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -43,23 +43,29 @@ impl Agent {
4343
pub fn new(name: Option<&str>, store: &impl Storelike) -> AtomicResult<Agent> {
4444
let keypair = generate_keypair()?;
4545

46-
Ok(Agent::new_from_private_key(name, store, &keypair.private))
46+
Agent::new_from_private_key(name, store, &keypair.private)
4747
}
4848

4949
pub fn new_from_private_key(
5050
name: Option<&str>,
5151
store: &impl Storelike,
5252
private_key: &str,
53-
) -> Agent {
53+
) -> AtomicResult<Agent> {
5454
let keypair = generate_public_key(private_key);
55+
println!("server url: {}", store.get_server_url());
56+
let subject = store
57+
.get_server_url()
58+
.url()
59+
.join(&format!("agents/{}", &keypair.public))?
60+
.to_string();
5561

56-
Agent {
62+
Ok(Agent {
5763
private_key: Some(keypair.private),
58-
public_key: keypair.public.clone(),
59-
subject: format!("{}/agents/{}", store.get_server_url(), keypair.public),
64+
public_key: keypair.public,
65+
subject,
6066
name: name.map(|x| x.to_owned()),
6167
created_at: crate::utils::now(),
62-
}
68+
})
6369
}
6470

6571
pub fn new_from_public_key(store: &impl Storelike, public_key: &str) -> AtomicResult<Agent> {

lib/src/atomic_url.rs

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
use serde::{Deserialize, Serialize, Serializer};
2+
use url::Url;
3+
4+
use crate::{errors::AtomicResult, utils::random_string};
5+
6+
pub enum Routes {
7+
Agents,
8+
AllVersions,
9+
Collections,
10+
Commits,
11+
CommitsUnsigned,
12+
Endpoints,
13+
Import,
14+
Tpf,
15+
Version,
16+
Setup,
17+
}
18+
19+
#[derive(Debug, Clone, PartialEq, Eq)]
20+
/// Wrapper for URLs / subjects.
21+
/// Has a bunch of methods for finding or creating commonly used paths.
22+
pub struct AtomicUrl {
23+
url: Url,
24+
}
25+
26+
impl AtomicUrl {
27+
pub fn new(url: Url) -> Self {
28+
Self { url }
29+
}
30+
31+
pub fn as_str(&self) -> &str {
32+
self.url.as_str()
33+
}
34+
35+
/// Returns the route to some common Endpoint
36+
pub fn set_route(&self, route: Routes) -> Self {
37+
let path = match route {
38+
Routes::AllVersions => "/all-versions".to_string(),
39+
Routes::Agents => "/agents".to_string(),
40+
Routes::Collections => "/collections".to_string(),
41+
Routes::Commits => "/commits".to_string(),
42+
Routes::CommitsUnsigned => "/commits-unsigned".to_string(),
43+
Routes::Endpoints => "/endpoints".to_string(),
44+
Routes::Import => "/import".to_string(),
45+
Routes::Tpf => "/tpf".to_string(),
46+
Routes::Version => "/version".to_string(),
47+
Routes::Setup => "/setup".to_string(),
48+
};
49+
let mut new = self.url.clone();
50+
new.set_path(&path);
51+
Self::new(new)
52+
}
53+
54+
/// Returns a new URL generated from the provided path_shortname and a random string.
55+
/// ```
56+
/// let url = atomic_lib::AtomicUrl::try_from("https://example.com").unwrap();
57+
/// let generated = url.generate_random("my-type");
58+
/// assert!(generated.to_string().starts_with("https://example.com/my-type/"));
59+
/// ```
60+
pub fn generate_random(&self, path_shortname: &str) -> Self {
61+
let mut url = self.url.clone();
62+
let path = format!("{path_shortname}/{}", random_string(10));
63+
url.set_path(&path);
64+
Self { url }
65+
}
66+
67+
/// Adds a sub-path to a URL.
68+
/// Adds a slash to the existing URL, followed by the passed path.
69+
///
70+
/// ```
71+
/// use atomic_lib::AtomicUrl;
72+
/// let start = "http://localhost";
73+
/// let mut url = AtomicUrl::try_from(start).unwrap();
74+
/// assert_eq!(url.to_string(), "http://localhost/");
75+
/// url.append("/");
76+
/// assert_eq!(url.to_string(), "http://localhost/");
77+
/// url.append("someUrl/123");
78+
/// assert_eq!(url.to_string(), "http://localhost/someUrl/123");
79+
/// url.append("/345");
80+
/// assert_eq!(url.to_string(), "http://localhost/someUrl/123/345");
81+
/// ```
82+
pub fn append(&mut self, path: &str) -> &Self {
83+
let mut new_path = self.url.path().to_string();
84+
match (new_path.ends_with('/'), path.starts_with('/')) {
85+
(true, true) => {
86+
new_path.pop();
87+
}
88+
(false, false) => new_path.push('/'),
89+
_other => {}
90+
};
91+
92+
// Remove first slash if it exists
93+
if new_path.starts_with('/') {
94+
new_path.remove(0);
95+
}
96+
97+
new_path.push_str(path);
98+
99+
self.url.set_path(&new_path);
100+
self
101+
}
102+
103+
/// Sets the subdomain of the URL.
104+
/// Removes an existing subdomain if the subdomain is None
105+
pub fn set_subdomain(&mut self, subdomain: Option<&str>) -> AtomicResult<&Self> {
106+
let mut host = self.url.host_str().unwrap().to_string();
107+
if let Some(subdomain) = subdomain {
108+
host = format!("{}.{}", subdomain, host);
109+
}
110+
self.url.set_host(Some(host.as_str()))?;
111+
Ok(self)
112+
}
113+
114+
pub fn set_path(&mut self, path: &str) -> &Self {
115+
self.url.set_path(path);
116+
self
117+
}
118+
119+
pub fn subdomain(&self) -> Option<String> {
120+
let url = self.url.clone();
121+
let host = url.host_str().unwrap();
122+
let parts: Vec<&str> = host.split('.').collect();
123+
if parts.len() > 2 {
124+
Some(parts[0].to_string())
125+
} else {
126+
None
127+
}
128+
}
129+
130+
/// Returns the inner {url::Url} struct that has a bunch of regular URL methods
131+
/// Useful if you need the host or something.
132+
pub fn url(&self) -> Url {
133+
self.url.clone()
134+
}
135+
}
136+
137+
impl TryFrom<&str> for AtomicUrl {
138+
type Error = url::ParseError;
139+
140+
fn try_from(value: &str) -> Result<Self, Self::Error> {
141+
let url = Url::parse(value)?;
142+
Ok(Self { url })
143+
}
144+
}
145+
146+
impl Serialize for AtomicUrl {
147+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
148+
where
149+
S: Serializer,
150+
{
151+
serializer.serialize_str(self.url.as_str())
152+
}
153+
}
154+
155+
impl<'de> Deserialize<'de> for AtomicUrl {
156+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
157+
where
158+
D: serde::Deserializer<'de>,
159+
{
160+
let s = String::deserialize(deserializer)?;
161+
let url = Url::parse(&s).map_err(serde::de::Error::custom)?;
162+
Ok(Self { url })
163+
}
164+
}
165+
166+
impl std::fmt::Display for AtomicUrl {
167+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168+
write!(f, "{}", self.url)
169+
}
170+
}
171+
172+
#[cfg(test)]
173+
mod test {
174+
use super::*;
175+
176+
#[test]
177+
fn test_url() {
178+
let _should_fail = AtomicUrl::try_from("nonsense").unwrap_err();
179+
let _should_succeed = AtomicUrl::try_from("http://localhost/someUrl").unwrap();
180+
}
181+
182+
#[test]
183+
fn subdomain() {
184+
let sub = "http://test.example.com";
185+
assert_eq!(
186+
AtomicUrl::try_from(sub).unwrap().subdomain(),
187+
Some("test".to_string())
188+
);
189+
let mut no_sub = AtomicUrl::try_from("http://example.com").unwrap();
190+
assert_eq!(no_sub.subdomain(), None);
191+
192+
let to_sub = no_sub.set_subdomain(Some("mysub")).unwrap();
193+
assert_eq!(to_sub.subdomain(), Some("mysub".to_string()));
194+
}
195+
}

lib/src/collections.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ impl CollectionBuilder {
9898
store: &impl Storelike,
9999
) -> CollectionBuilder {
100100
CollectionBuilder {
101-
subject: format!("{}/{}", store.get_server_url(), path),
101+
subject: store.get_server_url().clone().set_path(path).to_string(),
102102
property: Some(urls::IS_A.into()),
103103
value: Some(class_url.into()),
104104
sort_by: None,
@@ -429,7 +429,9 @@ pub fn create_collection_resource_for_class(
429429
let parent = if class.subject == urls::COLLECTION {
430430
drive.to_string()
431431
} else {
432-
format!("{}/collections", drive)
432+
drive
433+
.set_route(crate::atomic_url::Routes::Collections)
434+
.to_string()
433435
};
434436

435437
collection_resource.set_propval_string(urls::PARENT.into(), &parent, store)?;
@@ -533,7 +535,7 @@ mod test {
533535
println!("{:?}", subjects);
534536
let collections_collection = store
535537
.get_resource_extended(
536-
&format!("{}/collections", store.get_server_url()),
538+
&format!("{}collections", store.get_server_url()),
537539
false,
538540
None,
539541
)

lib/src/commit.rs

+17-8
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use std::collections::{HashMap, HashSet};
55
use urls::{SET, SIGNER};
66

77
use crate::{
8-
datatype::DataType, errors::AtomicResult, hierarchy, resources::PropVals, urls,
9-
values::SubResource, Atom, Resource, Storelike, Value,
8+
atomic_url::Routes, datatype::DataType, errors::AtomicResult, hierarchy, resources::PropVals,
9+
urls, values::SubResource, Atom, Resource, Storelike, Value,
1010
};
1111

1212
/// The `resource_new`, `resource_old` and `commit_resource` fields are only created if the Commit is persisted.
@@ -425,12 +425,21 @@ impl Commit {
425425
#[tracing::instrument(skip(store))]
426426
pub fn into_resource(self, store: &impl Storelike) -> AtomicResult<Resource> {
427427
let commit_subject = match self.signature.as_ref() {
428-
Some(sig) => format!("{}/commits/{}", store.get_server_url(), sig),
428+
Some(sig) => store
429+
.get_server_url()
430+
.set_route(Routes::Commits)
431+
.append(sig)
432+
.to_string(),
429433
None => {
430434
let now = crate::utils::now();
431-
format!("{}/commitsUnsigned/{}", store.get_server_url(), now)
435+
store
436+
.get_server_url()
437+
.set_route(Routes::CommitsUnsigned)
438+
.append(&now.to_string())
439+
.to_string()
432440
}
433441
};
442+
println!("commit subject: {}", commit_subject);
434443
let mut resource = Resource::new_instance(urls::COMMIT, store)?;
435444
resource.set_subject(commit_subject);
436445
resource.set_propval_unsafe(
@@ -752,10 +761,10 @@ mod test {
752761
let private_key = "CapMWIhFUT+w7ANv9oCPqrHrwZpkP2JhzF9JnyT6WcI=";
753762
let store = crate::Store::init().unwrap();
754763
store.populate().unwrap();
755-
let agent = Agent::new_from_private_key(None, &store, private_key);
764+
let agent = Agent::new_from_private_key(None, &store, private_key).unwrap();
756765
assert_eq!(
757766
&agent.subject,
758-
"local:store/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U="
767+
"http://noresolve.localhost/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U="
759768
);
760769
store.add_resource(&agent.to_resource().unwrap()).unwrap();
761770
let subject = "https://localhost/new_thing";
@@ -770,8 +779,8 @@ mod test {
770779
let signature = commit.signature.clone().unwrap();
771780
let serialized = commit.serialize_deterministically_json_ad(&store).unwrap();
772781

773-
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\"}");
774-
assert_eq!(signature, "JOGRyp1NCulc0RNuuNozgIagQPRoZy0Y5+mbSpHY2DKiN3vqUNYLjXbAPYT6Cga6vSG9zztEIa/ZcbQPo7wgBg==");
782+
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\"}");
783+
assert_eq!(signature, "CZbjUJW/tokEKSZTCFjEHWbWqGW+jyhZWYs82K9wt0SArxu9xGg+D3IniAlygQp0F3KcI4Z876th3/X3fJIVAQ==");
775784
}
776785

777786
#[test]

0 commit comments

Comments
 (0)