gitlab/api/users/personal_access_tokens/
create_for_user.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 PersonalAccessTokenScope {
19 Api,
21 ReadUser,
23 ReadApi,
25 ReadRepository,
27 WriteRepository,
29 ReadRegistry,
31 WriteRegistry,
33 Sudo,
35 AdminMode,
37 CreateRunner,
39 AiFeatures,
41 K8sProxy,
43 ReadServicePing,
45}
46
47impl PersonalAccessTokenScope {
48 pub(crate) fn as_str(self) -> &'static str {
50 match self {
51 Self::Api => "api",
52 Self::ReadUser => "read_user",
53 Self::ReadApi => "read_api",
54 Self::ReadRepository => "read_repository",
55 Self::WriteRepository => "write_repository",
56 Self::ReadRegistry => "read_registry",
57 Self::WriteRegistry => "write_registry",
58 Self::Sudo => "sudo",
59 Self::AdminMode => "admin_mode",
60 Self::CreateRunner => "create_runner",
61 Self::AiFeatures => "ai_features",
62 Self::K8sProxy => "k8s_proxy",
63 Self::ReadServicePing => "read_service_ping",
64 }
65 }
66
67 pub fn as_create_scope(self) -> Option<super::PersonalAccessTokenCreateScope> {
69 match self {
70 Self::K8sProxy => Some(super::PersonalAccessTokenCreateScope::K8sProxy),
71 _ => None,
72 }
73 }
74}
75
76impl ParamValue<'static> for PersonalAccessTokenScope {
77 fn as_value(&self) -> Cow<'static, str> {
78 self.as_str().into()
79 }
80}
81
82#[derive(Debug, Builder, Clone)]
84#[builder(setter(strip_option))]
85pub struct CreatePersonalAccessTokenForUser<'a> {
86 user: u64,
88 #[builder(setter(into))]
90 name: Cow<'a, str>,
91 #[builder(setter(name = "_scopes"), private)]
93 scopes: BTreeSet<PersonalAccessTokenScope>,
94
95 #[builder(setter(into), default)]
97 description: Option<Cow<'a, str>>,
98 #[builder(default)]
100 expires_at: Option<NaiveDate>,
101}
102
103impl<'a> CreatePersonalAccessTokenForUser<'a> {
104 pub fn builder() -> CreatePersonalAccessTokenForUserBuilder<'a> {
106 CreatePersonalAccessTokenForUserBuilder::default()
107 }
108}
109
110impl CreatePersonalAccessTokenForUserBuilder<'_> {
111 pub fn scope(&mut self, scope: PersonalAccessTokenScope) -> &mut Self {
113 self.scopes.get_or_insert_with(BTreeSet::new).insert(scope);
114 self
115 }
116
117 pub fn scopes<I>(&mut self, scopes: I) -> &mut Self
119 where
120 I: Iterator<Item = PersonalAccessTokenScope>,
121 {
122 self.scopes.get_or_insert_with(BTreeSet::new).extend(scopes);
123 self
124 }
125}
126
127impl Endpoint for CreatePersonalAccessTokenForUser<'_> {
128 fn method(&self) -> Method {
129 Method::POST
130 }
131
132 fn endpoint(&self) -> Cow<'static, str> {
133 format!("users/{}/personal_access_tokens", self.user).into()
134 }
135
136 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
137 let mut params = FormParams::default();
138
139 params
140 .push("name", &self.name)
141 .push_opt("description", self.description.as_ref())
142 .push_opt("expires_at", self.expires_at);
143
144 params.extend(self.scopes.iter().map(|&value| ("scopes[]", value)));
145
146 params.into_body()
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use chrono::NaiveDate;
153 use http::Method;
154
155 use crate::api::users::personal_access_tokens::{
156 CreatePersonalAccessTokenForUser, CreatePersonalAccessTokenForUserBuilderError,
157 PersonalAccessTokenCreateScope, PersonalAccessTokenScope,
158 };
159 use crate::api::{self, Query};
160 use crate::test::client::{ExpectedUrl, SingleTestClient};
161
162 #[test]
163 fn personal_access_token_scope_as_str() {
164 let items = &[
165 (PersonalAccessTokenScope::Api, "api"),
166 (PersonalAccessTokenScope::ReadUser, "read_user"),
167 (PersonalAccessTokenScope::ReadApi, "read_api"),
168 (PersonalAccessTokenScope::ReadRepository, "read_repository"),
169 (
170 PersonalAccessTokenScope::WriteRepository,
171 "write_repository",
172 ),
173 (PersonalAccessTokenScope::ReadRegistry, "read_registry"),
174 (PersonalAccessTokenScope::WriteRegistry, "write_registry"),
175 (PersonalAccessTokenScope::Sudo, "sudo"),
176 (PersonalAccessTokenScope::AdminMode, "admin_mode"),
177 (PersonalAccessTokenScope::CreateRunner, "create_runner"),
178 (PersonalAccessTokenScope::AiFeatures, "ai_features"),
179 (PersonalAccessTokenScope::K8sProxy, "k8s_proxy"),
180 (
181 PersonalAccessTokenScope::ReadServicePing,
182 "read_service_ping",
183 ),
184 ];
185
186 for (i, s) in items {
187 assert_eq!(i.as_str(), *s);
188 }
189 }
190
191 #[test]
192 fn personal_access_token_scope_as_create_scope() {
193 let items = &[
194 (PersonalAccessTokenScope::Api, None),
195 (PersonalAccessTokenScope::ReadUser, None),
196 (PersonalAccessTokenScope::ReadApi, None),
197 (PersonalAccessTokenScope::ReadRepository, None),
198 (PersonalAccessTokenScope::WriteRepository, None),
199 (PersonalAccessTokenScope::ReadRegistry, None),
200 (PersonalAccessTokenScope::WriteRegistry, None),
201 (PersonalAccessTokenScope::Sudo, None),
202 (PersonalAccessTokenScope::AdminMode, None),
203 (PersonalAccessTokenScope::CreateRunner, None),
204 (PersonalAccessTokenScope::AiFeatures, None),
205 (
206 PersonalAccessTokenScope::K8sProxy,
207 Some(PersonalAccessTokenCreateScope::K8sProxy),
208 ),
209 (PersonalAccessTokenScope::ReadServicePing, None),
210 ];
211
212 for (i, s) in items {
213 assert_eq!(i.as_create_scope(), *s);
214 }
215 }
216
217 #[test]
218 fn user_name_and_scopes_are_necessary() {
219 let err = CreatePersonalAccessTokenForUser::builder()
220 .build()
221 .unwrap_err();
222 crate::test::assert_missing_field!(
223 err,
224 CreatePersonalAccessTokenForUserBuilderError,
225 "user",
226 );
227 }
228
229 #[test]
230 fn user_is_necessary() {
231 let err = CreatePersonalAccessTokenForUser::builder()
232 .name("name")
233 .scope(PersonalAccessTokenScope::Api)
234 .build()
235 .unwrap_err();
236 crate::test::assert_missing_field!(
237 err,
238 CreatePersonalAccessTokenForUserBuilderError,
239 "user",
240 );
241 }
242
243 #[test]
244 fn name_is_necessary() {
245 let err = CreatePersonalAccessTokenForUser::builder()
246 .user(1)
247 .scope(PersonalAccessTokenScope::Api)
248 .build()
249 .unwrap_err();
250 crate::test::assert_missing_field!(
251 err,
252 CreatePersonalAccessTokenForUserBuilderError,
253 "name",
254 );
255 }
256
257 #[test]
258 fn scopes_is_necessary() {
259 let err = CreatePersonalAccessTokenForUser::builder()
260 .user(1)
261 .name("name")
262 .build()
263 .unwrap_err();
264 crate::test::assert_missing_field!(
265 err,
266 CreatePersonalAccessTokenForUserBuilderError,
267 "scopes",
268 );
269 }
270
271 #[test]
272 fn user_name_and_scopes_are_sufficient() {
273 CreatePersonalAccessTokenForUser::builder()
274 .user(1)
275 .name("name")
276 .scope(PersonalAccessTokenScope::ReadUser)
277 .build()
278 .unwrap();
279 }
280
281 #[test]
282 fn endpoint() {
283 let endpoint = ExpectedUrl::builder()
284 .method(Method::POST)
285 .endpoint("users/1/personal_access_tokens")
286 .content_type("application/x-www-form-urlencoded")
287 .body_str(concat!("name=name", "&scopes%5B%5D=api"))
288 .build()
289 .unwrap();
290 let client = SingleTestClient::new_raw(endpoint, "");
291
292 let endpoint = CreatePersonalAccessTokenForUser::builder()
293 .user(1)
294 .name("name")
295 .scopes([PersonalAccessTokenScope::Api].iter().cloned())
296 .build()
297 .unwrap();
298 api::ignore(endpoint).query(&client).unwrap();
299 }
300
301 #[test]
302 fn endpoint_description() {
303 let endpoint = ExpectedUrl::builder()
304 .method(Method::POST)
305 .endpoint("users/1/personal_access_tokens")
306 .content_type("application/x-www-form-urlencoded")
307 .body_str(concat!(
308 "name=name",
309 "&description=a+token+description",
310 "&scopes%5B%5D=api",
311 ))
312 .build()
313 .unwrap();
314 let client = SingleTestClient::new_raw(endpoint, "");
315
316 let endpoint = CreatePersonalAccessTokenForUser::builder()
317 .user(1)
318 .name("name")
319 .scope(PersonalAccessTokenScope::Api)
320 .description("a token description")
321 .build()
322 .unwrap();
323 api::ignore(endpoint).query(&client).unwrap();
324 }
325
326 #[test]
327 fn endpoint_expires_at() {
328 let endpoint = ExpectedUrl::builder()
329 .method(Method::POST)
330 .endpoint("users/1/personal_access_tokens")
331 .content_type("application/x-www-form-urlencoded")
332 .body_str(concat!(
333 "name=name",
334 "&expires_at=2022-01-01",
335 "&scopes%5B%5D=api",
336 ))
337 .build()
338 .unwrap();
339 let client = SingleTestClient::new_raw(endpoint, "");
340
341 let endpoint = CreatePersonalAccessTokenForUser::builder()
342 .user(1)
343 .name("name")
344 .scope(PersonalAccessTokenScope::Api)
345 .expires_at(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap())
346 .build()
347 .unwrap();
348 api::ignore(endpoint).query(&client).unwrap();
349 }
350}