hubcaps_ex/
deployments.rs

1//! Deployments interface
2use 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
11/// Interface for repository deployments
12pub struct Deployments {
13    github: Github,
14    owner: String,
15    repo: String,
16}
17
18/// Interface for deployment statuses
19pub 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    /// lists all statuses associated with a deployment
49    pub fn list(&self) -> Future<Vec<DeploymentStatus>> {
50        self.github.get(&self.path(""))
51    }
52
53    /// creates a new deployment status. For convenience, a DeploymentStatusOptions.builder
54    /// interface is required for building up a request
55    pub fn create(&self, status: &DeploymentStatusOptions) -> Future<DeploymentStatus> {
56        self.github.post(&self.path(""), json!(status))
57    }
58}
59
60impl Deployments {
61    /// Create a new deployments instance
62    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    /// lists all deployments for a repository
79    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    /// creates a new deployment for this repository
88    pub fn create(&self, dep: &DeploymentOptions) -> Future<Deployment> {
89        self.github.post(&self.path(""), json!(dep))
90    }
91
92    /// get a reference to the statuses api for a give deployment
93    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// representations
104
105#[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    /// contents of payload should be valid JSON
135    #[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    /// return a new instance of a builder for options
292    pub fn builder() -> DeploymentListOptionsBuilder {
293        DeploymentListOptionsBuilder::default()
294    }
295
296    /// serialize options as a string. returns None if no options are defined
297    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}