stateset_authz/
resource.rs1use std::fmt;
8
9use serde::{Deserialize, Serialize};
10
11use crate::PermissionLevel;
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub struct Resource {
27 resource_type: String,
28 resource_id: Option<String>,
29}
30
31impl Resource {
32 #[must_use]
34 pub fn new(resource_type: impl Into<String>) -> Self {
35 Self { resource_type: resource_type.into(), resource_id: None }
36 }
37
38 #[must_use]
40 pub fn with_id(resource_type: impl Into<String>, resource_id: impl Into<String>) -> Self {
41 Self { resource_type: resource_type.into(), resource_id: Some(resource_id.into()) }
42 }
43
44 #[must_use]
46 pub fn resource_type(&self) -> &str {
47 &self.resource_type
48 }
49
50 #[must_use]
52 pub fn resource_id(&self) -> Option<&str> {
53 self.resource_id.as_deref()
54 }
55}
56
57impl fmt::Display for Resource {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match &self.resource_id {
60 Some(id) => write!(f, "{}:{}", self.resource_type, id),
61 None => f.write_str(&self.resource_type),
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
79#[non_exhaustive]
80pub enum Action {
81 Create,
83 Read,
85 Update,
87 Delete,
89 List,
91 Execute,
93}
94
95impl Action {
96 #[must_use]
105 pub const fn required_permission(self) -> PermissionLevel {
106 match self {
107 Self::Read | Self::List => PermissionLevel::Read,
108 Self::Create | Self::Update | Self::Execute => PermissionLevel::Write,
109 Self::Delete => PermissionLevel::Delete,
110 }
111 }
112
113 #[must_use]
115 pub const fn all() -> &'static [Self] {
116 &[Self::Create, Self::Read, Self::Update, Self::Delete, Self::List, Self::Execute]
117 }
118}
119
120impl fmt::Display for Action {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 let s = match self {
123 Self::Create => "create",
124 Self::Read => "read",
125 Self::Update => "update",
126 Self::Delete => "delete",
127 Self::List => "list",
128 Self::Execute => "execute",
129 };
130 f.write_str(s)
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
141 fn resource_new_has_no_id() {
142 let r = Resource::new("orders");
143 assert_eq!(r.resource_type(), "orders");
144 assert!(r.resource_id().is_none());
145 }
146
147 #[test]
148 fn resource_with_id() {
149 let r = Resource::with_id("orders", "ord_123");
150 assert_eq!(r.resource_type(), "orders");
151 assert_eq!(r.resource_id(), Some("ord_123"));
152 }
153
154 #[test]
155 fn resource_display_no_id() {
156 let r = Resource::new("customers");
157 assert_eq!(r.to_string(), "customers");
158 }
159
160 #[test]
161 fn resource_display_with_id() {
162 let r = Resource::with_id("customers", "cust_42");
163 assert_eq!(r.to_string(), "customers:cust_42");
164 }
165
166 #[test]
167 fn resource_equality() {
168 let a = Resource::with_id("orders", "1");
169 let b = Resource::with_id("orders", "1");
170 let c = Resource::with_id("orders", "2");
171 assert_eq!(a, b);
172 assert_ne!(a, c);
173 }
174
175 #[test]
176 fn resource_serde_roundtrip() {
177 let r = Resource::with_id("products", "prod_99");
178 let json = serde_json::to_string(&r).unwrap();
179 let parsed: Resource = serde_json::from_str(&json).unwrap();
180 assert_eq!(parsed, r);
181 }
182
183 #[test]
186 fn action_required_permission_read_ops() {
187 assert_eq!(Action::Read.required_permission(), PermissionLevel::Read);
188 assert_eq!(Action::List.required_permission(), PermissionLevel::Read);
189 }
190
191 #[test]
192 fn action_required_permission_write_ops() {
193 assert_eq!(Action::Create.required_permission(), PermissionLevel::Write);
194 assert_eq!(Action::Update.required_permission(), PermissionLevel::Write);
195 assert_eq!(Action::Execute.required_permission(), PermissionLevel::Write);
196 }
197
198 #[test]
199 fn action_required_permission_delete() {
200 assert_eq!(Action::Delete.required_permission(), PermissionLevel::Delete);
201 }
202
203 #[test]
204 fn action_display() {
205 assert_eq!(Action::Create.to_string(), "create");
206 assert_eq!(Action::Read.to_string(), "read");
207 assert_eq!(Action::Update.to_string(), "update");
208 assert_eq!(Action::Delete.to_string(), "delete");
209 assert_eq!(Action::List.to_string(), "list");
210 assert_eq!(Action::Execute.to_string(), "execute");
211 }
212
213 #[test]
214 fn action_all_returns_six() {
215 assert_eq!(Action::all().len(), 6);
216 }
217
218 #[test]
219 fn action_serde_roundtrip() {
220 for &action in Action::all() {
221 let json = serde_json::to_string(&action).unwrap();
222 let parsed: Action = serde_json::from_str(&json).unwrap();
223 assert_eq!(parsed, action);
224 }
225 }
226}