Skip to main content

gitlab/api/users/
projects.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7//! User projects API endpoints.
8//!
9//! These endpoints are used for querying user projects.
10
11use 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/// Keys project results may be ordered by.
19#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
20#[non_exhaustive]
21pub enum UserProjectsOrderBy {
22    /// Order by the user ID.
23    Id,
24    /// Order by the user display name.
25    Name,
26    /// Order by the path.
27    Path,
28    /// Order by the creation date of the project.
29    #[default]
30    CreatedAt,
31    /// Order by the last updated date of the project.
32    UpdatedAt,
33    /// Order by a similarity score based on the search.
34    Similarity,
35    /// Order by the last activity date of the project.
36    LastActivityAt,
37    /// Order by star count.
38    StarCount,
39}
40
41impl UserProjectsOrderBy {
42    /// The ordering as a query parameter.
43    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/// Query projects of a user.
68#[derive(Debug, Clone, Builder)]
69#[builder(setter(strip_option))]
70pub struct UserProjects<'a> {
71    /// The user to query for projects.
72    #[builder(setter(into))]
73    user: NameOrId<'a>,
74
75    /// Limit by archived status.
76    #[builder(default)]
77    archived: Option<bool>,
78    /// Limit by visibility public, internal, or private
79    #[builder(default)]
80    visibility: Option<VisibilityLevel>,
81
82    /// Return projects ordered by keys.
83    #[builder(default)]
84    order_by: Option<UserProjectsOrderBy>,
85    /// Return projects sorted in asc or desc order.
86    #[builder(default)]
87    sort: Option<SortOrder>,
88    /// Search for projects using a query string.
89    ///
90    /// The search query will be escaped automatically.
91    #[builder(setter(into), default)]
92    search: Option<Cow<'a, str>>,
93
94    /// Return only the ID, URL, name, and path of each project.
95    #[builder(default)]
96    simple: Option<bool>,
97    /// Limit by projects owned by the user.
98    #[builder(default)]
99    owned: Option<bool>,
100    /// Limit by projects starred by the user.
101    #[builder(default)]
102    starred: Option<bool>,
103    /// Limit by projects with issues feature enabled.
104    #[builder(default)]
105    with_issues_enabled: Option<bool>,
106    /// Limit by projects with merge requests feature enabled.
107    #[builder(default)]
108    with_merge_requests_enabled: Option<bool>,
109    /// Filter by projects with the specified programming language.
110    #[builder(setter(into), default)]
111    with_programming_language: Option<Cow<'a, str>>,
112    /// Limit to projects where user has at least this access level.
113    #[builder(default)]
114    min_access_level: Option<AccessLevel>,
115    /// Include custom attributes in response (admins only).
116    #[builder(default)]
117    with_custom_attributes: Option<bool>,
118    /// Limit results to projects with IDs greater than the specified ID.
119    #[builder(default)]
120    id_after: Option<u64>,
121    /// Limit results to projects with IDs less than the specified ID.
122    #[builder(default)]
123    id_before: Option<u64>,
124    /// Limit by projects that the user is a member of.
125    #[builder(default)]
126    membership: Option<bool>,
127    /// Include project statistics.
128    #[builder(default)]
129    statistics: Option<bool>,
130    /// Limit results to projects updated after the specified time.
131    #[builder(default)]
132    updated_after: Option<DateTime<Utc>>,
133    /// Limit results to projects updated before the specified time.
134    #[builder(default)]
135    updated_before: Option<DateTime<Utc>>,
136}
137
138impl<'a> UserProjects<'a> {
139    /// Create a builder for the endpoint.
140    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}