snix_castore/directoryservice/
from_addr.rs

1use std::sync::Arc;
2
3use url::Url;
4
5use crate::composition::{
6    CompositionContext, DeserializeWithRegistry, REG, ServiceBuilder, with_registry,
7};
8
9use super::DirectoryService;
10
11/// Constructs a new instance of a [DirectoryService] from an URI.
12///
13/// The following URIs are supported:
14/// - `redb+memory:`
15///   Uses a in-memory implementation.
16/// - `redb:///absolute/path/to/somewhere`
17///   Uses redb, using a path on the disk for persistency. Can be only opened
18///   from one process at the same time.
19/// - `grpc+unix:///absolute/path/to/somewhere`
20///   Connects to a local snix-store gRPC service via Unix socket.
21/// - `grpc+http://host:port`, `grpc+https://host:port`
22///   Connects to a (remote) snix-store gRPC service.
23pub async fn from_addr(
24    uri: &str,
25) -> Result<Arc<dyn DirectoryService>, Box<dyn std::error::Error + Send + Sync>> {
26    #[allow(unused_mut)]
27    let mut url = Url::parse(uri).map_err(|e| format!("unable to parse url: {e}"))?;
28
29    let directory_service_config = with_registry(&REG, || {
30        <DeserializeWithRegistry<Box<dyn ServiceBuilder<Output = dyn DirectoryService>>>>::try_from(
31            url,
32        )
33    })?
34    .0;
35    let directory_service = directory_service_config
36        .build("anonymous", &CompositionContext::blank(&REG))
37        .await?;
38
39    Ok(directory_service)
40}
41
42#[cfg(test)]
43mod tests {
44    use std::sync::LazyLock;
45
46    use super::from_addr;
47    use rstest::rstest;
48    use tempfile::TempDir;
49
50    static TMPDIR_REDB_1: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
51    static TMPDIR_REDB_2: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
52
53    #[rstest]
54    /// This uses an unsupported scheme.
55    #[case::unsupported_scheme("http://foo.example/test", false)]
56    /// This configures redb without a path, which should fail.
57    #[case::redb_invalid_missing_path("redb://", false)]
58    /// This configures redb with /, which should fail.
59    #[case::redb_invalid_root("redb:///", false)]
60    /// This configures redb with a host, not path, which should fail.
61    #[case::redb_invalid_host("redb://foo.example", false)]
62    /// This configures redb with a valid path, which should succeed.
63    #[case::redb_valid_path(&format!("redb://{}", &TMPDIR_REDB_1.path().join("foo").to_str().unwrap()), true)]
64    /// This configures redb with a host, and a valid path path, which should fail.
65    #[case::redb_invalid_host_with_valid_path(&format!("redb://foo.example{}", &TMPDIR_REDB_2.path().join("bar").to_str().unwrap()), false)]
66    /// This configures redb in-memory.
67    #[case::redb_memory_valid("redb+memory:", true)]
68    /// This configures redb in-memory, but wrongly adds a path.
69    #[case::redb_memory_invalid_path("redb+memory:/foo/bar", false)]
70    /// This configures redb in-memory, but wrongly adds authority.
71    #[case::redb_memory_invalid_authority("redb+memory://", false)]
72    /// This configures redb in-memory, but wrongly adds a path (with authority).
73    #[case::redb_memory_invalid_authority_path("redb+memory:///foo/bar", false)]
74    /// Correct scheme to connect to a unix socket.
75    #[case::grpc_valid_unix_socket("grpc+unix:///path/to/somewhere", true)]
76    /// Correct scheme for unix socket, but setting a host too, which is invalid.
77    #[case::grpc_invalid_unix_socket_and_host("grpc+unix://host.example/path/to/somewhere", false)]
78    /// Correct scheme to connect to localhost, with port 12345
79    #[case::grpc_valid_ipv6_localhost_port_12345("grpc+http://[::1]:12345", true)]
80    /// Correct scheme to connect to localhost over http, without specifying a port.
81    #[case::grpc_valid_http_host_without_port("grpc+http://localhost", true)]
82    /// Correct scheme to connect to localhost over http, without specifying a port.
83    #[case::grpc_valid_https_host_without_port("grpc+https://localhost", true)]
84    /// Correct scheme to connect to localhost over http, but with additional path, which is invalid.
85    #[case::grpc_invalid_host_and_path("grpc+http://localhost/some-path", false)]
86    /// A valid example for store composition using anonymous urls
87    #[cfg_attr(
88        feature = "xp-composition-url-refs",
89        case::anonymous_url_composition("cache://?near=redb%2bmemory:&far=redb%2bmemory:", true)
90    )]
91    /// Store composition with anonymous urls should fail if the feature is disabled
92    #[cfg_attr(
93        not(feature = "xp-composition-url-refs"),
94        case::anonymous_url_composition("cache://?near=redb%2bmemory:&far=redb%2bmemory:", false)
95    )]
96    /// A valid example for Bigtable
97    #[cfg_attr(
98        all(feature = "cloud", feature = "integration"),
99        case::bigtable_valid_url(
100            "bigtable://instance-1?project_id=project-1&table_name=table-1&family_name=cf1",
101            true
102        )
103    )]
104    /// A valid example for Bigtable, specifying a custom channel size and timeout
105    #[cfg_attr(
106        all(feature = "cloud", feature = "integration"),
107        case::bigtable_valid_url(
108            "bigtable://instance-1?project_id=project-1&table_name=table-1&family_name=cf1&channel_size=10&timeout=10",
109            true
110        )
111    )]
112    /// A invalid Bigtable example (missing fields)
113    #[cfg_attr(
114        all(feature = "cloud", feature = "integration"),
115        case::bigtable_invalid_url("bigtable://instance-1", false)
116    )]
117    #[tokio::test]
118    async fn test_from_addr_tokio(#[case] uri_str: &str, #[case] exp_succeed: bool) {
119        if exp_succeed {
120            from_addr(uri_str).await.expect("should succeed");
121        } else {
122            assert!(from_addr(uri_str).await.is_err(), "should fail");
123        }
124    }
125}