1use std::fmt;
7
8use serde_json as json;
9
10use super::*;
11
12impl Error {
13 pub fn is_unauthenticated(&self) -> bool {
14 self.error_code == UNAUTHENTICATED
15 }
16
17 pub fn is_unauthorized(&self) -> bool {
18 self.error_code == NOT_AUTHORIZED
19 }
20
21 pub fn is_volume_not_found(&self) -> bool {
22 self.error_code == VOLUME_NOT_FOUND
23 }
24
25 pub fn is_cluster_not_found(&self) -> bool {
26 self.error_code == CLUSTER_NOT_FOUND
27 }
28
29 pub fn cluster_is_state_owner(&self) -> bool {
30 self.error_code == CLUSTER_IS_STATE_OWNER
31 }
32
33 pub fn state_already_exists(&self) -> bool {
34 self.error_code == STATE_ALREADY_EXISTS
35 }
36
37 pub fn unknown_error(status: http::StatusCode, bytes: &[u8]) -> Self {
38 let http_code = status.as_u16();
39 let http_status = status
40 .canonical_reason()
41 .unwrap_or_else(|| status.as_str())
42 .to_string();
43
44 let (error_code, msg, request_id) = match json::from_slice::<json::Value>(bytes) {
45 Ok(ref value) => (
46 get(value, "errorCode"),
47 get(value, "msg"),
48 get(value, "requestId"),
49 ),
50 Err(e) => {
51 let msg = format!("{e}\nCheck your config file (~/.statehub/config.toml by default) for valid api server url");
52 (None, Some(msg), None)
53 }
54 };
55 let error_code = error_code.unwrap_or_else(|| http_status.clone());
56
57 Self {
58 http_code,
59 http_status,
60 error_code,
61 msg,
62 request_id,
63 }
64 }
65}
66
67fn get(value: &json::Value, index: &str) -> Option<String> {
68 value.get(index)?.as_str().map(String::from)
69}
70
71impl fmt::Display for Error {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 let request_id = self
74 .request_id
75 .as_ref()
76 .map(|request_id| format!("\nRequest id: {request_id}"))
77 .unwrap_or_default();
78 let msg = self
79 .msg
80 .as_ref()
81 .map(|msg| format!(": {msg}"))
82 .unwrap_or_default();
83 let error_code = &self.error_code;
84 format!("{error_code}{msg}{request_id}").fmt(f)
85 }
86}
87
88impl Permission {
89 pub fn as_str(&self) -> &'static str {
90 match self {
91 Self::ReadClusters => "read:clusters",
92 Self::CreateClusters => "create:clusters",
93 Self::DeleteClusters => "delete:clusters",
94 Self::CreateClusterToken => "create:cluster_token",
95 Self::ReadClusterToken => "read:cluster_token",
96 Self::DeleteClusterToken => "delete:cluster_token",
97 Self::ReadClusterLocations => "read:cluster_locations",
98 Self::UpdateClusterLocations => "update:cluster_locations",
99 Self::ReadStates => "read:states",
100 Self::CreateStates => "create:states",
101 Self::DeleteStates => "delete:states",
102 Self::CreateStateOwner => "create:state_owner",
103 Self::DeleteStateOwner => "delete:state_owner",
104 Self::CreateStateLocations => "create:state_locations",
105 Self::ReadStateLocations => "read:state_locations",
106 Self::DeleteStateLocations => "delete:state_locations",
107 Self::ReadStateLocationPrincipals => "read:state_location_principals",
108 Self::CreateStateLocationPrincipals => "create:state_location_principals",
109 Self::UpdateStateLocationPle => "update:state_location_ple",
110 Self::ReadVolumes => "read:volumes",
111 Self::CreateVolumes => "create:volumes",
112 Self::DeleteVolumes => "delete:volumes",
113 Self::UpdateVolumeActiveLocation => "update:volume_active_location",
114 Self::DeleteVolumeActiveLocation => "delete:volume_active_location",
115 Self::ReadOrganization => "read:organization",
116 Self::UpdateOrganization => "update:organization",
117 Self::ReadOrganizationRole => "read:organization_roles",
118 Self::ReadPersonalTokens => "read:personal_tokens",
119 Self::CreatePersonalTokens => "create:personal_tokens",
120 Self::UpdatePersonalTokens => "update:personal_tokens",
121 Self::DeletePersonalTokens => "delete:personal_tokens",
122 Self::ReadInvitations => "read:invitations",
123 Self::CreateInvitations => "create:invitations",
124 Self::UpdateInvitations => "update:invitations",
125 Self::DeleteInvitations => "delete:invitations",
126 Self::ReadMembers => "read:members",
127 Self::CreateMembers => "create:members",
128 Self::UpdateMembers => "update:members",
129 Self::DeleteMembers => "delete:members",
130 Self::ReadProfile => "read:profile",
131 Self::UpdateProfile => "update:profile",
132 }
133 }
134}
135
136impl fmt::Display for Permission {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 self.as_str().fmt(f)
139 }
140}
141
142#[derive(Debug, Error)]
143#[error(r#"Invalid permission "{permission}"#)]
144pub struct InvalidPermission {
145 pub permission: String,
146}
147
148impl InvalidPermission {
149 pub(crate) fn new(permission: &str) -> Self {
150 let permission = permission.to_string();
151 Self { permission }
152 }
153}
154
155impl str::FromStr for Permission {
156 type Err = InvalidPermission;
157
158 fn from_str(text: &str) -> Result<Self, Self::Err> {
159 match text {
160 "read:clusters" => Ok(Self::ReadClusters),
161 "create:clusters" => Ok(Self::CreateClusters),
162 "delete:clusters" => Ok(Self::DeleteClusters),
163 "create:cluster_token" => Ok(Self::CreateClusterToken),
164 "read:cluster_token" => Ok(Self::ReadClusterToken),
165 "delete:cluster_token" => Ok(Self::DeleteClusterToken),
166 "read:cluster_locations" => Ok(Self::ReadClusterLocations),
167 "update:cluster_locations" => Ok(Self::UpdateClusterLocations),
168 "read:states" => Ok(Self::ReadStates),
169 "create:states" => Ok(Self::CreateStates),
170 "delete:states" => Ok(Self::DeleteStates),
171 "create:state_owner" => Ok(Self::CreateStateOwner),
172 "delete:state_owner" => Ok(Self::DeleteStateOwner),
173 "create:state_locations" => Ok(Self::CreateStateLocations),
174 "read:state_locations" => Ok(Self::ReadStateLocations),
175 "delete:state_locations" => Ok(Self::DeleteStateLocations),
176 "read:state_location_principals" => Ok(Self::ReadStateLocationPrincipals),
177 "create:state_location_principals" => Ok(Self::CreateStateLocationPrincipals),
178 "update:state_location_ple" => Ok(Self::UpdateStateLocationPle),
179 "read:volumes" => Ok(Self::ReadVolumes),
180 "create:volumes" => Ok(Self::CreateVolumes),
181 "delete:volumes" => Ok(Self::DeleteVolumes),
182 "update:volume_active_location" => Ok(Self::UpdateVolumeActiveLocation),
183 "delete:volume_active_location" => Ok(Self::DeleteVolumeActiveLocation),
184 "read:organization" => Ok(Self::ReadOrganization),
185 "update:organization" => Ok(Self::UpdateOrganization),
186 "read:organization_roles" => Ok(Self::ReadOrganizationRole),
187 "read:personal_tokens" => Ok(Self::ReadPersonalTokens),
188 "create:personal_tokens" => Ok(Self::CreatePersonalTokens),
189 "update:personal_tokens" => Ok(Self::UpdatePersonalTokens),
190 "delete:personal_tokens" => Ok(Self::DeletePersonalTokens),
191 "read:invitations" => Ok(Self::ReadInvitations),
192 "create:invitations" => Ok(Self::CreateInvitations),
193 "update:invitations" => Ok(Self::UpdateInvitations),
194 "delete:invitations" => Ok(Self::DeleteInvitations),
195 "read:members" => Ok(Self::ReadMembers),
196 "create:members" => Ok(Self::CreateMembers),
197 "update:members" => Ok(Self::UpdateMembers),
198 "delete:members" => Ok(Self::DeleteMembers),
199 "read:profile" => Ok(Self::ReadProfile),
200 "update:profile" => Ok(Self::UpdateProfile),
201 other => Err(InvalidPermission::new(other)),
202 }
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use serde_json as json;
209
210 use super::*;
211
212 #[test]
213 fn invalid_token() {
214 let text = r#"{"httpCode":401,"httpStatus":"Unauthorized","errorCode":"UnauthenticatedError","msg":"","requestId":"e4900455157d450e9a5285153b99225c"}"#;
215 let err: Error = json::from_str(text).unwrap();
216 assert!(err.is_unauthenticated());
217 }
218
219 #[test]
220 fn cluster_not_authorized1() {
221 let text = r#"{"httpCode":403,"httpStatus":"Forbidden","errorCode":"NotAuthorizedError","msg":"Not authorized to perform undefined","requestId":"e4900455157d450e9a5285153b99225c"}"#;
222 let err: Error = json::from_str(text).unwrap();
223 assert!(err.is_unauthorized());
224 }
225
226 #[test]
227 fn cluster_not_found() {
228 let text = r#"{"httpCode":404,"httpStatus":"Not Found","errorCode":"ClusterNotFoundError","msg":"Cluster undefined not found","requestId":"e4900455157d450e9a5285153b99225c"}"#;
229 let err: Error = json::from_str(text).unwrap();
230 assert!(err.is_cluster_not_found());
231 }
232
233 #[test]
234 fn cluster_is_state_owner() {
235 let text = r#"{"httpCode":409,"httpStatus":"Conflict","errorCode":"ClusterIsStateOwnerError","msg":"Cluster undefined is currently the owner of state undefined","requestId":"e4900455157d450e9a5285153b99225c"}"#;
236 let err: Error = json::from_str(text).unwrap();
237 assert!(err.cluster_is_state_owner());
238 }
239
240 #[test]
241 fn satte_already_exists() {
242 let text = r#"{"httpCode":409,"httpStatus":"Conflict","errorCode":"StateAlreadyExistsError","msg":"State undefined already exists","requestId":"e4900455157d450e9a5285153b99225c"}"#;
243
244 let err: Error = json::from_str(text).unwrap();
245 assert!(err.state_already_exists());
246 }
247
248 #[test]
249 fn volume_not_found() {
250 let text = r#"{"httpCode": 404,"httpStatus": "Not Found","errorCode": "VolumeNotFoundError","msg": "Volume undefined not found","requestId": "e4900455157d450e9a5285153b99225c"}"#;
251 let err: Error = json::from_str(text).unwrap();
252 assert!(err.is_volume_not_found());
253 }
254}