#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
#![cfg(feature = "glue-catalog")]
use crate::catalog::CatalogError;
use crate::catalog::rest_catalog_wrapper::KrishivRestCatalog;
#[derive(Debug, Clone)]
pub struct GlueCatalog {
inner: KrishivRestCatalog,
region: String,
database: String,
}
impl GlueCatalog {
pub async fn new(
region: &str,
database: &str,
catalog_id: Option<&str>,
) -> Result<Self, CatalogError> {
let iceberg_uri = format!("https://glue.{region}.amazonaws.com/iceberg/");
let warehouse = match catalog_id {
Some(id) => format!("{id}:{database}"),
None => database.to_owned(),
};
let inner = KrishivRestCatalog::new(&iceberg_uri, &warehouse, None).await?;
Ok(Self {
inner,
region: region.to_owned(),
database: database.to_owned(),
})
}
pub async fn from_env() -> Result<Self, CatalogError> {
let region = std::env::var("AWS_REGION")
.or_else(|_| std::env::var("AWS_DEFAULT_REGION"))
.map_err(|_| CatalogError::InvalidConfiguration {
message:
"AWS_REGION or AWS_DEFAULT_REGION must be set for the Glue catalog backend"
.into(),
})?;
let database = std::env::var("KRISHIV_GLUE_DATABASE").unwrap_or_else(|_| "default".into());
let catalog_id = std::env::var("KRISHIV_GLUE_CATALOG_ID").ok();
Self::new(®ion, &database, catalog_id.as_deref()).await
}
pub fn region(&self) -> &str {
&self.region
}
pub fn database(&self) -> &str {
&self.database
}
pub fn as_rest_catalog(&self) -> &KrishivRestCatalog {
&self.inner
}
}
impl std::ops::Deref for GlueCatalog {
type Target = KrishivRestCatalog;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_env_fails_without_region() {
unsafe {
std::env::remove_var("AWS_REGION");
std::env::remove_var("AWS_DEFAULT_REGION");
}
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let result = rt.block_on(GlueCatalog::from_env());
assert!(result.is_err());
let err = result.unwrap_err();
match err {
CatalogError::InvalidConfiguration { message } => {
assert!(message.contains("AWS_REGION"), "{message}");
}
other => panic!("expected InvalidConfiguration, got: {other}"),
}
}
#[test]
fn database_default_is_default() {
unsafe {
std::env::set_var("AWS_REGION", "us-east-1");
std::env::remove_var("KRISHIV_GLUE_DATABASE");
std::env::remove_var("KRISHIV_GLUE_CATALOG_ID");
}
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let catalog = rt
.block_on(GlueCatalog::from_env())
.expect("construction must succeed");
assert_eq!(catalog.database(), "default");
assert_eq!(catalog.region(), "us-east-1");
unsafe {
std::env::remove_var("AWS_REGION");
}
}
#[test]
fn glue_endpoint_includes_region() {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let catalog = rt
.block_on(GlueCatalog::new("eu-west-1", "my_db", None))
.expect("construction must succeed");
assert_eq!(catalog.region(), "eu-west-1");
assert_eq!(catalog.database(), "my_db");
}
}