diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/api/mod.rs | 21 | ||||
-rw-r--r-- | src/bt.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 21 |
3 files changed, 38 insertions, 6 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs index 9c771fd..c33e6ba 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -16,7 +16,7 @@ pub use self::{ use image::DynamicImage; use itertools::Itertools; -use reqwest::{Client, Url}; +use reqwest::{Client, StatusCode, Url}; use serde::{de::DeserializeOwned, Serialize}; use std::path::Path; @@ -28,6 +28,7 @@ const BASE_URL: &str = "https://api.guildwars2.com/v2/"; quick_error! { #[derive(Debug)] pub enum ApiError { + ItemNotFound {} SerializationError(err: serde_json::Error) { cause(err) from() @@ -47,6 +48,23 @@ quick_error! { } } +trait ApiResponse +where + Self: Sized, +{ + fn ensure_found(self) -> Result<Self, ApiError>; +} + +impl ApiResponse for reqwest::Response { + fn ensure_found(self) -> Result<Self, ApiError> { + if self.status() == StatusCode::PARTIAL_CONTENT || self.status() == StatusCode::NOT_FOUND { + Err(ApiError::ItemNotFound) + } else { + Ok(self) + } + } +} + /// Trait for API objects that have an ID. /// /// This is used by [`Api`](struct.Api.html) to properly retrieve and cache objects. @@ -143,6 +161,7 @@ impl Api { .get(url) .query(&[("ids", api_arg)]) .send()? + .ensure_found()? .json()?; for result in &resp { let cache_path = format!("{}{}", cache_prefix, result.get_id().to_string()); @@ -112,7 +112,7 @@ pub enum Legend { } impl Legend { - pub fn get_api_id(self) -> Option<String> { + pub fn api_id(self) -> Option<String> { Some( match self { Legend::None => return None, diff --git a/src/main.rs b/src/main.rs index d3839b2..181d64a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,6 +83,19 @@ impl StdError for NotFound {} /// 2. We don't want yet another error kind with a lot of kinds. type MainResult<T> = Result<T, Box<dyn StdError>>; +/// A trait for containers that only contain a single item. +trait SingleContainer<T> { + /// Extract the single element by consuming the container. + fn single(self) -> T; +} + +impl<T> SingleContainer<T> for Vec<T> { + fn single(self) -> T { + assert_eq!(self.len(), 1, "this container must have exactly 1 element."); + self.into_iter().next().unwrap() + } +} + /// Find the profession by the given name. fn find_profession(api: &mut Api, name: &str) -> MainResult<Profession> { let profession_ids = api.get_profession_ids()?; @@ -93,7 +106,7 @@ fn find_profession(api: &mut Api, name: &str) -> MainResult<Profession> { .ok_or_else(|| NotFound::Profession(name.to_owned()))? .clone(); - Ok(api.get_professions(&[profession_id])?.remove(0)) + Ok(api.get_professions(&[profession_id])?.single()) } /// Resolve a skill. @@ -106,7 +119,7 @@ fn resolve_skill(api: &mut Api, profession: &Profession, text: &str) -> MainResu if let Ok(num_id) = numeric { let exists = profession.skills.iter().any(|s| s.id == num_id); if exists { - return Ok(api.get_skills(&[num_id])?.remove(0)); + return Ok(api.get_skills(&[num_id])?.single()); } else { return Err(NotFound::SkillId(num_id).into()); } @@ -191,10 +204,10 @@ fn run_searching(api: &mut Api, matches: &ArgMatches) -> MainResult<BuildTemplat .map(|s| resolve_skill(api, &profession, s)) .collect::<Result<Vec<_>, _>>()? } else if let Some(l) = legends.first() { - let l = api.get_legends(&[l.get_api_id().unwrap()])?.remove(0); + let l = api.get_legends(&[l.api_id().unwrap()])?.single(); let mut result = Vec::new(); for skill_id in (&[l.heal]).iter().chain(&l.utilities).chain(&[l.elite]) { - let skill = api.get_skills(&[*skill_id])?.remove(0); + let skill = api.get_skills(&[*skill_id])?.single(); result.push(skill); } result |