1extern crate reqwest;
2extern crate serde;
3
4#[cfg(test)]
5use mockito;
6
7use reqwest::blocking::{Client as NetworkClient, RequestBuilder, Response};
8use reqwest::header::CONNECTION;
9use reqwest::{Method, StatusCode};
10use serde::Serialize;
11
12use std::fmt;
13use std::io::Read;
14use std::str;
15
16use crate::error::MMCError;
17use crate::error::MMCResult;
18
19#[cfg(not(test))]
20const LIVE_URL: &'static str = "https://media.services.pbs.org/api/v1";
21#[cfg(not(test))]
22const STAGING_URL: &'static str = "https://media-staging.services.pbs.org/api/v1";
23
24#[cfg(test)]
25const LIVE_URL: &'static str = mockito::SERVER_URL;
26#[cfg(test)]
27const STAGING_URL: &'static str = mockito::SERVER_URL;
28
29#[derive(Debug)]
31pub struct Client {
32 key: String,
33 secret: String,
34 base: String,
35 client: NetworkClient,
36}
37
38pub type Params<'a> = Vec<(&'a str, &'a str)>;
39
40type ParentEndpoint<'a> = (Endpoints, &'a str);
41
42#[derive(Serialize)]
43struct MoveTarget {
44 #[serde(skip_serializing_if = "Option::is_none")]
45 show: Option<String>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 season: Option<String>,
48}
49
50impl MoveTarget {
51 pub fn for_endpoint(endpoint: &Endpoints, id: &str) -> MMCResult<MoveTarget> {
52 match *endpoint {
53 Endpoints::Season => Ok(MoveTarget {
54 show: None,
55 season: Some(id.to_string()),
56 }),
57 Endpoints::Show => Ok(MoveTarget {
58 show: Some(id.to_string()),
59 season: None,
60 }),
61 _ => Err(MMCError::UnsupportedMoveParent(endpoint.to_string())),
62 }
63 }
64}
65
66#[derive(Serialize)]
67struct Move {
68 #[serde(rename = "type")]
69 _type: String,
70 id: String,
71 attributes: MoveTarget,
72}
73
74#[derive(Serialize)]
75struct MoveRequest {
76 data: Move,
77}
78
79#[derive(Clone, Debug)]
81pub enum Endpoints {
82 Asset,
84
85 Changelog,
87
88 Collection,
90
91 Episode,
93
94 Franchise,
96
97 Season,
99
100 Show,
102
103 Special,
105}
106
107impl Endpoints {
108 fn singular(&self) -> String {
109 match *self {
110 Endpoints::Asset => "asset",
111 Endpoints::Changelog => "changelog",
112 Endpoints::Collection => "collection",
113 Endpoints::Episode => "episode",
114 Endpoints::Franchise => "franchise",
115 Endpoints::Season => "season",
116 Endpoints::Show => "show",
117 Endpoints::Special => "special",
118 }
119 .to_string()
120 }
121}
122
123impl fmt::Display for Endpoints {
124 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
125 let string_form = match *self {
126 Endpoints::Asset => "assets",
127 Endpoints::Changelog => "changelog",
128 Endpoints::Collection => "collections",
129 Endpoints::Episode => "episodes",
130 Endpoints::Franchise => "franchises",
131 Endpoints::Season => "seasons",
132 Endpoints::Show => "shows",
133 Endpoints::Special => "specials",
134 };
135
136 write!(f, "{}", string_form)
137 }
138}
139
140impl str::FromStr for Endpoints {
141 type Err = MMCError;
142
143 fn from_str(s: &str) -> Result<Self, Self::Err> {
144 match s {
145 "asset" | "assets" => Ok(Endpoints::Asset),
146 "changelog" => Ok(Endpoints::Changelog),
147 "collection" | "collections" => Ok(Endpoints::Collection),
148 "episode" | "episodes" => Ok(Endpoints::Episode),
149 "franchise" | "franchises" => Ok(Endpoints::Franchise),
150 "season" | "seasons" => Ok(Endpoints::Season),
151 "show" | "shows" => Ok(Endpoints::Show),
152 "special" | "specials" => Ok(Endpoints::Special),
153 x => Err(MMCError::UnknownEndpoint(x.to_string())),
154 }
155 }
156}
157
158impl Client {
159 pub fn new(key: &str, secret: &str) -> MMCResult<Client> {
161 Client::client_builder(key, secret, LIVE_URL)
162 }
163
164 pub fn staging(key: &str, secret: &str) -> MMCResult<Client> {
166 Client::client_builder(key, secret, STAGING_URL)
167 }
168
169 fn client_builder(key: &str, secret: &str, base: &str) -> MMCResult<Client> {
170 NetworkClient::builder()
171 .build()
172 .map_err(MMCError::Network)
173 .and_then(|net_client| {
174 Ok(Client {
175 key: String::from(key),
176 secret: String::from(secret),
177 base: String::from(base),
178 client: net_client,
179 })
180 })
181 }
182
183 pub fn get(&self, endpoint: Endpoints, id: &str, params: Option<Params>) -> MMCResult<String> {
186 self.rq_get(
187 Client::build_url(
188 self.base.as_str(),
189 None,
190 endpoint,
191 Some(id),
192 params.unwrap_or(vec![]),
193 )
194 .as_str(),
195 )
196 }
197
198 pub fn list(&self, endpoint: Endpoints, params: Params) -> MMCResult<String> {
201 self.rq_get(Client::build_url(self.base.as_str(), None, endpoint, None, params).as_str())
202 }
203
204 pub fn child_list(
207 &self,
208 endpoint: Endpoints,
209 parent_id: &str,
210 parent_endpoint: Endpoints,
211 params: Option<Params>,
212 ) -> MMCResult<String> {
213 self.rq_get(
214 Client::build_url(
215 self.base.as_str(),
216 Some((parent_endpoint, parent_id)),
217 endpoint,
218 None,
219 params.unwrap_or(Vec::new()),
220 )
221 .as_str(),
222 )
223 }
224
225 pub fn create<T: Serialize>(
228 &self,
229 parent: Endpoints,
230 id: &str,
231 endpoint: Endpoints,
232 body: &T,
233 ) -> MMCResult<String> {
234 self.rq_post(
235 Client::build_url(
236 self.base.as_str(),
237 Some((parent, id)),
238 endpoint,
239 None,
240 vec![],
241 )
242 .as_str(),
243 body,
244 )
245 }
246
247 pub fn edit(&self, endpoint: Endpoints, id: &str) -> MMCResult<String> {
249 self.rq_get(
250 Client::build_edit_url(self.base.as_str(), None, endpoint, Some(id), vec![]).as_str(),
251 )
252 }
253
254 pub fn update<T: Serialize>(
256 &self,
257 endpoint: Endpoints,
258 id: &str,
259 body: &T,
260 ) -> MMCResult<String> {
261 self.rq_patch(
262 Client::build_edit_url(self.base.as_str(), None, endpoint, Some(id), vec![]).as_str(),
263 body,
264 )
265 }
266
267 pub fn delete(&self, endpoint: Endpoints, id: &str) -> MMCResult<String> {
269 self.rq_delete(
270 Client::build_edit_url(self.base.as_str(), None, endpoint, Some(id), vec![]).as_str(),
271 )
272 }
273
274 pub fn change_parent(
276 &self,
277 parent_endpoint: Endpoints,
278 parent_id: &str,
279 child_endpoint: Endpoints,
280 child_id: &str,
281 ) -> MMCResult<String> {
282 let move_request = MoveRequest {
283 data: Move {
284 _type: child_endpoint.singular(),
285 id: child_id.to_string(),
286 attributes: MoveTarget::for_endpoint(&parent_endpoint, parent_id)?,
287 },
288 };
289
290 self.rq_patch(
291 Client::build_url(
292 self.base.as_str(),
293 None,
294 child_endpoint,
295 Some(child_id),
296 vec![],
297 )
298 .as_str(),
299 &move_request,
300 )
301 }
302
303 pub fn url(&self, url: &str) -> MMCResult<String> {
305 self.rq_get(url)
306 }
307
308 pub fn asset(&self, id: &str, params: Option<Params>) -> MMCResult<String> {
310 self.get(Endpoints::Asset, id, params)
311 }
312
313 pub fn assets(
315 &self,
316 parent_id: &str,
317 parent_endpoint: Endpoints,
318 params: Option<Params>,
319 ) -> MMCResult<String> {
320 self.child_list(Endpoints::Asset, parent_id, parent_endpoint, params)
321 }
322
323 pub fn changelog(&self, params: Params) -> MMCResult<String> {
325 self.list(Endpoints::Changelog, params)
326 }
327
328 pub fn collection(&self, id: &str, params: Option<Params>) -> MMCResult<String> {
330 self.get(Endpoints::Collection, id, params)
331 }
332
333 pub fn collections(&self, params: Params) -> MMCResult<String> {
335 self.list(Endpoints::Collection, params)
336 }
337
338 pub fn episode(&self, id: &str, params: Option<Params>) -> MMCResult<String> {
340 self.get(Endpoints::Episode, id, params)
341 }
342
343 pub fn episodes(&self, season_id: &str, params: Option<Params>) -> MMCResult<String> {
345 self.child_list(Endpoints::Episode, season_id, Endpoints::Season, params)
346 }
347
348 pub fn franchise(&self, id: &str, params: Option<Params>) -> MMCResult<String> {
350 self.get(Endpoints::Franchise, id, params)
351 }
352
353 pub fn franchises(&self, params: Params) -> MMCResult<String> {
355 self.list(Endpoints::Franchise, params)
356 }
357
358 pub fn season(&self, id: &str, params: Option<Params>) -> MMCResult<String> {
360 self.get(Endpoints::Season, id, params)
361 }
362
363 pub fn seasons(&self, show_id: &str, params: Option<Params>) -> MMCResult<String> {
365 self.child_list(Endpoints::Season, show_id, Endpoints::Show, params)
366 }
367
368 pub fn special(&self, id: &str, params: Option<Params>) -> MMCResult<String> {
370 self.get(Endpoints::Special, id, params)
371 }
372
373 pub fn specials(&self, show_id: &str, params: Option<Params>) -> MMCResult<String> {
375 self.child_list(Endpoints::Special, show_id, Endpoints::Show, params)
376 }
377
378 pub fn show(&self, id: &str, params: Option<Params>) -> MMCResult<String> {
380 self.get(Endpoints::Show, id, params)
381 }
382
383 pub fn shows(&self, params: Params) -> MMCResult<String> {
385 self.list(Endpoints::Show, params)
386 }
387
388 fn rq_get(&self, url: &str) -> MMCResult<String> {
390 self.rq_send(self.client.get(url))
391 }
392
393 fn rq_post<T: Serialize>(&self, url: &str, body: &T) -> MMCResult<String> {
395 self.rq_send(self.client.post(url).json(body))
396 }
397
398 fn rq_patch<T: Serialize>(&self, url: &str, body: &T) -> MMCResult<String> {
400 self.rq_send(self.client.request(Method::PATCH, url).json(body))
401 }
402
403 fn rq_delete(&self, url: &str) -> MMCResult<String> {
405 self.rq_send(self.client.request(Method::DELETE, url))
406 }
407
408 fn rq_send(&self, req: RequestBuilder) -> MMCResult<String> {
410 req.basic_auth(self.key.to_string(), Some(self.secret.to_string()))
411 .header(CONNECTION, "close")
412 .send()
413 .map_err(MMCError::Network)
414 .and_then(Client::handle_response)
415 }
416
417 fn build_edit_url(
418 base_url: &str,
419 parent: Option<ParentEndpoint>,
420 endpoint: Endpoints,
421 id: Option<&str>,
422 params: Params,
423 ) -> String {
424 let mut url = Client::build_url(base_url, parent, endpoint, id, params);
425 url.push_str("edit/");
426
427 url
428 }
429
430 fn build_url(
431 base_url: &str,
432 parent: Option<ParentEndpoint>,
433 endpoint: Endpoints,
434 id: Option<&str>,
435 params: Params,
436 ) -> String {
437 let mut url = base_url.to_string();
439 url.push('/');
440
441 if let Some(p_endpoint) = parent {
443 url.push_str(p_endpoint.0.to_string().as_str());
444 url.push('/');
445 url.push_str(p_endpoint.1);
446 url.push('/');
447 }
448
449 let endpoint_string = endpoint.to_string();
451 url.push_str(endpoint_string.as_str());
452 url.push('/');
453
454 if let Some(id_val) = id {
456 url.push_str(id_val);
457 url.push('/');
458 }
459
460 url + Client::format_params(params).as_str()
462 }
463
464 fn format_params(params: Params) -> String {
465 if !params.is_empty() {
466 let param_string = params
467 .iter()
468 .map(|&(name, value)| format!("{}={}", name, value))
469 .collect::<Vec<String>>()
470 .join("&");
471
472 let mut args = "?".to_owned();
473 args.push_str(param_string.as_str());
474 args
475 } else {
476 String::new()
477 }
478 }
479
480 fn handle_response(response: Response) -> MMCResult<String> {
481 match response.status() {
482 StatusCode::OK | StatusCode::NO_CONTENT => Client::parse_success(response),
483 StatusCode::BAD_REQUEST => Client::parse_bad_request(response),
484 StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => Err(MMCError::NotAuthorized),
485 StatusCode::NOT_FOUND => Err(MMCError::ResourceNotFound),
486 x => Err(MMCError::APIFailure(x)),
487 }
488 }
489
490 fn parse_success(response: Response) -> MMCResult<String> {
491 Client::parse_response_body(response)
492 }
493
494 fn parse_bad_request(response: Response) -> MMCResult<String> {
495 Client::parse_response_body(response).and_then(|body| Err(MMCError::BadRequest(body)))
496 }
497
498 fn parse_response_body(mut response: Response) -> MMCResult<String> {
499 let mut buffer = Vec::new();
501
502 r#try!(response.read_to_end(&mut buffer).map_err(MMCError::Io));
505
506 let result = String::from_utf8(buffer);
508
509 match result {
511 Ok(string) => Ok(string),
512 Err(err) => Err(MMCError::Convert(err)),
513 }
514 }
515}