statehub_api/v0/error/
impls.rs

1//
2// Copyright (c) 2022 RepliXio Ltd. All rights reserved.
3// Use is subject to license terms.
4//
5
6use 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}