use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
enum Expected {
OneOf(&'static [&'static str]),
Format(&'static str),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError {
type_name: &'static str,
input: String,
expected: Expected,
}
impl ParseError {
pub(crate) fn one_of(
type_name: &'static str,
input: &str,
codes: &'static [&'static str],
) -> Self {
Self {
type_name,
input: input.to_owned(),
expected: Expected::OneOf(codes),
}
}
pub(crate) fn format(type_name: &'static str, input: &str, shape: &'static str) -> Self {
Self {
type_name,
input: input.to_owned(),
expected: Expected::Format(shape),
}
}
#[must_use]
pub const fn type_name(&self) -> &'static str {
self.type_name
}
#[must_use]
pub fn input(&self) -> &str {
&self.input
}
#[must_use]
pub const fn expected_values(&self) -> Option<&'static [&'static str]> {
match self.expected {
Expected::OneOf(codes) => Some(codes),
Expected::Format(_) => None,
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid {} {:?}: expected ", self.type_name, self.input)?;
match self.expected {
Expected::OneOf(codes) => write!(f, "one of {}", codes.join(", ")),
Expected::Format(shape) => f.write_str(shape),
}
}
}
impl std::error::Error for ParseError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::{IntervalResolution, ObisCode, QualityFlag, Sparte};
#[test]
fn message_names_the_type_and_the_input() {
let err = "KOHLE".parse::<Sparte>().unwrap_err();
assert_eq!(err.type_name(), "Sparte");
assert_eq!(err.input(), "KOHLE");
let msg = err.to_string();
assert!(msg.contains("Sparte"), "{msg}");
assert!(msg.contains("KOHLE"), "{msg}");
assert!(msg.contains("STROM"), "{msg}");
}
#[test]
fn closed_sets_expose_their_codes_and_formats_do_not() {
let closed = "nope".parse::<QualityFlag>().unwrap_err();
assert_eq!(closed.expected_values(), Some(QualityFlag::CODES));
let shaped = "nope".parse::<ObisCode>().unwrap_err();
assert_eq!(shaped.expected_values(), None);
assert!(shaped.to_string().contains("A-B:C.D.E*F"), "{shaped}");
let iso = "nope".parse::<IntervalResolution>().unwrap_err();
assert_eq!(iso.expected_values(), None);
assert!(iso.to_string().contains("PT15M"), "{iso}");
}
#[test]
fn every_parser_returns_the_same_error_type() {
fn decode(s: &str, q: &str, o: &str, r: &str) -> Result<(), ParseError> {
let _: Sparte = s.parse()?;
let _: QualityFlag = q.parse()?;
let _: ObisCode = o.parse()?;
let _: IntervalResolution = r.parse()?;
Ok(())
}
assert!(decode("GAS", "MEASURED", "7-0:3.0.0", "PT1H").is_ok());
let err = decode("GAS", "MEASURED", "bad", "PT1H").unwrap_err();
assert_eq!(err.type_name(), "ObisCode");
}
}