use super::{grpc::GRPCBuildService, BuildService, DummyBuildService};
use tvix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
use url::Url;
#[cfg(target_os = "linux")]
use super::oci::OCIBuildService;
#[cfg_attr(target_os = "macos", allow(unused_variables))]
pub async fn from_addr<BS, DS>(
uri: &str,
blob_service: BS,
directory_service: DS,
) -> std::io::Result<Box<dyn BuildService>>
where
BS: BlobService + Send + Sync + Clone + 'static,
DS: DirectoryService + Send + Sync + Clone + 'static,
{
let url = Url::parse(uri)
.map_err(|e| std::io::Error::other(format!("unable to parse url: {}", e)))?;
Ok(match url.scheme() {
"dummy" => Box::<DummyBuildService>::default(),
#[cfg(target_os = "linux")]
"oci" => {
if url.path().is_empty() {
Err(std::io::Error::other("oci needs a bundle dir as path"))?
}
Box::new(OCIBuildService::new(
url.path().into(),
blob_service,
directory_service,
))
}
scheme => {
if scheme.starts_with("grpc+") {
let client = crate::proto::build_service_client::BuildServiceClient::new(
tvix_castore::tonic::channel_from_url(&url)
.await
.map_err(std::io::Error::other)?,
);
Box::new(GRPCBuildService::from_client(client))
} else {
Err(std::io::Error::other(format!(
"unknown scheme: {}",
url.scheme()
)))?
}
}
})
}
#[cfg(test)]
mod tests {
use super::from_addr;
use rstest::rstest;
use std::sync::Arc;
#[cfg(target_os = "linux")]
use std::sync::LazyLock;
#[cfg(target_os = "linux")]
use tempfile::TempDir;
use tvix_castore::{
blobservice::{BlobService, MemoryBlobService},
directoryservice::{DirectoryService, MemoryDirectoryService},
};
#[cfg(target_os = "linux")]
static TMPDIR_OCI_1: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
#[rstest]
#[case::unsupported_scheme("http://foo.example/test", false)]
#[case::valid_dummy("dummy://", true)]
#[case::grpc_valid_unix_socket("grpc+unix:///path/to/somewhere", true)]
#[case::grpc_invalid_unix_socket_and_host("grpc+unix://host.example/path/to/somewhere", false)]
#[case::grpc_valid_ipv6_localhost_port_12345("grpc+http://[::1]:12345", true)]
#[case::grpc_valid_http_host_without_port("grpc+http://localhost", true)]
#[case::grpc_valid_https_host_without_port("grpc+https://localhost", true)]
#[case::grpc_invalid_host_and_path("grpc+http://localhost/some-path", false)]
#[cfg_attr(target_os = "linux", case::oci_missing_bundle_dir("oci://", false))]
#[cfg_attr(target_os = "linux", case::oci_bundle_path(&format!("oci://{}", TMPDIR_OCI_1.path().to_str().unwrap()), true))]
#[tokio::test]
async fn test_from_addr(#[case] uri_str: &str, #[case] exp_succeed: bool) {
let blob_service: Arc<dyn BlobService> = Arc::from(MemoryBlobService::default());
let directory_service: Arc<dyn DirectoryService> =
Arc::from(MemoryDirectoryService::default());
let resp = from_addr(uri_str, blob_service, directory_service).await;
if exp_succeed {
resp.expect("should succeed");
} else {
assert!(resp.is_err(), "should fail");
}
}
}