aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/api/mod.rs21
-rw-r--r--src/bt.rs2
-rw-r--r--src/main.rs21
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());
diff --git a/src/bt.rs b/src/bt.rs
index aac8484..21078ff 100644
--- a/src/bt.rs
+++ b/src/bt.rs
@@ -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