use crate::generator::{generate, AciGenerator, GenerateError};
use crate::parser::{parse, AciFormat, ParseError};
use std::fmt;
use std::marker::PhantomData;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum TransformError {
ParseError(String),
GenerateError(String),
ConversionError(String),
}
impl fmt::Display for TransformError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TransformError::ParseError(msg) => write!(f, "Parse error: {}", msg),
TransformError::GenerateError(msg) => write!(f, "Generation error: {}", msg),
TransformError::ConversionError(msg) => write!(f, "Conversion error: {}", msg),
}
}
}
impl std::error::Error for TransformError {}
impl From<ParseError> for TransformError {
fn from(e: ParseError) -> Self {
TransformError::ParseError(e.to_string())
}
}
impl From<GenerateError> for TransformError {
fn from(e: GenerateError) -> Self {
TransformError::GenerateError(e.to_string())
}
}
pub fn transform<Source, Target>(input: &str) -> Result<String, TransformError>
where
Source: AciFormat,
Target: AciGenerator,
{
let acis = parse::<Source>(input)?;
let output = generate::<Target>(&acis)?;
Ok(output)
}
pub struct TransformBuilder<Source, Target> {
_source: PhantomData<Source>,
_target: PhantomData<Target>,
}
impl<Source, Target> TransformBuilder<Source, Target>
where
Source: AciFormat,
Target: AciGenerator,
{
pub fn new() -> Self {
Self {
_source: PhantomData,
_target: PhantomData,
}
}
pub fn transform(&self, input: &str) -> Result<String, TransformError> {
transform::<Source, Target>(input)
}
pub fn transform_batch(&self, inputs: &[&str]) -> Result<String, TransformError> {
let mut all_acis = Vec::new();
for input in inputs {
let acis = parse::<Source>(input)?;
all_acis.extend(acis);
}
let output = generate::<Target>(&all_acis)?;
Ok(output)
}
}
impl<Source, Target> Default for TransformBuilder<Source, Target>
where
Source: AciFormat,
Target: AciGenerator,
{
fn default() -> Self {
Self::new()
}
}
pub use crate::parser::Ds389;
pub use crate::parser::OpenLdap;
pub use crate::generator::Ds389 as Ds389Gen;
pub use crate::generator::OpenLdap as OpenLdapGen;
#[cfg(test)]
mod tests {
use super::*;
use crate::{AciBuilder, BindRule, OperationType};
#[test]
fn test_transform_trait_ds389_to_openldap() {
let input = r#"(targetattr = "cn || mail")(version 3.0;acl "read public";allow (read,search) userdn = "ldap:///anyone";)"#;
let result = transform::<Ds389, OpenLdapGen>(input).expect("test should succeed");
assert!(result.contains("attrs=cn,mail") || result.contains("attrs=mail,cn"));
assert!(result.contains("by *"));
assert!(result.contains("read"));
}
#[test]
fn test_transform_trait_openldap_to_ds389() {
let input = r#"to dn.subtree="ou=people,dc=example,dc=com" attrs=cn,mail
by self write
by * read"#;
let result = transform::<OpenLdap, Ds389Gen>(input).expect("test should succeed");
assert!(result.contains("version 3.0"));
assert!(result.contains("targetattr"));
assert!(result.contains("cn || mail") || result.contains("mail || cn"));
}
#[test]
fn test_transform_builder() {
let input =
r#"(targetattr = "uid")(version 3.0;acl "test";allow (read) userdn = "ldap:///all";)"#;
let builder = TransformBuilder::<Ds389, OpenLdapGen>::new();
let result = builder.transform(input).expect("test should succeed");
assert!(result.contains("attrs=uid"));
assert!(result.contains("by users"));
}
#[test]
fn test_transform_builder_batch() {
let inputs = vec![
r#"(targetattr = "cn")(version 3.0;acl "aci1";allow (read) userdn = "ldap:///anyone";)"#,
r#"(targetattr = "mail")(version 3.0;acl "aci2";allow (write) userdn = "ldap:///self";)"#,
];
let builder = TransformBuilder::<Ds389, OpenLdapGen>::new();
let result = builder
.transform_batch(&inputs)
.expect("test should succeed");
assert!(result.contains("cn"));
assert!(result.contains("mail"));
assert!(result.contains("by *"));
assert!(result.contains("by self"));
}
#[test]
fn test_round_trip_ds389_openldap() {
let original =
r#"(targetattr = "cn")(version 3.0;acl "test";allow (read) userdn = "ldap:///all";)"#;
let openldap = transform::<Ds389, OpenLdapGen>(original).expect("test should succeed");
let back_to_ds389 =
transform::<OpenLdap, Ds389Gen>(&openldap).expect("test should succeed");
assert!(back_to_ds389.contains("version 3.0"));
assert!(back_to_ds389.contains("targetattr"));
assert!(back_to_ds389.contains("cn"));
assert!(back_to_ds389.contains("read"));
}
#[test]
fn test_batch_transform() {
let acis = vec![
r#"(targetattr = "cn")(version 3.0;acl "aci1";allow (read) userdn = "ldap:///anyone";)"#,
r#"(targetattr = "mail")(version 3.0;acl "aci2";allow (write) userdn = "ldap:///self";)"#,
];
let builder = TransformBuilder::<Ds389, OpenLdapGen>::new();
let result = builder.transform_batch(&acis).expect("test should succeed");
assert!(result.contains("cn"));
assert!(result.contains("mail"));
assert!(result.contains("by *"));
assert!(result.contains("by self"));
}
#[test]
fn test_anonymous_read_aci_transform() {
let aci = AciBuilder::new("System: Read Users")
.target_attribute("cn")
.permission(OperationType::Read)
.bind_rule(BindRule::Anyone)
.build();
let result = generate::<Ds389Gen>(&[aci]).expect("test should succeed");
assert!(result.contains("version 3.0"));
assert!(result.contains("userdn = \"ldap:///anyone\""));
}
}