1pub struct Codes {
4 pub content_type: &'static str,
6 pub credential: &'static str,
8 pub limit: &'static str,
10 pub malformed: &'static str,
12 pub refused: &'static str,
14 pub unauthorized: &'static str,
16 pub timeout: &'static str,
18 pub server_error: &'static str,
20 pub busy: &'static str,
22}
23
24#[macro_export]
26macro_rules! refusal_codes {
27 ($family:literal) => {
28 $crate::refusal::Codes {
29 content_type: concat!($family, "-content-type"),
30 credential: concat!($family, "-credential"),
31 limit: concat!($family, "-limit"),
32 malformed: concat!($family, "-malformed"),
33 refused: concat!($family, "-refused"),
34 unauthorized: concat!($family, "-unauthorized"),
35 timeout: concat!($family, "-timeout"),
36 server_error: concat!($family, "-server-error"),
37 busy: concat!($family, "-busy"),
38 }
39 };
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum Bootstrap {
45 Claimed,
46 SetupRejected,
47 SetupExpired,
48 Quota,
49 Unknown,
50}
51
52impl Bootstrap {
53 pub fn classify(code: Option<&str>, claim: bool) -> Self {
54 match code {
55 Some("bootstrap-storage-already-claimed") => Self::Claimed,
56 Some("bootstrap-setup-invitation-rejected") if claim => Self::SetupRejected,
57 Some("bootstrap-setup-invitation-expired") if claim => Self::SetupExpired,
58 Some("attachment-quota-exceeded") => Self::Quota,
59 _ => Self::Unknown,
60 }
61 }
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub enum Enrollment {
66 Stale,
67 Busy,
68 Unauthorized,
69 Timeout,
70 Refused,
71 Server,
72}
73
74impl Enrollment {
75 pub fn classify(status: u16, code: Option<&str>) -> Self {
76 match code {
77 Some("membership-stale") => Self::Stale,
78 Some("enrollment-busy") => Self::Busy,
79 Some("enrollment-unauthorized") => Self::Unauthorized,
80 Some("enrollment-timeout") => Self::Timeout,
81 Some(_) if (400..500).contains(&status) => Self::Refused,
82 _ => Self::Server,
83 }
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub enum Tail {
89 Malformed,
90 BatchKnown,
91 Stale,
92 Unauthorized,
93 PrefixIdentityCollision,
94 Quota,
95 Unknown,
96}
97
98impl Tail {
99 pub fn classify(code: Option<&str>) -> Self {
100 match code {
101 Some("encrypted-tail-malformed") => Self::Malformed,
102 Some("encrypted-tail-batch-known") => Self::BatchKnown,
103 Some("membership-stale") => Self::Stale,
104 Some("encrypted-tail-unauthorized") => Self::Unauthorized,
105 Some("encrypted-tail-prefix-identity-collision") => Self::PrefixIdentityCollision,
106 Some("attachment-quota-exceeded") => Self::Quota,
107 _ => Self::Unknown,
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn endpoint_refusals_preserve_access_and_outcome_uncertainty() {
118 assert_eq!(
119 Bootstrap::classify(Some("bootstrap-setup-invitation-expired"), true),
120 Bootstrap::SetupExpired
121 );
122 assert_eq!(
123 Bootstrap::classify(Some("bootstrap-setup-invitation-expired"), false),
124 Bootstrap::Unknown
125 );
126 assert_eq!(
127 Enrollment::classify(401, Some("enrollment-credential")),
128 Enrollment::Refused
129 );
130 assert_eq!(
131 Enrollment::classify(503, Some("enrollment-busy")),
132 Enrollment::Busy
133 );
134 assert_eq!(
135 Enrollment::classify(403, Some("enrollment-unauthorized")),
136 Enrollment::Unauthorized
137 );
138 assert_eq!(Enrollment::classify(403, None), Enrollment::Server);
139 assert_eq!(
140 Tail::classify(Some("encrypted-tail-credential")),
141 Tail::Unknown
142 );
143 assert_eq!(
144 Tail::classify(Some("encrypted-image-unauthorized")),
145 Tail::Unknown
146 );
147 assert_eq!(
148 Tail::classify(Some("encrypted-tail-unauthorized")),
149 Tail::Unauthorized
150 );
151 assert_eq!(
152 Tail::classify(Some("attachment-quota-exceeded")),
153 Tail::Quota
154 );
155 }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
162pub enum Hosting {
163 Blocked,
164 Deleted,
165 Quota,
166 Unavailable,
167}
168
169pub struct HostingCodes {
171 pub blocked: &'static str,
173 pub deleted: &'static str,
175 pub quota: &'static str,
177 pub unavailable: &'static str,
179}
180
181impl HostingCodes {
182 pub fn classify(&self, status: u16, code: Option<&str>) -> Option<Hosting> {
183 match (status, code) {
184 (403, Some(code)) if code == self.blocked => Some(Hosting::Blocked),
185 (403, Some(code)) if code == self.deleted => Some(Hosting::Deleted),
186 (429, Some(code)) if code == self.quota => Some(Hosting::Quota),
187 (503, Some(code)) if code == self.unavailable => Some(Hosting::Unavailable),
188 _ => None,
189 }
190 }
191}
192
193pub const BOOTSTRAP_HOSTING: HostingCodes = HostingCodes {
194 blocked: "bootstrap-hosting-blocked",
195 deleted: "bootstrap-hosting-deleted",
196 quota: "bootstrap-hosting-quota",
197 unavailable: "bootstrap-hosting-unavailable",
198};
199pub const ENROLLMENT_HOSTING: HostingCodes = HostingCodes {
200 blocked: "enrollment-hosting-blocked",
201 deleted: "enrollment-hosting-deleted",
202 quota: "enrollment-hosting-quota",
203 unavailable: "enrollment-hosting-unavailable",
204};
205pub const TAIL_HOSTING: HostingCodes = HostingCodes {
206 blocked: "encrypted-tail-hosting-blocked",
207 deleted: "encrypted-tail-hosting-deleted",
208 quota: "encrypted-tail-hosting-quota",
209 unavailable: "encrypted-tail-hosting-unavailable",
210};
211pub const IMAGE_HOSTING: HostingCodes = HostingCodes {
212 blocked: "encrypted-image-hosting-blocked",
213 deleted: "encrypted-image-hosting-deleted",
214 quota: "encrypted-image-hosting-quota",
215 unavailable: "encrypted-image-hosting-unavailable",
216};
217
218#[cfg(test)]
219mod hosting_tests {
220 use super::*;
221
222 #[test]
223 fn hosting_codes_require_their_endpoint_family_and_status() {
224 let families = [
225 &BOOTSTRAP_HOSTING,
226 &ENROLLMENT_HOSTING,
227 &TAIL_HOSTING,
228 &IMAGE_HOSTING,
229 ];
230 for (i, family) in families.iter().enumerate() {
231 for (status, code, expected) in [
232 (403, family.blocked, Hosting::Blocked),
233 (403, family.deleted, Hosting::Deleted),
234 (429, family.quota, Hosting::Quota),
235 (503, family.unavailable, Hosting::Unavailable),
236 ] {
237 assert_eq!(family.classify(status, Some(code)), Some(expected));
238 assert_eq!(family.classify(401, Some(code)), None);
239 for (j, other) in families.iter().enumerate() {
240 if i != j {
241 assert_eq!(other.classify(status, Some(code)), None);
242 }
243 }
244 }
245 assert_eq!(family.classify(403, Some("unknown")), None);
246 assert_eq!(family.classify(503, None), None);
247 }
248 }
249}