use async_trait::async_trait;
use lance_namespace::models::{DescribeTableRequest, DescribeTableResponse};
use lance_namespace::{Error as NamespaceError, LanceNamespace, Result};
#[derive(Debug, Clone)]
pub struct DirNamespace {
base_uri: String,
}
impl DirNamespace {
pub fn new(base_uri: impl Into<String>) -> Self {
let uri = base_uri.into();
let clean_uri = uri.trim_end_matches('/').to_string();
Self {
base_uri: clean_uri,
}
}
pub fn base_uri(&self) -> &str {
&self.base_uri
}
}
#[async_trait]
impl LanceNamespace for DirNamespace {
fn namespace_id(&self) -> String {
format!("DirNamespace {{ base_uri: '{}' }}", self.base_uri)
}
async fn describe_table(&self, request: DescribeTableRequest) -> Result<DescribeTableResponse> {
let id = request.id.ok_or_else(|| {
NamespaceError::invalid_input(
"DirNamespace requires the table identifier to be provided",
)
})?;
if id.len() != 1 {
return Err(NamespaceError::invalid_input(format!(
"DirNamespace expects identifiers with a single component, got {:?}",
id
)));
}
let table_name = &id[0];
let location = format!("{}/{}.lance", self.base_uri, table_name);
let mut response = DescribeTableResponse::new();
response.location = Some(location);
response.storage_options = None;
Ok(response)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn describe_table_returns_clean_location() {
let namespace = DirNamespace::new("s3://bucket/path/");
let mut request = DescribeTableRequest::new();
request.id = Some(vec!["users".to_string()]);
let response = namespace.describe_table(request).await.unwrap();
assert_eq!(
response.location.as_deref(),
Some("s3://bucket/path/users.lance")
);
}
#[tokio::test]
async fn describe_table_rejects_missing_identifier() {
let namespace = DirNamespace::new("file:///tmp");
let request = DescribeTableRequest::new();
let err = namespace.describe_table(request).await.unwrap_err();
assert!(
err.to_string()
.contains("DirNamespace requires the table identifier"),
"unexpected error: {err}"
);
}
#[tokio::test]
async fn describe_table_rejects_multi_component_identifier() {
let namespace = DirNamespace::new("memory://namespace");
let mut request = DescribeTableRequest::new();
request.id = Some(vec!["foo".into(), "bar".into()]);
let err = namespace.describe_table(request).await.unwrap_err();
assert!(
err.to_string()
.contains("expects identifiers with a single component"),
"unexpected error: {err}"
);
}
}