From 3b5504027f3b579ce3a954b6159ca01d63f57e60 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Fri, 30 Sep 2022 13:11:35 +0200 Subject: [PATCH] WIP refactor to AtomicUrl --- .cargo/config.toml | 32 +++++++ Cargo.lock | 1 + cli/src/main.rs | 6 +- cli/src/new.rs | 14 +-- cli/src/print.rs | 14 +-- lib/src/agents.rs | 18 ++-- lib/src/atomic_url.rs | 155 ++++++++++++++++++++++++++++++++ lib/src/commit.rs | 8 +- lib/src/db.rs | 20 ++--- lib/src/lib.rs | 1 + lib/src/parse.rs | 2 +- lib/src/populate.rs | 13 +-- lib/src/store.rs | 12 +-- lib/src/storelike.rs | 13 +-- lib/src/urls.rs | 10 +-- server/Cargo.toml | 1 + server/src/appstate.rs | 4 +- server/src/bin.rs | 9 +- server/src/errors.rs | 10 +++ server/src/handlers/resource.rs | 62 +++++++------ server/src/handlers/search.rs | 17 ++-- 21 files changed, 325 insertions(+), 97 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 lib/src/atomic_url.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..10dda65b3 --- /dev/null +++ b/.cargo/config.toml @@ -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 = '...' diff --git a/Cargo.lock b/Cargo.lock index a79b65cd2..5df4b658a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -522,6 +522,7 @@ dependencies = [ "tracing-opentelemetry", "tracing-subscriber", "ureq 2.5.0", + "url", "urlencoding", ] diff --git a/cli/src/main.rs b/cli/src/main.rs index 78cb38140..e438c298e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -55,7 +55,7 @@ fn set_agent_config() -> CLIResult { "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 { @@ -282,7 +282,7 @@ fn exec_command(context: &mut Context) -> AtomicResult<()> { fn list(context: &mut Context) { let mut string = String::new(); for (shortname, url) in context.mapping.lock().unwrap().clone().into_iter() { - string.push_str(&*format!( + string.push_str(&format!( "{0: <15}{1: <10} \n", shortname.blue().bold(), url @@ -297,7 +297,7 @@ 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().path_tpf().to_string(); let resources = atomic_lib::client::fetch_tpf(&endpoint, subject, property, value, &context.store)?; for r in resources { diff --git a/cli/src/new.rs b/cli/src/new.rs index ce025f47a..fe52fdbca 100644 --- a/cli/src/new.rs +++ b/cli/src/new.rs @@ -148,7 +148,7 @@ fn prompt_field( println!("Only letters, numbers and dashes - no spaces or special characters."); return Ok(None); } - None => (return Ok(None)), + None => return Ok(None), } } DataType::Integer => { @@ -158,7 +158,7 @@ fn prompt_field( Some(nr) => { input = Some(nr.to_string()); } - None => (return Ok(None)), + None => return Ok(None), } } DataType::Float => { @@ -168,7 +168,7 @@ fn prompt_field( Some(nr) => { input = Some(nr.to_string()); } - None => (return Ok(None)), + None => return Ok(None), } } DataType::Date => { @@ -184,7 +184,7 @@ fn prompt_field( println!("Not a valid date."); return Ok(None); } - None => (return Ok(None)), + None => return Ok(None), } } DataType::AtomicUrl => loop { @@ -264,7 +264,7 @@ fn prompt_field( Some(nr) => { input = Some(nr.to_string()); } - None => (return Ok(None)), + None => return Ok(None), } } DataType::Unsupported(unsup) => { @@ -277,7 +277,7 @@ fn prompt_field( Some(nr) => { input = Some(nr); } - None => (return Ok(None)), + None => return Ok(None), } } DataType::Boolean => { @@ -290,7 +290,7 @@ fn prompt_field( } return Ok(Some("false".to_string())); } - None => (return Ok(None)), + None => return Ok(None), } } }; diff --git a/cli/src/print.rs b/cli/src/print.rs index 095033fee..971c7dc88 100644 --- a/cli/src/print.rs +++ b/cli/src/print.rs @@ -16,13 +16,13 @@ pub const SERIALIZE_OPTIONS: [&str; 7] = pub fn get_serialization(argmatches: &ArgMatches) -> AtomicResult { let format = if let Some(preferred_format) = argmatches.value_of("as") { match preferred_format { - "pretty" => (Format::Pretty), - "json" => (Format::Json), - "jsonld" => (Format::JsonLd), - "jsonad" => (Format::JsonAd), - "nt" => (Format::NTriples), - "turtle" => (Format::NTriples), - "n3" => (Format::NTriples), + "pretty" => Format::Pretty, + "json" => Format::Json, + "jsonld" => Format::JsonLd, + "jsonad" => Format::JsonAd, + "nt" => Format::NTriples, + "turtle" => Format::NTriples, + "n3" => Format::NTriples, format => { return Err( format!("As {} not supported. Try {:?}", format, SERIALIZE_OPTIONS).into(), diff --git a/lib/src/agents.rs b/lib/src/agents.rs index 554d27e11..818c14070 100644 --- a/lib/src/agents.rs +++ b/lib/src/agents.rs @@ -43,23 +43,29 @@ impl Agent { pub fn new(name: Option<&str>, store: &impl Storelike) -> AtomicResult { 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 { 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 { diff --git a/lib/src/atomic_url.rs b/lib/src/atomic_url.rs new file mode 100644 index 000000000..76a187336 --- /dev/null +++ b/lib/src/atomic_url.rs @@ -0,0 +1,155 @@ +use serde::{Deserialize, Serialize, Serializer}; +use url::Url; + +use crate::{ + errors::AtomicResult, + urls::{PATH_COMMITS, PATH_IMPORT, PATH_PATH, PATH_TPF}, + utils::random_string, +}; + +#[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 URL to the `/tpf` endpoint + pub fn path_tpf(&self) -> Self { + let mut url = self.url.clone(); + url.set_path(PATH_TPF); + Self { url } + } + + /// Returns the URL to the `/import` endpoint + pub fn path_import(&self) -> Self { + let mut url = self.url.clone(); + url.set_path(PATH_IMPORT); + Self { url } + } + /// Returns the URL to the `/commits` endpoint + pub fn path_commits(&self) -> Self { + let mut url = self.url.clone(); + url.set_path(PATH_COMMITS); + Self { url } + } + + /// Returns the URL to the `/path` endpoint + pub fn path_path(&self) -> Self { + let mut url = self.url.clone(); + url.set_path(PATH_PATH); + Self { url } + } + + /// Returns a new URL generated from the provided path_shortname and a random string. + /// ``` + /// let url = AtomicUrl::new(Url::parse("https://example.com").unwrap()); + /// let generated = url.generate("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 path to a URL + pub fn join(mut self, path: &str) -> AtomicResult { + self.url = self.url.join(path)?; + Ok(self) + } + + pub fn subdomain(&self) -> Option { + 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 { + let url = Url::parse(value)?; + Ok(Self { url }) + } +} + +impl Serialize for AtomicUrl { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.url.as_str()) + } +} + +impl<'de> Deserialize<'de> for AtomicUrl { + fn deserialize(deserializer: D) -> Result + 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 join_url() { + let start = "http://localhost/someUrl"; + let mut url = AtomicUrl::try_from(start).unwrap(); + + assert_eq!(url.to_string(), start); + url = url.join("/123").unwrap(); + assert_eq!(url.to_string(), "http://localhost/someUrl/123") + } + + #[test] + fn subdomain() { + let sub = "http://test.example.com"; + assert_eq!( + AtomicUrl::try_from(sub).unwrap().subdomain(), + Some("test".to_string()) + ); + let no_sub = "http://example.com"; + assert_eq!(AtomicUrl::try_from(no_sub).unwrap().subdomain(), None); + } +} diff --git a/lib/src/commit.rs b/lib/src/commit.rs index f807e6186..d56061500 100644 --- a/lib/src/commit.rs +++ b/lib/src/commit.rs @@ -425,12 +425,16 @@ impl Commit { #[tracing::instrument(skip(store))] pub fn into_resource(self, store: &impl Storelike) -> AtomicResult { let commit_subject = match self.signature.as_ref() { - Some(sig) => format!("{}/commits/{}", store.get_server_url(), sig), + Some(sig) => store.get_server_url().path_commits().join(sig)?.to_string(), None => { let now = crate::utils::now(); format!("{}/commitsUnsigned/{}", store.get_server_url(), now) } }; + println!( + "commit subject: {}", + store.get_server_url().path_commits().join("adwdawawd")? + ); let mut resource = Resource::new_instance(urls::COMMIT, store)?; resource.set_subject(commit_subject); resource.set_propval_unsafe( @@ -752,7 +756,7 @@ 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=" diff --git a/lib/src/db.rs b/lib/src/db.rs index abebbcda3..339f84c72 100644 --- a/lib/src/db.rs +++ b/lib/src/db.rs @@ -7,9 +7,9 @@ use std::{ }; use tracing::{instrument, trace}; -use url::Url; use crate::{ + atomic_url::AtomicUrl, commit::CommitResponse, endpoints::{default_endpoints, Endpoint}, errors::{AtomicError, AtomicResult}, @@ -64,7 +64,7 @@ pub struct Db { /// See [collections_index] watched_queries: sled::Tree, /// The address where the db will be hosted, e.g. http://localhost/ - server_url: Url, + server_url: AtomicUrl, /// Endpoints are checked whenever a resource is requested. They calculate (some properties of) the resource and return it. endpoints: Vec, /// Function called whenever a Commit is applied. @@ -75,7 +75,7 @@ impl Db { /// Creates a new store at the specified path, or opens the store if it already exists. /// The server_url is the domain where the db will be hosted, e.g. http://localhost/ /// It is used for distinguishing locally defined items from externally defined ones. - pub fn init(path: &std::path::Path, server_url: String) -> AtomicResult { + pub fn init(path: &std::path::Path, server_url: &str) -> AtomicResult { let db = sled::open(path).map_err(|e|format!("Failed opening DB at this location: {:?} . Is another instance of Atomic Server running? {}", path, e))?; let resources = db.open_tree("resources_v1").map_err(|e|format!("Failed building resources. Your DB might be corrupt. Go back to a previous version and export your data. {}", e))?; let reference_index = db.open_tree("reference_index")?; @@ -87,7 +87,7 @@ impl Db { resources, reference_index, members_index, - server_url: Url::parse(&server_url)?, + server_url: server_url.try_into()?, watched_queries, endpoints: default_endpoints(), on_commit: None, @@ -103,10 +103,7 @@ impl Db { pub fn init_temp(id: &str) -> AtomicResult { let tmp_dir_path = format!(".temp/db/{}", id); let _try_remove_existing = std::fs::remove_dir_all(&tmp_dir_path); - let store = Db::init( - std::path::Path::new(&tmp_dir_path), - "https://localhost".into(), - )?; + let store = Db::init(std::path::Path::new(&tmp_dir_path), "https://localhost")?; let agent = store.create_agent(None)?; store.set_default_agent(agent); store.populate()?; @@ -219,6 +216,7 @@ impl Storelike for Db { update_index: bool, overwrite_existing: bool, ) -> AtomicResult<()> { + println!("add_resource_opts {}", resource.get_subject()); // This only works if no external functions rely on using add_resource for atom-like operations! // However, add_atom uses set_propvals, which skips the validation. let existing = self.get_propvals(resource.get_subject()).ok(); @@ -263,11 +261,11 @@ impl Storelike for Db { Ok(()) } - fn get_server_url(&self) -> &Url { + fn get_server_url(&self) -> &AtomicUrl { &self.server_url } - fn get_self_url(&self) -> Option<&Url> { + fn get_self_url(&self) -> Option<&AtomicUrl> { // Since the DB is often also the server, this should make sense. // Some edge cases might appear later on (e.g. a slave DB that only stores copies?) Some(self.get_server_url()) @@ -300,6 +298,8 @@ impl Storelike for Db { skip_dynamic: bool, for_agent: Option<&str>, ) -> AtomicResult { + println!("get resource {}", subject); + let url_span = tracing::span!(tracing::Level::TRACE, "URL parse").entered(); // This might add a trailing slash let url = url::Url::parse(subject)?; diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 0fd1bd63b..e5cc844d9 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -59,6 +59,7 @@ assert!(fetched_new_resource.get_shortname("description", &store).unwrap().to_st */ pub mod agents; +pub mod atomic_url; pub mod atoms; pub mod authentication; pub mod client; diff --git a/lib/src/parse.rs b/lib/src/parse.rs index 71ea150c4..5958915a1 100644 --- a/lib/src/parse.rs +++ b/lib/src/parse.rs @@ -142,7 +142,7 @@ pub fn parse_json_ad_commit_resource( .get(urls::SUBJECT) .ok_or("No subject field in Commit.")? .to_string(); - let subject = format!("{}/commits/{}", store.get_server_url(), signature); + let subject = format!("{}commits/{}", store.get_server_url(), signature); let mut resource = Resource::new(subject); let propvals = match parse_json_ad_map_to_resource(json, store, &ParseOpts::default())? { SubResource::Resource(r) => r.into_propvals(), diff --git a/lib/src/populate.rs b/lib/src/populate.rs index 736d48d5a..09f068886 100644 --- a/lib/src/populate.rs +++ b/lib/src/populate.rs @@ -158,17 +158,18 @@ pub fn create_drive( for_agent: &str, public_read: bool, ) -> AtomicResult { - let mut self_url = if let Some(url) = store.get_self_url() { + let self_url = if let Some(url) = store.get_self_url() { url.to_owned() } else { return Err("No self URL set. Cannot create drive.".into()); }; let drive_subject: String = if let Some(name) = drive_name { // Let's make a subdomain - let host = self_url.host().expect("No host in server_url"); + let mut url = self_url.url(); + let host = url.host().expect("No host in server_url"); let subdomain_host = format!("{}.{}", name, host); - self_url.set_host(Some(&subdomain_host))?; - self_url.to_string() + url.set_host(Some(&subdomain_host))?; + url.to_string() } else { self_url.to_string() }; @@ -186,7 +187,7 @@ pub fn create_drive( drive.set_class(urls::DRIVE); drive.set_propval_string( urls::NAME.into(), - drive_name.unwrap_or_else(|| self_url.host_str().unwrap()), + drive_name.unwrap_or_else(|| "Main drive"), store, )?; @@ -274,7 +275,7 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> { let base = store .get_self_url() .ok_or("No self URL in this Store - required for populating importer")?; - let mut importer = Resource::new(urls::construct_path_import(base)); + let mut importer = Resource::new(base.path_import().to_string()); importer.set_class(urls::IMPORTER); importer.set_propval( urls::PARENT.into(), diff --git a/lib/src/store.rs b/lib/src/store.rs index 46d899224..0879c1566 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -1,9 +1,8 @@ //! In-memory store of Atomic data. //! This provides many methods for finding, changing, serializing and parsing Atomic Data. -use url::Url; - use crate::{ + atomic_url::AtomicUrl, atoms::Atom, storelike::{ResourceCollection, Storelike}, }; @@ -19,10 +18,11 @@ pub struct Store { } /// The URL used for stores that are not accessible on the web. -pub const LOCAL_STORE_URL_STR: &str = "local:store"; +// I'd prefer this to a non-HTTP URI, but that causes parsing issues when we combine it with some paths (at least with Commits) +pub const LOCAL_STORE_URL_STR: &str = "http://noresolve.localhost"; lazy_static::lazy_static! { - static ref LOCAL_STORE_URL: Url = Url::parse(LOCAL_STORE_URL_STR).unwrap(); + static ref LOCAL_STORE_URL: AtomicUrl = AtomicUrl::try_from(LOCAL_STORE_URL_STR).unwrap(); } impl Store { @@ -96,13 +96,13 @@ impl Storelike for Store { all } - fn get_server_url(&self) -> &Url { + fn get_server_url(&self) -> &AtomicUrl { // TODO Should be implemented later when companion functionality is here // https://github.com/atomicdata-dev/atomic-data-rust/issues/6 &LOCAL_STORE_URL } - fn get_self_url(&self) -> Option<&Url> { + fn get_self_url(&self) -> Option<&AtomicUrl> { Some(self.get_server_url()) } diff --git a/lib/src/storelike.rs b/lib/src/storelike.rs index 29f6bb8a1..a736cfa9d 100644 --- a/lib/src/storelike.rs +++ b/lib/src/storelike.rs @@ -1,9 +1,8 @@ //! The Storelike Trait contains many useful methods for maniupulting / retrieving data. -use url::Url; - use crate::{ agents::Agent, + atomic_url::AtomicUrl, commit::CommitResponse, errors::AtomicError, hierarchy, @@ -80,15 +79,15 @@ pub trait Storelike: Sized { } /// Returns the base URL where the default store is. - /// E.g. `https://example.com` + /// E.g. `https://example.com/` /// This is where deltas should be sent to. /// Also useful for Subject URL generation. - fn get_server_url(&self) -> &Url; + fn get_server_url(&self) -> &AtomicUrl; /// Returns the root URL where this instance of the store is hosted. /// Should return `None` if this is simply a client and not a server. - /// E.g. `https://example.com` - fn get_self_url(&self) -> Option<&Url> { + /// E.g. `https://example.com.` + fn get_self_url(&self) -> Option<&AtomicUrl> { None } @@ -234,9 +233,11 @@ pub trait Storelike: Sized { let subject_host = subject_url.host().ok_or_else(|| { AtomicError::not_found(format!("Subject URL has no host: {}", subject)) })?; + let self_url = self_url.url(); let self_host = self_url.host().ok_or_else(|| { AtomicError::not_found(format!("Self URL has no host: {}", self_url)) })?; + println!("Subject host: {}, self_host: {}", subject_host, self_host); if subject_host == self_host { return Ok(false); } diff --git a/lib/src/urls.rs b/lib/src/urls.rs index e7fe8b550..ff9529a15 100644 --- a/lib/src/urls.rs +++ b/lib/src/urls.rs @@ -1,6 +1,5 @@ //! Contains some of the most important Atomic Data URLs. - -use url::Url; +//! See [crate::atomic_url] for the URL datatype. // Classes pub const CLASS: &str = "https://atomicdata.dev/classes/Class"; @@ -134,9 +133,8 @@ pub const DELETE: &str = "https://atomicdata.dev/methods/delete"; pub const PUBLIC_AGENT: &str = "https://atomicdata.dev/agents/publicAgent"; // Paths -pub fn construct_path_import(base: &Url) -> String { - format!("{base}{PATH_IMPORT}") -} - pub const PATH_IMPORT: &str = "/import"; pub const PATH_FETCH_BOOKMARK: &str = "/fetch-bookmark"; +pub const PATH_TPF: &str = "/tpf"; +pub const PATH_PATH: &str = "/path"; +pub const PATH_COMMITS: &str = "/commits"; diff --git a/server/Cargo.toml b/server/Cargo.toml index a5b25cea0..4d5c14229 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -41,6 +41,7 @@ tracing-chrome = "0.6" tracing-log = "0.1" ureq = "2" urlencoding = "2" +url = "2.3.1" [dependencies.acme-lib] optional = true diff --git a/server/src/appstate.rs b/server/src/appstate.rs index ca43a2b22..87a21d63f 100644 --- a/server/src/appstate.rs +++ b/server/src/appstate.rs @@ -41,7 +41,7 @@ pub fn init(config: Config) -> AtomicServerResult { } tracing::info!("Opening database at {:?}", &config.store_path); - let mut store = atomic_lib::Db::init(&config.store_path, config.server_url.clone())?; + let mut store = atomic_lib::Db::init(&config.store_path, &config.server_url)?; if config.initialize { tracing::info!("Initialize: creating and populating new Database"); atomic_lib::populate::populate_default_store(&store) @@ -113,7 +113,7 @@ fn set_default_agent(config: &Config, store: &impl Storelike) -> AtomicServerRes "server".into(), store, &agent_config.private_key, - ); + )?; store.add_resource(&recreated_agent.to_resource()?)?; agent_config } else { diff --git a/server/src/bin.rs b/server/src/bin.rs index fe17237de..1ab47ac01 100644 --- a/server/src/bin.rs +++ b/server/src/bin.rs @@ -1,4 +1,4 @@ -use atomic_lib::{urls, Storelike}; +use atomic_lib::Storelike; use std::{fs::File, io::Write}; mod actor_messages; @@ -69,7 +69,12 @@ async fn main_wrapped() -> errors::AtomicServerResult<()> { let importer_subject = if let Some(i) = &o.parent { i.into() } else { - urls::construct_path_import(appstate.store.get_self_url().expect("No self url")) + appstate + .store + .get_self_url() + .expect("No self URL") + .path_import() + .to_string() }; let parse_opts = atomic_lib::parse::ParseOpts { importer: Some(importer_subject), diff --git a/server/src/errors.rs b/server/src/errors.rs index 4dd3ed493..34f856198 100644 --- a/server/src/errors.rs +++ b/server/src/errors.rs @@ -182,3 +182,13 @@ impl From for AtomicServerError { } } } + +impl From for AtomicServerError { + fn from(error: url::ParseError) -> Self { + AtomicServerError { + message: error.to_string(), + error_type: AppErrorType::Other, + error_resource: None, + } + } +} diff --git a/server/src/handlers/resource.rs b/server/src/handlers/resource.rs index c4081cf9b..77859886d 100644 --- a/server/src/handlers/resource.rs +++ b/server/src/handlers/resource.rs @@ -9,7 +9,7 @@ use atomic_lib::Storelike; /// The URL should match the Subject of the resource. #[tracing::instrument(skip(appstate, req))] pub async fn handle_get_resource( - path: Option>, + path_opt: Option>, appstate: web::Data, req: actix_web::HttpRequest, conn: actix_web::dev::ConnectionInfo, @@ -32,36 +32,42 @@ pub async fn handle_get_resource( let content_type = get_accept(headers); let server_url = &appstate.config.server_url; println!("server_url: {}", server_url); - // Get the subject from the path, or return the home URL - let subject = if let Some(subj_end) = path { - let subj_end_string = subj_end.as_str(); - // If the request is for the root, return the home URL - if subj_end.as_str().is_empty() { - server_url.to_string() - } else { - // This might not be the best way of creating the subject. But I can't access the full URL from any actix stuff! - let querystring = if req.query_string().is_empty() { - "".to_string() - } else { - format!("?{}", req.query_string()) - }; - if let Some(sd) = subdomain { - // TODO: ONLY WORKS IN DEVELOPMENT, HACKY - format!("http://{}.localhost/{}{}", sd, subj_end_string, querystring) - } else { - format!("{}/{}{}", server_url, subj_end_string, querystring) - } - } - } else { - // There is no end string, so It's the root of the URL, the base URL! - String::from(server_url) - }; - println!("subject: {}", subject); + + // You'd think there would be a simpler way of getting the requested URL... + // See https://github.com/actix/actix-web/issues/2895 + let mut subject = url::Url::parse(server_url)?; + + // Doe this include the query params? + subject = subject.join(&req.uri().to_string())?; + + println!("server uri {} ", &req.uri()); + println!("subject {}", subject); + if let Some(sd) = subdomain { + format!("{}.{}", sd, subject.host().unwrap()); + subject.set_host(Some(&sd))?; + } + + // let subject = if let Some(path) = path_opt { + // // If the request is for the root, return the home URL + // if path.as_str().is_empty() { + // server_url.to_string() + // } else { + // let querystring = if req.query_string().is_empty() { + // "".to_string() + // } else { + // format!("?{}", req.query_string()) + // }; + + // } + // } else { + // // There is no end string, so It's the root of the URL, the base URL! + // String::from(server_url) + // }; let store = &appstate.store; timer.add("parse_headers"); - let for_agent = get_client_agent(headers, &appstate, subject.clone())?; + let for_agent = get_client_agent(headers, &appstate, subject.to_string())?; timer.add("get_agent"); let mut builder = HttpResponse::Ok(); @@ -75,7 +81,7 @@ pub async fn handle_get_resource( "no-store, no-cache, must-revalidate, private", )); - let resource = store.get_resource_extended(&subject, false, for_agent.as_deref())?; + let resource = store.get_resource_extended(subject.as_str(), false, for_agent.as_deref())?; timer.add("get_resource"); let response_body = match content_type { diff --git a/server/src/handlers/search.rs b/server/src/handlers/search.rs index 0840c3d65..485b7cb65 100644 --- a/server/src/handlers/search.rs +++ b/server/src/handlers/search.rs @@ -149,11 +149,18 @@ pub async fn search_query( // Create a valid atomic data resource. // You'd think there would be a simpler way of getting the requested URL... - let subject = format!( - "{}{}", - store.get_self_url().ok_or("No base URL set")?, - req.uri().path_and_query().ok_or("Add a query param")? - ); + // See https://github.com/actix/actix-web/issues/2895 + let subject: String = store + .get_self_url() + .ok_or("No base URL set")? + .url() + .join( + req.uri() + .path_and_query() + .ok_or("Add a query param")? + .as_str(), + )? + .to_string(); let mut results_resource = atomic_lib::plugins::search::search_endpoint().to_resource(store)?; results_resource.set_subject(subject.clone());