use std::sync::Arc;
use bytes::Bytes;
use crate::object_store::{ObjectMeta, ObjectStore, ObjectStoreError, PutOpts};
use crate::protocol::backend::{self, BackendError};
use crate::url::{ParseError, RemoteUrl, StorageEngine};
pub struct Remote {
store: Arc<dyn ObjectStore>,
prefix: String,
engine: StorageEngine,
}
impl Remote {
pub async fn connect(url_str: &str) -> Result<Self, RemoteError> {
let url = url_str.parse::<RemoteUrl>()?;
Ok(Self::open(&url).await?)
}
pub async fn open(url: &RemoteUrl) -> Result<Self, BackendError> {
let (store, engine) = backend::build(url).await?;
let prefix = url.prefix().unwrap_or_default().to_owned();
Ok(Self {
store,
prefix,
engine,
})
}
#[must_use]
pub fn key(&self, suffix: &str) -> String {
crate::keys::join(Some(&self.prefix), suffix)
}
#[must_use]
pub fn store(&self) -> &dyn ObjectStore {
&*self.store
}
#[cfg(any(test, feature = "test-util"))]
#[must_use]
pub fn new_for_test(
store: Arc<dyn ObjectStore>,
prefix: impl Into<String>,
engine: StorageEngine,
) -> Self {
Self {
store,
prefix: prefix.into(),
engine,
}
}
#[must_use]
pub fn prefix(&self) -> &str {
&self.prefix
}
#[must_use]
pub fn engine(&self) -> StorageEngine {
self.engine
}
pub async fn get_head(&self) -> Result<Bytes, ObjectStoreError> {
self.store.get_bytes(&self.key("HEAD")).await
}
pub async fn put_head(&self, content: Bytes) -> Result<(), ObjectStoreError> {
self.store
.put_bytes(&self.key("HEAD"), content, PutOpts::default())
.await
}
pub async fn list(&self, suffix: &str) -> Result<Vec<ObjectMeta>, ObjectStoreError> {
self.store.list(&self.key(suffix)).await
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_remote(prefix: &str) -> Remote {
Remote {
store: Arc::new(crate::object_store::mock::MockStore::new()),
prefix: prefix.to_owned(),
engine: StorageEngine::Bundle,
}
}
#[test]
fn key_with_prefix_joins_with_slash() {
let remote = make_remote("my-repo");
assert_eq!(remote.key("HEAD"), "my-repo/HEAD");
assert_eq!(remote.key("refs/heads/main/"), "my-repo/refs/heads/main/");
}
#[test]
fn key_without_prefix_returns_suffix_only() {
let remote = make_remote("");
assert_eq!(remote.key("HEAD"), "HEAD");
assert_eq!(remote.key("refs/heads/main/"), "refs/heads/main/");
}
#[test]
fn prefix_reflects_construction_value() {
assert_eq!(make_remote("my-repo").prefix(), "my-repo");
assert_eq!(make_remote("").prefix(), "");
}
}
#[derive(Debug, thiserror::Error)]
pub enum RemoteError {
#[error(transparent)]
Url(#[from] ParseError),
#[error(transparent)]
Backend(#[from] BackendError),
}