Skip to main content

typesec_odrl/
model.rs

1//! Serde data model for ODRL YAML policies.
2
3use std::fmt;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7/// Root document: a collection of ODRL policies.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct OdrlDocument {
10    /// The policies in this document.
11    pub policies: Vec<OdrlPolicy>,
12}
13
14impl OdrlDocument {
15    /// Parse from YAML.
16    pub fn from_yaml(yaml: &str) -> Result<Self, serde_yaml::Error> {
17        serde_yaml::from_str(yaml)
18    }
19}
20
21/// An ODRL Policy.
22///
23/// A policy bundles related rules under a unique identifier.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct OdrlPolicy {
26    /// Unique identifier (e.g., `"policy:ai-agent-001"`).
27    pub uid: String,
28    /// Policy type — `Set`, `Offer`, or `Agreement`.
29    #[serde(rename = "type")]
30    pub policy_type: String,
31    /// The rules in this policy.
32    pub rules: Vec<OdrlRule>,
33}
34
35/// An ODRL Rule (permission, prohibition, or duty).
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct OdrlRule {
38    /// Rule type: `"permission"`, `"prohibition"`, or `"duty"`.
39    #[serde(rename = "type")]
40    pub rule_type: OdrlRuleType,
41    /// The party granting permission (optional for prohibitions).
42    #[serde(default)]
43    pub assigner: Option<String>,
44    /// The party the rule applies to.
45    pub assignee: String,
46    /// The action this rule covers.
47    pub action: RuleAction,
48    /// The asset this rule applies to.
49    pub target: String,
50    /// Constraints that must hold for the rule to apply.
51    #[serde(default)]
52    pub constraints: Vec<OdrlConstraint>,
53}
54
55/// The type of an ODRL rule.
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "lowercase")]
58pub enum OdrlRuleType {
59    /// Grants the assignee the action on the target (if constraints hold).
60    Permission,
61    /// Denies the assignee the action on the target (if constraints hold).
62    Prohibition,
63    /// Obligates the assignee to perform the action.
64    Duty,
65}
66
67/// An ODRL action.
68///
69/// Maps to our `Permission::name()` strings plus the special `"use"` wildcard.
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(rename_all = "lowercase")]
72pub enum RuleAction {
73    /// Read access.
74    Read,
75    /// Write access.
76    Write,
77    /// Delete access.
78    Delete,
79    /// Execute access.
80    Execute,
81    /// Delegation.
82    Delegate,
83    /// Read internal data.
84    #[serde(rename = "read_internal")]
85    ReadInternal,
86    /// Read sensitive data.
87    #[serde(rename = "read_sensitive")]
88    ReadSensitive,
89    /// Write sensitive data.
90    #[serde(rename = "write_sensitive")]
91    WriteSensitive,
92    /// AI inference.
93    #[serde(rename = "ai:infer")]
94    AiInfer,
95    /// AI training.
96    #[serde(rename = "ai:train")]
97    AiTrain,
98    /// Data exfiltration.
99    #[serde(rename = "exfiltrate")]
100    Exfiltrate,
101    /// Wildcard — applies to all actions.
102    Use,
103}
104
105impl RuleAction {
106    /// Convert to the `Permission::name()` string.
107    pub fn as_permission_name(&self) -> &str {
108        match self {
109            RuleAction::Read => "read",
110            RuleAction::Write => "write",
111            RuleAction::Delete => "delete",
112            RuleAction::Execute => "execute",
113            RuleAction::Delegate => "delegate",
114            RuleAction::ReadInternal => "read_internal",
115            RuleAction::ReadSensitive => "read_sensitive",
116            RuleAction::WriteSensitive => "write_sensitive",
117            RuleAction::AiInfer => "ai:infer",
118            RuleAction::AiTrain => "ai:train",
119            RuleAction::Exfiltrate => "ai:exfiltrate",
120            RuleAction::Use => "*",
121        }
122    }
123
124    /// Returns `true` if this action matches the given permission name.
125    pub fn matches_action(&self, action: &str) -> bool {
126        self == &RuleAction::Use || self.as_permission_name() == action
127    }
128}
129
130/// An ODRL constraint on a rule.
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct OdrlConstraint {
133    /// The left operand (e.g., `"purpose"`, `"dateTime"`, `"count"`).
134    #[serde(rename = "leftOperand")]
135    pub left_operand: ConstraintOperand,
136    /// The comparison operator.
137    pub operator: ConstraintOperator,
138    /// The right operand value (string representation).
139    #[serde(rename = "rightOperand")]
140    pub right_operand: String,
141}
142
143/// Typed ODRL constraint operands supported by TypeSec.
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub enum ConstraintOperand {
146    /// The request purpose.
147    Purpose,
148    /// The request timestamp; accepts `dateTime` and legacy `date` in YAML.
149    DateTime,
150    /// A count value supplied in the custom request context.
151    Count,
152    /// An extension operand supplied through custom request context.
153    Custom(String),
154}
155
156impl ConstraintOperand {
157    /// Parse an ODRL operand name.
158    pub fn parse(name: impl Into<String>) -> Self {
159        let name = name.into();
160        match name.as_str() {
161            "purpose" => Self::Purpose,
162            "dateTime" | "date" => Self::DateTime,
163            "count" => Self::Count,
164            _ => Self::Custom(name),
165        }
166    }
167
168    /// Return the canonical ODRL operand name.
169    pub fn as_str(&self) -> &str {
170        match self {
171            Self::Purpose => "purpose",
172            Self::DateTime => "dateTime",
173            Self::Count => "count",
174            Self::Custom(name) => name,
175        }
176    }
177}
178
179impl fmt::Display for ConstraintOperand {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        f.write_str(self.as_str())
182    }
183}
184
185impl Serialize for ConstraintOperand {
186    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
187    where
188        S: Serializer,
189    {
190        serializer.serialize_str(self.as_str())
191    }
192}
193
194impl<'de> Deserialize<'de> for ConstraintOperand {
195    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
196    where
197        D: Deserializer<'de>,
198    {
199        String::deserialize(deserializer).map(Self::parse)
200    }
201}
202
203/// ODRL constraint operators.
204#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
205#[serde(rename_all = "camelCase")]
206pub enum ConstraintOperator {
207    /// Equal.
208    Eq,
209    /// Not equal.
210    Neq,
211    /// Less than.
212    Lt,
213    /// Less than or equal.
214    Lteq,
215    /// Greater than.
216    Gt,
217    /// Greater than or equal.
218    Gteq,
219    /// Is in a comma-separated list.
220    #[serde(rename = "isPartOf")]
221    IsPartOf,
222}