diff --git a/data_validator/src/main.rs b/data_validator/src/main.rs index 9510049d..baf02e1b 100644 --- a/data_validator/src/main.rs +++ b/data_validator/src/main.rs @@ -4,6 +4,10 @@ use std::path::Path; use csv::{ReaderBuilder, StringRecord}; +/// `3!stations.csv` の列インデックス。 +/// `2!lines.csv` の列インデックス。 +const LINES_COL_LINE_CD: usize = 0; + /// `3!stations.csv` の列インデックス。 const STATIONS_COL_STATION_CD: usize = 0; const STATIONS_COL_LINE_CD: usize = 13; @@ -29,6 +33,7 @@ const EXPECTED_CONSECUTIVE_STATION_ORDERS: &[(u32, &[u32])] = &[ fn main() -> Result<(), Box> { let mut invalid_station_ids: Vec = Vec::new(); let mut invalid_type_ids: Vec = Vec::new(); + let mut invalid_line_ids: Vec = Vec::new(); let data_path: &Path = Path::new("data"); let mut rdr = ReaderBuilder::new().from_path(data_path.join("3!stations.csv"))?; @@ -43,6 +48,18 @@ fn main() -> Result<(), Box> { }) .collect(); + let mut rdr = ReaderBuilder::new().from_path(data_path.join("2!lines.csv"))?; + let line_records: Vec = rdr.records().collect::, _>>()?; + let line_ids: HashSet = line_records + .iter() + .map(|row| { + row.get(LINES_COL_LINE_CD) + .unwrap() + .parse::() + .unwrap() + }) + .collect(); + let mut rdr = ReaderBuilder::new().from_path(data_path.join("4!types.csv"))?; let records: Vec = rdr.records().collect::, _>>()?; let type_ids: HashSet = records @@ -83,6 +100,29 @@ fn main() -> Result<(), Box> { } } + // `3!stations.csv` の各行について、紐づく `line_cd` が + // `2!lines.csv` に存在することを検証する (#1030)。 + for record in &station_records { + let line = || record.iter().collect::>().join(","); + + let line_cd: u32 = match record + .get(STATIONS_COL_LINE_CD) + .and_then(|v| v.parse().ok()) + { + Some(id) => id, + None => { + println!("[INVALID] Failed to parse line_cd from row: {}", line()); + invalid_line_ids.push(line()); + continue; + } + }; + + if !line_ids.contains(&line_cd) { + println!("[INVALID] Unrecognized Line ID {:?} Found!", line_cd); + invalid_line_ids.push(line()); + } + } + let invalid_station_orders = validate_station_orders(&station_records); for message in &invalid_station_orders { println!("[INVALID] {message}"); @@ -90,12 +130,14 @@ fn main() -> Result<(), Box> { let has_err = !invalid_station_ids.is_empty() || !invalid_type_ids.is_empty() + || !invalid_line_ids.is_empty() || !invalid_station_orders.is_empty(); if has_err { let report = build_markdown_report( &invalid_station_ids, &invalid_type_ids, + &invalid_line_ids, &invalid_station_orders, ); let report_path = @@ -160,6 +202,7 @@ fn validate_station_orders(station_records: &[StringRecord]) -> Vec { fn build_markdown_report( invalid_station_ids: &[String], invalid_type_ids: &[String], + invalid_line_ids: &[String], invalid_station_orders: &[String], ) -> String { let mut md = String::new(); @@ -173,6 +216,12 @@ fn build_markdown_report( ); } + if !invalid_line_ids.is_empty() { + md.push_str( + "`3!stations.csv` に `2!lines.csv` に存在しない外部キーへの参照が含まれています。\n\n", + ); + } + if !invalid_station_ids.is_empty() { md.push_str(&format!( "### 不正な Station ID ({} 件)\n\n", @@ -201,6 +250,20 @@ fn build_markdown_report( md.push_str("\n\n\n"); } + if !invalid_line_ids.is_empty() { + md.push_str(&format!( + "### 不正な Line ID ({} 件)\n\n", + invalid_line_ids.len() + )); + md.push_str("`2!lines.csv` に存在しない `line_cd` が参照されています。\n\n"); + md.push_str("
\n該当レコード一覧\n\n"); + md.push_str("| 行データ |\n|---|\n"); + for line in invalid_line_ids { + md.push_str(&format!("| `{}` |\n", escape_markdown_cell(line))); + } + md.push_str("\n
\n\n"); + } + if !invalid_station_orders.is_empty() { md.push_str(&format!( "### 駅の並び順エラー ({} 件)\n\n",