surrealdb_core/iam/entities/resources/
level.rs

1use revision::revisioned;
2use std::{
3	collections::{HashMap, HashSet},
4	str::FromStr,
5};
6
7use cedar_policy::{Entity, EntityTypeName, EntityUid, RestrictedExpression};
8use serde::{Deserialize, Serialize};
9
10#[revisioned(revision = 1)]
11#[derive(Clone, Default, Debug, Eq, PartialEq, PartialOrd, Deserialize, Serialize, Hash)]
12#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
13#[non_exhaustive]
14pub enum Level {
15	#[default]
16	No,
17	Root,
18	Namespace(String),
19	Database(String, String),
20	Record(String, String, String),
21}
22
23impl std::fmt::Display for Level {
24	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25		match self {
26			Level::No => write!(f, "No"),
27			Level::Root => write!(f, "/"),
28			Level::Namespace(ns) => write!(f, "/ns:{ns}/"),
29			Level::Database(ns, db) => write!(f, "/ns:{ns}/db:{db}/"),
30			Level::Record(ns, db, id) => write!(f, "/ns:{ns}/db:{db}/id:{id}/"),
31		}
32	}
33}
34
35impl Level {
36	pub fn level_name(&self) -> &str {
37		match self {
38			Level::No => "No",
39			Level::Root => "Root",
40			Level::Namespace(_) => "Namespace",
41			Level::Database(_, _) => "Database",
42			Level::Record(_, _, _) => "Record",
43		}
44	}
45
46	pub fn ns(&self) -> Option<&str> {
47		match self {
48			Level::Namespace(ns) => Some(ns),
49			Level::Database(ns, _) => Some(ns),
50			Level::Record(ns, _, _) => Some(ns),
51			_ => None,
52		}
53	}
54
55	pub fn db(&self) -> Option<&str> {
56		match self {
57			Level::Database(_, db) => Some(db),
58			Level::Record(_, db, _) => Some(db),
59			_ => None,
60		}
61	}
62
63	pub fn id(&self) -> Option<&str> {
64		match self {
65			Level::Record(_, _, id) => Some(id),
66			_ => None,
67		}
68	}
69
70	fn parent(&self) -> Option<Level> {
71		match self {
72			Level::No => None,
73			Level::Root => None,
74			Level::Namespace(_) => Some(Level::Root),
75			Level::Database(ns, _) => Some(Level::Namespace(ns.to_owned())),
76			Level::Record(ns, db, _) => Some(Level::Database(ns.to_owned(), db.to_owned())),
77		}
78	}
79
80	// Cedar policy helpers
81	pub fn cedar_attrs(&self) -> HashMap<String, RestrictedExpression> {
82		let mut attrs = HashMap::with_capacity(5);
83		attrs.insert("type".into(), RestrictedExpression::new_string(self.level_name().to_owned()));
84
85		if let Some(ns) = self.ns() {
86			attrs.insert("ns".into(), RestrictedExpression::new_string(ns.to_owned()));
87		}
88
89		if let Some(db) = self.db() {
90			attrs.insert("db".into(), RestrictedExpression::new_string(db.to_owned()));
91		}
92
93		if let Some(id) = self.id() {
94			attrs.insert("id".into(), RestrictedExpression::new_string(id.to_owned()));
95		}
96
97		attrs
98	}
99
100	pub fn cedar_parents(&self) -> HashSet<EntityUid> {
101		if let Some(parent) = self.parent() {
102			return HashSet::from([parent.into()]);
103		}
104		HashSet::with_capacity(0)
105	}
106
107	pub fn cedar_entities(&self) -> Vec<Entity> {
108		let mut entities = Vec::new();
109
110		entities.push(self.into());
111
112		// Find all the parents
113		let mut parent = self.parent();
114		while let Some(p) = parent {
115			parent = p.parent();
116			entities.push(p.into());
117		}
118
119		entities
120	}
121}
122
123impl From<()> for Level {
124	fn from(_: ()) -> Self {
125		Level::Root
126	}
127}
128
129impl From<(&str,)> for Level {
130	fn from((ns,): (&str,)) -> Self {
131		Level::Namespace(ns.to_owned())
132	}
133}
134
135impl From<(&str, &str)> for Level {
136	fn from((ns, db): (&str, &str)) -> Self {
137		Level::Database(ns.to_owned(), db.to_owned())
138	}
139}
140
141impl From<(&str, &str, &str)> for Level {
142	fn from((ns, db, id): (&str, &str, &str)) -> Self {
143		Level::Record(ns.to_owned(), db.to_owned(), id.to_owned())
144	}
145}
146
147impl From<(Option<&str>, Option<&str>, Option<&str>)> for Level {
148	fn from(val: (Option<&str>, Option<&str>, Option<&str>)) -> Self {
149		match val {
150			(None, None, None) => ().into(),
151			(Some(ns), None, None) => (ns,).into(),
152			(Some(ns), Some(db), None) => (ns, db).into(),
153			(Some(ns), Some(db), Some(id)) => (ns, db, id).into(),
154			_ => Level::No,
155		}
156	}
157}
158
159impl std::convert::From<Level> for EntityUid {
160	fn from(level: Level) -> Self {
161		EntityUid::from_type_name_and_id(
162			EntityTypeName::from_str("Level").unwrap(),
163			format!("{}", level).parse().unwrap(),
164		)
165	}
166}
167
168impl std::convert::From<&Level> for EntityUid {
169	fn from(level: &Level) -> Self {
170		level.to_owned().into()
171	}
172}
173
174impl std::convert::From<Level> for Entity {
175	fn from(level: Level) -> Self {
176		Entity::new(level.to_owned().into(), level.cedar_attrs(), level.cedar_parents())
177	}
178}
179
180impl std::convert::From<&Level> for Entity {
181	fn from(level: &Level) -> Self {
182		level.to_owned().into()
183	}
184}
185
186impl std::convert::From<Level> for RestrictedExpression {
187	fn from(level: Level) -> Self {
188		format!("{}", EntityUid::from(level)).parse().unwrap()
189	}
190}
191
192impl std::convert::From<&Level> for RestrictedExpression {
193	fn from(level: &Level) -> Self {
194		level.to_owned().into()
195	}
196}