use enum_iterator::Sequence; use std::fmt::Display; use std::str::FromStr; use std::sync::atomic::AtomicU32; use std::sync::Arc; use log::{trace}; use crate::error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result}; pub const COUNTER_FIELDS: &[&str] = &["name", "title", "caption", "chno"]; pub const MAPPER_FIELDS: &[&str] = &[ "name", "title", "caption", "group", "id", "chno", "logo", "logo_small", "parent_code", "audio_track", "time_shift", "rec", "url", "epg_channel_id", "epg_id" ]; #[macro_export] macro_rules! valid_property { ($key:expr, $array:expr) => {{ $array.contains(&$key) }}; } pub use valid_property; use crate::foundation::filter::{apply_templates_to_pattern_single, get_filter, prepare_templates, Filter}; use crate::foundation::mapper::MapperScript; use crate::info_err; use crate::model::PatternTemplate; #[derive(Debug, Default, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq)] pub enum CounterModifier { #[default] #[serde(rename = "assign")] Assign, #[serde(rename = "suffix")] Suffix, #[serde(rename = "prefix")] Prefix, } impl CounterModifier { const ASSIGN: &'static str = "assign"; const SUFFIX: &'static str = "suffix"; const PREFIX: &'static str = "prefix"; } impl Display for CounterModifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", match self { Self::Assign => Self::ASSIGN, Self::Suffix => Self::SUFFIX, Self::Prefix => Self::PREFIX, }) } } impl FromStr for CounterModifier { type Err = TuliproxError; fn from_str(s: &str) -> Result { if s.eq("assign") { Ok(Self::Assign) } else if s.eq("suffix") { Ok(Self::Suffix) } else if s.eq("prefix") { Ok(Self::Prefix) } else { create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown CounterModifier: {}", s) } } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq)] pub struct MappingCounterDefinition { pub filter: String, pub field: String, #[serde(default)] pub concat: String, #[serde(default)] pub modifier: CounterModifier, #[serde(default)] pub value: u32, #[serde(default)] pub padding: u8, } #[derive(Debug, Clone)] pub struct MappingCounter { pub filter: Filter, pub field: String, pub concat: String, pub modifier: CounterModifier, pub value: Arc, pub padding: u8, } impl PartialEq for MappingCounter { fn eq(&self, other: &Self) -> bool { self.filter == other.filter && self.field == other.field && self.concat == other.concat && self.modifier == other.modifier && self.padding == other.padding // value is not compared! } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(tag = "modifier", rename_all = "snake_case")] pub enum MapperOperation { Lowercase { field: String }, Uppercase { field: String }, Capitalize { field: String }, Suffix { field: String, value: String }, Prefix { field: String, value: String }, Set { field: String, value: String }, Copy { field: String, source: String }, } impl MapperOperation { pub fn prepare(&mut self, templates: Option<&Vec>) -> Result<(), TuliproxError> { match self { MapperOperation::Lowercase {ref field } | MapperOperation::Uppercase { ref field } | MapperOperation::Capitalize { ref field } => { if !valid_property!(field.as_str(), MAPPER_FIELDS) { return Err(info_err!(format!("Invalid mapper attribute field {field}"))); } } MapperOperation::Copy { ref field, ref source } => { if !valid_property!(field.as_str(), MAPPER_FIELDS) { return Err(info_err!(format!("Invalid mapper attribute field {field}"))); } if !valid_property!(source.as_str(), MAPPER_FIELDS) { return Err(info_err!(format!("Invalid mapper source field {source}"))); } } MapperOperation::Suffix { ref field, ref mut value } | MapperOperation::Prefix { ref field, ref mut value } | MapperOperation::Set { ref field, ref mut value } => { if !valid_property!(field.as_str(), MAPPER_FIELDS) { return Err(info_err!(format!("Invalid mapper attribute field {field}"))); } if templates.is_some() { *value = apply_templates_to_pattern_single(value, templates)?; } } } Ok(()) } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq)] pub struct MapperDto { pub filter: String, pub script: String, #[serde(skip_serializing, skip_deserializing)] pub t_filter: Option, #[serde(skip_serializing, skip_deserializing)] pub t_script: Option, } impl MapperDto { /// # Panics /// /// Will panic if default `RegEx` gets invalid pub fn prepare(&mut self, templates: Option<&Vec>) -> Result<(), TuliproxError> { self.t_filter = Some(get_filter(&self.filter, templates)?); let script = if templates.is_some() { apply_templates_to_pattern_single(&self.script, templates)? } else { self.script.to_string() }; trace!("Mapper script: {script}"); self.t_script = Some(MapperScript::parse(&script, templates)?); Ok(()) } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq)] pub struct MappingDto { pub id: String, #[serde(default)] pub match_as_ascii: bool, #[serde(default)] pub create_alias: bool, pub mapper: Option>, pub counter: Option>, #[serde(skip_serializing, skip_deserializing)] pub t_counter: Option>, #[serde(skip_serializing, skip_deserializing)] pub templates: Option> } impl MappingDto { pub fn prepare(&mut self, templates: Option<&Vec>) -> Result<(), TuliproxError> { self.templates = templates.map(|t| t.iter().map(PatternTemplate::clone).collect::>()); if let Some(mapper_list) = &mut self.mapper { for mapper in mapper_list { mapper.prepare(templates)?; } } if let Some(counter_def_list) = &self.counter { let mut counters = vec![]; for def in counter_def_list { if !valid_property!(def.field.as_str(), COUNTER_FIELDS) { return Err(info_err!(format!("Invalid counter field {}", def.field))); } match get_filter(&def.filter, templates) { Ok(flt) => { counters.push(MappingCounter { filter: flt, field: def.field.clone(), concat: def.concat.clone(), modifier: def.modifier, value: Arc::new(AtomicU32::new(def.value)), padding: def.padding, }); } Err(e) => return Err(info_err!(e.to_string())) } } self.t_counter = Some(counters); } Ok(()) } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] pub struct MappingDefinitionDto { pub templates: Option>, pub mapping: Vec, } impl MappingDefinitionDto { pub fn prepare(&mut self) -> Result<(), TuliproxError> { if let Some(templates) = &mut self.templates { match prepare_templates(templates) { Ok(tmplts) => { self.templates = Some(tmplts); } Err(err) => return Err(err), } } for mapping in &mut self.mapping { let template_list = self.templates.as_ref(); mapping.prepare(template_list)?; } Ok(()) } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] pub struct MappingsDto { pub mappings: MappingDefinitionDto, } impl MappingsDto { pub fn prepare(&mut self) -> Result<(), TuliproxError> { self.mappings.prepare() } }