Skip to content

Commit 53ae737

Browse files
committed
#502 refactor to AtomicUrl
WIP Path URL #502 URLs
1 parent 1abd2af commit 53ae737

24 files changed

+388
-120
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
@@ -41,6 +41,8 @@ jobs:
4141
with:
4242
command: nextest
4343
args: run --all-features --retries 3
44+
# https://github.com/nextest-rs/nextest/issues/16
45+
- run: cargo test --doc
4446

4547
coverage:
4648
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

+2-1
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 {

lib/src/agents.rs

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

48-
Ok(Agent::new_from_private_key(name, store, &keypair.private))
48+
Agent::new_from_private_key(name, store, &keypair.private)
4949
}
5050

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

58-
Agent {
64+
Ok(Agent {
5965
private_key: Some(keypair.private),
60-
public_key: keypair.public.clone(),
61-
subject: format!("{}/agents/{}", store.get_server_url(), keypair.public),
66+
public_key: keypair.public,
67+
subject,
6268
name: name.map(|x| x.to_owned()),
6369
created_at: crate::utils::now(),
64-
}
70+
})
6571
}
6672

6773
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
@@ -91,7 +91,7 @@ impl CollectionBuilder {
9191
store: &impl Storelike,
9292
) -> CollectionBuilder {
9393
CollectionBuilder {
94-
subject: format!("{}/{}", store.get_server_url(), path),
94+
subject: store.get_server_url().clone().set_path(path).to_string(),
9595
property: Some(urls::IS_A.into()),
9696
value: Some(class_url.into()),
9797
sort_by: None,
@@ -425,7 +425,9 @@ pub fn create_collection_resource_for_class(
425425
let parent = if class.subject == urls::COLLECTION {
426426
drive.to_string()
427427
} else {
428-
format!("{}/collections", drive)
428+
drive
429+
.set_route(crate::atomic_url::Routes::Collections)
430+
.to_string()
429431
};
430432

431433
collection_resource.set_propval_string(urls::PARENT.into(), &parent, store)?;
@@ -529,7 +531,7 @@ mod test {
529531
println!("{:?}", subjects);
530532
let collections_collection = store
531533
.get_resource_extended(
532-
&format!("{}/collections", store.get_server_url()),
534+
&format!("{}collections", store.get_server_url()),
533535
false,
534536
None,
535537
)

lib/src/commit.rs

+15-6
Original file line numberDiff line numberDiff line change
@@ -435,12 +435,21 @@ impl Commit {
435435
#[tracing::instrument(skip(store))]
436436
pub fn into_resource(&self, store: &impl Storelike) -> AtomicResult<Resource> {
437437
let commit_subject = match self.signature.as_ref() {
438-
Some(sig) => format!("{}/commits/{}", store.get_server_url(), sig),
438+
Some(sig) => store
439+
.get_server_url()
440+
.set_route(Routes::Commits)
441+
.append(sig)
442+
.to_string(),
439443
None => {
440444
let now = crate::utils::now();
441-
format!("{}/commitsUnsigned/{}", store.get_server_url(), now)
445+
store
446+
.get_server_url()
447+
.set_route(Routes::CommitsUnsigned)
448+
.append(&now.to_string())
449+
.to_string()
442450
}
443451
};
452+
println!("commit subject: {}", commit_subject);
444453
let mut resource = Resource::new_instance(urls::COMMIT, store)?;
445454
resource.set_subject(commit_subject);
446455
resource.set_propval_unsafe(
@@ -761,10 +770,10 @@ mod test {
761770
let private_key = "CapMWIhFUT+w7ANv9oCPqrHrwZpkP2JhzF9JnyT6WcI=";
762771
let store = crate::Store::init().unwrap();
763772
store.populate().unwrap();
764-
let agent = Agent::new_from_private_key(None, &store, private_key);
773+
let agent = Agent::new_from_private_key(None, &store, private_key).unwrap();
765774
assert_eq!(
766775
&agent.subject,
767-
"local:store/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U="
776+
"http://noresolve.localhost/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U="
768777
);
769778
store.add_resource(&agent.to_resource().unwrap()).unwrap();
770779
let subject = "https://localhost/new_thing";
@@ -779,8 +788,8 @@ mod test {
779788
let signature = commit.signature.clone().unwrap();
780789
let serialized = commit.serialize_deterministically_json_ad(&store).unwrap();
781790

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\":\"local:store/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U=\",\"https://atomicdata.dev/properties/subject\":\"https://localhost/new_thing\"}");
783-
assert_eq!(signature, "JOGRyp1NCulc0RNuuNozgIagQPRoZy0Y5+mbSpHY2DKiN3vqUNYLjXbAPYT6Cga6vSG9zztEIa/ZcbQPo7wgBg==");
791+
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\"}");
792+
assert_eq!(signature, "CZbjUJW/tokEKSZTCFjEHWbWqGW+jyhZWYs82K9wt0SArxu9xGg+D3IniAlygQp0F3KcI4Z876th3/X3fJIVAQ==");
784793
}
785794

786795
#[test]

0 commit comments

Comments
 (0)