1use chrono::{DateTime, Utc};
12use derive_builder::Builder;
13
14use crate::api::common::{AccessLevel, NameOrId, SortOrder, VisibilityLevel};
15use crate::api::endpoint_prelude::*;
16use crate::api::ParamValue;
17
18#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
20#[non_exhaustive]
21pub enum UserProjectsOrderBy {
22 Id,
24 Name,
26 Path,
28 #[default]
30 CreatedAt,
31 UpdatedAt,
33 Similarity,
35 LastActivityAt,
37 StarCount,
39}
40
41impl UserProjectsOrderBy {
42 fn as_str(self) -> &'static str {
44 match self {
45 UserProjectsOrderBy::Id => "id",
46 UserProjectsOrderBy::Name => "name",
47 UserProjectsOrderBy::Path => "path",
48 UserProjectsOrderBy::CreatedAt => "created_at",
49 UserProjectsOrderBy::UpdatedAt => "updated_at",
50 UserProjectsOrderBy::Similarity => "similarity",
51 UserProjectsOrderBy::LastActivityAt => "last_activity_at",
52 UserProjectsOrderBy::StarCount => "star_count",
53 }
54 }
55
56 fn use_keyset_pagination(self) -> bool {
57 matches!(self, Self::Id)
58 }
59}
60
61impl ParamValue<'static> for UserProjectsOrderBy {
62 fn as_value(&self) -> Cow<'static, str> {
63 self.as_str().into()
64 }
65}
66
67#[derive(Debug, Clone, Builder)]
69#[builder(setter(strip_option))]
70pub struct UserProjects<'a> {
71 #[builder(setter(into))]
73 user: NameOrId<'a>,
74
75 #[builder(default)]
77 archived: Option<bool>,
78 #[builder(default)]
80 visibility: Option<VisibilityLevel>,
81
82 #[builder(default)]
84 order_by: Option<UserProjectsOrderBy>,
85 #[builder(default)]
87 sort: Option<SortOrder>,
88 #[builder(setter(into), default)]
92 search: Option<Cow<'a, str>>,
93
94 #[builder(default)]
96 simple: Option<bool>,
97 #[builder(default)]
99 owned: Option<bool>,
100 #[builder(default)]
102 starred: Option<bool>,
103 #[builder(default)]
105 with_issues_enabled: Option<bool>,
106 #[builder(default)]
108 with_merge_requests_enabled: Option<bool>,
109 #[builder(setter(into), default)]
111 with_programming_language: Option<Cow<'a, str>>,
112 #[builder(default)]
114 min_access_level: Option<AccessLevel>,
115 #[builder(default)]
117 with_custom_attributes: Option<bool>,
118 #[builder(default)]
120 id_after: Option<u64>,
121 #[builder(default)]
123 id_before: Option<u64>,
124 #[builder(default)]
126 membership: Option<bool>,
127 #[builder(default)]
129 statistics: Option<bool>,
130 #[builder(default)]
132 updated_after: Option<DateTime<Utc>>,
133 #[builder(default)]
135 updated_before: Option<DateTime<Utc>>,
136}
137
138impl<'a> UserProjects<'a> {
139 pub fn builder() -> UserProjectsBuilder<'a> {
141 UserProjectsBuilder::default()
142 }
143}
144
145impl Endpoint for UserProjects<'_> {
146 fn method(&self) -> Method {
147 Method::GET
148 }
149
150 fn endpoint(&self) -> Cow<'static, str> {
151 format!("users/{}/projects", self.user).into()
152 }
153
154 fn parameters(&self) -> QueryParams<'_> {
155 let mut params = QueryParams::default();
156
157 params
158 .push_opt("archived", self.archived)
159 .push_opt("visibility", self.visibility)
160 .push_opt("order_by", self.order_by)
161 .push_opt("sort", self.sort)
162 .push_opt("search", self.search.as_ref())
163 .push_opt("simple", self.simple)
164 .push_opt("owned", self.owned)
165 .push_opt("starred", self.starred)
166 .push_opt("with_issues_enabled", self.with_issues_enabled)
167 .push_opt(
168 "with_merge_requests_enabled",
169 self.with_merge_requests_enabled,
170 )
171 .push_opt(
172 "with_programming_language",
173 self.with_programming_language.as_ref(),
174 )
175 .push_opt(
176 "min_access_level",
177 self.min_access_level.map(AccessLevel::as_u64),
178 )
179 .push_opt("with_custom_attributes", self.with_custom_attributes)
180 .push_opt("id_after", self.id_after)
181 .push_opt("id_before", self.id_before)
182 .push_opt("membership", self.membership)
183 .push_opt("statistics", self.statistics)
184 .push_opt("updated_after", self.updated_after)
185 .push_opt("updated_before", self.updated_before);
186
187 params
188 }
189}
190
191impl Pageable for UserProjects<'_> {
192 fn use_keyset_pagination(&self) -> bool {
193 self.order_by.unwrap_or_default().use_keyset_pagination()
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use chrono::{TimeZone, Utc};
200
201 use crate::api::common::{AccessLevel, SortOrder, VisibilityLevel};
202 use crate::api::users::projects::{
203 UserProjects, UserProjectsBuilderError, UserProjectsOrderBy,
204 };
205 use crate::api::{self, Pageable, Query};
206 use crate::test::client::{ExpectedUrl, SingleTestClient};
207
208 #[test]
209 fn order_by_default() {
210 assert_eq!(
211 UserProjectsOrderBy::default(),
212 UserProjectsOrderBy::CreatedAt,
213 );
214 }
215
216 #[test]
217 fn order_by_as_str() {
218 let items = &[
219 (UserProjectsOrderBy::Id, "id"),
220 (UserProjectsOrderBy::Name, "name"),
221 (UserProjectsOrderBy::Path, "path"),
222 (UserProjectsOrderBy::CreatedAt, "created_at"),
223 (UserProjectsOrderBy::UpdatedAt, "updated_at"),
224 (UserProjectsOrderBy::Similarity, "similarity"),
225 (UserProjectsOrderBy::LastActivityAt, "last_activity_at"),
226 (UserProjectsOrderBy::StarCount, "star_count"),
227 ];
228
229 for (i, s) in items {
230 assert_eq!(i.as_str(), *s);
231 }
232 }
233
234 #[test]
235 fn order_by_use_keyset_pagination() {
236 let items = &[
237 (UserProjectsOrderBy::Id, true),
238 (UserProjectsOrderBy::Name, false),
239 (UserProjectsOrderBy::Path, false),
240 (UserProjectsOrderBy::CreatedAt, false),
241 (UserProjectsOrderBy::UpdatedAt, false),
242 (UserProjectsOrderBy::Similarity, false),
243 (UserProjectsOrderBy::LastActivityAt, false),
244 ];
245
246 for (i, k) in items {
247 assert_eq!(i.use_keyset_pagination(), *k);
248 }
249 }
250
251 #[test]
252 fn user_is_needed() {
253 let err = UserProjects::builder().build().unwrap_err();
254 crate::test::assert_missing_field!(err, UserProjectsBuilderError, "user");
255 }
256
257 #[test]
258 fn user_is_sufficient() {
259 UserProjects::builder().user(1).build().unwrap();
260 }
261
262 #[test]
263 fn endpoint_use_keyset_pagination() {
264 let items = &[
265 (UserProjectsOrderBy::Id, true),
266 (UserProjectsOrderBy::Name, false),
267 (UserProjectsOrderBy::Path, false),
268 (UserProjectsOrderBy::CreatedAt, false),
269 (UserProjectsOrderBy::UpdatedAt, false),
270 (UserProjectsOrderBy::Similarity, false),
271 (UserProjectsOrderBy::LastActivityAt, false),
272 ];
273
274 for (i, k) in items {
275 let endpoint = UserProjects::builder()
276 .user(1)
277 .order_by(*i)
278 .build()
279 .unwrap();
280 assert_eq!(endpoint.use_keyset_pagination(), *k);
281 }
282 }
283
284 #[test]
285 fn endpoint() {
286 let endpoint = ExpectedUrl::builder()
287 .endpoint("users/user/projects")
288 .build()
289 .unwrap();
290 let client = SingleTestClient::new_raw(endpoint, "");
291
292 let endpoint = UserProjects::builder().user("user").build().unwrap();
293 api::ignore(endpoint).query(&client).unwrap();
294 }
295
296 #[test]
297 fn endpoint_archived() {
298 let endpoint = ExpectedUrl::builder()
299 .endpoint("users/user/projects")
300 .add_query_params(&[("archived", "true")])
301 .build()
302 .unwrap();
303 let client = SingleTestClient::new_raw(endpoint, "");
304
305 let endpoint = UserProjects::builder()
306 .user("user")
307 .archived(true)
308 .build()
309 .unwrap();
310 api::ignore(endpoint).query(&client).unwrap();
311 }
312
313 #[test]
314 fn endpoint_visibility() {
315 let endpoint = ExpectedUrl::builder()
316 .endpoint("users/user/projects")
317 .add_query_params(&[("visibility", "private")])
318 .build()
319 .unwrap();
320 let client = SingleTestClient::new_raw(endpoint, "");
321
322 let endpoint = UserProjects::builder()
323 .user("user")
324 .visibility(VisibilityLevel::Private)
325 .build()
326 .unwrap();
327 api::ignore(endpoint).query(&client).unwrap();
328 }
329
330 #[test]
331 fn endpoint_order_by() {
332 let endpoint = ExpectedUrl::builder()
333 .endpoint("users/user/projects")
334 .add_query_params(&[("order_by", "id")])
335 .build()
336 .unwrap();
337 let client = SingleTestClient::new_raw(endpoint, "");
338
339 let endpoint = UserProjects::builder()
340 .user("user")
341 .order_by(UserProjectsOrderBy::Id)
342 .build()
343 .unwrap();
344 api::ignore(endpoint).query(&client).unwrap();
345 }
346
347 #[test]
348 fn endpoint_sort() {
349 let endpoint = ExpectedUrl::builder()
350 .endpoint("users/user/projects")
351 .add_query_params(&[("sort", "asc")])
352 .build()
353 .unwrap();
354 let client = SingleTestClient::new_raw(endpoint, "");
355
356 let endpoint = UserProjects::builder()
357 .user("user")
358 .sort(SortOrder::Ascending)
359 .build()
360 .unwrap();
361 api::ignore(endpoint).query(&client).unwrap();
362 }
363
364 #[test]
365 fn endpoint_search() {
366 let endpoint = ExpectedUrl::builder()
367 .endpoint("users/user/projects")
368 .add_query_params(&[("search", "name")])
369 .build()
370 .unwrap();
371 let client = SingleTestClient::new_raw(endpoint, "");
372
373 let endpoint = UserProjects::builder()
374 .user("user")
375 .search("name")
376 .build()
377 .unwrap();
378 api::ignore(endpoint).query(&client).unwrap();
379 }
380
381 #[test]
382 fn endpoint_simple() {
383 let endpoint = ExpectedUrl::builder()
384 .endpoint("users/user/projects")
385 .add_query_params(&[("simple", "true")])
386 .build()
387 .unwrap();
388 let client = SingleTestClient::new_raw(endpoint, "");
389
390 let endpoint = UserProjects::builder()
391 .user("user")
392 .simple(true)
393 .build()
394 .unwrap();
395 api::ignore(endpoint).query(&client).unwrap();
396 }
397
398 #[test]
399 fn endpoint_owned() {
400 let endpoint = ExpectedUrl::builder()
401 .endpoint("users/user/projects")
402 .add_query_params(&[("owned", "true")])
403 .build()
404 .unwrap();
405 let client = SingleTestClient::new_raw(endpoint, "");
406
407 let endpoint = UserProjects::builder()
408 .user("user")
409 .owned(true)
410 .build()
411 .unwrap();
412 api::ignore(endpoint).query(&client).unwrap();
413 }
414
415 #[test]
416 fn endpoint_starred() {
417 let endpoint = ExpectedUrl::builder()
418 .endpoint("users/user/projects")
419 .add_query_params(&[("starred", "true")])
420 .build()
421 .unwrap();
422 let client = SingleTestClient::new_raw(endpoint, "");
423
424 let endpoint = UserProjects::builder()
425 .user("user")
426 .starred(true)
427 .build()
428 .unwrap();
429 api::ignore(endpoint).query(&client).unwrap();
430 }
431
432 #[test]
433 fn endpoint_with_issues_enabled() {
434 let endpoint = ExpectedUrl::builder()
435 .endpoint("users/user/projects")
436 .add_query_params(&[("with_issues_enabled", "true")])
437 .build()
438 .unwrap();
439 let client = SingleTestClient::new_raw(endpoint, "");
440
441 let endpoint = UserProjects::builder()
442 .user("user")
443 .with_issues_enabled(true)
444 .build()
445 .unwrap();
446 api::ignore(endpoint).query(&client).unwrap();
447 }
448
449 #[test]
450 fn endpoint_with_merge_requests_enabled() {
451 let endpoint = ExpectedUrl::builder()
452 .endpoint("users/user/projects")
453 .add_query_params(&[("with_merge_requests_enabled", "true")])
454 .build()
455 .unwrap();
456 let client = SingleTestClient::new_raw(endpoint, "");
457
458 let endpoint = UserProjects::builder()
459 .user("user")
460 .with_merge_requests_enabled(true)
461 .build()
462 .unwrap();
463 api::ignore(endpoint).query(&client).unwrap();
464 }
465
466 #[test]
467 fn endpoint_with_programming_language() {
468 let endpoint = ExpectedUrl::builder()
469 .endpoint("users/user/projects")
470 .add_query_params(&[("with_programming_language", "rust")])
471 .build()
472 .unwrap();
473 let client = SingleTestClient::new_raw(endpoint, "");
474
475 let endpoint = UserProjects::builder()
476 .user("user")
477 .with_programming_language("rust")
478 .build()
479 .unwrap();
480 api::ignore(endpoint).query(&client).unwrap();
481 }
482
483 #[test]
484 fn endpoint_min_access_level() {
485 let endpoint = ExpectedUrl::builder()
486 .endpoint("users/user/projects")
487 .add_query_params(&[("min_access_level", "30")])
488 .build()
489 .unwrap();
490 let client = SingleTestClient::new_raw(endpoint, "");
491
492 let endpoint = UserProjects::builder()
493 .user("user")
494 .min_access_level(AccessLevel::Developer)
495 .build()
496 .unwrap();
497 api::ignore(endpoint).query(&client).unwrap();
498 }
499
500 #[test]
501 fn endpoint_with_custom_attributes() {
502 let endpoint = ExpectedUrl::builder()
503 .endpoint("users/user/projects")
504 .add_query_params(&[("with_custom_attributes", "true")])
505 .build()
506 .unwrap();
507 let client = SingleTestClient::new_raw(endpoint, "");
508
509 let endpoint = UserProjects::builder()
510 .user("user")
511 .with_custom_attributes(true)
512 .build()
513 .unwrap();
514 api::ignore(endpoint).query(&client).unwrap();
515 }
516
517 #[test]
518 fn endpoint_id_after() {
519 let endpoint = ExpectedUrl::builder()
520 .endpoint("users/user/projects")
521 .add_query_params(&[("id_after", "100")])
522 .build()
523 .unwrap();
524 let client = SingleTestClient::new_raw(endpoint, "");
525
526 let endpoint = UserProjects::builder()
527 .user("user")
528 .id_after(100)
529 .build()
530 .unwrap();
531 api::ignore(endpoint).query(&client).unwrap();
532 }
533
534 #[test]
535 fn endpoint_id_before() {
536 let endpoint = ExpectedUrl::builder()
537 .endpoint("users/user/projects")
538 .add_query_params(&[("id_before", "200")])
539 .build()
540 .unwrap();
541 let client = SingleTestClient::new_raw(endpoint, "");
542
543 let endpoint = UserProjects::builder()
544 .user("user")
545 .id_before(200)
546 .build()
547 .unwrap();
548 api::ignore(endpoint).query(&client).unwrap();
549 }
550
551 #[test]
552 fn endpoint_membership() {
553 let endpoint = ExpectedUrl::builder()
554 .endpoint("users/user/projects")
555 .add_query_params(&[("membership", "true")])
556 .build()
557 .unwrap();
558 let client = SingleTestClient::new_raw(endpoint, "");
559
560 let endpoint = UserProjects::builder()
561 .user("user")
562 .membership(true)
563 .build()
564 .unwrap();
565 api::ignore(endpoint).query(&client).unwrap();
566 }
567
568 #[test]
569 fn endpoint_statistics() {
570 let endpoint = ExpectedUrl::builder()
571 .endpoint("users/user/projects")
572 .add_query_params(&[("statistics", "true")])
573 .build()
574 .unwrap();
575 let client = SingleTestClient::new_raw(endpoint, "");
576
577 let endpoint = UserProjects::builder()
578 .user("user")
579 .statistics(true)
580 .build()
581 .unwrap();
582 api::ignore(endpoint).query(&client).unwrap();
583 }
584
585 #[test]
586 fn endpoint_updated_after() {
587 let time = Utc.with_ymd_and_hms(2023, 10, 20, 5, 0, 0).unwrap();
588 let endpoint = ExpectedUrl::builder()
589 .endpoint("users/user/projects")
590 .add_query_params(&[("updated_after", "2023-10-20T05:00:00Z")])
591 .build()
592 .unwrap();
593 let client = SingleTestClient::new_raw(endpoint, "");
594
595 let endpoint = UserProjects::builder()
596 .user("user")
597 .updated_after(time)
598 .build()
599 .unwrap();
600 api::ignore(endpoint).query(&client).unwrap();
601 }
602
603 #[test]
604 fn endpoint_updated_before() {
605 let time = Utc.with_ymd_and_hms(2023, 10, 21, 15, 30, 0).unwrap();
606 let endpoint = ExpectedUrl::builder()
607 .endpoint("users/user/projects")
608 .add_query_params(&[("updated_before", "2023-10-21T15:30:00Z")])
609 .build()
610 .unwrap();
611 let client = SingleTestClient::new_raw(endpoint, "");
612
613 let endpoint = UserProjects::builder()
614 .user("user")
615 .updated_before(time)
616 .build()
617 .unwrap();
618 api::ignore(endpoint).query(&client).unwrap();
619 }
620}