use async_trait::async_trait;
use crate::error::PodError;
#[cfg(feature = "tokio-runtime")]
use crate::storage::Storage;
use crate::wac::document::AclDocument;
#[cfg(feature = "tokio-runtime")]
use crate::wac::parse_jsonld_acl;
#[cfg(feature = "tokio-runtime")]
use crate::wac::parser::parse_turtle_acl;
#[async_trait]
pub trait AclResolver: Send + Sync {
async fn find_effective_acl(
&self,
resource_path: &str,
) -> Result<Option<AclDocument>, PodError>;
}
#[cfg(feature = "tokio-runtime")]
pub struct StorageAclResolver<S: Storage> {
storage: std::sync::Arc<S>,
}
#[cfg(feature = "tokio-runtime")]
impl<S: Storage> StorageAclResolver<S> {
pub fn new(storage: std::sync::Arc<S>) -> Self {
Self { storage }
}
}
#[cfg(feature = "tokio-runtime")]
#[async_trait]
impl<S: Storage> AclResolver for StorageAclResolver<S> {
async fn find_effective_acl(
&self,
resource_path: &str,
) -> Result<Option<AclDocument>, PodError> {
let mut path = resource_path.to_string();
loop {
let acl_key = if path == "/" {
"/.acl".to_string()
} else {
format!("{}.acl", path.trim_end_matches('/'))
};
if let Ok((body, meta)) = self.storage.get(&acl_key).await {
match parse_jsonld_acl(&body) {
Ok(doc) => return Ok(Some(doc)),
Err(PodError::BadRequest(_)) => {
return Err(PodError::BadRequest(
"ACL document exceeds bounds".into(),
));
}
Err(PodError::PayloadTooLarge(msg)) => {
return Err(PodError::PayloadTooLarge(msg));
}
Err(_) => {}
}
let ct = meta.content_type.to_ascii_lowercase();
let looks_turtle = ct.starts_with("text/turtle")
|| ct.starts_with("application/turtle")
|| ct.starts_with("application/x-turtle");
let text = std::str::from_utf8(&body).unwrap_or("");
if looks_turtle || text.contains("@prefix") || text.contains("acl:Authorization") {
if let Ok(doc) = parse_turtle_acl(text) {
return Ok(Some(doc));
}
}
}
if path == "/" || path.is_empty() {
break;
}
let trimmed = path.trim_end_matches('/');
path = match trimmed.rfind('/') {
Some(0) => "/".to_string(),
Some(pos) => trimmed[..pos].to_string(),
None => "/".to_string(),
};
}
Ok(None)
}
}