snix_build/buildservice/
from_addr.rs1use super::{BuildService, DummyBuildService, grpc::GRPCBuildService};
2use snix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
3use url::Url;
4
5#[cfg(target_os = "linux")]
6use super::oci::OCIBuildService;
7#[cfg(all(not(target_os = "linux"), doc))]
8struct OCIBuildService;
9
10#[cfg_attr(target_os = "macos", allow(unused_variables))]
20pub async fn from_addr<BS, DS>(
21 uri: &str,
22 blob_service: BS,
23 directory_service: DS,
24) -> std::io::Result<Box<dyn BuildService>>
25where
26 BS: BlobService + Send + Sync + Clone + 'static,
27 DS: DirectoryService + Send + Sync + Clone + 'static,
28{
29 let url =
30 Url::parse(uri).map_err(|e| std::io::Error::other(format!("unable to parse url: {e}")))?;
31
32 Ok(match url.scheme() {
33 "dummy" => Box::<DummyBuildService>::default(),
35 #[cfg(target_os = "linux")]
36 "oci" => {
37 if url.path().is_empty() {
39 Err(std::io::Error::other("oci needs a bundle dir as path"))?
40 }
41
42 Box::new(OCIBuildService::new(
45 url.path().into(),
46 blob_service,
47 directory_service,
48 ))
49 }
50 scheme => {
51 if scheme.starts_with("grpc+") {
52 let client = crate::proto::build_service_client::BuildServiceClient::new(
53 snix_castore::tonic::channel_from_url(&url)
54 .await
55 .map_err(std::io::Error::other)?,
56 );
57 Box::new(GRPCBuildService::from_client(client))
60 } else {
61 Err(std::io::Error::other(format!(
62 "unknown scheme: {}",
63 url.scheme()
64 )))?
65 }
66 }
67 })
68}
69
70#[cfg(test)]
71mod tests {
72 use super::from_addr;
73 use rstest::rstest;
74 use snix_castore::{
75 blobservice::{BlobService, MemoryBlobService},
76 directoryservice::{DirectoryService, MemoryDirectoryService},
77 };
78 use std::sync::Arc;
79 #[cfg(target_os = "linux")]
80 use std::sync::LazyLock;
81 #[cfg(target_os = "linux")]
82 use tempfile::TempDir;
83
84 #[cfg(target_os = "linux")]
85 static TMPDIR_OCI_1: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
86
87 #[rstest]
88 #[case::unsupported_scheme("http://foo.example/test", false)]
90 #[case::valid_dummy("dummy://", true)]
92 #[case::grpc_valid_unix_socket("grpc+unix:///path/to/somewhere", true)]
94 #[case::grpc_invalid_unix_socket_and_host("grpc+unix://host.example/path/to/somewhere", false)]
96 #[case::grpc_valid_ipv6_localhost_port_12345("grpc+http://[::1]:12345", true)]
98 #[case::grpc_valid_http_host_without_port("grpc+http://localhost", true)]
100 #[case::grpc_valid_https_host_without_port("grpc+https://localhost", true)]
102 #[case::grpc_invalid_host_and_path("grpc+http://localhost/some-path", false)]
104 #[cfg_attr(target_os = "linux", case::oci_missing_bundle_dir("oci://", false))]
106 #[cfg_attr(target_os = "linux", case::oci_bundle_path(&format!("oci://{}", TMPDIR_OCI_1.path().to_str().unwrap()), true))]
108 #[tokio::test]
109 async fn test_from_addr(#[case] uri_str: &str, #[case] exp_succeed: bool) {
110 let blob_service: Arc<dyn BlobService> = Arc::from(MemoryBlobService::default());
111 let directory_service: Arc<dyn DirectoryService> =
112 Arc::from(MemoryDirectoryService::default());
113
114 let resp = from_addr(uri_str, blob_service, directory_service).await;
115
116 if exp_succeed {
117 resp.expect("should succeed");
118 } else {
119 assert!(resp.is_err(), "should fail");
120 }
121 }
122}