gitlab/api/users/impersonation_tokens/
create.rs1use std::collections::BTreeSet;
8
9use chrono::NaiveDate;
10use derive_builder::Builder;
11
12use crate::api::endpoint_prelude::*;
13use crate::api::ParamValue;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17#[non_exhaustive]
18pub enum ImpersonationTokenScope {
19 Api,
21 ReadUser,
23}
24
25impl ImpersonationTokenScope {
26 pub(crate) fn as_str(self) -> &'static str {
28 match self {
29 ImpersonationTokenScope::Api => "api",
30 ImpersonationTokenScope::ReadUser => "read_user",
31 }
32 }
33}
34
35impl ParamValue<'static> for ImpersonationTokenScope {
36 fn as_value(&self) -> Cow<'static, str> {
37 self.as_str().into()
38 }
39}
40
41#[derive(Debug, Builder, Clone)]
43#[builder(setter(strip_option))]
44pub struct CreateImpersonationToken<'a> {
45 user: u64,
47 #[builder(setter(into))]
49 name: Cow<'a, str>,
50 #[builder(setter(name = "_scopes"), private)]
52 scopes: BTreeSet<ImpersonationTokenScope>,
53
54 #[builder(setter(into), default)]
56 description: Option<Cow<'a, str>>,
57 #[builder(default)]
59 expires_at: Option<NaiveDate>,
60}
61
62impl<'a> CreateImpersonationToken<'a> {
63 pub fn builder() -> CreateImpersonationTokenBuilder<'a> {
65 CreateImpersonationTokenBuilder::default()
66 }
67}
68
69impl CreateImpersonationTokenBuilder<'_> {
70 pub fn scope(&mut self, scope: ImpersonationTokenScope) -> &mut Self {
72 self.scopes.get_or_insert_with(BTreeSet::new).insert(scope);
73 self
74 }
75
76 pub fn scopes<I>(&mut self, scopes: I) -> &mut Self
78 where
79 I: Iterator<Item = ImpersonationTokenScope>,
80 {
81 self.scopes.get_or_insert_with(BTreeSet::new).extend(scopes);
82 self
83 }
84}
85
86impl Endpoint for CreateImpersonationToken<'_> {
87 fn method(&self) -> Method {
88 Method::POST
89 }
90
91 fn endpoint(&self) -> Cow<'static, str> {
92 format!("users/{}/impersonation_tokens", self.user).into()
93 }
94
95 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
96 let mut params = FormParams::default();
97
98 params
99 .push("name", &self.name)
100 .push_opt("description", self.description.as_ref())
101 .push_opt("expires_at", self.expires_at);
102
103 params.extend(self.scopes.iter().map(|&value| ("scopes[]", value)));
104
105 params.into_body()
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use chrono::NaiveDate;
112 use http::Method;
113
114 use crate::api::users::impersonation_tokens::{
115 CreateImpersonationToken, CreateImpersonationTokenBuilderError, ImpersonationTokenScope,
116 };
117 use crate::api::{self, Query};
118 use crate::test::client::{ExpectedUrl, SingleTestClient};
119
120 #[test]
121 fn impersonation_token_scope_as_str() {
122 let items = &[
123 (ImpersonationTokenScope::Api, "api"),
124 (ImpersonationTokenScope::ReadUser, "read_user"),
125 ];
126
127 for (i, s) in items {
128 assert_eq!(i.as_str(), *s);
129 }
130 }
131
132 #[test]
133 fn user_name_and_scopes_are_necessary() {
134 let err = CreateImpersonationToken::builder().build().unwrap_err();
135 crate::test::assert_missing_field!(err, CreateImpersonationTokenBuilderError, "user");
136 }
137
138 #[test]
139 fn user_is_necessary() {
140 let err = CreateImpersonationToken::builder()
141 .name("name")
142 .scope(ImpersonationTokenScope::Api)
143 .build()
144 .unwrap_err();
145 crate::test::assert_missing_field!(err, CreateImpersonationTokenBuilderError, "user");
146 }
147
148 #[test]
149 fn name_is_necessary() {
150 let err = CreateImpersonationToken::builder()
151 .user(1)
152 .scope(ImpersonationTokenScope::Api)
153 .build()
154 .unwrap_err();
155 crate::test::assert_missing_field!(err, CreateImpersonationTokenBuilderError, "name");
156 }
157
158 #[test]
159 fn scopes_is_necessary() {
160 let err = CreateImpersonationToken::builder()
161 .user(1)
162 .name("name")
163 .build()
164 .unwrap_err();
165 crate::test::assert_missing_field!(err, CreateImpersonationTokenBuilderError, "scopes");
166 }
167
168 #[test]
169 fn user_name_and_scopes_are_sufficient() {
170 CreateImpersonationToken::builder()
171 .user(1)
172 .name("name")
173 .scope(ImpersonationTokenScope::ReadUser)
174 .build()
175 .unwrap();
176 }
177
178 #[test]
179 fn endpoint() {
180 let endpoint = ExpectedUrl::builder()
181 .method(Method::POST)
182 .endpoint("users/1/impersonation_tokens")
183 .content_type("application/x-www-form-urlencoded")
184 .body_str(concat!("name=name", "&scopes%5B%5D=api"))
185 .build()
186 .unwrap();
187 let client = SingleTestClient::new_raw(endpoint, "");
188
189 let endpoint = CreateImpersonationToken::builder()
190 .user(1)
191 .name("name")
192 .scopes([ImpersonationTokenScope::Api].iter().cloned())
193 .build()
194 .unwrap();
195 api::ignore(endpoint).query(&client).unwrap();
196 }
197
198 #[test]
199 fn endpoint_description() {
200 let endpoint = ExpectedUrl::builder()
201 .method(Method::POST)
202 .endpoint("users/1/impersonation_tokens")
203 .content_type("application/x-www-form-urlencoded")
204 .body_str(concat!(
205 "name=name",
206 "&description=a+token+description",
207 "&scopes%5B%5D=api",
208 ))
209 .build()
210 .unwrap();
211 let client = SingleTestClient::new_raw(endpoint, "");
212
213 let endpoint = CreateImpersonationToken::builder()
214 .user(1)
215 .name("name")
216 .scope(ImpersonationTokenScope::Api)
217 .description("a token description")
218 .build()
219 .unwrap();
220 api::ignore(endpoint).query(&client).unwrap();
221 }
222
223 #[test]
224 fn endpoint_expires_at() {
225 let endpoint = ExpectedUrl::builder()
226 .method(Method::POST)
227 .endpoint("users/1/impersonation_tokens")
228 .content_type("application/x-www-form-urlencoded")
229 .body_str(concat!(
230 "name=name",
231 "&expires_at=2022-01-01",
232 "&scopes%5B%5D=api",
233 ))
234 .build()
235 .unwrap();
236 let client = SingleTestClient::new_raw(endpoint, "");
237
238 let endpoint = CreateImpersonationToken::builder()
239 .user(1)
240 .name("name")
241 .scope(ImpersonationTokenScope::Api)
242 .expires_at(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap())
243 .build()
244 .unwrap();
245 api::ignore(endpoint).query(&client).unwrap();
246 }
247}