use super::constraint_evaluator::Constraint;
use super::{Duty, OdrlAction};
use crate::ids::types::{IdsError, IdsResult, IdsUri, Party};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OdrlPolicy {
#[serde(rename = "@id")]
pub uid: IdsUri,
#[serde(rename = "@type")]
pub policy_type: PolicyType,
#[serde(rename = "@context", skip_serializing_if = "Option::is_none")]
pub context: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<String>,
#[serde(default)]
pub permissions: Vec<Permission>,
#[serde(default)]
pub prohibitions: Vec<Prohibition>,
#[serde(default)]
pub obligations: Vec<Obligation>,
#[serde(default)]
pub targets: Vec<AssetTarget>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assigner: Option<Party>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assignee: Option<Party>,
#[serde(skip_serializing_if = "Option::is_none")]
pub inherits_from: Option<IdsUri>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conflict: Option<ConflictStrategy>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PolicyType {
Set,
Offer,
Agreement,
Privacy,
Request,
Ticket,
Assertion,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Permission {
#[serde(rename = "@id", skip_serializing_if = "Option::is_none")]
pub uid: Option<IdsUri>,
pub action: OdrlAction,
#[serde(default)]
pub constraints: Vec<Constraint>,
#[serde(default)]
pub duties: Vec<Duty>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<AssetTarget>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assignee: Option<Party>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assigner: Option<Party>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Prohibition {
#[serde(rename = "@id", skip_serializing_if = "Option::is_none")]
pub uid: Option<IdsUri>,
pub action: OdrlAction,
#[serde(default)]
pub constraints: Vec<Constraint>,
#[serde(default)]
pub remedies: Vec<Duty>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<AssetTarget>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Obligation {
#[serde(rename = "@id", skip_serializing_if = "Option::is_none")]
pub uid: Option<IdsUri>,
pub action: OdrlAction,
#[serde(default)]
pub constraints: Vec<Constraint>,
#[serde(default)]
pub consequences: Vec<Duty>,
#[serde(default)]
pub compensations: Vec<Duty>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AssetTarget {
#[serde(rename = "@id")]
pub uid: IdsUri,
#[serde(rename = "@type", skip_serializing_if = "Option::is_none")]
pub asset_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum ConflictStrategy {
Perm,
Prohibit,
Invalid,
}
pub struct OdrlParser;
impl OdrlParser {
pub fn parse(json: &str) -> IdsResult<OdrlPolicy> {
serde_json::from_str(json).map_err(|e| {
IdsError::SerializationError(format!("Failed to parse ODRL policy: {}", e))
})
}
pub fn serialize(policy: &OdrlPolicy) -> IdsResult<String> {
serde_json::to_string_pretty(policy).map_err(|e| {
IdsError::SerializationError(format!("Failed to serialize ODRL policy: {}", e))
})
}
pub fn validate(policy: &OdrlPolicy) -> IdsResult<()> {
if policy.permissions.is_empty()
&& policy.prohibitions.is_empty()
&& policy.obligations.is_empty()
{
return Err(IdsError::PolicyViolation(
"Policy must have at least one permission, prohibition, or obligation".to_string(),
));
}
for perm in &policy.permissions {
if matches!(perm.action, OdrlAction::Custom(ref s) if s.is_empty()) {
return Err(IdsError::PolicyViolation(
"Custom action must have a non-empty URI".to_string(),
));
}
}
if policy.policy_type == PolicyType::Agreement
&& (policy.assigner.is_none() || policy.assignee.is_none())
{
return Err(IdsError::PolicyViolation(
"Agreement must have both assigner and assignee".to_string(),
));
}
Ok(())
}
pub fn create_read_permission(
uid: IdsUri,
assigner: Party,
assignee: Party,
target: AssetTarget,
) -> OdrlPolicy {
OdrlPolicy {
uid,
policy_type: PolicyType::Agreement,
context: Some(serde_json::json!("http://www.w3.org/ns/odrl.jsonld")),
profile: None,
permissions: vec![Permission {
uid: None,
action: OdrlAction::Read,
constraints: Vec::new(),
duties: Vec::new(),
target: Some(target.clone()),
assignee: Some(assignee.clone()),
assigner: Some(assigner.clone()),
}],
prohibitions: Vec::new(),
obligations: Vec::new(),
targets: vec![target],
assigner: Some(assigner),
assignee: Some(assignee),
inherits_from: None,
conflict: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_policy() {
let json = r#"{
"@id": "https://example.org/policy/read-only",
"@type": "Agreement",
"permissions": [{
"action": "read"
}]
}"#;
let policy = OdrlParser::parse(json).unwrap();
assert_eq!(policy.policy_type, PolicyType::Agreement);
assert_eq!(policy.permissions.len(), 1);
assert_eq!(policy.permissions[0].action, OdrlAction::Read);
}
#[test]
fn test_validate_policy() {
let policy = OdrlPolicy {
uid: IdsUri::new("https://example.org/policy/1").unwrap(),
policy_type: PolicyType::Set,
context: None,
profile: None,
permissions: vec![Permission {
uid: None,
action: OdrlAction::Read,
constraints: Vec::new(),
duties: Vec::new(),
target: None,
assignee: None,
assigner: None,
}],
prohibitions: Vec::new(),
obligations: Vec::new(),
targets: Vec::new(),
assigner: None,
assignee: None,
inherits_from: None,
conflict: None,
};
assert!(OdrlParser::validate(&policy).is_ok());
}
#[test]
fn test_validate_empty_policy() {
let policy = OdrlPolicy {
uid: IdsUri::new("https://example.org/policy/empty").unwrap(),
policy_type: PolicyType::Set,
context: None,
profile: None,
permissions: Vec::new(),
prohibitions: Vec::new(),
obligations: Vec::new(),
targets: Vec::new(),
assigner: None,
assignee: None,
inherits_from: None,
conflict: None,
};
assert!(OdrlParser::validate(&policy).is_err());
}
#[test]
fn test_create_read_permission() {
let assigner = Party {
id: IdsUri::new("https://provider.example.org").expect("valid uri"),
name: "Data Provider".to_string(),
legal_name: None,
description: None,
contact: None,
gaiax_participant_id: None,
};
let assignee = Party {
id: IdsUri::new("https://consumer.example.org").expect("valid uri"),
name: "Data Consumer".to_string(),
legal_name: None,
description: None,
contact: None,
gaiax_participant_id: None,
};
let target = AssetTarget {
uid: IdsUri::new("https://example.org/data/dataset1").unwrap(),
asset_type: None,
title: Some("Test Dataset".to_string()),
description: None,
};
let policy = OdrlParser::create_read_permission(
IdsUri::new("https://example.org/policy/read").unwrap(),
assigner,
assignee,
target,
);
assert_eq!(policy.policy_type, PolicyType::Agreement);
assert_eq!(policy.permissions.len(), 1);
assert_eq!(policy.permissions[0].action, OdrlAction::Read);
assert!(policy.assigner.is_some());
assert!(policy.assignee.is_some());
}
#[test]
fn test_roundtrip_serialization() {
let policy = OdrlPolicy {
uid: IdsUri::new("https://example.org/policy/test").unwrap(),
policy_type: PolicyType::Offer,
context: None,
profile: None,
permissions: vec![Permission {
uid: None,
action: OdrlAction::Use,
constraints: Vec::new(),
duties: Vec::new(),
target: None,
assignee: None,
assigner: None,
}],
prohibitions: Vec::new(),
obligations: Vec::new(),
targets: Vec::new(),
assigner: None,
assignee: None,
inherits_from: None,
conflict: None,
};
let json = OdrlParser::serialize(&policy).unwrap();
let parsed = OdrlParser::parse(&json).unwrap();
assert_eq!(parsed.uid, policy.uid);
assert_eq!(parsed.policy_type, policy.policy_type);
assert_eq!(parsed.permissions.len(), policy.permissions.len());
}
}