@@ -22,6 +22,23 @@ use async_trait::async_trait;
22
22
use iceberg:: { Catalog , CatalogBuilder , Error , ErrorKind , Result } ;
23
23
use iceberg_catalog_rest:: RestCatalogBuilder ;
24
24
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
+
25
42
#[ async_trait]
26
43
pub trait BoxedCatalogBuilder {
27
44
async fn load (
@@ -43,13 +60,45 @@ impl<T: CatalogBuilder + 'static> BoxedCatalogBuilder for T {
43
60
}
44
61
}
45
62
63
+ /// Load a catalog from a string.
46
64
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 (
50
73
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
53
102
}
54
103
}
55
104
@@ -59,7 +108,7 @@ mod tests {
59
108
60
109
use iceberg_catalog_rest:: REST_CATALOG_PROP_URI ;
61
110
62
- use crate :: load;
111
+ use crate :: { CatalogLoader , load} ;
63
112
64
113
#[ tokio:: test]
65
114
async fn test_load_rest_catalog ( ) {
@@ -79,4 +128,40 @@ mod tests {
79
128
80
129
assert ! ( catalog. is_ok( ) ) ;
81
130
}
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
+ }
82
167
}
0 commit comments