Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

エラーメッセージ改善 #53

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions tlmcmddb-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ readme.workspace = true
[dependencies]
anyhow = { version = "1", features = ["backtrace"] }
clap = { version = "4.4.11", features = ["derive"] }
tlmcmddb = "2.5"
tlmcmddb-csv = "2.5"
tlmcmddb = { path = "../tlmcmddb" }
tlmcmddb-csv = { path = "../tlmcmddb-csv" }
serde_json = "1"
2 changes: 1 addition & 1 deletion tlmcmddb-csv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ readme.workspace = true
[dependencies]
anyhow = "1"
csv = "1.3.0"
tlmcmddb = "2.5"
tlmcmddb = { path = "../tlmcmddb" }
serde = { version = "1.0.193", features = ["derive"] }

[dev-dependencies]
Expand Down
36 changes: 22 additions & 14 deletions tlmcmddb-csv/src/cmd.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::io::Read;

use anyhow::{anyhow, ensure, Result};
use anyhow::{anyhow, ensure, Context, Result};
use csv::StringRecord;
use serde::{de::Visitor, Deserialize, Deserializer};
use tlmcmddb::cmd as model;

use crate::{escape::unescape, macros::check_header, util};
use crate::{
escape::unescape, macros::check_header, util, ErrWithPosition, PosStringRecord,
PosStringRecordIterator,
};

