gitlab/api/projects/members/
add.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 chrono::NaiveDate;
8use derive_builder::Builder;
9
10use crate::api::common::{AccessLevel, CommaSeparatedList, NameOrId};
11use crate::api::endpoint_prelude::*;
12
13/// Add a user as a member of a project.
14#[derive(Debug, Builder, Clone)]
15#[builder(setter(strip_option))]
16pub struct AddProjectMember<'a> {
17    /// The project to add the user to.
18    #[builder(setter(into))]
19    project: NameOrId<'a>,
20    /// The user to add to the project.
21    #[builder(setter(name = "_user"), private)]
22    user_ids: CommaSeparatedList<u64>,
23    /// The access level for the user in the project.
24    access_level: AccessLevel,
25
26    /// When the user's access expires.
27    #[builder(default)]
28    expires_at: Option<NaiveDate>,
29    /// The source of the invitation.
30    #[builder(setter(into), default)]
31    invite_source: Option<Cow<'a, str>>,
32}
33
34impl<'a> AddProjectMember<'a> {
35    /// Create a builder for the endpoint.
36    pub fn builder() -> AddProjectMemberBuilder<'a> {
37        AddProjectMemberBuilder::default()
38    }
39}
40
41impl AddProjectMemberBuilder<'_> {
42    /// The user to add (by ID).
43    pub fn user(&mut self, user: u64) -> &mut Self {
44        self.user_ids
45            .get_or_insert_with(CommaSeparatedList::new)
46            .push(user);
47        self
48    }
49
50    /// Add a set of users (by ID).
51    pub fn users<I>(&mut self, iter: I) -> &mut Self
52    where
53        I: Iterator<Item = u64>,
54    {
55        self.user_ids
56            .get_or_insert_with(CommaSeparatedList::new)
57            .extend(iter);
58        self
59    }
60}
61
62impl Endpoint for AddProjectMember<'_> {
63    fn method(&self) -> Method {
64        Method::POST
65    }
66
67    fn endpoint(&self) -> Cow<'static, str> {
68        format!("projects/{}/members", self.project).into()
69    }
70
71    fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
72        let mut params = FormParams::default();
73
74        params
75            .push("user_id", &self.user_ids)
76            .push("access_level", self.access_level.as_u64())
77            .push_opt("expires_at", self.expires_at)
78            .push_opt("invite_source", self.invite_source.as_ref());
79
80        params.into_body()
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use chrono::NaiveDate;
87    use http::Method;
88
89    use crate::api::common::AccessLevel;
90    use crate::api::projects::members::{AddProjectMember, AddProjectMemberBuilderError};
91    use crate::api::{self, Query};
92    use crate::test::client::{ExpectedUrl, SingleTestClient};
93
94    #[test]
95    fn all_parameters_are_needed() {
96        let err = AddProjectMember::builder().build().unwrap_err();
97        crate::test::assert_missing_field!(err, AddProjectMemberBuilderError, "project");
98    }
99
100    #[test]
101    fn project_is_necessary() {
102        let err = AddProjectMember::builder()
103            .user(1)
104            .access_level(AccessLevel::Developer)
105            .build()
106            .unwrap_err();
107        crate::test::assert_missing_field!(err, AddProjectMemberBuilderError, "project");
108    }
109
110    #[test]
111    fn user_is_necessary() {
112        let err = AddProjectMember::builder()
113            .project(1)
114            .access_level(AccessLevel::Developer)
115            .build()
116            .unwrap_err();
117        crate::test::assert_missing_field!(err, AddProjectMemberBuilderError, "user_ids");
118    }
119
120    #[test]
121    fn access_level_is_necessary() {
122        let err = AddProjectMember::builder()
123            .project(1)
124            .user(1)
125            .build()
126            .unwrap_err();
127        crate::test::assert_missing_field!(err, AddProjectMemberBuilderError, "access_level");
128    }
129
130    #[test]
131    fn sufficient_parameters() {
132        AddProjectMember::builder()
133            .project("project")
134            .user(1)
135            .access_level(AccessLevel::Developer)
136            .build()
137            .unwrap();
138    }
139
140    #[test]
141    fn endpoint() {
142        let endpoint = ExpectedUrl::builder()
143            .method(Method::POST)
144            .endpoint("projects/simple%2Fproject/members")
145            .content_type("application/x-www-form-urlencoded")
146            .body_str(concat!("user_id=1", "&access_level=30"))
147            .build()
148            .unwrap();
149        let client = SingleTestClient::new_raw(endpoint, "");
150
151        let endpoint = AddProjectMember::builder()
152            .project("simple/project")
153            .user(1)
154            .access_level(AccessLevel::Developer)
155            .build()
156            .unwrap();
157        api::ignore(endpoint).query(&client).unwrap();
158    }
159
160    #[test]
161    fn endpoint_user_multiple() {
162        let endpoint = ExpectedUrl::builder()
163            .method(Method::POST)
164            .endpoint("projects/simple%2Fproject/members")
165            .content_type("application/x-www-form-urlencoded")
166            .body_str(concat!(
167                "user_id=1%2C3%2C2",
168                "&access_level=30",
169                "&expires_at=2020-01-01",
170            ))
171            .build()
172            .unwrap();
173        let client = SingleTestClient::new_raw(endpoint, "");
174
175        let endpoint = AddProjectMember::builder()
176            .project("simple/project")
177            .user(1)
178            .users([3, 2].iter().cloned())
179            .access_level(AccessLevel::Developer)
180            .expires_at(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
181            .build()
182            .unwrap();
183        api::ignore(endpoint).query(&client).unwrap();
184    }
185
186    #[test]
187    fn endpoint_expires_at() {
188        let endpoint = ExpectedUrl::builder()
189            .method(Method::POST)
190            .endpoint("projects/simple%2Fproject/members")
191            .content_type("application/x-www-form-urlencoded")
192            .body_str(concat!(
193                "user_id=1",
194                "&access_level=30",
195                "&expires_at=2020-01-01",
196            ))
197            .build()
198            .unwrap();
199        let client = SingleTestClient::new_raw(endpoint, "");
200
201        let endpoint = AddProjectMember::builder()
202            .project("simple/project")
203            .user(1)
204            .access_level(AccessLevel::Developer)
205            .expires_at(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
206            .build()
207            .unwrap();
208        api::ignore(endpoint).query(&client).unwrap();
209    }
210
211    #[test]
212    fn endpoint_invite_source() {
213        let endpoint = ExpectedUrl::builder()
214            .method(Method::POST)
215            .endpoint("projects/simple%2Fproject/members")
216            .content_type("application/x-www-form-urlencoded")
217            .body_str(concat!(
218                "user_id=1",
219                "&access_level=30",
220                "&invite_source=tuesday",
221            ))
222            .build()
223            .unwrap();
224        let client = SingleTestClient::new_raw(endpoint, "");
225
226        let endpoint = AddProjectMember::builder()
227            .project("simple/project")
228            .user(1)
229            .access_level(AccessLevel::Developer)
230            .invite_source("tuesday")
231            .build()
232            .unwrap();
233        api::ignore(endpoint).query(&client).unwrap();
234    }
235}