diff --git a/Cargo.lock b/Cargo.lock index e8d384e0..19824151 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3575,6 +3575,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "path-clean" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -5301,6 +5307,7 @@ dependencies = [ "native-tls", "nom", "once_cell", + "path-clean", "pbkdf2", "pharos", "pin-project-lite", diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 7b4fd07d..f29e5f67 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -86,6 +86,7 @@ nanoid = "0.4.0" native-tls = { version = "0.2.11", optional = true } nom = { version = "7.1.3", features = ["alloc"] } once_cell = "1.18.0" +path-clean = "1.0.1" pbkdf2 = { version = "0.12.2", features = ["simple"] } pin-project-lite = "0.2.12" radix_trie = { version = "0.2.1", features = ["serde"] } diff --git a/lib/src/api/engine/any/mod.rs b/lib/src/api/engine/any/mod.rs index cd1a3497..0245a3eb 100644 --- a/lib/src/api/engine/any/mod.rs +++ b/lib/src/api/engine/any/mod.rs @@ -95,7 +95,10 @@ use crate::api::opt::Endpoint; use crate::api::Connect; use crate::api::Result; use crate::api::Surreal; +use crate::opt::replace_tilde; +use path_clean::PathClean; use std::marker::PhantomData; +use std::path::Path; use std::sync::Arc; use std::sync::OnceLock; use url::Url; @@ -108,12 +111,24 @@ pub trait IntoEndpoint { impl IntoEndpoint for &str { fn into_endpoint(self) -> Result { - let url = match self { - "memory" => "mem://", - _ => self, + let (url, path) = match self { + "memory" | "mem://" => (Url::parse("mem://").unwrap(), "memory".to_owned()), + url if url.starts_with("ws") | url.starts_with("http") => { + (Url::parse(url).map_err(|_| Error::InvalidUrl(self.to_owned()))?, String::new()) + } + _ => { + let (scheme, _) = self.split_once(':').unwrap_or((self, "")); + let path = replace_tilde(self); + ( + Url::parse(&format!("{scheme}://")) + .map_err(|_| Error::InvalidUrl(self.to_owned()))?, + Path::new(&path).clean().display().to_string(), + ) + } }; Ok(Endpoint { - endpoint: Url::parse(url).map_err(|_| Error::InvalidUrl(self.to_owned()))?, + url, + path, config: Default::default(), }) } diff --git a/lib/src/api/engine/any/native.rs b/lib/src/api/engine/any/native.rs index 36e5ab4e..5801b522 100644 --- a/lib/src/api/engine/any/native.rs +++ b/lib/src/api/engine/any/native.rs @@ -61,7 +61,7 @@ impl Connection for Any { let (conn_tx, conn_rx) = flume::bounded::>(1); let mut features = HashSet::new(); - match address.endpoint.scheme() { + match address.url.scheme() { "fdb" => { #[cfg(feature = "kv-fdb")] { @@ -151,7 +151,7 @@ impl Connection for Any { }; } let client = builder.build()?; - let base_url = address.endpoint; + let base_url = address.url; engine::remote::http::health( client.get(base_url.join(Method::Health.as_str())?), ) @@ -169,7 +169,7 @@ impl Connection for Any { "ws" | "wss" => { #[cfg(feature = "protocol-ws")] { - let url = address.endpoint.join(engine::remote::ws::PATH)?; + let url = address.url.join(engine::remote::ws::PATH)?; #[cfg(any(feature = "native-tls", feature = "rustls"))] let maybe_connector = address.config.tls_config.map(Connector::from); #[cfg(not(any(feature = "native-tls", feature = "rustls")))] diff --git a/lib/src/api/engine/any/wasm.rs b/lib/src/api/engine/any/wasm.rs index 4e624662..efef3639 100644 --- a/lib/src/api/engine/any/wasm.rs +++ b/lib/src/api/engine/any/wasm.rs @@ -46,7 +46,7 @@ impl Connection for Any { let (conn_tx, conn_rx) = flume::bounded::>(1); let mut features = HashSet::new(); - match address.endpoint.scheme() { + match address.url.scheme() { "fdb" => { #[cfg(feature = "kv-fdb")] { @@ -144,7 +144,7 @@ impl Connection for Any { #[cfg(feature = "protocol-ws")] { let mut address = address; - address.endpoint = address.endpoint.join(engine::remote::ws::PATH)?; + address.url = address.url.join(engine::remote::ws::PATH)?; engine::remote::ws::wasm::router(address, capacity, conn_tx, route_rx); conn_rx.into_recv_async().await??; } diff --git a/lib/src/api/engine/local/native.rs b/lib/src/api/engine/local/native.rs index 03f11d82..f56df676 100644 --- a/lib/src/api/engine/local/native.rs +++ b/lib/src/api/engine/local/native.rs @@ -6,7 +6,6 @@ use crate::api::conn::Route; use crate::api::conn::Router; use crate::api::engine::local::Db; use crate::api::engine::local::DEFAULT_TICK_INTERVAL; -use crate::api::err::Error; use crate::api::opt::Endpoint; use crate::api::ExtraFeatures; use crate::api::OnceLockExt; @@ -95,7 +94,6 @@ pub(crate) fn router( route_rx: Receiver>, ) { tokio::spawn(async move { - let url = address.endpoint; let configured_root = match address.config.auth { Level::Root => Some(Root { username: &address.config.username, @@ -104,36 +102,21 @@ pub(crate) fn router( _ => None, }; - let kvs = { - let path = match url.scheme() { - "mem" => "memory".to_owned(), - "fdb" | "rocksdb" | "speedb" | "file" => match url.to_file_path() { - Ok(path) => format!("{}://{}", url.scheme(), path.display()), - Err(_) => { - let error = Error::InvalidUrl(url.as_str().to_owned()); + let kvs = match Datastore::new(&address.path).await { + Ok(kvs) => { + // If a root user is specified, setup the initial datastore credentials + if let Some(root) = configured_root { + if let Err(error) = kvs.setup_initial_creds(root).await { let _ = conn_tx.into_send_async(Err(error.into())).await; return; } - }, - _ => url.as_str().to_owned(), - }; - - match Datastore::new(&path).await { - Ok(kvs) => { - // If a root user is specified, setup the initial datastore credentials - if let Some(root) = configured_root { - if let Err(error) = kvs.setup_initial_creds(root).await { - let _ = conn_tx.into_send_async(Err(error.into())).await; - return; - } - } - let _ = conn_tx.into_send_async(Ok(())).await; - kvs.with_auth_enabled(configured_root.is_some()) - } - Err(error) => { - let _ = conn_tx.into_send_async(Err(error.into())).await; - return; } + let _ = conn_tx.into_send_async(Ok(())).await; + kvs.with_auth_enabled(configured_root.is_some()) + } + Err(error) => { + let _ = conn_tx.into_send_async(Err(error.into())).await; + return; } }; diff --git a/lib/src/api/engine/local/wasm.rs b/lib/src/api/engine/local/wasm.rs index a9e76b9b..d5aa6952 100644 --- a/lib/src/api/engine/local/wasm.rs +++ b/lib/src/api/engine/local/wasm.rs @@ -91,7 +91,6 @@ pub(crate) fn router( route_rx: Receiver>, ) { spawn_local(async move { - let url = address.endpoint; let configured_root = match address.config.auth { Level::Root => Some(Root { username: &address.config.username, @@ -100,12 +99,7 @@ pub(crate) fn router( _ => None, }; - let path = match url.scheme() { - "mem" => "memory", - _ => url.as_str(), - }; - - let kvs = match Datastore::new(path).await { + let kvs = match Datastore::new(&address.path).await { Ok(kvs) => { // If a root user is specified, setup the initial datastore credentials if let Some(root) = configured_root { diff --git a/lib/src/api/engine/remote/http/native.rs b/lib/src/api/engine/remote/http/native.rs index a80857b0..6288b16f 100644 --- a/lib/src/api/engine/remote/http/native.rs +++ b/lib/src/api/engine/remote/http/native.rs @@ -57,7 +57,7 @@ impl Connection for Client { let client = builder.build()?; - let base_url = address.endpoint; + let base_url = address.url; super::health(client.get(base_url.join(Method::Health.as_str())?)).await?; diff --git a/lib/src/api/engine/remote/http/wasm.rs b/lib/src/api/engine/remote/http/wasm.rs index a91b1a61..ff0d4335 100644 --- a/lib/src/api/engine/remote/http/wasm.rs +++ b/lib/src/api/engine/remote/http/wasm.rs @@ -94,7 +94,7 @@ pub(crate) fn router( route_rx: Receiver>, ) { spawn_local(async move { - let base_url = address.endpoint; + let base_url = address.url; let client = match client(&base_url).await { Ok(client) => { diff --git a/lib/src/api/engine/remote/ws/native.rs b/lib/src/api/engine/remote/ws/native.rs index 0d797317..036da875 100644 --- a/lib/src/api/engine/remote/ws/native.rs +++ b/lib/src/api/engine/remote/ws/native.rs @@ -104,7 +104,7 @@ impl Connection for Client { capacity: usize, ) -> Pin>> + Send + Sync + 'static>> { Box::pin(async move { - let url = address.endpoint.join(PATH)?; + let url = address.url.join(PATH)?; #[cfg(any(feature = "native-tls", feature = "rustls"))] let maybe_connector = address.config.tls_config.map(Connector::from); #[cfg(not(any(feature = "native-tls", feature = "rustls")))] diff --git a/lib/src/api/engine/remote/ws/wasm.rs b/lib/src/api/engine/remote/ws/wasm.rs index 83a88fac..7439d4c2 100644 --- a/lib/src/api/engine/remote/ws/wasm.rs +++ b/lib/src/api/engine/remote/ws/wasm.rs @@ -69,7 +69,7 @@ impl Connection for Client { capacity: usize, ) -> Pin>> + Send + Sync + 'static>> { Box::pin(async move { - address.endpoint = address.endpoint.join(PATH)?; + address.url = address.url.join(PATH)?; let (route_tx, route_rx) = match capacity { 0 => flume::unbounded(), @@ -118,7 +118,7 @@ pub(crate) fn router( route_rx: Receiver>, ) { spawn_local(async move { - let (mut ws, mut socket) = match WsMeta::connect(&address.endpoint, None).await { + let (mut ws, mut socket) = match WsMeta::connect(&address.url, None).await { Ok(pair) => pair, Err(error) => { let _ = conn_tx.into_send_async(Err(error.into())).await; @@ -330,7 +330,7 @@ pub(crate) fn router( 'reconnect: loop { trace!("Reconnecting..."); - match WsMeta::connect(&address.endpoint, None).await { + match WsMeta::connect(&address.url, None).await { Ok((mut meta, stream)) => { socket = stream; events = { diff --git a/lib/src/api/method/tests/protocol.rs b/lib/src/api/method/tests/protocol.rs index b51a9d4f..0b88bc37 100644 --- a/lib/src/api/method/tests/protocol.rs +++ b/lib/src/api/method/tests/protocol.rs @@ -30,7 +30,8 @@ impl IntoEndpoint for () { fn into_endpoint(self) -> Result { Ok(Endpoint { - endpoint: Url::parse("test://")?, + url: Url::parse("test://")?, + path: String::new(), config: Default::default(), }) } diff --git a/lib/src/api/opt/endpoint/fdb.rs b/lib/src/api/opt/endpoint/fdb.rs index 0739c0d3..5aecb569 100644 --- a/lib/src/api/opt/endpoint/fdb.rs +++ b/lib/src/api/opt/endpoint/fdb.rs @@ -1,6 +1,5 @@ use crate::api::engine::local::Db; use crate::api::engine::local::FDb; -use crate::api::err::Error; use crate::api::opt::Config; use crate::api::opt::Endpoint; use crate::api::opt::IntoEndpoint; @@ -16,9 +15,10 @@ macro_rules! endpoints { type Client = Db; fn into_endpoint(self) -> Result { - let url = super::make_url("fdb", self); + let protocol = "fdb://"; Ok(Endpoint { - endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + url: Url::parse(protocol).unwrap(), + path: super::path_to_string(protocol, self), config: Default::default(), }) } diff --git a/lib/src/api/opt/endpoint/http.rs b/lib/src/api/opt/endpoint/http.rs index f4adbbcb..66cf9957 100644 --- a/lib/src/api/opt/endpoint/http.rs +++ b/lib/src/api/opt/endpoint/http.rs @@ -18,7 +18,8 @@ macro_rules! endpoints { fn into_endpoint(self) -> Result { let url = format!("http://{self}"); Ok(Endpoint { - endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + url: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + path: String::new(), config: Default::default(), }) } @@ -40,7 +41,8 @@ macro_rules! endpoints { fn into_endpoint(self) -> Result { let url = format!("https://{self}"); Ok(Endpoint { - endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + url: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + path: String::new(), config: Default::default(), }) } diff --git a/lib/src/api/opt/endpoint/indxdb.rs b/lib/src/api/opt/endpoint/indxdb.rs index 1956db97..df840115 100644 --- a/lib/src/api/opt/endpoint/indxdb.rs +++ b/lib/src/api/opt/endpoint/indxdb.rs @@ -1,6 +1,5 @@ use crate::api::engine::local::Db; use crate::api::engine::local::IndxDb; -use crate::api::err::Error; use crate::api::opt::Config; use crate::api::opt::Endpoint; use crate::api::opt::IntoEndpoint; @@ -14,9 +13,10 @@ macro_rules! endpoints { type Client = Db; fn into_endpoint(self) -> Result { - let url = format!("indxdb://{self}"); + let protocol = "indxdb://"; Ok(Endpoint { - endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + url: Url::parse(protocol).unwrap(), + path: super::path_to_string(protocol, self), config: Default::default(), }) } diff --git a/lib/src/api/opt/endpoint/mem.rs b/lib/src/api/opt/endpoint/mem.rs index ef595450..0f2346e1 100644 --- a/lib/src/api/opt/endpoint/mem.rs +++ b/lib/src/api/opt/endpoint/mem.rs @@ -11,7 +11,8 @@ impl IntoEndpoint for () { fn into_endpoint(self) -> Result { Ok(Endpoint { - endpoint: Url::parse("mem://").unwrap(), + url: Url::parse("mem://").unwrap(), + path: "memory".to_owned(), config: Default::default(), }) } diff --git a/lib/src/api/opt/endpoint/mod.rs b/lib/src/api/opt/endpoint/mod.rs index 070870f7..6099d621 100644 --- a/lib/src/api/opt/endpoint/mod.rs +++ b/lib/src/api/opt/endpoint/mod.rs @@ -26,7 +26,8 @@ use super::Config; #[derive(Debug)] #[allow(dead_code)] // used by the embedded and remote connections pub struct Endpoint { - pub(crate) endpoint: Url, + pub(crate) url: Url, + pub(crate) path: String, pub(crate) config: Config, } @@ -38,7 +39,17 @@ pub trait IntoEndpoint { fn into_endpoint(self) -> Result; } -#[cfg(any(feature = "kv-fdb", feature = "kv-rocksdb", feature = "kv-speedb"))] -fn make_url(scheme: &str, path: impl AsRef) -> String { - format!("{scheme}://{}", path.as_ref().display()) +pub(crate) fn replace_tilde(path: &str) -> String { + let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_owned()); + path.replacen("://~", &format!("://{home}"), 1).replacen(":~", &format!(":{home}"), 1) +} + +#[allow(dead_code)] +fn path_to_string(protocol: &str, path: impl AsRef) -> String { + use path_clean::PathClean; + use std::path::Path; + + let path = format!("{protocol}{}", path.as_ref().display()); + let expanded = replace_tilde(&path); + Path::new(&expanded).clean().display().to_string() } diff --git a/lib/src/api/opt/endpoint/rocksdb.rs b/lib/src/api/opt/endpoint/rocksdb.rs index 19c75d06..b7b6fc34 100644 --- a/lib/src/api/opt/endpoint/rocksdb.rs +++ b/lib/src/api/opt/endpoint/rocksdb.rs @@ -1,7 +1,6 @@ use crate::api::engine::local::Db; use crate::api::engine::local::File; use crate::api::engine::local::RocksDb; -use crate::api::err::Error; use crate::api::opt::Config; use crate::api::opt::Endpoint; use crate::api::opt::IntoEndpoint; @@ -17,9 +16,10 @@ macro_rules! endpoints { type Client = Db; fn into_endpoint(self) -> Result { - let url = super::make_url("rocksdb", self); + let protocol = "rocksdb://"; Ok(Endpoint { - endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + url: Url::parse(protocol).unwrap(), + path: super::path_to_string(protocol, self), config: Default::default(), }) } @@ -39,9 +39,10 @@ macro_rules! endpoints { type Client = Db; fn into_endpoint(self) -> Result { - let url = super::make_url("file", self); + let protocol = "file://"; Ok(Endpoint { - endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + url: Url::parse(protocol).unwrap(), + path: super::path_to_string(protocol, self), config: Default::default(), }) } diff --git a/lib/src/api/opt/endpoint/speedb.rs b/lib/src/api/opt/endpoint/speedb.rs index f3a3d624..83ae2716 100644 --- a/lib/src/api/opt/endpoint/speedb.rs +++ b/lib/src/api/opt/endpoint/speedb.rs @@ -1,6 +1,5 @@ use crate::api::engine::local::Db; use crate::api::engine::local::SpeeDb; -use crate::api::err::Error; use crate::api::opt::Config; use crate::api::opt::Endpoint; use crate::api::opt::IntoEndpoint; @@ -16,9 +15,10 @@ macro_rules! endpoints { type Client = Db; fn into_endpoint(self) -> Result { - let url = super::make_url("speedb", self); + let protocol = "speedb://"; Ok(Endpoint { - endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + url: Url::parse(protocol).unwrap(), + path: super::path_to_string(protocol, self), config: Default::default(), }) } diff --git a/lib/src/api/opt/endpoint/tikv.rs b/lib/src/api/opt/endpoint/tikv.rs index 4854e191..84b72072 100644 --- a/lib/src/api/opt/endpoint/tikv.rs +++ b/lib/src/api/opt/endpoint/tikv.rs @@ -1,6 +1,5 @@ use crate::api::engine::local::Db; use crate::api::engine::local::TiKv; -use crate::api::err::Error; use crate::api::opt::Config; use crate::api::opt::Endpoint; use crate::api::opt::IntoEndpoint; @@ -17,7 +16,8 @@ macro_rules! endpoints { fn into_endpoint(self) -> Result { let url = format!("tikv://{self}"); Ok(Endpoint { - endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + url: Url::parse(&url).unwrap(), + path: url, config: Default::default(), }) } diff --git a/lib/src/api/opt/endpoint/ws.rs b/lib/src/api/opt/endpoint/ws.rs index ceaaed13..0d6f8387 100644 --- a/lib/src/api/opt/endpoint/ws.rs +++ b/lib/src/api/opt/endpoint/ws.rs @@ -18,7 +18,8 @@ macro_rules! endpoints { fn into_endpoint(self) -> Result { let url = format!("ws://{self}"); Ok(Endpoint { - endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + url: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + path: String::new(), config: Default::default(), }) } @@ -40,7 +41,8 @@ macro_rules! endpoints { fn into_endpoint(self) -> Result { let url = format!("wss://{self}"); Ok(Endpoint { - endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + url: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?, + path: String::new(), config: Default::default(), }) }