use std::fmt;
use crate::{guess, json, lint, ParseError, Version};
pub fn parse_with_version(cdr_json: &str, version: Version) -> Result<Report<'_>, ParseError> {
match version {
Version::V221 => {
let schema = &*crate::v221::TARIFF_SCHEMA;
let report =
json::parse_with_schema(cdr_json, schema).map_err(ParseError::from_cdr_err)?;
let json::Report {
element,
unexpected_fields,
} = report;
Ok(Report {
tariff: Versioned::V221(element),
unexpected_fields,
})
}
Version::V211 => {
let schema = &*crate::v211::TARIFF_SCHEMA;
let report =
json::parse_with_schema(cdr_json, schema).map_err(ParseError::from_cdr_err)?;
let json::Report {
element,
unexpected_fields,
} = report;
Ok(Report {
tariff: Versioned::V211(element),
unexpected_fields,
})
}
}
}
pub fn parse(tariff_json: &str) -> Result<guess::TariffVersion<'_>, ParseError> {
guess::tariff_version(tariff_json)
}
pub fn parse_and_report(tariff_json: &str) -> Result<guess::TariffReport<'_>, ParseError> {
guess::tariff_version_with_report(tariff_json)
}
#[derive(Debug)]
pub struct Report<'buf> {
pub tariff: Versioned<'buf>,
pub unexpected_fields: json::UnexpectedFields<'buf>,
}
pub enum Versioned<'buf> {
V211(json::Element<'buf>),
V221(json::Element<'buf>),
}
impl fmt::Debug for Versioned<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
match self {
Versioned::V211(elem) | Versioned::V221(elem) => fmt::Debug::fmt(elem, f),
}
} else {
match self {
Versioned::V211(_) => f.write_str("V211(...)"),
Versioned::V221(_) => f.write_str("V221(...)"),
}
}
}
}
impl crate::Versioned for Versioned<'_> {
fn version(&self) -> Version {
match self {
Versioned::V211(_) => Version::V211,
Versioned::V221(_) => Version::V221,
}
}
}
impl<'buf> Versioned<'buf> {
pub fn into_element(self) -> json::Element<'buf> {
match self {
Versioned::V211(element) | Versioned::V221(element) => element,
}
}
pub fn as_element(&self) -> &json::Element<'buf> {
match self {
Versioned::V211(element) | Versioned::V221(element) => element,
}
}
}
#[derive(Debug)]
pub struct Unversioned<'buf>(json::Element<'buf>);
impl<'buf> Unversioned<'buf> {
pub(crate) fn new(elem: json::Element<'buf>) -> Self {
Self(elem)
}
pub fn into_element(self) -> json::Element<'buf> {
self.0
}
}
impl<'buf> crate::Unversioned for Unversioned<'buf> {
type Versioned = Versioned<'buf>;
fn force_into_versioned(self, version: Version) -> Self::Versioned {
match version {
Version::V221 => Versioned::V221(self.0),
Version::V211 => Versioned::V211(self.0),
}
}
}
pub fn lint(tariff: &Versioned<'_>) -> Result<lint::tariff::Report, lint::Error> {
lint::tariff(tariff)
}
#[cfg(test)]
mod test_real_world {
use std::path::Path;
use assert_matches::assert_matches;
use crate::{guess, test, Version, Versioned as _};
use super::{
parse_and_report,
test::{assert_parse_report, parse_expect_json},
};
#[test_each::file(
glob = "ocpi-tariffs/test_data/v211/real_world/*/tariff*.json",
name(segments = 2)
)]
fn test_parse_v211(tariff_json: &str, path: &Path) {
test::setup();
expect_version(tariff_json, path, Version::V211);
}
#[test_each::file(
glob = "ocpi-tariffs/test_data/v221/real_world/*/tariff*.json",
name(segments = 2)
)]
fn test_parse_v221(tariff_json: &str, path: &Path) {
test::setup();
expect_version(tariff_json, path, Version::V221);
}
fn expect_version(tariff_json: &str, path: &Path, expected_version: Version) {
let report = parse_and_report(tariff_json).unwrap();
let expect_json = test::read_expect_json(path, "parse");
let parse_expect = parse_expect_json(expect_json.as_deref());
let tariff = assert_matches!(report.version(), guess::Version::Certain(tariff) => tariff);
assert_eq!(tariff.version(), expected_version);
assert_parse_report(report, parse_expect);
}
}
#[cfg(test)]
pub mod test {
#![allow(clippy::missing_panics_doc, reason = "tests are allowed to panic")]
#![allow(clippy::panic, reason = "tests are allowed panic")]
use crate::{
guess, json,
test::{assert_no_unexpected_fields, Expectation},
};
#[derive(Debug, serde::Deserialize)]
pub struct ParseExpect<'buf> {
#[serde(borrow, default)]
unexpected_fields: Expectation<Vec<json::test::PathGlob<'buf>>>,
}
#[track_caller]
pub fn parse_expect_json(expect_json: Option<&str>) -> Option<ParseExpect<'_>> {
expect_json.map(|json| serde_json::from_str(json).expect("Unable to parse expect JSON"))
}
#[track_caller]
pub fn assert_parse_report<'bin>(
report: guess::TariffReport<'bin>,
expect: Option<ParseExpect<'_>>,
) -> guess::TariffVersion<'bin> {
let (version, mut unexpected_fields) = report.into_parts();
let Some(expect) = expect else {
assert_no_unexpected_fields(&unexpected_fields);
return version;
};
let ParseExpect {
unexpected_fields: unexpected_fields_expect,
} = expect;
if let Expectation::Present(expectation) = unexpected_fields_expect {
let unexpected_fields_expect = expectation.expect_value();
for expect_glob in unexpected_fields_expect {
unexpected_fields.filter_matches(&expect_glob);
}
assert_no_unexpected_fields(&unexpected_fields);
} else {
assert_no_unexpected_fields(&unexpected_fields);
}
version
}
}