Skip to content

Commit

Permalink
Merge pull request #2 from YuukiToriyama/feature/return-parse-error-i…
Browse files Browse the repository at this point in the history
…f-a-panic-occurs

パースエラーが発生した場合、エラーの内容をパース結果と共に返すようにした
  • Loading branch information
YuukiToriyama authored Nov 23, 2023
2 parents f826552 + 5062ab3 commit 4bb8d0e
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 40 deletions.
13 changes: 10 additions & 3 deletions src/entity.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::err::Error;
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
Expand All @@ -21,20 +22,26 @@ pub struct Town {
}

#[derive(Serialize)]
pub struct ParsedAddress {
pub struct Address {
pub prefecture: String,
pub city: String,
pub town: String,
pub rest: String,
}

impl ParsedAddress {
impl Address {
pub fn new(prefecture_name: &str, city_name: &str, town_name: &str, rest_name: &str) -> Self {
ParsedAddress {
Address {
prefecture: prefecture_name.to_string(),
city: city_name.to_string(),
town: town_name.to_string(),
rest: rest_name.to_string(),
}
}
}

#[derive(Serialize)]
pub struct ParseResult {
pub address: Address,
pub error: Option<Error>,
}
41 changes: 41 additions & 0 deletions src/err.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::fmt::{Display, Formatter};

use serde::Serialize;

#[derive(Serialize, Debug, PartialEq)]
pub struct Error {
pub error_type: String,
pub error_message: String,
}

impl Error {
pub fn new_parse_error(parse_error_kind: ParseErrorKind) -> Self {
Error {
error_type: "ParseError".to_string(),
error_message: parse_error_kind.to_string(),
}
}
pub fn new_resource_unavailable_error(message: &str) -> Self {
Error {
error_type: "ResourceUnavailableError".to_string(),
error_message: message.to_string(),
}
}
}

pub enum ParseErrorKind {
PREFECTURE,
CITY,
TOWN,
}

impl Display for ParseErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let label = match *self {
Self::PREFECTURE => "都道府県",
Self::CITY => "市区町村",
Self::TOWN => "町名",
};
write!(f, "一致する{}がありませんでした", label)
}
}
12 changes: 11 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

mod api;
mod entity;
mod err;
mod parser;

use crate::api::wasm::ApiImplForWasm;
Expand Down Expand Up @@ -37,7 +38,16 @@ mod integration_tests {
let parser = Parser();
assert_eq!(
parser.parse("岩手県盛岡市内丸10番1号").await,
"{\"prefecture\":\"岩手県\",\"city\":\"盛岡市\",\"town\":\"内丸\",\"rest\":\"10番1号\"}".to_string()
"{\"address\":{\"prefecture\":\"岩手県\",\"city\":\"盛岡市\",\"town\":\"内丸\",\"rest\":\"10番1号\"},\"error\":null}".to_string()
)
}

#[wasm_bindgen_test]
async fn parse_fail_unknown_town_name() {
let parser = Parser();
assert_eq!(
parser.parse("東京都中央区銀座九丁目").await,
"{\"address\":{\"prefecture\":\"東京都\",\"city\":\"中央区\",\"town\":\"\",\"rest\":\"銀座九丁目\"},\"error\":{\"error_type\":\"ParseError\",\"error_message\":\"一致する町名がありませんでした\"}}".to_string()
)
}
}
107 changes: 71 additions & 36 deletions src/parser.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::api::Api;
use crate::entity::ParsedAddress;
use crate::entity::{Address, ParseResult};
use crate::err::{Error, ParseErrorKind};
use crate::parser::read_city::read_city;
use crate::parser::read_prefecture::read_prefecture;
use crate::parser::read_town::read_town;
Expand All @@ -8,17 +9,27 @@ mod read_city;
mod read_prefecture;
mod read_town;