/*
+------------+-------+---------+-------+---------------------------------------------------------------------------------------------------------------------------+---------+-------------+--------------+-------+
Expand Down Expand Up @@ -96,38 +99,42 @@ fn build_comment(record: StringRecord) -> model::Comment {

fn parse_body<I, E>(mut iter: I) -> Result<Vec<model::Entry>>
where
I: Iterator<Item = Result<StringRecord, E>>,
I: Iterator<Item = Result<PosStringRecord, E>>,
E: std::error::Error + Send + Sync + 'static,
{
let mut entries = vec![];
while let Some(record) = util::try_next_record(&mut iter)? {
let PosStringRecord { record, position } = &record;
ensure!(record.len() >= 21, "the number of columns is mismatch");
if record[0].is_empty() {
let line: Line = record.deserialize(None)?;
let command = line.try_into()?;
let line: Line = record.deserialize(None).err_with_position(position)?;
let command = line.try_into().context(format!(
"The following error has caused at line {}",
position.line() + 1
))?;
entries.push(model::Entry::Command(command));
} else {
entries.push(model::Entry::Comment(build_comment(record)));
entries.push(model::Entry::Comment(build_comment(record.clone())));
}
}
Ok(entries)
}

pub fn parse<I, E>(mut iter: I) -> Result<(String, model::Database)>
where
I: Iterator<Item = Result<StringRecord, E>>,
I: Iterator<Item = Result<PosStringRecord, E>>,
E: std::error::Error + Send + Sync + 'static,
{
check_first_header(util::next_record(&mut iter)?)?;
let component = parse_second_header(util::next_record(&mut iter)?)?;
check_third_header(util::next_record(&mut iter)?)?;
check_first_header(util::next_record(&mut iter)?.record)?;
let component = parse_second_header(util::next_record(&mut iter)?.record)?;
check_third_header(util::next_record(&mut iter)?.record)?;
let entries = parse_body(&mut iter)?;
Ok((component, model::Database { entries }))
}

pub fn parse_csv<R: Read>(rdr: R) -> Result<(String, model::Database)> {
let mut csv = crate::csv_reader_builder().from_reader(rdr);
let mut iter = csv.records();
let csv = crate::csv_reader_builder().from_reader(rdr);
let mut iter = PosStringRecordIterator::from_reader(csv);
parse(&mut iter)
}

Expand Down Expand Up @@ -275,16 +282,17 @@ impl<'de> Visitor<'de> for HexVisitor {
#[cfg(test)]
mod tests {
use super::*;
use crate::PosStringRecordIterator;

#[test]
fn test() {
let csv = include_bytes!("../fixtures/CMD_DB/valid.csv");
let json = include_bytes!("../fixtures/CMD_DB/valid.json");
let expected: model::Database = serde_json::from_slice(json).unwrap();
let mut rdr = csv::ReaderBuilder::new()
let rdr = csv::ReaderBuilder::new()
.has_headers(false)
.from_reader(csv.as_slice());
let mut iter = rdr.records();
let mut iter = PosStringRecordIterator::from_reader(rdr);
let (_component, actual) = parse(&mut iter).unwrap();
assert_eq!(expected, actual);
}
Expand Down
77 changes: 77 additions & 0 deletions tlmcmddb-csv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,83 @@ pub mod cmd;
pub mod escape;
pub mod tlm;

pub struct PosStringRecord {
pub position: csv::Position,
pub record: csv::StringRecord,
}

struct PosStringRecordIterator<R>(pub csv::StringRecordsIntoIter<R>);

impl<R> PosStringRecordIterator<R>
where
R: std::io::Read,
{
fn from_reader(reader: csv::Reader<R>) -> PosStringRecordIterator<R> {
PosStringRecordIterator(reader.into_records())
}
}

#[derive(Debug)]
struct PositionError<E> {
position: csv::Position,
error: E,
}

impl<E> std::fmt::Display for PositionError<E>
where
E: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Error at line {}: {}",
self.position.line() + 1,
self.error
)
}
}

impl<E> std::error::Error for PositionError<E>
where
E: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.error)
}
}

trait ErrWithPosition {
type T;
type E;
fn err_with_position(self, position: &csv::Position) -> Result<Self::T, PositionError<Self::E>>
where
Self: Sized;
}

impl<T, E> ErrWithPosition for Result<T, E> {
type T = T;
type E = E;
fn err_with_position(self, position: &csv::Position) -> Result<T, PositionError<E>> {
self.map_err(|error| PositionError {
position: position.clone(),
error,
})
}
}

impl<'r, R: 'r> Iterator for PosStringRecordIterator<R>
where
R: std::io::Read,
{
type Item = Result<PosStringRecord, csv::Error>;

fn next(&mut self) -> Option<Self::Item> {
let position = self.0.reader().position().clone();
let record = self.0.next()?;
Some(record.map(|record| PosStringRecord { position, record }))
}
}

pub fn csv_reader_builder() -> csv::ReaderBuilder {
let mut builder = csv::ReaderBuilder::new();
builder.has_headers(false);
Expand Down
5 changes: 3 additions & 2 deletions tlmcmddb-csv/src/tlm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ pub mod telemetry;

pub use filename::Filename;

use crate::PosStringRecordIterator;
use anyhow::Result;
use std::io::Read;

pub fn parse_csv<R: Read>(telemetry_name: String, rdr: R) -> Result<tlmcmddb::tlm::Telemetry> {
let mut csv = crate::csv_reader_builder().from_reader(rdr);
let mut iter = csv.records();
let csv = crate::csv_reader_builder().from_reader(rdr);
let mut iter = PosStringRecordIterator::from_reader(csv);
telemetry::parse(telemetry_name, &mut iter)
}
29 changes: 18 additions & 11 deletions tlmcmddb-csv/src/tlm/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use csv::StringRecord;
use serde::Deserialize;
use tlmcmddb::tlm::{self as model};

use crate::{escape::unescape, macros::check_header, util};
use crate::{escape::unescape, macros::check_header, util, ErrWithPosition, PosStringRecord};

/*
+-------+--------+---------------+---------------------------------------------+------------------------------------------------------+--------+-------+
Expand Down Expand Up @@ -79,12 +79,12 @@ fn check_third_header(record: StringRecord) -> Result<()> {

fn check_headers<I, E>(mut iter: I) -> Result<()>
where
I: Iterator<Item = Result<StringRecord, E>>,
I: Iterator<Item = Result<PosStringRecord, E>>,
E: std::error::Error + Send + Sync + 'static,
{
check_first_header(util::next_record(&mut iter)?)?;
check_second_header(util::next_record(&mut iter)?)?;
check_third_header(util::next_record(&mut iter)?)?;
check_first_header(util::next_record(&mut iter)?.record)?;
check_second_header(util::next_record(&mut iter)?.record)?;
check_third_header(util::next_record(&mut iter)?.record)?;
Ok(())
}

Expand All @@ -100,15 +100,21 @@ fn build_comment(record: StringRecord) -> model::Comment {

fn parse_entries<I, E>(mut iter: I) -> Result<Vec<model::Entry>>
where
I: Iterator<Item = Result<StringRecord, E>>,
I: Iterator<Item = Result<PosStringRecord, E>>,
E: std::error::Error + Send + Sync + 'static,
{
let mut entries = vec![];
let mut current_bit_field_group = None;
while let Some(record) = util::try_next_record(&mut iter)? {
let PosStringRecord { record, position } = record;
if record[0].is_empty() {
let line = record.deserialize::<Line>(None)?;
match line.try_into()? {
let line = record
.deserialize::<Line>(None)
.err_with_position(&position)?;
match line.try_into().context(format!(
"The following error has caused at line {}",
position.line() + 1
))? {
LineModel::BitFieldGroup(bit_field_group) => {
if let Some(bit_field_group) = current_bit_field_group.take() {
entries.push(model::Entry::FieldGroup(bit_field_group));
Expand Down Expand Up @@ -144,7 +150,7 @@ where

pub fn parse<I, E>(mut iter: I) -> Result<Vec<model::Entry>>
where
I: Iterator<Item = Result<StringRecord, E>>,
I: Iterator<Item = Result<PosStringRecord, E>>,
E: std::error::Error + Send + Sync + 'static,
{
check_headers(&mut iter)?;
Expand Down Expand Up @@ -428,16 +434,17 @@ enum ConversionType {
#[cfg(test)]
mod tests {
use super::*;
use crate::PosStringRecordIterator;

#[test]
fn test() {
let csv = include_bytes!("../../fixtures/TLM_DB/valid_body.csv");
let json = include_bytes!("../../fixtures/TLM_DB/valid_body.json");
let expected: Vec<model::Entry> = serde_json::from_slice(json).unwrap();
let mut rdr = csv::ReaderBuilder::new()
let rdr = csv::ReaderBuilder::new()
.has_headers(false)
.from_reader(csv.as_slice());
let mut iter = rdr.records();
let mut iter = PosStringRecordIterator::from_reader(rdr);
let actual = parse(&mut iter).unwrap();
assert_eq!(expected, actual)

Expand Down
17 changes: 9 additions & 8 deletions tlmcmddb-csv/src/tlm/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use anyhow::{anyhow, ensure, Context, Result};
use csv::StringRecord;
use tlmcmddb::tlm as model;

use crate::{escape::unescape, macros::check_header, util};
use crate::{escape::unescape, macros::check_header, util, PosStringRecord};

/*
+---+-----------------+---------+-------------------+
Expand Down Expand Up @@ -82,13 +82,13 @@ fn parse_fourth_line(record: StringRecord) -> Result<bool> {

pub fn parse<I, E>(mut iter: I) -> Result<model::Metadata>
where
I: Iterator<Item = Result<StringRecord, E>>,
I: Iterator<Item = Result<PosStringRecord, E>>,
E: std::error::Error + Send + Sync + 'static,
{
let target = parse_first_line(util::next_record(&mut iter)?)?;
let (packet_id, local_variables) = parse_second_line(util::next_record(&mut iter)?)?;
let is_enabled = parse_third_line(util::next_record(&mut iter)?)?;
let is_restricted = parse_fourth_line(util::next_record(&mut iter)?)?;
let target = parse_first_line(util::next_record(&mut iter)?.record)?;
let (packet_id, local_variables) = parse_second_line(util::next_record(&mut iter)?.record)?;
let is_enabled = parse_third_line(util::next_record(&mut iter)?.record)?;
let is_restricted = parse_fourth_line(util::next_record(&mut iter)?.record)?;
let _padding_line = util::next_record(&mut iter)?;
Ok(model::Metadata {
target,
Expand All @@ -102,14 +102,15 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::PosStringRecordIterator;

#[test]
fn test() {
let csv = include_bytes!("../../fixtures/TLM_DB/valid_metadata.csv");
let mut rdr = csv::ReaderBuilder::new()
let rdr = csv::ReaderBuilder::new()
.has_headers(false)
.from_reader(csv.as_slice());
let mut iter = rdr.records();
let mut iter = PosStringRecordIterator::from_reader(rdr);
let metadata = parse(&mut iter).unwrap();
assert_eq!("OBC", metadata.target);
assert_eq!(0xf0, metadata.packet_id);
Expand Down
4 changes: 2 additions & 2 deletions tlmcmddb-csv/src/tlm/telemetry.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use anyhow::Result;
use csv::StringRecord;
use tlmcmddb::tlm as model;

use super::{body, metadata};
use crate::PosStringRecord;

pub fn parse<I, E>(telemetry_name: String, mut iter: I) -> Result<model::Telemetry>
where
I: Iterator<Item = Result<StringRecord, E>>,
I: Iterator<Item = Result<PosStringRecord, E>>,
E: std::error::Error + Send + Sync + 'static,
{
let metadata = metadata::parse(&mut iter)?;
Expand Down
12 changes: 6 additions & 6 deletions tlmcmddb-csv/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
use crate::PosStringRecord;
use anyhow::{anyhow, Result};
use csv::StringRecord;

pub fn next_record<I, E>(iter: &mut I) -> Result<StringRecord>
pub fn next_record<I, E>(iter: &mut I) -> Result<PosStringRecord>
where
I: Iterator<Item = Result<StringRecord, E>>,
I: Iterator<Item = Result<PosStringRecord, E>>,
E: std::error::Error + Send + Sync + 'static,
{
Ok(iter
.next()
.ok_or_else(|| anyhow!("unexpected end of data"))??)
}

pub fn try_next_record<I, E>(iter: &mut I) -> Result<Option<StringRecord>>
pub fn try_next_record<I, E>(iter: &mut I) -> Result<Option<PosStringRecord>>
where
I: Iterator<Item = Result<StringRecord, E>>,
I: Iterator<Item = Result<PosStringRecord, E>>,
E: std::error::Error + Send + Sync + 'static,
{
let Some(record) = iter.next().transpose()? else {
return Ok(None);
};
let is_empty = record.is_empty() || record.iter().all(|col| col.is_empty());
let is_empty = record.record.is_empty() || record.record.iter().all(|col| col.is_empty());
if is_empty {
return Ok(None);
}
Expand Down