use cedar_policy::Schema;
use std::path::Path;
use super::error::{CedarError, CedarResult};
pub fn load_schema(path: impl AsRef<Path>) -> CedarResult<Schema> {
let path = path.as_ref();
let content = std::fs::read_to_string(path).map_err(|e| {
CedarError::SchemaParse(format!("Failed to read schema file {:?}: {}", path, e))
})?;
parse_schema(&content).map_err(|e| {
CedarError::SchemaParse(format!(
"Failed to parse schema file {:?}: {}",
path, e
))
})
}
pub fn parse_schema(src: &str) -> CedarResult<Schema> {
src.parse()
.map_err(|e| CedarError::SchemaParse(format!("{}", e)))
}
pub fn validate_policies(
schema: &Schema,
policies: &cedar_policy::PolicySet,
) -> CedarResult<()> {
let validator = cedar_policy::Validator::new(schema.clone());
let result = validator.validate(policies, cedar_policy::ValidationMode::default());
if result.validation_passed() {
Ok(())
} else {
let errors: Vec<String> = result
.validation_errors()
.map(|e| format!("{}", e))
.collect();
let warnings: Vec<String> = result
.validation_warnings()
.map(|w| format!("{}", w))
.collect();
let mut msg = String::new();
if !errors.is_empty() {
msg.push_str(&format!("Errors:\n{}", errors.join("\n")));
}
if !warnings.is_empty() {
if !msg.is_empty() {
msg.push('\n');
}
msg.push_str(&format!("Warnings:\n{}", warnings.join("\n")));
}
Err(CedarError::Validation(msg))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_schema() {
let schema_str = r#"
entity User = {
email?: String,
role?: String,
};
entity Document = {
status: String,
owner?: String,
};
action View, Edit, Delete appliesTo {
principal: [User],
resource: [Document],
};
"#;
let schema = parse_schema(schema_str).expect("Schema should parse");
drop(schema);
}
#[test]
fn test_parse_namespaced_schema() {
let schema_str = r#"
namespace MyService {
entity User = {
email?: String,
};
entity Resource = {
name: String,
};
action View appliesTo {
principal: [User],
resource: [Resource],
};
}
"#;
let schema = parse_schema(schema_str).expect("Namespaced schema should parse");
drop(schema);
}
#[test]
fn test_parse_minimal_schema() {
let schema_str = r#"
entity User;
"#;
let schema = parse_schema(schema_str).expect("Minimal schema should parse");
drop(schema);
}
#[test]
fn test_parse_invalid_schema_fails() {
let result = parse_schema("this is not a valid schema!!!");
assert!(result.is_err());
}
#[test]
fn test_validate_policies_against_schema() {
let schema_str = r#"
entity User = {
role?: String,
};
entity Doc = {};
action View appliesTo {
principal: [User],
resource: [Doc],
};
"#;
let schema = parse_schema(schema_str).unwrap();
let policy_str = r#"
permit(
principal == User::"alice",
action == Action::"View",
resource == Doc::"doc1"
);
"#;
let policies: cedar_policy::PolicySet = policy_str.parse().unwrap();
assert!(validate_policies(&schema, &policies).is_ok());
}
#[test]
fn test_load_nonexistent_schema_fails() {
let result = load_schema("/nonexistent/path/schema.cedarschema");
assert!(result.is_err());
}
}