agentguard_core/
schema.rs1use crate::error::{Error, Result};
5use cedar_policy::Schema;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone)]
10pub struct SchemaParsed {
11 pub schema: cedar_policy::Schema,
12 pub source: String,
13}
14
15pub fn parse_schema(src: &str) -> Result<cedar_policy::Schema> {
17 let (s, _w) = cedar_policy::Schema::from_cedarschema_str(src)
18 .map_err(|e| Error::Schema(e.to_string()))?;
19 Ok(s)
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct EntityDef {
24 pub name: String,
25 pub kind: EntityKind,
26 pub attributes: Vec<AttrDef>,
27}
28
29#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
30#[serde(rename_all = "snake_case")]
31pub enum EntityKind {
32 Entity,
33 Action,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct AttrDef {
38 pub name: String,
39 pub ty: String,
40 pub required: bool,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct SchemaSummary {
45 pub entities: Vec<EntityDef>,
46 pub actions: Vec<EntityDef>,
47}
48
49pub fn describe(src: &str) -> Result<SchemaSummary> {
51 let schema = parse_schema(src)?;
52 Ok(build_summary(&schema))
53}
54
55fn build_summary(schema: &Schema) -> SchemaSummary {
56 let mut entities = Vec::new();
57 for et in schema.entity_types() {
58 entities.push(entity_def(et));
59 }
60 let mut actions = Vec::new();
61 for a in schema.actions() {
62 actions.push(action_def(a));
63 }
64 SchemaSummary { entities, actions }
65}
66
67fn entity_def(et: &cedar_policy::EntityTypeName) -> EntityDef {
68 EntityDef {
73 name: et.to_string(),
74 kind: EntityKind::Entity,
75 attributes: vec![],
76 }
77}
78
79fn action_def(a: &cedar_policy::EntityUid) -> EntityDef {
80 let raw = a.to_string();
84 let name = raw
85 .strip_prefix("Action::\"")
86 .and_then(|s| s.strip_suffix('"'))
87 .unwrap_or(&raw)
88 .to_string();
89 EntityDef {
90 name,
91 kind: EntityKind::Action,
92 attributes: vec![],
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn describe_extracts_entity_and_action_names() {
102 let src = r#"
103entity User;
104entity Mailbox;
105action "ToolCall::send_email" appliesTo { principal: [User], resource: [Mailbox] };
106action "ToolCall::read_doc" appliesTo { principal: [User], resource: [Mailbox] };
107"#;
108 let s = describe(src).unwrap();
109 let names: Vec<&str> = s.entities.iter().map(|e| e.name.as_str()).collect();
110 assert!(names.contains(&"User"));
111 assert!(names.contains(&"Mailbox"));
112 let actions: Vec<&str> = s.actions.iter().map(|a| a.name.as_str()).collect();
113 assert!(actions.contains(&"ToolCall::send_email"));
114 assert!(actions.contains(&"ToolCall::read_doc"));
115 }
116
117 #[test]
118 fn describe_handles_malformed_schema() {
119 let res = describe("not a valid schema");
122 assert!(res.is_err());
123 }
124}