Skip to content

Commit

Permalink
Implement tag namespace RemoteConfig property
Browse files Browse the repository at this point in the history
Signed-off-by: J Robert Ray <jrray@jrray.org>
  • Loading branch information
jrray committed Mar 6, 2024
1 parent 9be0a36 commit b9a33a1
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 10 deletions.
64 changes: 54 additions & 10 deletions crates/spfs/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ use std::sync::{Arc, RwLock};

use derive_builder::Builder;
use once_cell::sync::OnceCell;
use relative_path::RelativePath;
use serde::{Deserialize, Serialize};
use storage::{FromConfig, FromUrl};
use tokio_stream::StreamExt;

use crate::storage::TagNamespaceBuf;
use crate::storage::{TagNamespaceBuf, TagStorageMut};
use crate::{runtime, storage, tracking, Error, Result};

#[cfg(test)]
Expand Down Expand Up @@ -152,20 +153,35 @@ pub struct RemoteConfig {
#[builder(setter(strip_option), default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub when: Option<tracking::TimeSpec>,
#[builder(setter(strip_option), default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tag_namespace: Option<TagNamespaceBuf>,
#[serde(flatten)]
pub inner: RepositoryConfig,
}

impl ToAddress for RemoteConfig {
fn to_address(&self) -> Result<url::Url> {
let mut inner = self.inner.to_address()?;
if let Some(when) = &self.when {
let Self {
when,
tag_namespace,
inner,
} = self;
let mut inner = inner.to_address()?;
if let Some(when) = when {
let query = format!("when={when}");
match inner.query() {
None | Some("") => inner.set_query(Some(&query)),
Some(q) => inner.set_query(Some(&format!("{q}&{query}"))),
}
}
if let Some(tag_namespace) = tag_namespace {
let query = format!("tag_namespace={}", tag_namespace.as_rel_path());
match inner.query() {
None | Some("") => inner.set_query(Some(&query)),
Some(q) => inner.set_query(Some(&format!("{q}&{query}"))),
}
}
Ok(inner)
}
}
Expand Down Expand Up @@ -195,8 +211,14 @@ impl RemoteConfig {
pub async fn from_address(url: url::Url) -> Result<Self> {
let mut builder = RemoteConfigBuilder::default();
for (k, v) in url.query_pairs() {
if let "when" = k.as_ref() {
builder.when(tracking::TimeSpec::parse(v)?);
match k.as_ref() {
"when" => {
builder.when(tracking::TimeSpec::parse(v)?);
}
"tag_namespace" => {
builder.tag_namespace(TagNamespaceBuf::new(RelativePath::new(&v)));
}
_ => (),
}
}
let result = match url.scheme() {
Expand Down Expand Up @@ -233,7 +255,12 @@ impl RemoteConfig {

/// Open a handle to a repository using this configuration
pub async fn open(&self) -> storage::OpenRepositoryResult<storage::RepositoryHandle> {
let handle = match self.inner.clone() {
let Self {
when,
tag_namespace,
inner,
} = self;
let mut handle: storage::RepositoryHandle = match inner.clone() {
RepositoryConfig::Fs(config) => {
storage::fs::FsRepository::from_config(config).await?.into()
}
Expand All @@ -247,10 +274,27 @@ impl RemoteConfig {
.await?
.into(),
};
match &self.when {
None => Ok(handle),
Some(ts) => Ok(handle.into_pinned(ts.to_datetime_from_now())),
}
// Set tag namespace first before pinning, because it is not possible
// to set the tag namespace on a pinned handle.
let handle = match tag_namespace {
None => handle,
Some(tag_namespace) => {
handle
.try_set_tag_namespace(Some(tag_namespace.clone()))
.map_err(
|err| storage::OpenRepositoryError::FailedToSetTagNamespace {
tag_namespace: tag_namespace.clone(),
source: Box::new(err),
},
)?;
handle
}
};
let handle = match when {
None => handle,
Some(ts) => handle.into_pinned(ts.to_datetime_from_now()),
};
Ok(handle)
}
}

Expand Down
19 changes: 19 additions & 0 deletions crates/spfs/src/config_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ async fn test_remote_config_pinned_from_address() {
)
}

#[rstest]
#[tokio::test]
async fn test_remote_config_with_tag_namespace_from_address() {
let address =
url::Url::parse("http2://test.local?lazy=true&tag_namespace=ns").expect("a valid url");
let config = RemoteConfig::from_address(address)
.await
.expect("can parse address with 'tag_namespace' query");
let repo = config
.open()
.await
.expect("should open repo address with tag namespace");
assert_eq!(
repo.get_tag_namespace().unwrap().as_rel_path(),
"ns",
"using a tag_namespace query should create a repo with a tag namespace"
)
}

static ENV_MUTEX: once_cell::sync::Lazy<std::sync::Mutex<()>> =
once_cell::sync::Lazy::new(|| std::sync::Mutex::new(()));

Expand Down
8 changes: 8 additions & 0 deletions crates/spfs/src/storage/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// https://github.com/imageworks/spk

use super::TagNamespaceBuf;

#[derive(Debug, miette::Diagnostic, thiserror::Error)]
#[diagnostic()]
pub enum OpenRepositoryError {
Expand Down Expand Up @@ -90,6 +92,12 @@ pub enum OpenRepositoryError {
},
#[error("Pinned repository is read only")]
RepositoryIsPinned,

#[error("Failed to set tag namespace '{tag_namespace}'")]
FailedToSetTagNamespace {
tag_namespace: TagNamespaceBuf,
source: Box<dyn miette::Diagnostic + Send + Sync>,
},
}

impl OpenRepositoryError {
Expand Down
7 changes: 7 additions & 0 deletions crates/spfs/src/storage/tag_namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub const TAG_NAMESPACE_MARKER: &str = "#ns";

/// A borrowed tag namespace name
#[repr(transparent)]
#[derive(Debug)]
pub struct TagNamespace(RelativePath);

impl TagNamespace {
Expand Down Expand Up @@ -57,6 +58,12 @@ impl std::borrow::Borrow<TagNamespace> for TagNamespaceBuf {
}
}

impl std::fmt::Display for TagNamespaceBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

impl std::ops::Deref for TagNamespaceBuf {
type Target = TagNamespace;

Expand Down

0 comments on commit b9a33a1

Please sign in to comment.