pivotal_tracker/
client.rs1use core::fmt;
2use reqwest::header::HeaderMap;
3use serde::{de::DeserializeOwned, Deserialize, Serialize};
4use std::{error::Error, fmt::Display};
5use url::ParseError;
6
7#[derive(Debug)]
8pub struct Client {
9 pub api_version: u8,
10 pub token: String,
11}
12
13impl Client {
14 pub fn new(options: ClientNewOptions) -> Self {
15 Client {
16 api_version: options.api_version,
17 token: options.token,
18 }
19 }
20
21 pub async fn request<TSuccess, TGetBuilder>(
22 &self,
23 func: TGetBuilder,
24 ) -> Result<TSuccess, RequestError>
25 where
26 TSuccess: DeserializeOwned,
27 TGetBuilder: Fn(&reqwest::Client, String) -> reqwest::RequestBuilder,
28 {
29 let mut headers = HeaderMap::new();
30
31 headers.append("X-TrackerToken", self.token.parse().unwrap());
32
33 let client = reqwest::Client::builder()
34 .default_headers(headers)
35 .build()?;
36 let base_url = format!(
37 "https://www.pivotaltracker.com/services/v{}",
38 self.api_version
39 );
40 let res = func(&client, base_url).send().await?;
41
42 if res.status().as_u16() >= 400 {
43 let result = res.json::<ResponseError>().await?;
44
45 return Err(RequestError::Response(result));
46 }
47
48 let result = res.json::<TSuccess>().await?;
49
50 Ok(result)
51 }
52}
53
54#[derive(Debug)]
55pub struct ClientNewOptions {
56 pub token: String,
57 pub api_version: u8,
58}
59
60#[derive(Debug)]
61pub enum RequestError {
62 Reqwest(reqwest::Error),
63 Parse(ParseError),
64 Response(ResponseError),
65}
66
67impl Display for RequestError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "RequestError")
70 }
71}
72
73impl Error for RequestError {}
74
75impl From<reqwest::Error> for RequestError {
76 fn from(err: reqwest::Error) -> Self {
77 Self::Reqwest(err)
78 }
79}
80
81impl From<ParseError> for RequestError {
82 fn from(err: ParseError) -> Self {
83 Self::Parse(err)
84 }
85}
86
87impl From<ResponseError> for RequestError {
88 fn from(err: ResponseError) -> Self {
89 Self::Response(err)
90 }
91}
92
93#[derive(Debug, Serialize, Deserialize)]
94pub struct ResponseError {
95 pub code: ResponseErrorCode,
97 pub error: String,
98
99 pub general_problem: Option<String>,
101 pub kind: String,
102
103 pub possible_fix: Option<String>,
107
108 pub requirement: Option<String>,
111
112 pub validation_errors: Option<Vec<ValidationError>>,
114}
115
116#[derive(Debug, Serialize, Deserialize)]
117#[serde(rename_all = "snake_case")]
118pub enum ResponseErrorCode {
119 #[serde(rename = "api_external_error")]
120 APIExternalError,
121
122 #[serde(rename = "api_internal_error")]
123 APIInternalError,
124 CantParseJSON,
125 CapabilityAccessDenied,
126 ComponentUnavailable,
127 ContentFormatNotFound,
128 CouldNotParseBody,
129 Deadlock,
130 DuplicateEntry,
131 EnterpriseSignupError,
132
133 #[serde(rename = "http_method_not_supported")]
134 HTTPMethodNotSupported,
135
136 #[serde(rename = "https_required")]
137 HTTPSRequired,
138 IntegrationError,
139 InvalidAuthentication,
140 InvalidParameter,
141 InvalidUpload,
142 NotAcceptable,
143 RequestEntityTooLarge,
144 RequiresGet,
145 RequiresPost,
146 RouteNotFound,
147 ServerUnderLoad,
148 Timeout,
149 Unauthenticated,
150 UnauthorizedOperation,
151 UnfoundResource,
152 UnhandledCondition,
153
154 #[serde(rename = "xhr_required")]
155 XHRRequired,
156}
157
158#[derive(Debug, Serialize, Deserialize)]
159pub struct ValidationError {
160 pub field: String,
162
163 pub problem: String,
165}