From 103e94f8a26383f0c1b5abd1d910dc63e398b554 Mon Sep 17 00:00:00 2001 From: Edoardo Morandi Date: Sat, 29 Jan 2022 18:01:43 +0100 Subject: [PATCH] Initial support for DGC settings --- src/lib.rs | 2 + src/settings.rs | 775 +++++++++++++++++++++++++++++++++++++++ tests/data/settings.json | 217 +++++++++++ tests/lib.rs | 88 +++++ 4 files changed, 1082 insertions(+) create mode 100644 src/settings.rs create mode 100644 tests/data/settings.json diff --git a/src/lib.rs b/src/lib.rs index a3e88be..128b69c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ mod dgc; mod dgc_container; mod parse; mod recovery; +pub mod settings; mod test; mod trustlist; mod vaccination; @@ -15,6 +16,7 @@ pub use cwt::*; pub use dgc_container::*; pub use parse::*; pub use recovery::*; +pub use settings::Settings; pub use test::*; pub use trustlist::*; pub use vaccination::*; diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..e4ea3ef --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,775 @@ +//! A set of helpers to handle official DGC settings. + +use std::{borrow::Cow, fmt}; + +use serde::{ + de::{self, value::StrDeserializer, IntoDeserializer}, + Deserialize, Serialize, +}; + +/// The URL from which the settings can be retrieved in JSON format. +pub const URL: &str = "https://get.dgc.gov.it/v1/dgc/settings"; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Settings<'a> { + pub vaccines: Vaccines, + pub deny_list: DenyList<'a>, + pub min_versions: MinVersions<'a>, + pub tests: Tests, + pub recovery: Recovery, + pub unknown: Vec>, +} + +#[derive(Debug, Default)] +struct PartialSettings<'a> { + vaccines: PartialVaccines, + deny_list: Option<&'a str>, + min_versions: PartialMinVersions<'a>, + tests: PartialTests, + recovery: PartialRecovery, + unknown: Vec>, +} + +impl<'a> PartialSettings<'a> { + fn get_field(&mut self, ty: SettingType, name: SettingName) -> Option> { + use SettingName::*; + use SettingType::*; + + Some(match ty { + JensenVaccine | VaxzevriaVaccine | SpikevaxVaccine | ComirnatyVaccine + | CoviShieldVaccine | RCoviVaccine | RecombinantVaccine | SputnikVaccine => { + let vaccines = &mut self.vaccines; + let vaccine = match ty { + JensenVaccine => &mut vaccines.jensen, + VaxzevriaVaccine => &mut vaccines.vaxzevria, + SpikevaxVaccine => &mut vaccines.spikevax, + ComirnatyVaccine => &mut vaccines.comirnaty, + CoviShieldVaccine => &mut vaccines.covi_shield, + RCoviVaccine => &mut vaccines.r_covi, + RecombinantVaccine => &mut vaccines.recombinant, + SputnikVaccine => &mut vaccines.sputnik, + _ => unreachable!(), + }; + + InnerField::U16(match name { + VaccineStartDayComplete => &mut vaccine.start_day_complete, + VaccineEndDayComplete => &mut vaccine.end_day_complete, + VaccineStartDayNotComplete => &mut vaccine.start_day_not_complete, + VaccineEndDayNotComplete => &mut vaccine.end_day_not_complete, + _ => return None, + }) + } + Generic => match name { + RapidTestStartHours => InnerField::U8(&mut self.tests.rapid.start_hours), + RapidTestEndHours => InnerField::U8(&mut self.tests.rapid.end_hours), + MolecularTestStartHours => InnerField::U8(&mut self.tests.molecular.start_hours), + MolecularTestEndHours => InnerField::U8(&mut self.tests.molecular.end_hours), + RecoveryCertStartDay + | RecoveryCertEndDay + | RecoveryPvCertStartDay + | RecoveryPvCertEndDay => { + let recovery = &mut self.recovery; + InnerField::U16(match name { + RecoveryCertStartDay => &mut recovery.cert_start_day, + RecoveryCertEndDay => &mut recovery.cert_end_day, + RecoveryPvCertStartDay => &mut recovery.pv_cert_start_day, + RecoveryPvCertEndDay => &mut recovery.pv_cert_end_day, + _ => unreachable!(), + }) + } + _ => return None, + }, + AppMinVersion => { + let min_ver = &mut self.min_versions; + InnerField::Str(match name { + Ios => &mut min_ver.ios, + Android => &mut min_ver.android, + _ => return None, + }) + } + DenyList => match name { + BlackListUvci => InnerField::Str(&mut self.deny_list), + _ => return None, + }, + }) + } +} + +#[derive(Debug)] +enum InnerField<'a: 'b, 'b> { + U8(&'b mut Option), + U16(&'b mut Option), + Str(&'b mut Option<&'a str>), +} + +#[derive(Debug)] +enum InnerFieldOwned<'a> { + U8(u8), + U16(u16), + Str(&'a str), +} + +impl<'a: 'b, 'b> InnerField<'a, 'b> { + fn try_set(&mut self, raw: &'a str) -> Result>, &str> { + use InnerField::*; + Ok(match self { + U8(value) => value + .replace(raw.parse().map_err(|_| "u8 str")?) + .map(InnerFieldOwned::U8), + U16(value) => value + .replace(raw.parse().map_err(|_| "u16 str")?) + .map(InnerFieldOwned::U16), + Str(s) => s.replace(raw).map(InnerFieldOwned::Str), + }) + } +} + +#[derive(Debug)] +enum Setting<'a> { + Raw(RawSetting<'a>), + Parsed { + name: SettingName, + ty: SettingType, + value: &'a str, + }, +} + +impl<'de: 'a, 'a> Deserialize<'de> for Setting<'a> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct SettingVisitor; + impl<'de> de::Visitor<'de> for SettingVisitor { + type Value = Setting<'de>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map with name, type and value") + } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + #[derive(Debug)] + enum Data<'a, T> { + Raw(&'a str), + Parsed { data: T, raw: &'a str }, + } + + let mut name = None; + let mut ty = None; + let mut value = None; + + while let Some((key, val)) = map.next_entry::<_, &str>()? { + macro_rules! try_deserialize { + ($field:ident: $ty:ty, $field_name:literal) => {{ + let new_val = <$ty>::deserialize::>( + val.into_deserializer(), + ) + .ok() + .map(|data| Data::Parsed { raw: val, data }) + .unwrap_or_else(|| Data::Raw(val)); + + if $field.replace(new_val).is_some() { + return Err(de::Error::duplicate_field($field_name)); + } + }}; + } + + match key { + "name" => try_deserialize!(name: SettingName, "name"), + "type" => { + try_deserialize!(ty: SettingType, "type") + } + "value" => { + if value.replace(val).is_some() { + return Err(de::Error::custom( + "type field found more than one time", + )); + } + } + _ => { + // FIXME: Should we ignore unknown fields or should we return an error? + // Or log if a logging crate is available? + } + } + } + + match (name, ty, value) { + (None, _, _) => Err(de::Error::missing_field("name")), + (_, None, _) => Err(de::Error::missing_field("type")), + (_, _, None) => Err(de::Error::missing_field("value")), + ( + Some(Data::Raw(name)), + Some(Data::Raw(ty) | Data::Parsed { raw: ty, .. }), + Some(value), + ) + | (Some(Data::Parsed { raw: name, .. }), Some(Data::Raw(ty)), Some(value)) => { + let name = name.into(); + let ty = ty.into(); + let value = value.into(); + Ok(Setting::Raw(RawSetting { name, ty, value })) + } + ( + Some(Data::Parsed { data: name, .. }), + Some(Data::Parsed { data: ty, .. }), + Some(value), + ) => Ok(Setting::Parsed { name, ty, value }), + } + } + } + + deserializer.deserialize_map(SettingVisitor) + } +} + +impl<'de: 'a, 'a> Deserialize<'de> for Settings<'a> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct SettingsVisitor; + impl<'de> de::Visitor<'de> for SettingsVisitor { + type Value = PartialSettings<'de>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence of maps") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + let mut partial_settings = PartialSettings::default(); + while let Some(setting) = seq.next_element()? { + match setting { + Setting::Parsed { name, ty, value } => { + let mut field = partial_settings + .get_field(ty, name) + .ok_or_else(|| de::Error::custom(InvalidSetting { name, ty }))?; + + let old_value = field.try_set(value).map_err(|expected| { + de::Error::invalid_value(de::Unexpected::Str(value), &expected) + })?; + if old_value.is_some() { + return Err(de::Error::duplicate_field(name.as_str())); + } + } + Setting::Raw(raw_setting) => partial_settings.unknown.push(raw_setting), + } + } + Ok(partial_settings) + } + } + + let PartialSettings { + vaccines, + deny_list, + min_versions, + tests, + recovery, + unknown, + } = deserializer.deserialize_seq(SettingsVisitor)?; + + let vaccines = vaccines.into_complete().map_err(de::Error::custom)?; + let deny_list = deny_list + .map(|deny_list| DenyList(deny_list.into())) + .ok_or_else(|| de::Error::custom(IncompleteSettings::MissingDenyList))?; + let min_versions = min_versions.into_complete().map_err(de::Error::custom)?; + let tests = tests.into_complete().map_err(de::Error::custom)?; + let recovery = recovery.into_complete().map_err(de::Error::custom)?; + + Ok(Self { + vaccines, + deny_list, + min_versions, + tests, + recovery, + unknown, + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct InvalidSetting { + pub name: SettingName, + pub ty: SettingType, +} + +impl fmt::Display for InvalidSetting { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + r#"invalid setting with type "{}" and name {}"#, + self.ty, self.name + ) + } +} + +/// A direct representation of a DGC setting. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub struct RawSetting<'a> { + /// The name of the setting for the given type. + pub name: Cow<'a, str>, + + /// The type of setting. + #[serde(rename = "type")] + pub ty: Cow<'a, str>, + + /// The value of the setting for the given type. + pub value: Cow<'a, str>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Vaccines { + pub jensen: VaccineSettings, + pub vaxzevria: VaccineSettings, + pub spikevax: VaccineSettings, + pub comirnaty: VaccineSettings, + pub covi_shield: VaccineSettings, + pub r_covi: VaccineSettings, + pub recombinant: VaccineSettings, + pub sputnik: VaccineSettings, +} + +#[derive(Debug, Default)] +struct PartialVaccines { + jensen: PartialVaccineSettings, + vaxzevria: PartialVaccineSettings, + spikevax: PartialVaccineSettings, + comirnaty: PartialVaccineSettings, + covi_shield: PartialVaccineSettings, + r_covi: PartialVaccineSettings, + recombinant: PartialVaccineSettings, + sputnik: PartialVaccineSettings, +} + +impl PartialVaccines { + fn into_complete(self) -> Result { + use SettingType::*; + + let Self { + jensen, + vaxzevria, + spikevax, + comirnaty, + covi_shield, + r_covi, + recombinant, + sputnik, + } = self; + + let jensen = jensen.into_complete(JensenVaccine)?; + let vaxzevria = vaxzevria.into_complete(VaxzevriaVaccine)?; + let spikevax = spikevax.into_complete(SpikevaxVaccine)?; + let comirnaty = comirnaty.into_complete(ComirnatyVaccine)?; + let covi_shield = covi_shield.into_complete(CoviShieldVaccine)?; + let r_covi = r_covi.into_complete(RCoviVaccine)?; + let recombinant = recombinant.into_complete(RecombinantVaccine)?; + let sputnik = sputnik.into_complete(SputnikVaccine)?; + + Ok(Vaccines { + jensen, + vaxzevria, + spikevax, + comirnaty, + covi_shield, + r_covi, + recombinant, + sputnik, + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct VaccineSettings { + pub start_day_complete: u16, + pub end_day_complete: u16, + pub start_day_not_complete: u16, + pub end_day_not_complete: u16, +} + +#[derive(Debug, Default)] +struct PartialVaccineSettings { + start_day_complete: Option, + end_day_complete: Option, + start_day_not_complete: Option, + end_day_not_complete: Option, +} + +impl PartialVaccineSettings { + fn into_complete(self, ty: SettingType) -> Result { + use SettingName::*; + let Self { + start_day_complete, + end_day_complete, + start_day_not_complete, + end_day_not_complete, + } = self; + + let start_day_complete = + start_day_complete.ok_or(IncompleteSettings::IncompleteVaccine(IncompleteSetting { + setting: ty, + missing_field: VaccineStartDayComplete, + }))?; + let end_day_complete = + end_day_complete.ok_or(IncompleteSettings::IncompleteVaccine(IncompleteSetting { + setting: ty, + missing_field: VaccineEndDayComplete, + }))?; + let start_day_not_complete = start_day_not_complete.ok_or( + IncompleteSettings::IncompleteVaccine(IncompleteSetting { + setting: ty, + missing_field: VaccineStartDayNotComplete, + }), + )?; + let end_day_not_complete = end_day_not_complete.ok_or( + IncompleteSettings::IncompleteVaccine(IncompleteSetting { + setting: ty, + missing_field: VaccineEndDayNotComplete, + }), + )?; + + Ok(VaccineSettings { + start_day_complete, + end_day_complete, + start_day_not_complete, + end_day_not_complete, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct DenyList<'a>(pub Cow<'a, str>); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct MinVersions<'a> { + pub ios: Cow<'a, str>, + pub android: Cow<'a, str>, +} + +#[derive(Debug, Default)] +struct PartialMinVersions<'a> { + ios: Option<&'a str>, + android: Option<&'a str>, +} + +impl<'a> PartialMinVersions<'a> { + fn into_complete(self) -> Result, IncompleteSettings> { + use SettingName::*; + use SettingType::*; + + let Self { ios, android } = self; + + let ios = ios + .map(Cow::from) + .ok_or(IncompleteSettings::IncompleteMinVersion( + IncompleteSetting { + setting: AppMinVersion, + missing_field: Ios, + }, + ))?; + let android = android + .map(Cow::from) + .ok_or(IncompleteSettings::IncompleteMinVersion( + IncompleteSetting { + setting: AppMinVersion, + missing_field: Android, + }, + ))?; + + Ok(MinVersions { ios, android }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Tests { + pub rapid: TestData, + pub molecular: TestData, +} + +#[derive(Debug, Default)] +struct PartialTests { + rapid: PartialTestData, + molecular: PartialTestData, +} + +impl PartialTests { + fn into_complete(self) -> Result { + use SettingName::*; + use SettingType::*; + + let Self { rapid, molecular } = self; + + let rapid = { + let PartialTestData { + start_hours, + end_hours, + } = rapid; + + let start_hours = + start_hours.ok_or(IncompleteSettings::IncompleteTest(IncompleteSetting { + setting: Generic, + missing_field: RapidTestStartHours, + }))?; + let end_hours = + end_hours.ok_or(IncompleteSettings::IncompleteTest(IncompleteSetting { + setting: Generic, + missing_field: RapidTestEndHours, + }))?; + + TestData { + start_hours, + end_hours, + } + }; + let molecular = { + let PartialTestData { + start_hours, + end_hours, + } = molecular; + + let start_hours = + start_hours.ok_or(IncompleteSettings::IncompleteTest(IncompleteSetting { + setting: Generic, + missing_field: MolecularTestStartHours, + }))?; + let end_hours = + end_hours.ok_or(IncompleteSettings::IncompleteTest(IncompleteSetting { + setting: Generic, + missing_field: MolecularTestEndHours, + }))?; + + TestData { + start_hours, + end_hours, + } + }; + + Ok(Tests { rapid, molecular }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TestData { + pub start_hours: u8, + pub end_hours: u8, +} + +#[derive(Debug, Default)] +struct PartialTestData { + start_hours: Option, + end_hours: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Recovery { + pub cert_start_day: u16, + pub cert_end_day: u16, + pub pv_cert_start_day: u16, + pub pv_cert_end_day: u16, +} + +#[derive(Debug, Default)] +struct PartialRecovery { + cert_start_day: Option, + cert_end_day: Option, + pv_cert_start_day: Option, + pv_cert_end_day: Option, +} + +impl PartialRecovery { + fn into_complete(self) -> Result { + use SettingName::*; + use SettingType::*; + + let Self { + cert_start_day, + cert_end_day, + pv_cert_start_day, + pv_cert_end_day, + } = self; + + let cert_start_day = + cert_start_day.ok_or(IncompleteSettings::IncompleteRecovery(IncompleteSetting { + setting: Generic, + missing_field: RecoveryCertStartDay, + }))?; + let cert_end_day = + cert_end_day.ok_or(IncompleteSettings::IncompleteRecovery(IncompleteSetting { + setting: Generic, + missing_field: RecoveryCertEndDay, + }))?; + let pv_cert_start_day = + pv_cert_start_day.ok_or(IncompleteSettings::IncompleteRecovery(IncompleteSetting { + setting: Generic, + missing_field: RecoveryPvCertStartDay, + }))?; + let pv_cert_end_day = + pv_cert_end_day.ok_or(IncompleteSettings::IncompleteRecovery(IncompleteSetting { + setting: Generic, + missing_field: RecoveryPvCertEndDay, + }))?; + + Ok(Recovery { + cert_start_day, + cert_end_day, + pv_cert_start_day, + pv_cert_end_day, + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[non_exhaustive] +pub enum SettingType { + #[serde(rename = "EU/1/20/1525")] + JensenVaccine, + + #[serde(rename = "EU/1/21/1529")] + VaxzevriaVaccine, + + #[serde(rename = "EU/1/20/1507")] + SpikevaxVaccine, + + #[serde(rename = "EU/1/20/1528")] + ComirnatyVaccine, + + #[serde(rename = "GENERIC")] + Generic, + + #[serde(rename = "APP_MIN_VERSION")] + AppMinVersion, + + #[serde(rename = "Covishield")] + CoviShieldVaccine, + + #[serde(rename = "R-COVI")] + RCoviVaccine, + + #[serde(rename = "Covid-19-recombinant")] + RecombinantVaccine, + + #[serde(rename = "black_list_uvci")] + DenyList, + + #[serde(rename = "Sputnik-V")] + SputnikVaccine, +} + +impl fmt::Display for SettingType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use SettingType::*; + let s = match self { + JensenVaccine => "Jensen Vaccine (EU/1/20/1525)", + VaxzevriaVaccine => "Vaxzevria Vaccine (EU/1/21/1529)", + SpikevaxVaccine => "Spikevax Vaccine (EU/1/20/1507)", + ComirnatyVaccine => "Comirnaty Vaccine (EU/1/20/1528)", + Generic => "Generic (GENERIC)", + AppMinVersion => "App minimum version (APP_MIN_VERSION)", + CoviShieldVaccine => "Covishield Vaccine (Covishield)", + RCoviVaccine => "R-CoVI (R-COVI)", + RecombinantVaccine => "Covid-19 vaccine-recombinant (Covid-19-recombinant)", + DenyList => "Deny list (black_list_uvci)", + SputnikVaccine => "Sputnik V Vaccine (Sputnik-V)", + }; + f.write_str(s) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum SettingName { + VaccineStartDayComplete, + VaccineEndDayComplete, + VaccineStartDayNotComplete, + VaccineEndDayNotComplete, + RapidTestStartHours, + RapidTestEndHours, + MolecularTestStartHours, + MolecularTestEndHours, + RecoveryCertStartDay, + RecoveryCertEndDay, + Ios, + Android, + BlackListUvci, + RecoveryPvCertStartDay, + RecoveryPvCertEndDay, +} + +impl SettingName { + pub fn as_str(self) -> &'static str { + use SettingName::*; + + match self { + VaccineStartDayComplete => "vaccine_start_day_complete", + VaccineEndDayComplete => "vaccine_end_day_complete", + VaccineStartDayNotComplete => "vaccine_start_day_not_complete", + VaccineEndDayNotComplete => "vaccine_end_day_not_Complete", + RapidTestStartHours => "rapid_test_start_hours", + RapidTestEndHours => "rapid_test_end_hours", + MolecularTestStartHours => "molecular_test_start_hours", + MolecularTestEndHours => "molecular_test_end_hours", + RecoveryCertStartDay => "recovery_cert_start_day", + RecoveryCertEndDay => "recovery_cert_end_day", + Ios => "ios", + Android => "android", + BlackListUvci => "black_list_uvci", + RecoveryPvCertStartDay => "recovery_pv_cert_start_day", + RecoveryPvCertEndDay => "recovery_pv_cert_end_day", + } + } +} + +impl fmt::Display for SettingName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum IncompleteSettings { + IncompleteVaccine(IncompleteSetting), + MissingDenyList, + IncompleteMinVersion(IncompleteSetting), + IncompleteTest(IncompleteSetting), + IncompleteRecovery(IncompleteSetting), +} + +impl fmt::Display for IncompleteSettings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use IncompleteSettings::*; + + match self { + MissingDenyList => f.write_str("UVCI deny list is missing"), + IncompleteVaccine(incomplete) + | IncompleteMinVersion(incomplete) + | IncompleteTest(incomplete) + | IncompleteRecovery(incomplete) => match self { + IncompleteVaccine(_) => write!(f, "incomplete vaccines, {}", incomplete), + IncompleteMinVersion(_) => write!(f, "incomplete app min versions, {}", incomplete), + IncompleteTest(_) => write!(f, "incomplete tests, {}", incomplete), + IncompleteRecovery(_) => write!(f, "incomplete recovery, {}", incomplete), + MissingDenyList => unreachable!(), + }, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct IncompleteSetting { + setting: SettingType, + missing_field: SettingName, +} + +impl fmt::Display for IncompleteSetting { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + r#"setting = "{}", missing field = "{}""#, + self.setting, self.missing_field + ) + } +} diff --git a/tests/data/settings.json b/tests/data/settings.json new file mode 100644 index 0000000..e8cc1f4 --- /dev/null +++ b/tests/data/settings.json @@ -0,0 +1,217 @@ +[ + { + "name": "vaccine_end_day_complete", + "type": "EU/1/20/1525", + "value": "270" + }, + { + "name": "vaccine_start_day_complete", + "type": "EU/1/20/1525", + "value": "15" + }, + { + "name": "vaccine_end_day_not_complete", + "type": "EU/1/20/1525", + "value": "270" + }, + { + "name": "vaccine_start_day_not_complete", + "type": "EU/1/20/1525", + "value": "15" + }, + { + "name": "vaccine_end_day_complete", + "type": "EU/1/21/1529", + "value": "270" + }, + { + "name": "vaccine_start_day_complete", + "type": "EU/1/21/1529", + "value": "0" + }, + { + "name": "vaccine_end_day_not_complete", + "type": "EU/1/21/1529", + "value": "84" + }, + { + "name": "vaccine_start_day_not_complete", + "type": "EU/1/21/1529", + "value": "15" + }, + { + "name": "vaccine_end_day_complete", + "type": "EU/1/20/1507", + "value": "270" + }, + { + "name": "vaccine_start_day_complete", + "type": "EU/1/20/1507", + "value": "0" + }, + { + "name": "vaccine_end_day_not_complete", + "type": "EU/1/20/1507", + "value": "42" + }, + { + "name": "vaccine_start_day_not_complete", + "type": "EU/1/20/1507", + "value": "15" + }, + { + "name": "vaccine_end_day_complete", + "type": "EU/1/20/1528", + "value": "270" + }, + { + "name": "vaccine_start_day_complete", + "type": "EU/1/20/1528", + "value": "0" + }, + { + "name": "vaccine_end_day_not_complete", + "type": "EU/1/20/1528", + "value": "42" + }, + { + "name": "vaccine_start_day_not_complete", + "type": "EU/1/20/1528", + "value": "15" + }, + { + "name": "rapid_test_start_hours", + "type": "GENERIC", + "value": "0" + }, + { + "name": "rapid_test_end_hours", + "type": "GENERIC", + "value": "48" + }, + { + "name": "molecular_test_start_hours", + "type": "GENERIC", + "value": "0" + }, + { + "name": "molecular_test_end_hours", + "type": "GENERIC", + "value": "72" + }, + { + "name": "recovery_cert_start_day", + "type": "GENERIC", + "value": "0" + }, + { + "name": "recovery_cert_end_day", + "type": "GENERIC", + "value": "180" + }, + { + "name": "ios", + "type": "APP_MIN_VERSION", + "value": "1.2.0" + }, + { + "name": "android", + "type": "APP_MIN_VERSION", + "value": "1.2.0" + }, + { + "name": "vaccine_start_day_not_complete", + "type": "Covishield", + "value": "15" + }, + { + "name": "vaccine_end_day_not_complete", + "type": "Covishield", + "value": "84" + }, + { + "name": "vaccine_start_day_complete", + "type": "Covishield", + "value": "0" + }, + { + "name": "vaccine_end_day_complete", + "type": "Covishield", + "value": "270" + }, + { + "name": "vaccine_start_day_not_complete", + "type": "R-COVI", + "value": "15" + }, + { + "name": "vaccine_end_day_not_complete", + "type": "R-COVI", + "value": "84" + }, + { + "name": "vaccine_start_day_complete", + "type": "R-COVI", + "value": "0" + }, + { + "name": "vaccine_end_day_complete", + "type": "R-COVI", + "value": "270" + }, + { + "name": "vaccine_start_day_not_complete", + "type": "Covid-19-recombinant", + "value": "15" + }, + { + "name": "vaccine_end_day_not_complete", + "type": "Covid-19-recombinant", + "value": "84" + }, + { + "name": "vaccine_start_day_complete", + "type": "Covid-19-recombinant", + "value": "0" + }, + { + "name": "vaccine_end_day_complete", + "type": "Covid-19-recombinant", + "value": "270" + }, + { + "name": "black_list_uvci", + "type": "black_list_uvci", + "value": "URN:UVCI:01:FR:W7V2BE46QSBJ#L;URN:UVCI:01:FR:T5DWTJYS4ZR8#4" + }, + { + "name": "vaccine_start_day_not_complete", + "type": "Sputnik-V", + "value": "15" + }, + { + "name": "vaccine_end_day_not_complete", + "type": "Sputnik-V", + "value": "21" + }, + { + "name": "vaccine_start_day_complete", + "type": "Sputnik-V", + "value": "0" + }, + { + "name": "vaccine_end_day_complete", + "type": "Sputnik-V", + "value": "270" + }, + { + "name": "recovery_pv_cert_start_day", + "type": "GENERIC", + "value": "0" + }, + { + "name": "recovery_pv_cert_end_day", + "type": "GENERIC", + "value": "270" + } +] diff --git a/tests/lib.rs b/tests/lib.rs index 781d3f6..d202009 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -3,6 +3,7 @@ use dgc::*; // Tests the library against some of the test data available at use rstest::rstest; use serde_json::Value; +use std::borrow::Cow; use std::fs; use std::path::PathBuf; @@ -680,3 +681,90 @@ fn test_case(#[case] test_file: &str) { assert_eq!(signature_validity.is_valid(), expected_verify); } } + +#[test] +fn settings() { + use settings::*; + + const RAW_SETTINGS: &str = include_str!("data/settings.json"); + + assert_eq!( + serde_json::from_str::(RAW_SETTINGS).unwrap(), + Settings { + vaccines: Vaccines { + jensen: VaccineSettings { + start_day_complete: 15, + end_day_complete: 270, + start_day_not_complete: 15, + end_day_not_complete: 270, + }, + vaxzevria: VaccineSettings { + start_day_complete: 0, + end_day_complete: 270, + start_day_not_complete: 15, + end_day_not_complete: 84, + }, + spikevax: VaccineSettings { + start_day_complete: 0, + end_day_complete: 270, + start_day_not_complete: 15, + end_day_not_complete: 42, + }, + comirnaty: VaccineSettings { + start_day_complete: 0, + end_day_complete: 270, + start_day_not_complete: 15, + end_day_not_complete: 42, + }, + covi_shield: VaccineSettings { + start_day_complete: 0, + end_day_complete: 270, + start_day_not_complete: 15, + end_day_not_complete: 84, + }, + r_covi: VaccineSettings { + start_day_complete: 0, + end_day_complete: 270, + start_day_not_complete: 15, + end_day_not_complete: 84, + }, + recombinant: VaccineSettings { + start_day_complete: 0, + end_day_complete: 270, + start_day_not_complete: 15, + end_day_not_complete: 84, + }, + sputnik: VaccineSettings { + start_day_complete: 0, + end_day_complete: 270, + start_day_not_complete: 15, + end_day_not_complete: 21, + } + }, + deny_list: DenyList(Cow::Borrowed( + "URN:UVCI:01:FR:W7V2BE46QSBJ#L;URN:UVCI:01:FR:T5DWTJYS4ZR8#4" + )), + min_versions: MinVersions { + ios: Cow::Borrowed("1.2.0"), + android: Cow::Borrowed("1.2.0") + }, + tests: Tests { + rapid: TestData { + start_hours: 0, + end_hours: 48, + }, + molecular: TestData { + start_hours: 0, + end_hours: 72 + } + }, + recovery: Recovery { + cert_start_day: 0, + cert_end_day: 180, + pv_cert_start_day: 0, + pv_cert_end_day: 270 + }, + unknown: Vec::new(), + } + ); +}