pub async fn parse<T: Api>(api: T, input: &str) -> ParsedAddress {
pub async fn parse<T: Api>(api: T, input: &str) -> ParseResult {
// 都道府県を特定
let (rest, prefecture_name) = match read_prefecture(input) {
None => return ParsedAddress::new("", "", "", input),
None => {
return ParseResult {
address: Address::new("", "", "", input),
error: Some(Error::new_parse_error(ParseErrorKind::PREFECTURE)),
}
}
Some(result) => result,
};
// その都道府県の市町村名リストを取得
let prefecture = api.get_prefecture_master(prefecture_name).await.unwrap();
// 市町村名を特定
let (rest, city_name) = match read_city(rest, prefecture) {
None => return ParsedAddress::new(prefecture_name, "", "", rest),
None => {
return ParseResult {
address: Address::new(prefecture_name, "", "", rest),
error: Some(Error::new_parse_error(ParseErrorKind::CITY)),
}
}
Some(result) => result,
};
// その市町村の町名リストを取得
Expand All @@ -28,79 +39,103 @@ pub async fn parse<T: Api>(api: T, input: &str) -> ParsedAddress {
.unwrap();
// 町名を特定
let (rest, town_name) = match read_town(rest, city) {
None => return ParsedAddress::new(prefecture_name, city_name, "", rest),
None => {
return ParseResult {
address: Address::new(prefecture_name, city_name, "", rest),
error: Some(Error::new_parse_error(ParseErrorKind::TOWN)),
}
}
Some(result) => result,
};

ParsedAddress::new(prefecture_name, city_name, town_name, rest)
ParseResult {
address: Address::new(prefecture_name, city_name, town_name, rest),
error: None,
}
}

#[cfg(test)]
mod parser_tests {
use crate::api::mock::ApiMock;
use crate::api::wasm::ApiImplForWasm;
use crate::err::ParseErrorKind;
use crate::parser::parse;
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};

#[tokio::test]
async fn parse_mocked_success_神奈川県平塚市御殿二丁目() {
let api = ApiMock { should_fail: false };
let address = parse(api, "神奈川県平塚市御殿二丁目2-23").await;
assert_eq!(address.prefecture, "神奈川県".to_string());
assert_eq!(address.city, "平塚市".to_string());
assert_eq!(address.town, "御殿二丁目".to_string());
assert_eq!(address.rest, "2-23".to_string());
let result = parse(api, "神奈川県平塚市御殿二丁目2-23").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, "2-23".to_string());
assert_eq!(result.error, None);
}

#[tokio::test]
async fn parse_mocked_success_神奈川県平塚市桜ケ丘() {
let api = ApiMock { should_fail: false };
let address = parse(api, "神奈川県平塚市桜ケ丘100-1").await;
assert_eq!(address.prefecture, "神奈川県".to_string());
assert_eq!(address.city, "平塚市".to_string());
assert_eq!(address.town, "桜ケ丘".to_string());
assert_eq!(address.rest, "100-1".to_string());
let result = parse(api, "神奈川県平塚市桜ケ丘100-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, "100-1".to_string());
assert_eq!(result.error, None);
}

#[tokio::test]
async fn parse_mocked_fail_都道府県名が間違っている場合() {
let api = ApiMock { should_fail: false };
let address = parse(api, "神奈側県平塚市桜ケ丘100-1").await;
assert_eq!(address.prefecture, "".to_string());
assert_eq!(address.city, "".to_string());
assert_eq!(address.town, "".to_string());
assert_eq!(address.rest, "神奈側県平塚市桜ケ丘100-1".to_string());
let result = parse(api, "神奈側県平塚市桜ケ丘100-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, "神奈側県平塚市桜ケ丘100-1".to_string());
assert_eq!(
result.error.unwrap().error_message,
ParseErrorKind::PREFECTURE.to_string()
);
}

#[tokio::test]
async fn parse_mocked_fail_市町村名が間違っている場合() {
let api = ApiMock { should_fail: false };
let address = parse(api, "神奈川県平束市桜ケ丘100-1").await;
assert_eq!(address.prefecture, "神奈川県".to_string());
assert_eq!(address.city, "".to_string());
assert_eq!(address.town, "".to_string());
assert_eq!(address.rest, "平束市桜ケ丘100-1".to_string());
let result = parse(api, "神奈川県平束市桜ケ丘100-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, "平束市桜ケ丘100-1".to_string());
assert_eq!(
result.error.unwrap().error_message,
ParseErrorKind::CITY.to_string()
);
}

#[tokio::test]
async fn parse_mocked_fail_町名が間違っている場合() {
let api = ApiMock { should_fail: false };
let address = parse(api, "神奈川県平塚市新百合ヶ丘100-1").await;
assert_eq!(address.prefecture, "神奈川県".to_string());
assert_eq!(address.city, "平塚市".to_string());
assert_eq!(address.town, "".to_string());
assert_eq!(address.rest, "新百合ヶ丘100-1".to_string());
let result = parse(api, "神奈川県平塚市新百合ヶ丘100-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, "新百合ヶ丘100-1".to_string());
assert_eq!(
result.error.unwrap().error_message,
ParseErrorKind::TOWN.to_string()
);
}

wasm_bindgen_test_configure!(run_in_browser);

#[wasm_bindgen_test]
async fn parse_wasm_success() {
let api = ApiImplForWasm {};
let address = parse(api, "兵庫県淡路市生穂新島8番地").await;
assert_eq!(address.prefecture, "兵庫県".to_string());
assert_eq!(address.city, "淡路市".to_string());
assert_eq!(address.town, "生穂".to_string());
assert_eq!(address.rest, "新島8番地".to_string());
let result = parse(api, "兵庫県淡路市生穂新島8番地").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番地".to_string());
assert_eq!(result.error, None);
}
}

0 comments on commit 4bb8d0e

Please sign in to comment.