gitlab/api/groups/projects/
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
7use derive_builder::Builder;
8
9use crate::api::common::{AccessLevel, NameOrId, SortOrder, VisibilityLevel};
10use crate::api::endpoint_prelude::*;
11use crate::api::ParamValue;
12
13/// Keys project results may be ordered by.
14#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum GroupProjectsOrderBy {
17    /// Order by the user ID.
18    Id,
19    /// Order by the user display name.
20    Name,
21    /// Order by the path.
22    Path,
23    /// Order by the creation date of the project.
24    #[default]
25    CreatedAt,
26    /// Order by the last updated date of the project.
27    UpdatedAt,
28    /// Order by a similarity score based on the search.
29    Similarity,
30    /// Order by the last activity date of the project.
31    LastActivityAt,
32    /// Order by star count.
33    StarCount,
34}
35
36impl GroupProjectsOrderBy {
37    /// The ordering as a query parameter.
38    fn as_str(self) -> &'static str {
39        match self {
40            GroupProjectsOrderBy::Id => "id",
41            GroupProjectsOrderBy::Name => "name",
42            GroupProjectsOrderBy::Path => "path",
43            GroupProjectsOrderBy::CreatedAt => "created_at",
44            GroupProjectsOrderBy::UpdatedAt => "updated_at",
45            GroupProjectsOrderBy::Similarity => "similarity",
46            GroupProjectsOrderBy::LastActivityAt => "last_activity_at",
47            GroupProjectsOrderBy::StarCount => "star_count",
48        }
49    }
50
51    fn use_keyset_pagination(self) -> bool {
52        matches!(self, Self::Id)
53    }
54}
55
56impl ParamValue<'static> for GroupProjectsOrderBy {
57    fn as_value(&self) -> Cow<'static, str> {
58        self.as_str().into()
59    }
60}
61
62/// Query projects of a group.
63#[derive(Debug, Clone, Builder)]
64#[builder(setter(strip_option))]
65pub struct GroupProjects<'a> {
66    /// The group to query for projects.
67    #[builder(setter(into))]
68    group: NameOrId<'a>,
69
70    /// Limit by archived status.
71    #[builder(default)]
72    archived: Option<bool>,
73    /// Limit by visibility public, internal, or private
74    #[builder(default)]
75    visibility: Option<VisibilityLevel>,
76
77    /// Return projects ordered by keys.
78    #[builder(default)]
79    order_by: Option<GroupProjectsOrderBy>,
80    /// Return projects sorted in asc or desc order.
81    #[builder(default)]
82    sort: Option<SortOrder>,
83    /// Search for projects using a query string.
84    ///
85    /// The search query will be escaped automatically.
86    #[builder(setter(into), default)]
87    search: Option<Cow<'a, str>>,
88
89    /// Return only the ID, URL, name, and path of each project.
90    #[builder(default)]
91    simple: Option<bool>,
92    /// Limit by projects owned by the current user.
93    #[builder(default)]
94    owned: Option<bool>,
95    /// Limit by projects starred by the current user.
96    #[builder(default)]
97    starred: Option<bool>,
98    /// Limit by projects with issues feature enabled.
99    #[builder(default)]
100    with_issues_enabled: Option<bool>,
101    /// Limit by projects with merge requests feature enabled.
102    #[builder(default)]
103    with_merge_requests_enabled: Option<bool>,
104    /// Include projects shared to this group.
105    #[builder(default)]
106    with_shared: Option<bool>,
107    /// Include projects in subgroups of this group.
108    #[builder(default)]
109    include_subgroups: Option<bool>,
110    /// Limit to projects where current user has at least this access level.
111    #[builder(default)]
112    min_access_level: Option<AccessLevel>,
113    /// Include custom attributes in response (admins only).
114    #[builder(default)]
115    with_custom_attributes: Option<bool>,
116    /// Return only projects that have security reports artifacts present in any of their builds.
117    #[builder(default)]
118    with_security_reports: Option<bool>,
119}
120
121impl<'a> GroupProjects<'a> {
122    /// Create a builder for the endpoint.
123    pub fn builder() -> GroupProjectsBuilder<'a> {
124        GroupProjectsBuilder::default()
125    }
126}
127
128impl Endpoint for GroupProjects<'_> {
129    fn method(&self) -> Method {
130        Method::GET
131    }
132
133    fn endpoint(&self) -> Cow<'static, str> {
134        format!("groups/{}/projects", self.group).into()
135    }
136
137    fn parameters(&self) -> QueryParams<'_> {
138        let mut params = QueryParams::default();
139
140        params
141            .push_opt("archived", self.archived)
142            .push_opt("visibility", self.visibility)
143            .push_opt("order_by", self.order_by)
144            .push_opt("sort", self.sort)
145            .push_opt("search", self.search.as_ref())
146            .push_opt("simple", self.simple)
147            .push_opt("owned", self.owned)
148            .push_opt("starred", self.starred)
149            .push_opt("with_issues_enabled", self.with_issues_enabled)
150            .push_opt(
151                "with_merge_requests_enabled",
152                self.with_merge_requests_enabled,
153            )
154            .push_opt("with_shared", self.with_shared)
155            .push_opt("include_subgroups", self.include_subgroups)
156            .push_opt(
157                "min_access_level",
158                self.min_access_level.map(AccessLevel::as_u64),
159            )
160            .push_opt("with_custom_attributes", self.with_custom_attributes)
161            .push_opt("with_security_reports", self.with_security_reports);
162
163        params
164    }
165}
166
167impl Pageable for GroupProjects<'_> {
168    fn use_keyset_pagination(&self) -> bool {
169        self.order_by.unwrap_or_default().use_keyset_pagination()
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use crate::api::common::{AccessLevel, SortOrder, VisibilityLevel};
176    use crate::api::groups::projects::{
177        GroupProjects, GroupProjectsBuilderError, GroupProjectsOrderBy,
178    };
179    use crate::api::{self, Pageable, Query};
180    use crate::test::client::{ExpectedUrl, SingleTestClient};
181
182    #[test]
183    fn order_by_default() {
184        assert_eq!(
185            GroupProjectsOrderBy::default(),
186            GroupProjectsOrderBy::CreatedAt
187        );
188    }
189
190    #[test]
191    fn order_by_as_str() {
192        let items = &[
193            (GroupProjectsOrderBy::Id, "id"),
194            (GroupProjectsOrderBy::Name, "name"),
195            (GroupProjectsOrderBy::Path, "path"),
196            (GroupProjectsOrderBy::CreatedAt, "created_at"),
197            (GroupProjectsOrderBy::UpdatedAt, "updated_at"),
198            (GroupProjectsOrderBy::Similarity, "similarity"),
199            (GroupProjectsOrderBy::LastActivityAt, "last_activity_at"),
200            (GroupProjectsOrderBy::StarCount, "star_count"),
201        ];
202
203        for (i, s) in items {
204            assert_eq!(i.as_str(), *s);
205        }
206    }
207
208    #[test]
209    fn order_by_use_keyset_pagination() {
210        let items = &[
211            (GroupProjectsOrderBy::Id, true),
212            (GroupProjectsOrderBy::Name, false),
213            (GroupProjectsOrderBy::Path, false),
214            (GroupProjectsOrderBy::CreatedAt, false),
215            (GroupProjectsOrderBy::UpdatedAt, false),
216            (GroupProjectsOrderBy::Similarity, false),
217            (GroupProjectsOrderBy::LastActivityAt, false),
218        ];
219
220        for (i, k) in items {
221            assert_eq!(i.use_keyset_pagination(), *k);
222        }
223    }
224
225    #[test]
226    fn group_is_needed() {
227        let err = GroupProjects::builder().build().unwrap_err();
228        crate::test::assert_missing_field!(err, GroupProjectsBuilderError, "group");
229    }
230
231    #[test]
232    fn group_is_sufficient() {
233        GroupProjects::builder().group(1).build().unwrap();
234    }
235
236    #[test]
237    fn endpoint_use_keyset_pagination() {
238        let items = &[
239            (GroupProjectsOrderBy::Id, true),
240            (GroupProjectsOrderBy::Name, false),
241            (GroupProjectsOrderBy::Path, false),
242            (GroupProjectsOrderBy::CreatedAt, false),
243            (GroupProjectsOrderBy::UpdatedAt, false),
244            (GroupProjectsOrderBy::Similarity, false),
245            (GroupProjectsOrderBy::LastActivityAt, false),
246        ];
247
248        for (i, k) in items {
249            let endpoint = GroupProjects::builder()
250                .group("group/subgroup")
251                .order_by(*i)
252                .build()
253                .unwrap();
254            assert_eq!(endpoint.use_keyset_pagination(), *k);
255        }
256    }
257
258    #[test]
259    fn endpoint() {
260        let endpoint = ExpectedUrl::builder()
261            .endpoint("groups/group%2Fsubgroup/projects")
262            .build()
263            .unwrap();
264        let client = SingleTestClient::new_raw(endpoint, "");
265
266        let endpoint = GroupProjects::builder()
267            .group("group/subgroup")
268            .build()
269            .unwrap();
270        api::ignore(endpoint).query(&client).unwrap();
271    }
272
273    #[test]
274    fn endpoint_archived() {
275        let endpoint = ExpectedUrl::builder()
276            .endpoint("groups/group%2Fsubgroup/projects")
277            .add_query_params(&[("archived", "true")])
278            .build()
279            .unwrap();
280        let client = SingleTestClient::new_raw(endpoint, "");
281
282        let endpoint = GroupProjects::builder()
283            .group("group/subgroup")
284            .archived(true)
285            .build()
286            .unwrap();
287        api::ignore(endpoint).query(&client).unwrap();
288    }
289
290    #[test]
291    fn endpoint_visibility() {
292        let endpoint = ExpectedUrl::builder()
293            .endpoint("groups/group%2Fsubgroup/projects")
294            .add_query_params(&[("visibility", "private")])
295            .build()
296            .unwrap();
297        let client = SingleTestClient::new_raw(endpoint, "");
298
299        let endpoint = GroupProjects::builder()
300            .group("group/subgroup")
301            .visibility(VisibilityLevel::Private)
302            .build()
303            .unwrap();
304        api::ignore(endpoint).query(&client).unwrap();
305    }
306
307    #[test]
308    fn endpoint_order_by() {
309        let endpoint = ExpectedUrl::builder()
310            .endpoint("groups/group%2Fsubgroup/projects")
311            .add_query_params(&[("order_by", "id")])
312            .build()
313            .unwrap();
314        let client = SingleTestClient::new_raw(endpoint, "");
315
316        let endpoint = GroupProjects::builder()
317            .group("group/subgroup")
318            .order_by(GroupProjectsOrderBy::Id)
319            .build()
320            .unwrap();
321        api::ignore(endpoint).query(&client).unwrap();
322    }
323
324    #[test]
325    fn endpoint_sort() {
326        let endpoint = ExpectedUrl::builder()
327            .endpoint("groups/group%2Fsubgroup/projects")
328            .add_query_params(&[("sort", "asc")])
329            .build()
330            .unwrap();
331        let client = SingleTestClient::new_raw(endpoint, "");
332
333        let endpoint = GroupProjects::builder()
334            .group("group/subgroup")
335            .sort(SortOrder::Ascending)
336            .build()
337            .unwrap();
338        api::ignore(endpoint).query(&client).unwrap();
339    }
340
341    #[test]
342    fn endpoint_search() {
343        let endpoint = ExpectedUrl::builder()
344            .endpoint("groups/group%2Fsubgroup/projects")
345            .add_query_params(&[("search", "name")])
346            .build()
347            .unwrap();
348        let client = SingleTestClient::new_raw(endpoint, "");
349
350        let endpoint = GroupProjects::builder()
351            .group("group/subgroup")
352            .search("name")
353            .build()
354            .unwrap();
355        api::ignore(endpoint).query(&client).unwrap();
356    }
357
358    #[test]
359    fn endpoint_simple() {
360        let endpoint = ExpectedUrl::builder()
361            .endpoint("groups/group%2Fsubgroup/projects")
362            .add_query_params(&[("simple", "true")])
363            .build()
364            .unwrap();
365        let client = SingleTestClient::new_raw(endpoint, "");
366
367        let endpoint = GroupProjects::builder()
368            .group("group/subgroup")
369            .simple(true)
370            .build()
371            .unwrap();
372        api::ignore(endpoint).query(&client).unwrap();
373    }
374
375    #[test]
376    fn endpoint_owned() {
377        let endpoint = ExpectedUrl::builder()
378            .endpoint("groups/group%2Fsubgroup/projects")
379            .add_query_params(&[("owned", "true")])
380            .build()
381            .unwrap();
382        let client = SingleTestClient::new_raw(endpoint, "");
383
384        let endpoint = GroupProjects::builder()
385            .group("group/subgroup")
386            .owned(true)
387            .build()
388            .unwrap();
389        api::ignore(endpoint).query(&client).unwrap();
390    }
391
392    #[test]
393    fn endpoint_starred() {
394        let endpoint = ExpectedUrl::builder()
395            .endpoint("groups/group%2Fsubgroup/projects")
396            .add_query_params(&[("starred", "true")])
397            .build()
398            .unwrap();
399        let client = SingleTestClient::new_raw(endpoint, "");
400
401        let endpoint = GroupProjects::builder()
402            .group("group/subgroup")
403            .starred(true)
404            .build()
405            .unwrap();
406        api::ignore(endpoint).query(&client).unwrap();
407    }
408
409    #[test]
410    fn endpoint_with_issues_enabled() {
411        let endpoint = ExpectedUrl::builder()
412            .endpoint("groups/group%2Fsubgroup/projects")
413            .add_query_params(&[("with_issues_enabled", "true")])
414            .build()
415            .unwrap();
416        let client = SingleTestClient::new_raw(endpoint, "");
417
418        let endpoint = GroupProjects::builder()
419            .group("group/subgroup")
420            .with_issues_enabled(true)
421            .build()
422            .unwrap();
423        api::ignore(endpoint).query(&client).unwrap();
424    }
425
426    #[test]
427    fn endpoint_with_merge_requests_enabled() {
428        let endpoint = ExpectedUrl::builder()
429            .endpoint("groups/group%2Fsubgroup/projects")
430            .add_query_params(&[("with_merge_requests_enabled", "true")])
431            .build()
432            .unwrap();
433        let client = SingleTestClient::new_raw(endpoint, "");
434
435        let endpoint = GroupProjects::builder()
436            .group("group/subgroup")
437            .with_merge_requests_enabled(true)
438            .build()
439            .unwrap();
440        api::ignore(endpoint).query(&client).unwrap();
441    }
442
443    #[test]
444    fn endpoint_with_shared() {
445        let endpoint = ExpectedUrl::builder()
446            .endpoint("groups/group%2Fsubgroup/projects")
447            .add_query_params(&[("with_shared", "true")])
448            .build()
449            .unwrap();
450        let client = SingleTestClient::new_raw(endpoint, "");
451
452        let endpoint = GroupProjects::builder()
453            .group("group/subgroup")
454            .with_shared(true)
455            .build()
456            .unwrap();
457        api::ignore(endpoint).query(&client).unwrap();
458    }
459
460    #[test]
461    fn endpoint_include_subgroups() {
462        let endpoint = ExpectedUrl::builder()
463            .endpoint("groups/group%2Fsubgroup/projects")
464            .add_query_params(&[("include_subgroups", "true")])
465            .build()
466            .unwrap();
467        let client = SingleTestClient::new_raw(endpoint, "");
468
469        let endpoint = GroupProjects::builder()
470            .group("group/subgroup")
471            .include_subgroups(true)
472            .build()
473            .unwrap();
474        api::ignore(endpoint).query(&client).unwrap();
475    }
476
477    #[test]
478    fn endpoint_min_access_level() {
479        let endpoint = ExpectedUrl::builder()
480            .endpoint("groups/group%2Fsubgroup/projects")
481            .add_query_params(&[("min_access_level", "30")])
482            .build()
483            .unwrap();
484        let client = SingleTestClient::new_raw(endpoint, "");
485
486        let endpoint = GroupProjects::builder()
487            .group("group/subgroup")
488            .min_access_level(AccessLevel::Developer)
489            .build()
490            .unwrap();
491        api::ignore(endpoint).query(&client).unwrap();
492    }
493
494    #[test]
495    fn endpoint_with_custom_attributes() {
496        let endpoint = ExpectedUrl::builder()
497            .endpoint("groups/group%2Fsubgroup/projects")
498            .add_query_params(&[("with_custom_attributes", "true")])
499            .build()
500            .unwrap();
501        let client = SingleTestClient::new_raw(endpoint, "");
502
503        let endpoint = GroupProjects::builder()
504            .group("group/subgroup")
505            .with_custom_attributes(true)
506            .build()
507            .unwrap();
508        api::ignore(endpoint).query(&client).unwrap();
509    }
510
511    #[test]
512    fn endpoint_with_security_reports() {
513        let endpoint = ExpectedUrl::builder()
514            .endpoint("groups/group%2Fsubgroup/projects")
515            .add_query_params(&[("with_security_reports", "true")])
516            .build()
517            .unwrap();
518        let client = SingleTestClient::new_raw(endpoint, "");
519
520        let endpoint = GroupProjects::builder()
521            .group("group/subgroup")
522            .with_security_reports(true)
523            .build()
524            .unwrap();
525        api::ignore(endpoint).query(&client).unwrap();
526    }
527}