1use std::collections::HashMap;
3
4use serde::{Deserialize, Serialize};
5use url::form_urlencoded;
6
7use crate::statuses::State;
8use crate::users::User;
9use crate::{Future, Github};
10
11pub struct Deployments {
13 github: Github,
14 owner: String,
15 repo: String,
16}
17
18pub struct DeploymentStatuses {
20 github: Github,
21 owner: String,
22 repo: String,
23 id: u64,
24}
25
26impl DeploymentStatuses {
27 #[doc(hidden)]
28 pub fn new<O, R>(github: Github, owner: O, repo: R, id: u64) -> Self
29 where
30 O: Into<String>,
31 R: Into<String>,
32 {
33 DeploymentStatuses {
34 github,
35 owner: owner.into(),
36 repo: repo.into(),
37 id,
38 }
39 }
40
41 fn path(&self, more: &str) -> String {
42 format!(
43 "/repos/{}/{}/deployments/{}/statuses{}",
44 self.owner, self.repo, self.id, more
45 )
46 }
47
48 pub fn list(&self) -> Future<Vec<DeploymentStatus>> {
50 self.github.get(&self.path(""))
51 }
52
53 pub fn create(&self, status: &DeploymentStatusOptions) -> Future<DeploymentStatus> {
56 self.github.post(&self.path(""), json!(status))
57 }
58}
59
60impl Deployments {
61 pub fn new<O, R>(github: Github, owner: O, repo: R) -> Deployments
63 where
64 O: Into<String>,
65 R: Into<String>,
66 {
67 Deployments {
68 github,
69 owner: owner.into(),
70 repo: repo.into(),
71 }
72 }
73
74 fn path(&self, more: &str) -> String {
75 format!("/repos/{}/{}/deployments{}", self.owner, self.repo, more)
76 }
77
78 pub fn list(&self, opts: &DeploymentListOptions) -> Future<Vec<Deployment>> {
80 let mut uri = vec![self.path("")];
81 if let Some(query) = opts.serialize() {
82 uri.push(query);
83 }
84 self.github.get(&uri.join("?"))
85 }
86
87 pub fn create(&self, dep: &DeploymentOptions) -> Future<Deployment> {
89 self.github.post(&self.path(""), json!(dep))
90 }
91
92 pub fn statuses(&self, id: u64) -> DeploymentStatuses {
94 DeploymentStatuses::new(
95 self.github.clone(),
96 self.owner.as_str(),
97 self.repo.as_str(),
98 id,
99 )
100 }
101}
102
103#[derive(Debug, Deserialize)]
106pub struct Deployment {
107 pub url: String,
108 pub id: u64,
109 pub sha: String,
110 #[serde(rename = "ref")]
111 pub commit_ref: String,
112 pub task: String,
113 pub payload: serde_json::Value,
114 pub environment: String,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub description: Option<String>,
117 pub creator: User,
118 pub created_at: String,
119 pub updated_at: String,
120 pub statuses_url: String,
121 pub repository_url: String,
122}
123
124#[derive(Debug, Default, Serialize)]
125pub struct DeploymentOptions {
126 #[serde(rename = "ref")]
127 pub commit_ref: String,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub task: Option<String>,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 pub auto_merge: Option<bool>,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub required_contexts: Option<Vec<String>>,
134 #[serde(skip_serializing_if = "Option::is_none")]
136 pub payload: Option<String>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub environment: Option<String>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub description: Option<String>,
141}
142
143impl DeploymentOptions {
144 pub fn builder<C>(commit: C) -> DeploymentOptionsBuilder
145 where
146 C: Into<String>,
147 {
148 DeploymentOptionsBuilder::new(commit)
149 }
150}
151
152pub struct DeploymentOptionsBuilder(DeploymentOptions);
153
154impl DeploymentOptionsBuilder {
155 pub(crate) fn new<C>(commit: C) -> DeploymentOptionsBuilder
156 where
157 C: Into<String>,
158 {
159 DeploymentOptionsBuilder(DeploymentOptions {
160 commit_ref: commit.into(),
161 ..DeploymentOptions::default()
162 })
163 }
164
165 pub fn task<T>(&mut self, task: T) -> &mut Self
166 where
167 T: Into<String>,
168 {
169 self.0.task = Some(task.into());
170 self
171 }
172
173 pub fn auto_merge(&mut self, auto_merge: bool) -> &mut Self {
174 self.0.auto_merge = Some(auto_merge);
175 self
176 }
177
178 pub fn required_contexts<C>(&mut self, ctxs: Vec<C>) -> &mut Self
179 where
180 C: Into<String>,
181 {
182 self.0.required_contexts =
183 Some(ctxs.into_iter().map(|c| c.into()).collect::<Vec<String>>());
184 self
185 }
186
187 pub fn payload<T: serde::ser::Serialize>(&mut self, pl: T) -> &mut Self {
188 self.0.payload = serde_json::ser::to_string(&pl).ok();
189 self
190 }
191
192 pub fn environment<E>(&mut self, env: E) -> &mut Self
193 where
194 E: Into<String>,
195 {
196 self.0.environment = Some(env.into());
197 self
198 }
199
200 pub fn description<D>(&mut self, desc: D) -> &mut Self
201 where
202 D: Into<String>,
203 {
204 self.0.description = Some(desc.into());
205 self
206 }
207
208 pub fn build(&self) -> DeploymentOptions {
209 DeploymentOptions {
210 commit_ref: self.0.commit_ref.clone(),
211 task: self.0.task.clone(),
212 auto_merge: self.0.auto_merge,
213 required_contexts: self.0.required_contexts.clone(),
214 payload: self.0.payload.clone(),
215 environment: self.0.environment.clone(),
216 description: self.0.description.clone(),
217 }
218 }
219}
220
221#[derive(Debug, Deserialize)]
222pub struct DeploymentStatus {
223 pub url: String,
224 pub created_at: String,
225 pub updated_at: String,
226 pub state: State,
227 pub target_url: Option<String>,
228 pub description: Option<String>,
229 pub id: u64,
230 pub deployment_url: String,
231 pub repository_url: String,
232 pub creator: User,
233}
234
235pub struct DeploymentStatusOptionsBuilder(DeploymentStatusOptions);
236
237impl DeploymentStatusOptionsBuilder {
238 pub(crate) fn new(state: State) -> DeploymentStatusOptionsBuilder {
239 DeploymentStatusOptionsBuilder(DeploymentStatusOptions {
240 state,
241 ..Default::default()
242 })
243 }
244
245 pub fn target_url<T>(&mut self, url: T) -> &mut DeploymentStatusOptionsBuilder
246 where
247 T: Into<String>,
248 {
249 self.0.target_url = Some(url.into());
250 self
251 }
252
253 pub fn description<D>(&mut self, desc: D) -> &mut DeploymentStatusOptionsBuilder
254 where
255 D: Into<String>,
256 {
257 self.0.description = Some(desc.into());
258 self
259 }
260
261 pub fn build(&self) -> DeploymentStatusOptions {
262 DeploymentStatusOptions {
263 state: self.0.state.clone(),
264 target_url: self.0.target_url.clone(),
265 description: self.0.description.clone(),
266 }
267 }
268}
269
270#[derive(Debug, Default, Serialize)]
271pub struct DeploymentStatusOptions {
272 state: State,
273 #[serde(skip_serializing_if = "Option::is_none")]
274 target_url: Option<String>,
275 #[serde(skip_serializing_if = "Option::is_none")]
276 description: Option<String>,
277}
278
279impl DeploymentStatusOptions {
280 pub fn builder(state: State) -> DeploymentStatusOptionsBuilder {
281 DeploymentStatusOptionsBuilder::new(state)
282 }
283}
284
285#[derive(Default)]
286pub struct DeploymentListOptions {
287 params: HashMap<&'static str, String>,
288}
289
290impl DeploymentListOptions {
291 pub fn builder() -> DeploymentListOptionsBuilder {
293 DeploymentListOptionsBuilder::default()
294 }
295
296 pub fn serialize(&self) -> Option<String> {
298 if self.params.is_empty() {
299 None
300 } else {
301 let encoded: String = form_urlencoded::Serializer::new(String::new())
302 .extend_pairs(&self.params)
303 .finish();
304 Some(encoded)
305 }
306 }
307}
308
309#[derive(Default)]
310pub struct DeploymentListOptionsBuilder(DeploymentListOptions);
311
312impl DeploymentListOptionsBuilder {
313 pub fn sha<S>(&mut self, s: S) -> &mut Self
314 where
315 S: Into<String>,
316 {
317 self.0.params.insert("sha", s.into());
318 self
319 }
320
321 pub fn commit_ref<G>(&mut self, r: G) -> &mut Self
322 where
323 G: Into<String>,
324 {
325 self.0.params.insert("ref", r.into());
326 self
327 }
328
329 pub fn task<T>(&mut self, t: T) -> &mut Self
330 where
331 T: Into<String>,
332 {
333 self.0.params.insert("task", t.into());
334 self
335 }
336
337 pub fn environment<E>(&mut self, e: E) -> &mut Self
338 where
339 E: Into<String>,
340 {
341 self.0.params.insert("environment", e.into());
342 self
343 }
344
345 pub fn build(&self) -> DeploymentListOptions {
346 DeploymentListOptions {
347 params: self.0.params.clone(),
348 }
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::{DeploymentOptions, DeploymentStatusOptions};
355 use crate::statuses::State;
356 use serde::ser::Serialize;
357 use std::collections::BTreeMap;
358
359 fn test_encoding<E: Serialize>(tests: Vec<(E, &str)>) {
360 for test in tests {
361 let (k, v) = test;
362 assert_eq!(serde_json::to_string(&k).unwrap(), v);
363 }
364 }
365
366 #[test]
367 fn deployment_reqs() {
368 let mut payload = BTreeMap::new();
369 payload.insert("user", "atmos");
370 payload.insert("room_id", "123456");
371 let tests = vec![
372 (
373 DeploymentOptions::builder("test").build(),
374 r#"{"ref":"test"}"#,
375 ),
376 (
377 DeploymentOptions::builder("test").task("launchit").build(),
378 r#"{"ref":"test","task":"launchit"}"#,
379 ),
380 (
381 DeploymentOptions::builder("topic-branch")
382 .description("description")
383 .payload(payload)
384 .build(),
385 concat!(
386 "{",
387 r#""ref":"topic-branch","#,
388 r#""payload":"{\"room_id\":\"123456\",\"user\":\"atmos\"}","#,
389 r#""description":"description""#,
390 "}"
391 ),
392 ),
393 ];
394 test_encoding(tests)
395 }
396
397 #[test]
398 fn deployment_status_reqs() {
399 let tests = vec![
400 (
401 DeploymentStatusOptions::builder(State::Pending).build(),
402 r#"{"state":"pending"}"#,
403 ),
404 (
405 DeploymentStatusOptions::builder(State::Pending)
406 .target_url("http://host.com")
407 .build(),
408 r#"{"state":"pending","target_url":"http://host.com"}"#,
409 ),
410 (
411 DeploymentStatusOptions::builder(State::Pending)
412 .target_url("http://host.com")
413 .description("desc")
414 .build(),
415 r#"{"state":"pending","target_url":"http://host.com","description":"desc"}"#,
416 ),
417 ];
418 test_encoding(tests)
419 }
420}