From 22f9d440e7c66e2d10c816241ebe4435613b0d6f Mon Sep 17 00:00:00 2001 From: Yuuki Toriyama Date: Thu, 23 Nov 2023 22:48:32 +0900 Subject: [PATCH 1/7] rename: `Error::new_resource_unavailable_error()` -> `Error::new_api_error()` --- src/err.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/err.rs b/src/err.rs index 1cd65ac0..6b2cf2a0 100644 --- a/src/err.rs +++ b/src/err.rs @@ -15,7 +15,7 @@ impl Error { error_message: parse_error_kind.to_string(), } } - pub fn new_resource_unavailable_error(message: &str) -> Self { + pub fn new_api_error(message: &str) -> Self { Error { error_type: "ResourceUnavailableError".to_string(), error_message: message.to_string(), From 5b83824622b234481aba1e0617b37abc7a06c55c Mon Sep 17 00:00:00 2001 From: Yuuki Toriyama Date: Thu, 23 Nov 2023 22:53:16 +0900 Subject: [PATCH 2/7] =?UTF-8?q?API=E3=82=A8=E3=83=A9=E3=83=BC=E6=99=82?= =?UTF-8?q?=E3=81=AE=E3=82=A8=E3=83=A9=E3=83=BC=E3=82=92=E6=A0=BC=E7=B4=8D?= =?UTF-8?q?=E3=81=99=E3=82=8Benum`ApiErrorKind`=E3=82=92=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=E3=81=97=E3=80=81`Error::new=5Fapi=5Ferror()`=E3=81=A7?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E6=A7=8B=E9=80=A0=E4=BD=93=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/err.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/err.rs b/src/err.rs index 6b2cf2a0..6e74fff3 100644 --- a/src/err.rs +++ b/src/err.rs @@ -15,10 +15,14 @@ impl Error { error_message: parse_error_kind.to_string(), } } - pub fn new_api_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, } } } @@ -39,3 +43,8 @@ impl Display for ParseErrorKind { write!(f, "一致する{}がありませんでした", label) } } + +pub enum ApiErrorKind { + FETCH(String), + DESERIALIZE(String), +} From 3808d50538cb2ab9b0baf737cedeee1bc9377e98 Mon Sep 17 00:00:00 2001 From: Yuuki Toriyama Date: Thu, 23 Nov 2023 23:10:33 +0900 Subject: [PATCH 3/7] =?UTF-8?q?`Api.get=5Fprefecture=5Fmaster()`=E3=81=AE?= =?UTF-8?q?=E8=BF=94=E3=82=8A=E5=80=A4=E3=81=AE=E5=9E=8B=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api.rs | 3 ++- src/api/mock.rs | 7 +++++-- src/api/wasm.rs | 16 +++++++++++----- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/api.rs b/src/api.rs index 926c26db..89139225 100644 --- a/src/api.rs +++ b/src/api.rs @@ -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; + async fn get_prefecture_master(&self, prefecture_name: &str) -> Result; async fn get_city_master(&self, prefecture_name: &str, city_name: &str) -> Result; } diff --git a/src/api/mock.rs b/src/api/mock.rs index 926a7494..8b15244c 100644 --- a/src/api/mock.rs +++ b/src/api/mock.rs @@ -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 { + async fn get_prefecture_master(&self, _prefecture_name: &str) -> Result { 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(), diff --git a/src/api/wasm.rs b/src/api/wasm.rs index 8f62aab2..c05c5091 100644 --- a/src/api/wasm.rs +++ b/src/api/wasm.rs @@ -1,21 +1,27 @@ 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 { + async fn get_prefecture_master(&self, prefecture_name: &str) -> Result { 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::().await.unwrap(); - Ok(prefecture) + match response.json::().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))) } } From 9bbc7bb391708aeac6730422371488b18adf1ca7 Mon Sep 17 00:00:00 2001 From: Yuuki Toriyama Date: Thu, 23 Nov 2023 23:19:47 +0900 Subject: [PATCH 4/7] =?UTF-8?q?`Api.get=5Fcity=5Fmaster()`=E3=81=AE?= =?UTF-8?q?=E8=BF=94=E3=82=8A=E5=80=A4=E3=81=AE=E5=9E=8B=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api.rs | 2 +- src/api/mock.rs | 9 ++++----- src/api/wasm.rs | 23 +++++++++++++++-------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/api.rs b/src/api.rs index 89139225..3cc46c76 100644 --- a/src/api.rs +++ b/src/api.rs @@ -7,5 +7,5 @@ use crate::err::Error; pub trait Api { async fn get_prefecture_master(&self, prefecture_name: &str) -> Result; async fn get_city_master(&self, prefecture_name: &str, city_name: &str) - -> Result; + -> Result; } diff --git a/src/api/mock.rs b/src/api/mock.rs index 8b15244c..befd1862 100644 --- a/src/api/mock.rs +++ b/src/api/mock.rs @@ -29,12 +29,11 @@ impl Api for ApiMock { &self, _prefecture_name: &str, _city_name: &str, - ) -> Result { + ) -> Result { 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(), diff --git a/src/api/wasm.rs b/src/api/wasm.rs index c05c5091..e3808f7f 100644 --- a/src/api/wasm.rs +++ b/src/api/wasm.rs @@ -29,20 +29,27 @@ impl Api for ApiImplForWasm { &self, prefecture_name: &str, city_name: &str, - ) -> Result { + ) -> Result { 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::>().await.unwrap(); - Ok(City { - name: city_name.to_string(), - towns, - }) + match response.json::>().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) + )) } } } From e314369ed203988b76fd95e0adf21501f0d3f752 Mon Sep 17 00:00:00 2001 From: Yuuki Toriyama Date: Thu, 23 Nov 2023 23:40:36 +0900 Subject: [PATCH 5/7] =?UTF-8?q?ApiImplForWasm=E3=81=AE=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E5=8A=A0=E7=AD=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit panicすることを確かめるのではなく、エラーメッセージを検証するように変更 --- src/api/wasm.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/api/wasm.rs b/src/api/wasm.rs index e3808f7f..626b5f12 100644 --- a/src/api/wasm.rs +++ b/src/api/wasm.rs @@ -91,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] @@ -112,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() + ); } } From 00ff3c32aa79f7a139d89881bf9152d15d1bba56 Mon Sep 17 00:00:00 2001 From: Yuuki Toriyama Date: Thu, 23 Nov 2023 23:57:58 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=E9=83=BD=E9=81=93=E5=BA=9C=E7=9C=8C?= =?UTF-8?q?=E3=83=9E=E3=82=B9=E3=82=BF=E3=81=AE=E5=8F=96=E5=BE=97=E3=81=AB?= =?UTF-8?q?=E5=A4=B1=E6=95=97=E3=81=97=E3=81=9F=E5=A0=B4=E5=90=88=E3=80=81?= =?UTF-8?q?ApiError=E3=81=A8=E3=81=97=E3=81=A6=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92ParseResu?= =?UTF-8?q?lt=E3=81=AB=E5=90=AB=E3=82=81=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index c9e65492..defa65bf 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -21,7 +21,13 @@ pub async fn parse(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 => { @@ -98,6 +104,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 }; From 832b4bfaf085bb44c3031a800f3b9f946433b154 Mon Sep 17 00:00:00 2001 From: Yuuki Toriyama Date: Fri, 24 Nov 2023 00:07:29 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=E5=B8=82=E5=8C=BA=E7=94=BA=E6=9D=91?= =?UTF-8?q?=E3=83=9E=E3=82=B9=E3=82=BF=E3=81=AE=E5=8F=96=E5=BE=97=E3=81=AB?= =?UTF-8?q?=E5=A4=B1=E6=95=97=E3=81=97=E3=81=9F=E5=A0=B4=E5=90=88=E3=80=81?= =?UTF-8?q?ApiError=E3=81=A8=E3=81=97=E3=81=A6=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92ParseResu?= =?UTF-8?q?lt=E3=81=AB=E5=90=AB=E3=82=81=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index defa65bf..132037ab 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -39,10 +39,13 @@ pub async fn parse(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 => { @@ -132,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 };