ironflow_store/entities/
api_key_scope.rs1use std::fmt;
4use std::str::FromStr;
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum ApiKeyScope {
15 WorkflowsRead,
17 RunsRead,
19 RunsWrite,
21 RunsManage,
23 StatsRead,
25 Admin,
27}
28
29impl ApiKeyScope {
30 pub fn permits(&self, required: &ApiKeyScope) -> bool {
32 match self {
33 ApiKeyScope::Admin => true,
34 other => other == required,
35 }
36 }
37
38 pub fn has_permission(scopes: &[ApiKeyScope], required: &ApiKeyScope) -> bool {
40 scopes.iter().any(|s| s.permits(required))
41 }
42
43 pub fn all_non_admin() -> Vec<ApiKeyScope> {
45 vec![
46 ApiKeyScope::WorkflowsRead,
47 ApiKeyScope::RunsRead,
48 ApiKeyScope::RunsWrite,
49 ApiKeyScope::RunsManage,
50 ApiKeyScope::StatsRead,
51 ]
52 }
53}
54
55impl fmt::Display for ApiKeyScope {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 let s = match self {
58 ApiKeyScope::WorkflowsRead => "workflows_read",
59 ApiKeyScope::RunsRead => "runs_read",
60 ApiKeyScope::RunsWrite => "runs_write",
61 ApiKeyScope::RunsManage => "runs_manage",
62 ApiKeyScope::StatsRead => "stats_read",
63 ApiKeyScope::Admin => "admin",
64 };
65 f.write_str(s)
66 }
67}
68
69#[derive(Debug, Clone)]
71pub struct InvalidScope(pub String);
72
73impl fmt::Display for InvalidScope {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 write!(f, "invalid API key scope: {}", self.0)
76 }
77}
78
79impl std::error::Error for InvalidScope {}
80
81impl FromStr for ApiKeyScope {
82 type Err = InvalidScope;
83
84 fn from_str(s: &str) -> Result<Self, Self::Err> {
85 match s {
86 "workflows_read" => Ok(ApiKeyScope::WorkflowsRead),
87 "runs_read" => Ok(ApiKeyScope::RunsRead),
88 "runs_write" => Ok(ApiKeyScope::RunsWrite),
89 "runs_manage" => Ok(ApiKeyScope::RunsManage),
90 "stats_read" => Ok(ApiKeyScope::StatsRead),
91 "admin" => Ok(ApiKeyScope::Admin),
92 _ => Err(InvalidScope(s.to_string())),
93 }
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn admin_permits_everything() {
103 let admin = ApiKeyScope::Admin;
104 assert!(admin.permits(&ApiKeyScope::RunsRead));
105 assert!(admin.permits(&ApiKeyScope::RunsWrite));
106 assert!(admin.permits(&ApiKeyScope::RunsManage));
107 assert!(admin.permits(&ApiKeyScope::WorkflowsRead));
108 assert!(admin.permits(&ApiKeyScope::StatsRead));
109 assert!(admin.permits(&ApiKeyScope::Admin));
110 }
111
112 #[test]
113 fn regular_scope_only_permits_itself() {
114 let scope = ApiKeyScope::RunsRead;
115 assert!(scope.permits(&ApiKeyScope::RunsRead));
116 assert!(!scope.permits(&ApiKeyScope::RunsWrite));
117 assert!(!scope.permits(&ApiKeyScope::Admin));
118 }
119
120 #[test]
121 fn has_permission_with_multiple_scopes() {
122 let scopes = vec![ApiKeyScope::RunsRead, ApiKeyScope::WorkflowsRead];
123 assert!(ApiKeyScope::has_permission(&scopes, &ApiKeyScope::RunsRead));
124 assert!(ApiKeyScope::has_permission(
125 &scopes,
126 &ApiKeyScope::WorkflowsRead
127 ));
128 assert!(!ApiKeyScope::has_permission(
129 &scopes,
130 &ApiKeyScope::RunsWrite
131 ));
132 }
133
134 #[test]
135 fn roundtrip_display_parse() {
136 let scopes = vec![
137 ApiKeyScope::WorkflowsRead,
138 ApiKeyScope::RunsRead,
139 ApiKeyScope::RunsWrite,
140 ApiKeyScope::RunsManage,
141 ApiKeyScope::StatsRead,
142 ApiKeyScope::Admin,
143 ];
144 for scope in scopes {
145 let s = scope.to_string();
146 let parsed: ApiKeyScope = s.parse().expect("should parse");
147 assert_eq!(parsed, scope);
148 }
149 }
150
151 #[test]
152 fn parse_invalid_scope() {
153 let result = "invalid".parse::<ApiKeyScope>();
154 assert!(result.is_err());
155 }
156
157 #[test]
158 fn serde_roundtrip() {
159 let scope = ApiKeyScope::RunsWrite;
160 let json = serde_json::to_string(&scope).expect("serialize");
161 assert_eq!(json, "\"runs_write\"");
162 let parsed: ApiKeyScope = serde_json::from_str(&json).expect("deserialize");
163 assert_eq!(parsed, scope);
164 }
165
166 #[test]
167 fn all_non_admin_excludes_admin() {
168 let scopes = ApiKeyScope::all_non_admin();
169 assert!(!scopes.contains(&ApiKeyScope::Admin));
170 assert_eq!(scopes.len(), 5);
171 }
172}