wp_goji_patch/
lib.rs

1//! Goji provides an interface for Jira's REST api
2
3#[macro_use]
4extern crate log;
5extern crate reqwest;
6extern crate serde;
7#[macro_use]
8extern crate serde_derive;
9extern crate serde_json;
10extern crate url;
11
12use std::io::Read;
13
14use reqwest::header::CONTENT_TYPE;
15use reqwest::{blocking::Client, Method, StatusCode};
16use serde::de::DeserializeOwned;
17use serde::Serialize;
18
19mod builder;
20mod errors;
21pub mod issues;
22mod rep;
23mod search;
24mod transitions;
25
26pub mod worklogs;
27
28pub use crate::builder::*;
29pub use crate::errors::*;
30pub use crate::issues::*;
31pub use crate::rep::*;
32pub use crate::search::Search;
33pub use crate::transitions::*;
34pub use crate::worklogs::*;
35pub mod boards;
36pub mod resolution;
37pub use crate::boards::*;
38pub mod sprints;
39pub use crate::sprints::*;
40
41#[derive(Deserialize, Debug)]
42pub struct EmptyResponse;
43
44pub type Result<T> = std::result::Result<T, Error>;
45
46/// Types of authentication credentials
47#[derive(Clone, Debug)]
48pub enum Credentials {
49    /// username and password credentials
50    Basic(String, String), // todo: OAuth
51}
52
53/// Entrypoint into client interface
54/// https://docs.atlassian.com/jira/REST/latest/
55#[derive(Clone, Debug)]
56pub struct Jira {
57    host: String,
58    credentials: Credentials,
59    client: Client,
60}
61
62impl Jira {
63    /// creates a new instance of a jira client
64    pub fn new<H>(host: H, credentials: Credentials) -> Result<Jira>
65    where
66        H: Into<String>,
67    {
68        Ok(Jira {
69            host: host.into(),
70            client: Client::new(),
71            credentials,
72        })
73    }
74
75    /// creates a new instance of a jira client using a specified reqwest client
76    pub fn from_client<H>(host: H, credentials: Credentials, client: Client) -> Result<Jira>
77    where
78        H: Into<String>,
79    {
80        Ok(Jira {
81            host: host.into(),
82            credentials,
83            client,
84        })
85    }
86
87    /// return transitions interface
88    pub fn transitions<K>(&self, key: K) -> Transitions
89    where
90        K: Into<String>,
91    {
92        Transitions::new(self, key)
93    }
94
95    /// return search interface
96    pub fn search(&self) -> Search {
97        Search::new(self)
98    }
99
100    // return issues interface
101    pub fn issues(&self) -> Issues {
102        Issues::new(self)
103    }
104
105    // return boards interface
106    pub fn boards(&self) -> Boards {
107        Boards::new(self)
108    }
109
110    // return boards interface
111    pub fn sprints(&self) -> Sprints {
112        Sprints::new(self)
113    }
114
115        // return boards interface
116        pub fn worklogs(&self) -> Worklogs {
117            Worklogs::new(self)
118        }
119
120    fn post<D, S>(&self, api_name: &str, endpoint: &str, body: S) -> Result<D>
121    where
122        D: DeserializeOwned,
123        S: Serialize,
124    {
125        let data = serde_json::to_string::<S>(&body)?;
126        debug!("Json request: {}", data);
127        self.request::<D>(Method::POST, api_name, endpoint, Some(data.into_bytes()))
128    }
129
130    fn get<D>(&self, api_name: &str, endpoint: &str) -> Result<D>
131    where
132        D: DeserializeOwned,
133    {
134        self.request::<D>(Method::GET, api_name, endpoint, None)
135    }
136
137    fn request<D>(
138        &self,
139        method: Method,
140        api_name: &str,
141        endpoint: &str,
142        body: Option<Vec<u8>>,
143    ) -> Result<D>
144    where
145        D: DeserializeOwned,
146    {
147        let url = format!("{}/rest/{}/latest{}", self.host, api_name, endpoint);
148        debug!("url -> {:?}", url);
149
150        let req = self.client.request(method, &url);
151        let builder = match self.credentials {
152            Credentials::Basic(ref user, ref pass) => req
153                .basic_auth(user.to_owned(), Some(pass.to_owned()))
154                .header(CONTENT_TYPE, "application/json"),
155        };
156
157        let mut res = match body {
158            Some(bod) => builder.body(bod).send()?,
159            _ => builder.send()?,
160        };
161
162        let mut body = String::new();
163        res.read_to_string(&mut body)?;
164        debug!("status {:?} body '{:?}'", res.status(), body);
165        match res.status() {
166            StatusCode::UNAUTHORIZED => Err(Error::Unauthorized),
167            StatusCode::METHOD_NOT_ALLOWED => Err(Error::MethodNotAllowed),
168            StatusCode::NOT_FOUND => Err(Error::NotFound),
169            client_err if client_err.is_client_error() => Err(Error::Fault {
170                code: res.status(),
171                errors: serde_json::from_str::<Errors>(&body)?,
172            }),
173            _ => {
174                let data = if body == "" { "null" } else { &body };
175                Ok(serde_json::from_str::<D>(data)?)
176            }
177        }
178    }
179}