Skip to content

Commit

Permalink
Merge pull request #4 from YuukiToriyama/feature/return-api-error-if-…
Browse files Browse the repository at this point in the history
…a-panic-occurs

APIエラーが発生した場合、エラーの内容をパース結果と共に返すようにした
  • Loading branch information
YuukiToriyama authored Nov 24, 2023
2 parents 82a3470 + 832b4bf commit b0883c5
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 34 deletions.
5 changes: 3 additions & 2 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ pub mod mock;
pub mod wasm;

use crate::entity::{City, Prefecture};
use crate::err::Error;

pub trait Api {
async fn get_prefecture_master(&self, prefecture_name: &str) -> Result<Prefecture, String>;
async fn get_prefecture_master(&self, prefecture_name: &str) -> Result<Prefecture, Error>;
async fn get_city_master(&self, prefecture_name: &str, city_name: &str)
-> Result<City, String>;
-> Result<City, Error>;
}
16 changes: 9 additions & 7 deletions src/api/mock.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use crate::api::Api;
use crate::entity::{City, Prefecture, Town};
use crate::err::{ApiErrorKind, Error};

pub struct ApiMock {
pub should_fail: bool,
}

impl Api for ApiMock {
async fn get_prefecture_master(&self, _prefecture_name: &str) -> Result<Prefecture, String> {
async fn get_prefecture_master(&self, _prefecture_name: &str) -> Result<Prefecture, Error> {
if self.should_fail {
Err("Failed to fetch https://yuukitoriyama.github.io/geolonia-japanese-addresses-accompanist/神奈川県/master.json".to_string())
Err(Error::new_api_error(
ApiErrorKind::FETCH("https://yuukitoriyama.github.io/geolonia-japanese-addresses-accompanist/神奈川県/master.json".to_string())
))
} else {
Ok(Prefecture {
name: "神奈川県".to_string(),
Expand All @@ -26,12 +29,11 @@ impl Api for ApiMock {
&self,
_prefecture_name: &str,
_city_name: &str,
) -> Result<City, String> {
) -> Result<City, Error> {
if self.should_fail {
Err(
"https://geolonia.github.io/japanese-addresses/api/ja/神奈川県/平塚市.json"
.to_string(),
)
Err(Error::new_api_error(
ApiErrorKind::FETCH("https://geolonia.github.io/japanese-addresses/api/ja/神奈川県/平塚市.json".to_string())
))
} else {
Ok(City {
name: "平塚市".to_string(),
Expand Down
55 changes: 38 additions & 17 deletions src/api/wasm.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,55 @@
use crate::api::Api;
use crate::entity::{City, Prefecture, Town};
use crate::err::{ApiErrorKind, Error};
use gloo_net::http::Request;

pub struct ApiImplForWasm {}

impl Api for ApiImplForWasm {
async fn get_prefecture_master(&self, prefecture_name: &str) -> Result<Prefecture, String> {
async fn get_prefecture_master(&self, prefecture_name: &str) -> Result<Prefecture, Error> {
let endpoint = format!(
"https://yuukitoriyama.github.io/geolonia-japanese-addresses-accompanist/{}/master.json",
prefecture_name
);
let response = Request::get(&endpoint).send().await.unwrap();
let response = match Request::get(&endpoint).send().await {
Ok(result) => result,
Err(_) => return Err(Error::new_api_error(ApiErrorKind::FETCH(endpoint))),
};
if response.ok() {
let prefecture = response.json::<Prefecture>().await.unwrap();
Ok(prefecture)
match response.json::<Prefecture>().await {
Ok(result) => Ok(result),
Err(_) => Err(Error::new_api_error(ApiErrorKind::DESERIALIZE(endpoint))),
}
} else {
Err(format!("Failed to fetch {}", &endpoint))
Err(Error::new_api_error(ApiErrorKind::FETCH(endpoint)))
}
}

async fn get_city_master(
&self,
prefecture_name: &str,
city_name: &str,
) -> Result<City, String> {
) -> Result<City, Error> {
let endpoint = format!(
"https://geolonia.github.io/japanese-addresses/api/ja/{}/{}.json",
prefecture_name, city_name
);
let response = Request::get(&endpoint).send().await.unwrap();
let response = match Request::get(&endpoint).send().await {
Ok(result) => result,
Err(_) => return Err(Error::new_api_error(ApiErrorKind::FETCH(endpoint))),
};
if response.ok() {
let towns = response.json::<Vec<Town>>().await.unwrap();
Ok(City {
name: city_name.to_string(),
towns,
})
match response.json::<Vec<Town>>().await {
Ok(towns) => Ok(City {
name: city_name.to_string(),
towns,
}),
Err(_) => Err(Error::new_api_error(ApiErrorKind::DESERIALIZE(endpoint)))
}
} else {
Err(format!("Failed to fetch {}", &endpoint))
Err(Error::new_api_error(
ApiErrorKind::FETCH(endpoint)
))
}
}
}
Expand Down Expand Up @@ -78,10 +91,14 @@ mod api_tests {
}

#[wasm_bindgen_test]
#[should_panic]
async fn get_prefecture_master_fail() {
let api = ApiImplForWasm {};
api.get_prefecture_master("大阪都").await.unwrap();
let result = api.get_prefecture_master("大阪都").await;
assert!(result.is_err());
assert_eq!(
result.err().unwrap().error_message,
"https://yuukitoriyama.github.io/geolonia-japanese-addresses-accompanist/大阪都/master.jsonを取得できませんでした".to_string()
)
}

#[wasm_bindgen_test]
Expand All @@ -99,9 +116,13 @@ mod api_tests {
}

#[wasm_bindgen_test]
#[should_panic]
async fn get_city_master_fail() {
let api = ApiImplForWasm {};
api.get_city_master("石川県", "敦賀市").await.unwrap();
let result = api.get_city_master("石川県", "敦賀市").await;
assert!(result.is_err());
assert_eq!(
result.err().unwrap().error_message,
"https://geolonia.github.io/japanese-addresses/api/ja/石川県/敦賀市.jsonを取得できませんでした".to_string()
);
}
}
15 changes: 12 additions & 3 deletions src/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ impl Error {
error_message: parse_error_kind.to_string(),
}
}
pub fn new_resource_unavailable_error(message: &str) -> Self {
pub fn new_api_error(api_error_kind: ApiErrorKind) -> Self {
let error_message = match api_error_kind {
ApiErrorKind::FETCH(url) => format!("{}を取得できませんでした", url),
ApiErrorKind::DESERIALIZE(url) => format!("{}のデシリアライズに失敗しました", url),
};
Error {
error_type: "ResourceUnavailableError".to_string(),
error_message: message.to_string(),
error_type: "ApiError".to_string(),
error_message,
}
}
}
Expand All @@ -39,3 +43,8 @@ impl Display for ParseErrorKind {
write!(f, "一致する{}がありませんでした", label)
}
}

pub enum ApiErrorKind {
FETCH(String),
DESERIALIZE(String),
}
38 changes: 33 additions & 5 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ pub async fn parse<T: Api>(api: T, input: &str) -> ParseResult {
Some(result) => result,
};
// その都道府県の市町村名リストを取得
let prefecture = api.get_prefecture_master(prefecture_name).await.unwrap();
let prefecture = match api.get_prefecture_master(prefecture_name).await {
Err(error) => return ParseResult {
address: Address::new(prefecture_name, "", "", rest),
error: Some(error)
},
Ok(result) => result,
};
// 市町村名を特定
let (rest, city_name) = match read_city(rest, prefecture) {
None => {
Expand All @@ -33,10 +39,13 @@ pub async fn parse<T: Api>(api: T, input: &str) -> ParseResult {
Some(result) => result,
};
// その市町村の町名リストを取得
let city = api
.get_city_master(prefecture_name, city_name)
.await
.unwrap();
let city = match api.get_city_master(prefecture_name, city_name).await {
Err(error) => return ParseResult {
address: Address::new(prefecture_name, city_name, "", rest),
error: Some(error)
},
Ok(result) => result
};
// 町名を特定
let (rest, town_name) = match read_town(rest, city) {
None => {
Expand Down Expand Up @@ -98,6 +107,20 @@ mod parser_tests {
);
}

#[tokio::test]
async fn parse_mocked_fail_都道府県マスタの取得に失敗する() {
let api = ApiMock {should_fail: true};
let result = parse(api, "東京都新宿区西新宿二丁目8-1").await;
assert_eq!(result.address.prefecture, "東京都".to_string());
assert_eq!(result.address.city, "".to_string());
assert_eq!(result.address.town, "".to_string());
assert_eq!(result.address.rest, "新宿区西新宿二丁目8-1".to_string());
assert_eq!(
result.error.unwrap().error_type,
"ApiError".to_string()
);
}

#[tokio::test]
async fn parse_mocked_fail_市町村名が間違っている場合() {
let api = ApiMock { should_fail: false };
Expand All @@ -112,6 +135,11 @@ mod parser_tests {
);
}

#[tokio::test]
async fn parse_mocked_fail_市区町村マスタの取得に失敗する() {
// TODO: ApiMockの仕様を修正しないとこのテストコードは書けない
}

#[tokio::test]
async fn parse_mocked_fail_町名が間違っている場合() {
let api = ApiMock { should_fail: false };
Expand Down

0 comments on commit b0883c5

Please sign in to comment.