gitlab/api/projects/members/
add.rs1use chrono::NaiveDate;
8use derive_builder::Builder;
9
10use crate::api::common::{AccessLevel, CommaSeparatedList, NameOrId};
11use crate::api::endpoint_prelude::*;
12
13#[derive(Debug, Builder, Clone)]
15#[builder(setter(strip_option))]
16pub struct AddProjectMember<'a> {
17 #[builder(setter(into))]
19 project: NameOrId<'a>,
20 #[builder(setter(name = "_user"), private)]
22 user_ids: CommaSeparatedList<u64>,
23 access_level: AccessLevel,
25
26 #[builder(default)]
28 expires_at: Option<NaiveDate>,
29 #[builder(setter(into), default)]
31 invite_source: Option<Cow<'a, str>>,
32}
33
34impl<'a> AddProjectMember<'a> {
35 pub fn builder() -> AddProjectMemberBuilder<'a> {
37 AddProjectMemberBuilder::default()
38 }
39}
40
41impl AddProjectMemberBuilder<'_> {
42 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 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}