Skip to content

Commit 83c6f3a

Browse files
committed
feat(catalog): catalog registry introspection and clearer error messages
- Add supported_types() to list registry entries - Include supported types in unsupported-type error
1 parent bc469c3 commit 83c6f3a

File tree

1 file changed

+91
-6
lines changed
  • crates/catalog/loader/src

1 file changed

+91
-6
lines changed

crates/catalog/loader/src/lib.rs

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@ use async_trait::async_trait;
2222
use iceberg::{Catalog, CatalogBuilder, Error, ErrorKind, Result};
2323
use iceberg_catalog_rest::RestCatalogBuilder;
2424

25+
/// A CatalogBuilderFactory creating a new catalog builder.
26+
type CatalogBuilderFactory = fn() -> Box<dyn BoxedCatalogBuilder>;
27+
28+
// A registry of catalog builders.
29+
fn rest_factory() -> Box<dyn BoxedCatalogBuilder> {
30+
Box::new(RestCatalogBuilder::default())
31+
}
32+
33+
/// A registry of catalog builders.
34+
static CATALOG_REGISTRY: &[(&str, CatalogBuilderFactory)] =
35+
&[("rest", rest_factory as CatalogBuilderFactory)];
36+
37+
/// Return the list of supported catalog types.
38+
pub fn supported_types() -> Vec<&'static str> {
39+
CATALOG_REGISTRY.iter().map(|(k, _)| *k).collect()
40+
}
41+
2542
#[async_trait]
2643
pub trait BoxedCatalogBuilder {
2744
async fn load(
@@ -43,13 +60,45 @@ impl<T: CatalogBuilder + 'static> BoxedCatalogBuilder for T {
4360
}
4461
}
4562

63+
/// Load a catalog from a string.
4664
pub fn load(r#type: &str) -> Result<Box<dyn BoxedCatalogBuilder>> {
47-
match r#type {
48-
"rest" => Ok(Box::new(RestCatalogBuilder::default()) as Box<dyn BoxedCatalogBuilder>),
49-
_ => Err(Error::new(
65+
let key = r#type.trim();
66+
if let Some((_, factory)) = CATALOG_REGISTRY
67+
.iter()
68+
.find(|(k, _)| k.eq_ignore_ascii_case(key))
69+
{
70+
Ok(factory())
71+
} else {
72+
Err(Error::new(
5073
ErrorKind::FeatureUnsupported,
51-
format!("Unsupported catalog type: {}", r#type),
52-
)),
74+
format!(
75+
"Unsupported catalog type: {}. Supported types: {}",
76+
r#type,
77+
supported_types().join(", ")
78+
),
79+
))
80+
}
81+
}
82+
83+
/// Ergonomic catalog loader builder pattern.
84+
pub struct CatalogLoader<'a> {
85+
catalog_type: &'a str,
86+
}
87+
88+
impl<'a> From<&'a str> for CatalogLoader<'a> {
89+
fn from(s: &'a str) -> Self {
90+
Self { catalog_type: s }
91+
}
92+
}
93+
94+
impl CatalogLoader<'_> {
95+
pub async fn load(
96+
self,
97+
name: String,
98+
props: HashMap<String, String>,
99+
) -> Result<Arc<dyn Catalog>> {
100+
let builder = load(self.catalog_type)?;
101+
builder.load(name, props).await
53102
}
54103
}
55104

@@ -59,7 +108,7 @@ mod tests {
59108

60109
use iceberg_catalog_rest::REST_CATALOG_PROP_URI;
61110

62-
use crate::load;
111+
use crate::{CatalogLoader, load};
63112

64113
#[tokio::test]
65114
async fn test_load_rest_catalog() {
@@ -79,4 +128,40 @@ mod tests {
79128

80129
assert!(catalog.is_ok());
81130
}
131+
132+
#[tokio::test]
133+
async fn test_load_unsupported_catalog() {
134+
let result = load("unsupported");
135+
assert!(result.is_err());
136+
}
137+
138+
#[tokio::test]
139+
async fn test_catalog_loader_pattern() {
140+
let catalog = CatalogLoader::from("rest")
141+
.load(
142+
"rest".to_string(),
143+
HashMap::from([
144+
(
145+
REST_CATALOG_PROP_URI.to_string(),
146+
"http://localhost:8080".to_string(),
147+
),
148+
("key".to_string(), "value".to_string()),
149+
]),
150+
)
151+
.await;
152+
153+
assert!(catalog.is_ok());
154+
}
155+
156+
#[tokio::test]
157+
async fn test_error_message_includes_supported_types() {
158+
let err = match load("does-not-exist") {
159+
Ok(_) => panic!("expected error for unsupported type"),
160+
Err(e) => e,
161+
};
162+
let msg = err.message().to_string();
163+
assert!(msg.contains("Supported types:"));
164+
// Should include at least the built-in type
165+
assert!(msg.contains("rest"));
166+
}
82167
}

0 commit comments

Comments
 (0)