From 2a6037479f5ac230cdd43d158b667965d9771ddf Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Tue, 16 Sep 2025 21:07:44 +0200 Subject: [PATCH] Developpement de la library pmoupnp --- .gitignore | 2 +- Cargo.lock | 16 + pmoupnp/Cargo.toml | 13 + pmoupnp/src/lib.rs | 14 + pmoupnp/src/object_trait.rs | 39 ++ pmoupnp/src/state_variables/errors.rs | 28 ++ .../src/state_variables/instance_methods.rs | 58 +++ pmoupnp/src/state_variables/mod.rs | 47 +++ .../src/state_variables/variable_methods.rs | 353 ++++++++++++++++++ pmoupnp/src/state_variables/variable_trait.rs | 73 ++++ pmoupnp/src/value_ranges/methods.rs | 69 ++++ pmoupnp/src/value_ranges/mod.rs | 10 + pmoupnp/src/variable_types/cast.rs | 37 ++ pmoupnp/src/variable_types/default_value.rs | 43 +++ pmoupnp/src/variable_types/display_type.rs | 35 ++ pmoupnp/src/variable_types/display_value.rs | 45 +++ pmoupnp/src/variable_types/errors.rs | 28 ++ pmoupnp/src/variable_types/fromstr.rs | 36 ++ pmoupnp/src/variable_types/mod.rs | 96 +++++ pmoupnp/src/variable_types/type_methods.rs | 109 ++++++ pmoupnp/src/variable_types/type_trait.rs | 57 +++ pmoupnp/src/variable_types/value_methods.rs | 89 +++++ pmoupnp/src/variable_types/value_trait.rs | 2 + .../variable_types/values_from_datetime.rs | 33 ++ pmoupnp/src/variable_types/values_from_f32.rs | 82 ++++ pmoupnp/src/variable_types/values_from_f64.rs | 53 +++ pmoupnp/src/variable_types/values_from_i16.rs | 46 +++ pmoupnp/src/variable_types/values_from_i32.rs | 46 +++ pmoupnp/src/variable_types/values_from_i64.rs | 45 +++ pmoupnp/src/variable_types/values_from_i8.rs | 46 +++ .../variable_types/values_from_naivedate.rs | 32 ++ .../values_from_naivedatetime.rs | 31 ++ .../variable_types/values_from_naivetime.rs | 31 ++ .../src/variable_types/values_from_type.rs | 37 ++ pmoupnp/src/variable_types/values_from_u16.rs | 47 +++ pmoupnp/src/variable_types/values_from_u32.rs | 45 +++ pmoupnp/src/variable_types/values_from_u8.rs | 45 +++ pmoupnp/src/variable_types/values_from_uri.rs | 31 ++ .../src/variable_types/values_from_uuid.rs | 31 ++ .../src/variable_types/values_from_vec_u8.rs | 45 +++ 40 files changed, 2024 insertions(+), 1 deletion(-) create mode 100644 pmoupnp/Cargo.toml create mode 100644 pmoupnp/src/lib.rs create mode 100644 pmoupnp/src/object_trait.rs create mode 100644 pmoupnp/src/state_variables/errors.rs create mode 100644 pmoupnp/src/state_variables/instance_methods.rs create mode 100644 pmoupnp/src/state_variables/mod.rs create mode 100644 pmoupnp/src/state_variables/variable_methods.rs create mode 100644 pmoupnp/src/state_variables/variable_trait.rs create mode 100644 pmoupnp/src/value_ranges/methods.rs create mode 100644 pmoupnp/src/value_ranges/mod.rs create mode 100644 pmoupnp/src/variable_types/cast.rs create mode 100644 pmoupnp/src/variable_types/default_value.rs create mode 100644 pmoupnp/src/variable_types/display_type.rs create mode 100644 pmoupnp/src/variable_types/display_value.rs create mode 100644 pmoupnp/src/variable_types/errors.rs create mode 100644 pmoupnp/src/variable_types/fromstr.rs create mode 100644 pmoupnp/src/variable_types/mod.rs create mode 100644 pmoupnp/src/variable_types/type_methods.rs create mode 100644 pmoupnp/src/variable_types/type_trait.rs create mode 100644 pmoupnp/src/variable_types/value_methods.rs create mode 100644 pmoupnp/src/variable_types/value_trait.rs create mode 100644 pmoupnp/src/variable_types/values_from_datetime.rs create mode 100644 pmoupnp/src/variable_types/values_from_f32.rs create mode 100644 pmoupnp/src/variable_types/values_from_f64.rs create mode 100644 pmoupnp/src/variable_types/values_from_i16.rs create mode 100644 pmoupnp/src/variable_types/values_from_i32.rs create mode 100644 pmoupnp/src/variable_types/values_from_i64.rs create mode 100644 pmoupnp/src/variable_types/values_from_i8.rs create mode 100644 pmoupnp/src/variable_types/values_from_naivedate.rs create mode 100644 pmoupnp/src/variable_types/values_from_naivedatetime.rs create mode 100644 pmoupnp/src/variable_types/values_from_naivetime.rs create mode 100644 pmoupnp/src/variable_types/values_from_type.rs create mode 100644 pmoupnp/src/variable_types/values_from_u16.rs create mode 100644 pmoupnp/src/variable_types/values_from_u32.rs create mode 100644 pmoupnp/src/variable_types/values_from_u8.rs create mode 100644 pmoupnp/src/variable_types/values_from_uri.rs create mode 100644 pmoupnp/src/variable_types/values_from_uuid.rs create mode 100644 pmoupnp/src/variable_types/values_from_vec_u8.rs diff --git a/.gitignore b/.gitignore index da997c10..db70e28b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ xxx **/.pmomusic_covers/** **/.DS_Strore/** **/.DS_Strore -/target/** +/target/ .pmomusic_covers C/src/soxr-0.1.3/Release/tests **/Release/ diff --git a/Cargo.lock b/Cargo.lock index 0a97afc2..6f21dc06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,6 +290,7 @@ dependencies = [ "thiserror", "url", "uuid", + "xmltree", ] [[package]] @@ -583,6 +584,21 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "xml-rs" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" + +[[package]] +name = "xmltree" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b619f8c85654798007fb10afa5125590b43b088c225a25fc2fec100a9fad0fc6" +dependencies = [ + "xml-rs", +] + [[package]] name = "yoke" version = "0.8.0" diff --git a/pmoupnp/Cargo.toml b/pmoupnp/Cargo.toml new file mode 100644 index 00000000..b62f2dd2 --- /dev/null +++ b/pmoupnp/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "pmoupnp" +version = "0.1.0" +edition = "2024" + +[dependencies] +chrono = "0.4.42" +url = "2.5.7" +uuid = "1.18.1" +hex = "0.4.3" +base64 = "0.22.1" +thiserror = "2.0.16" +xmltree = "0.11.0" \ No newline at end of file diff --git a/pmoupnp/src/lib.rs b/pmoupnp/src/lib.rs new file mode 100644 index 00000000..436f39a1 --- /dev/null +++ b/pmoupnp/src/lib.rs @@ -0,0 +1,14 @@ +mod object_trait; + +pub mod variable_types; +pub mod state_variables; +pub mod value_ranges; + +pub use crate::object_trait::UpnpObject; + + +#[derive(Clone)] +pub struct UpnpObjectType { + name: String, + object_type: String, +} \ No newline at end of file diff --git a/pmoupnp/src/object_trait.rs b/pmoupnp/src/object_trait.rs new file mode 100644 index 00000000..fbc373e1 --- /dev/null +++ b/pmoupnp/src/object_trait.rs @@ -0,0 +1,39 @@ +use xmltree::{Element, EmitterConfig}; + +use crate::UpnpObjectType; + + +pub trait UpnpObject { + fn as_upnp_object_type(&self) -> &UpnpObjectType; + fn to_xml_element(&self) -> Element; + + fn get_name(&self)-> &String { + return &self.as_upnp_object_type().name; + } + + fn get_object_type(&self) -> &String { + &self.as_upnp_object_type().object_type + } + + fn to_xml(&self) -> String { + let elem = self.to_xml_element(); + + // Configurer l'indentation + let config = EmitterConfig::new() + .perform_indent(true) + .indent_string(" "); // 2 espaces + + // Sérialiser dans un buffer + let mut buf = Vec::new(); + // écrire l'élément + elem.write_with_config(&mut buf, config) + .expect("Failed to write XML"); + + // Préfixer avec l'en-tête XML + let mut xml_string = "\n".to_string(); + xml_string.push_str(&String::from_utf8(buf).expect("Invalid UTF-8")); + + xml_string + } + +} \ No newline at end of file diff --git a/pmoupnp/src/state_variables/errors.rs b/pmoupnp/src/state_variables/errors.rs new file mode 100644 index 00000000..294cc1d0 --- /dev/null +++ b/pmoupnp/src/state_variables/errors.rs @@ -0,0 +1,28 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum StateVariableError { + #[error("Conversion error: {0}")] + ConversionError(String), + + #[error("Validation error: {0}")] + ValidationError(String), + + #[error("Range error: {0}")] + RangeError(String), + + #[error("Type error: {0}")] + TypeError(String), + + #[error("Parse error: {0}")] + ParseError(String), + + #[error("Event condition error: {0}")] + EventConditionError(String), + + #[error("Arithmetic error: {0}")] + ArithmeticError(String), + + #[error("Unknown error: {0}")] + Unknown(String), +} diff --git a/pmoupnp/src/state_variables/instance_methods.rs b/pmoupnp/src/state_variables/instance_methods.rs new file mode 100644 index 00000000..0e4da790 --- /dev/null +++ b/pmoupnp/src/state_variables/instance_methods.rs @@ -0,0 +1,58 @@ +use chrono::{DateTime, Utc}; +use xmltree::Element; + +use crate::{ + state_variables::{StateVarInstance, StateVariable, UpnpVariable}, + variable_types::StateValue, + UpnpObject, + UpnpObjectType +}; + +impl UpnpVariable for StateVarInstance { + fn get_definition(&self) -> &StateVariable { + return &self.definition; + } +} + +impl UpnpObject for StateVarInstance { + fn as_upnp_object_type(&self) -> &UpnpObjectType { + return &self.object; + } + + fn to_xml_element(&self) -> Element { + self.get_definition().to_xml_element() + } +} + +impl StateVarInstance { + pub fn new(from: &StateVariable) -> Self { + Self { + object: UpnpObjectType { + name: from.object.name.clone(), + object_type: "StateVarInstance".to_string(), + }, + definition: from.clone(), + value: from.get_default(), + old_value: from.get_default(), + last_modified: Utc::now(), + last_notification: Utc::now() + } + } + + pub fn set_value(&mut self, new_value: StateValue) { + self.old_value = self.value.clone(); + self.value = new_value; + self.last_modified = Utc::now(); // mise à jour automatique + } + + /// Accès à la valeur + pub fn value(&self) -> &StateValue { + &self.value + } + + /// Accès au timestamp + pub fn last_modified(&self) -> DateTime { + self.last_modified + } + +} diff --git a/pmoupnp/src/state_variables/mod.rs b/pmoupnp/src/state_variables/mod.rs new file mode 100644 index 00000000..33b9c6d0 --- /dev/null +++ b/pmoupnp/src/state_variables/mod.rs @@ -0,0 +1,47 @@ +mod errors; +mod variable_trait; +mod variable_methods; +mod instance_methods; + +use std::{collections::HashMap, sync::{Arc, RwLock}}; + +use chrono::{DateTime, Utc}; +pub use errors::{StateVariableError}; +pub use crate::state_variables::variable_trait::UpnpVariable; + + +use crate::{value_ranges::ValueRange, variable_types::{StateValue, StateVarType}, UpnpObjectType}; + +/// Type pour les fonctions de condition d'événement +pub type StateConditionFunc = Arc bool + Send + Sync>; + +/// Type pour les fonctions de parsing de valeurs depuis des chaînes +pub type StringValueParser = Arc Result + Send + Sync>; + +/// Type pour les fonctions de sérialisation de valeurs vers des chaînes +pub type ValueSerializer = Arc Result + Send + Sync>; + + +pub struct StateVariable { + object: UpnpObjectType, + value_type: StateVarType, + step: Option, + modifiable: bool, + event_conditions: Arc>>, + description: String, + default_value: Option, + value_range: Option, + allowed_values: Arc>>, + send_events: bool, + parse: Option, + marshal: Option, +} + +pub struct StateVarInstance { + object: UpnpObjectType, + definition: StateVariable, + value: StateValue, + old_value: StateValue, + last_modified: DateTime, + last_notification: DateTime, +} \ No newline at end of file diff --git a/pmoupnp/src/state_variables/variable_methods.rs b/pmoupnp/src/state_variables/variable_methods.rs new file mode 100644 index 00000000..a442fee2 --- /dev/null +++ b/pmoupnp/src/state_variables/variable_methods.rs @@ -0,0 +1,353 @@ +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; + +use xmltree::{Element, XMLNode}; + +use crate::{ + UpnpObject, UpnpObjectType, + state_variables::{ + StateConditionFunc, StateVariable, StringValueParser, ValueSerializer, + variable_trait::UpnpVariable, + }, + value_ranges::ValueRange, + variable_types::{StateValue, StateValueError, StateVarType, UpnpVarType}, +}; + +impl UpnpObject for StateVariable { + fn as_upnp_object_type(&self) -> &UpnpObjectType { + return &self.object; + } + +fn to_xml_element(&self) -> Element { + // Création de l'élément racine + let mut root = Element::new("stateVariable"); + root.attributes.insert( + "sendEvents".to_string(), + if self.send_events { "yes" } else { "no" }.to_string(), + ); + + // + let mut name_elem = Element::new("name"); + name_elem + .children + .push(XMLNode::Text(self.get_name().clone())); + + // + let mut datatype_elem = Element::new("dataType"); + datatype_elem + .children + .push(XMLNode::Text(self.value_type.to_string())); // StateVarType doit impl Display + + // si défini + if let Some(default) = &self.default_value { + let mut def_elem = Element::new("defaultValue"); + def_elem.children.push(XMLNode::Text(default.to_string())); + root.children.push(XMLNode::Element(def_elem)); + } + + // si défini + if let Ok(av) = self.allowed_values.read() { + if !av.is_empty() { + let mut list_elem = Element::new("allowedValueList"); + for val in av.iter() { + let mut val_elem = Element::new("allowedValue"); + val_elem.children.push(XMLNode::Text(val.to_string())); + list_elem.children.push(XMLNode::Element(val_elem)); + } + root.children.push(XMLNode::Element(list_elem)); + } + } + + // si défini + if let Some(range) = &self.value_range { + let mut range_elem = Element::new("allowedValueRange"); + + let mut min_elem = Element::new("minimum"); + min_elem.children.push(XMLNode::Text(range.get_minimum().to_string())); + range_elem.children.push(XMLNode::Element(min_elem)); + + let mut max_elem = Element::new("maximum"); + max_elem.children.push(XMLNode::Text(range.get_maximum().to_string())); + range_elem.children.push(XMLNode::Element(max_elem)); + + if let Some(step) = &self.step { + let mut step_elem = Element::new("step"); + step_elem.children.push(XMLNode::Text(step.to_string())); + range_elem.children.push(XMLNode::Element(step_elem)); + } + + root.children.push(XMLNode::Element(range_elem)); + } + + // Ajouter les enfants communs + root.children.push(XMLNode::Element(name_elem)); + root.children.push(XMLNode::Element(datatype_elem)); + + root + } +} + +impl Clone for StateVariable { + fn clone(&self) -> Self { + // clone safe des structures protégées par RwLock en prenant un read lock + let event_conditions_clone = { + // si le lock est "poisoned" on panic - tu peux adapter la gestion si tu veux + let guard = self + .event_conditions + .read() + .expect("RwLock poisoned during clone"); + // nécessite que Key: Clone, Value: Clone + Arc::new(RwLock::new(guard.clone())) + }; + + let allowed_values_clone = { + let guard = self + .allowed_values + .read() + .expect("RwLock poisoned during clone"); + Arc::new(RwLock::new(guard.clone())) + }; + + Self { + object: self.object.clone(), + value_type: self.value_type.clone(), + step: self.step.clone(), + modifiable: self.modifiable, + event_conditions: event_conditions_clone, + description: self.description.clone(), + default_value: self.default_value.clone(), + value_range: self.value_range.clone(), + allowed_values: allowed_values_clone, + send_events: self.send_events, + // parse et marshal sont typiquement des Arc — on clone l'Arc (shallow). + // Deep-cloner une closure ou un trait-objet n'est pas possible en général. + parse: self.parse.clone(), + marshal: self.marshal.clone(), + } + } +} + +impl UpnpVarType for StateVariable { + fn as_state_var_type(&self) -> StateVarType { + self.value_type // utilise ton From<&StateValue> existant + } +} + +impl UpnpVariable for StateVariable { + fn get_definition(&self) -> &StateVariable { + return self; + } +} + +impl StateVariable { + pub fn new(vartype: StateVarType, name: String) -> StateVariable { + Self { + object: UpnpObjectType { + name, + object_type: "StateVariable".to_string(), + }, + value_type: vartype.clone(), + step: None, + modifiable: true, + event_conditions: Arc::new(RwLock::new(HashMap::new())), + description: "".to_string(), + default_value: None, + value_range: None, + allowed_values: Arc::new(RwLock::new(Vec::new())), + send_events: false, + parse: None, + marshal: None, + } + } + + pub fn set_step(&mut self, step: StateValue) -> Result<(), StateValueError> { + if self.as_state_var_type() != step.as_state_var_type() { + return Err(StateValueError::TypeError("Bad step type".to_string())); + } + + self.step = Some(step); + Ok(()) + } + + pub fn set_range(&mut self, min: &StateValue, max: &StateValue) -> Result<(), StateValueError> { + if self.as_state_var_type() != min.as_state_var_type() { + return Err(StateValueError::TypeError("Bad range type".to_string())); + } + + let range = ValueRange::new(min, max)?; // ? propage l'erreur si elle existe + self.value_range = Some(range); + Ok(()) + } + + pub fn update_minimum(&mut self, min: &StateValue) -> Result<(), StateValueError> { + if !self.has_range() { + return Err(StateValueError::RangeError( + "No range specified for this variable".to_string(), + )); + } + if self + .value_range + .as_ref() + .expect("Range is not defined") + .as_state_var_type() + != min.as_state_var_type() + { + return Err(StateValueError::TypeError( + "new minimum is not the same than state variable".to_string(), + )); + } + self.value_range + .as_mut() + .expect("Range is not defined") + .set_minimum(min); + return Ok(()); + } + + pub fn update_maximum(&mut self, min: &StateValue) -> Result<(), StateValueError> { + if !self.has_range() { + return Err(StateValueError::RangeError( + "No range specified for this variable".to_string(), + )); + } + if self + .value_range + .as_ref() + .expect("Range is not defined") + .as_state_var_type() + != min.as_state_var_type() + { + return Err(StateValueError::TypeError( + "new minimum is not the same than state variable".to_string(), + )); + } + self.value_range + .as_mut() + .expect("Range is not defined") + .set_maximum(min); + return Ok(()); + } + + pub fn get_range(&self) -> Option<&ValueRange> { + return self.value_range.as_ref(); + } + + pub fn set_modifiable(&mut self) { + self.modifiable = true; + } + + pub fn set_not_modifiable(&mut self) { + self.modifiable = false; + } + + pub fn add_event_condition(&self, name: String, func: StateConditionFunc) { + // on lock en écriture + let mut guard = self.event_conditions.write().unwrap(); + guard.insert(name, func); + // le lock est automatiquement relâché ici (RAII) + } + + pub fn remove_event_condition(&self, name: &str) { + let mut guard = self.event_conditions.write().unwrap(); + guard.remove(name); + } + + pub fn clear_event_conditions(&mut self) { + let mut guard = self.event_conditions.write().unwrap(); + guard.clear() + } + + pub fn set_description(&mut self, description: String) { + self.description = description; + } + + pub fn set_default(&mut self, value: StateValue) -> Result<(), StateValueError> { + if self.as_state_var_type() != value.as_state_var_type() { + return Err(StateValueError::TypeError( + "value does not have the right type".to_string(), + )); + } + self.default_value = Some(value.clone()); + return Ok(()); + } + + pub fn unset_default(&mut self) { + self.default_value = None; + } + + pub fn extend_allowed_values(&mut self, values: &[StateValue]) -> Result<(), StateValueError> { + let mut av = self + .allowed_values + .write() + .map_err(|_| StateValueError::TypeError("Lock poisoned".to_string()))?; + + for v in values { + if self.as_state_var_type() == v.as_state_var_type() { + av.push(v.clone()); + } else { + return Err(StateValueError::TypeError( + "new allowed value does not have the right type".to_string(), + )); + } + } + + Ok(()) + } + + pub fn push_allowed_value(&mut self, value: &StateValue) -> Result<(), StateValueError> { + let mut av = self + .allowed_values + .write() + .map_err(|_| StateValueError::TypeError("Lock poisoned".to_string()))?; + + if self.as_state_var_type() == value.as_state_var_type() { + av.push(value.clone()); + } else { + return Err(StateValueError::TypeError( + "new allowed value does not have the right type".to_string(), + )); + } + + return Ok(()); + } + + pub fn set_send_notification(&mut self) { + self.send_events = true; + } + + pub fn unset_send_notification(&mut self) { + self.send_events = false; + } + + pub fn set_value_parseur(&mut self, parser: StringValueParser) -> Result<(), StateValueError> { + if self.as_state_var_type() == StateVarType::String { + self.parse = Some(parser); + return Ok(()); + } + return Err(StateValueError::TypeError( + "Only String variables can have a parser".to_string(), + )); + } + + pub fn unset_value_parser(&mut self) { + self.parse = None; + } + + pub fn set_value_marshaler( + &mut self, + marshaler: ValueSerializer, + ) -> Result<(), StateValueError> { + if self.as_state_var_type() == StateVarType::String { + self.marshal = Some(marshaler); + return Ok(()); + } + return Err(StateValueError::TypeError( + "Only String variables can have a marshaler".to_string(), + )); + } + + pub fn unset_value_marshaler(&mut self) { + self.marshal = None; + } +} diff --git a/pmoupnp/src/state_variables/variable_trait.rs b/pmoupnp/src/state_variables/variable_trait.rs new file mode 100644 index 00000000..6925645a --- /dev/null +++ b/pmoupnp/src/state_variables/variable_trait.rs @@ -0,0 +1,73 @@ +use crate::{ + state_variables::StateVariable, + variable_types::{StateValue, UpnpVarType}, +}; + +pub trait UpnpVariable { + fn get_definition(&self) -> &StateVariable; + + fn has_step(&self) -> bool { + return self.get_definition().step.is_some(); + } + + fn get_step(&self) -> Option { + return self.get_definition().step.clone(); + } + + fn has_range(&self) -> bool { + return self.get_definition().value_range.is_some(); + } + + fn is_modifiable(&self) -> bool { + return self.get_definition().modifiable; + } + + fn has_event_conditions(&self) -> bool { + return self.get_definition().event_conditions.read().unwrap().len() > 0; + } + + fn has_event_condition(&self, name: &String) -> bool { + let guard = self.get_definition().event_conditions.read().unwrap(); + return guard.contains_key(name); + } + + fn has_description(&self) -> bool { + return !String::is_empty(&self.get_definition().description); + } + + fn get_description(&self) -> String { + return self.get_definition().description.clone(); + } + + fn has_default(&self) -> bool { + return self.get_definition().default_value.is_some(); + } + + fn get_default(&self) -> StateValue { + self.get_definition() + .default_value + .clone() + .unwrap_or_else(|| self.get_definition().as_state_var_type().default_value()) + } + + fn has_allowed_values(&self) -> bool { + return self.get_definition().allowed_values.read().unwrap().len() > 0; + } + + fn is_an_allowed_values(&self, value: &StateValue) -> bool { + let guard = self.get_definition().allowed_values.read().unwrap(); + return guard.contains(value); + } + + fn is_sending_notification(&self) -> bool { + self.get_definition().send_events + } + + fn has_value_parser(&self) -> bool { + self.get_definition().parse.is_some() + } + + fn has_value_marshaler(&self) -> bool { + self.get_definition().marshal.is_some() + } +} diff --git a/pmoupnp/src/value_ranges/methods.rs b/pmoupnp/src/value_ranges/methods.rs new file mode 100644 index 00000000..cf12f640 --- /dev/null +++ b/pmoupnp/src/value_ranges/methods.rs @@ -0,0 +1,69 @@ +use std::cmp::Ordering; + +use crate::{ + value_ranges::ValueRange, + variable_types::{StateValue, StateValueError, StateVarType, UpnpVarType}, +}; + +impl UpnpVarType for ValueRange { + fn as_state_var_type(&self) -> StateVarType { + self.min.as_state_var_type() // utilise ton From<&StateValue> existant + } +} + +impl ValueRange { + pub fn new(min: &StateValue, max: &StateValue) -> Result { + if min.as_state_var_type() != max.as_state_var_type() { + return Err(StateValueError::TypeError( + "min and max do not belong the same time".to_string(), + )); + } + + // Vérifier que min <= max + if let Some(cmp) = min.partial_cmp(max) { + if cmp == Ordering::Greater { + return Err(StateValueError::RangeError( + "Minimum cannot be greater than maximum".to_string(), + )); + } + } + + Ok(Self { + min: min.clone(), + max: max.clone(), + }) + } + + pub fn get_minimum(self: &ValueRange) -> StateValue { + return self.min.clone(); + } + + pub fn set_minimum(&mut self, value: &StateValue) { + self.min = value.clone() + } + + pub fn get_maximum(self: &ValueRange) -> StateValue { + return self.max.clone(); + } + + pub fn set_maximum(&mut self, value: &StateValue) { + self.max = value.clone() + } + + pub fn is_in_range(&self, value: &StateValue) -> bool { + if self.as_state_var_type() == value.as_state_var_type() + && let Some(cmp) = self.min.partial_cmp(value) + { + if cmp == Ordering::Greater { + return false; + } + if let Some(cmp2) = self.max.partial_cmp(value) { + if cmp2 == Ordering::Less { + return false; + } + return true; + } + } + return false; + } +} diff --git a/pmoupnp/src/value_ranges/mod.rs b/pmoupnp/src/value_ranges/mod.rs new file mode 100644 index 00000000..389c87d3 --- /dev/null +++ b/pmoupnp/src/value_ranges/mod.rs @@ -0,0 +1,10 @@ +mod methods; + + +use crate::variable_types::StateValue; + +#[derive(Debug, Clone)] +pub struct ValueRange { + min: StateValue, + max: StateValue, +} \ No newline at end of file diff --git a/pmoupnp/src/variable_types/cast.rs b/pmoupnp/src/variable_types/cast.rs new file mode 100644 index 00000000..89eac526 --- /dev/null +++ b/pmoupnp/src/variable_types/cast.rs @@ -0,0 +1,37 @@ +use crate::variable_types::{StateValue, StateVarType, StateValueError}; +use std::convert::TryFrom; + +impl StateValue { + pub fn try_cast(&self, target: StateVarType) -> Result { + + let source = StateVarType::from(self); + + // Identité (même type) + if source == target { + return Ok(self.clone()); + } + + match (self, target) { + (val, StateVarType::String) => Ok(StateValue::String(val.to_string())), + + (_,StateVarType::UI1) => Ok(StateValue::UI1(u8::try_from(self)?)), + (_,StateVarType::UI2) => Ok(StateValue::UI2(u16::try_from(self)?)), + (_,StateVarType::UI4) => Ok(StateValue::UI4(u32::try_from(self)?)), + (_,StateVarType::I1) => Ok(StateValue::I1(i8::try_from(self)?)), + (_,StateVarType::I2) => Ok(StateValue::I2(i16::try_from(self)?)), + (_,StateVarType::I4) => Ok(StateValue::I4(i32::try_from(self)?)), + (_,StateVarType::Int) => Ok(StateValue::Int(i32::try_from(self)?)), + + (_,StateVarType::R8) => Ok(StateValue::R8(f64::try_from(self)?)), + (_,StateVarType::Number) => Ok(StateValue::Number(f64::try_from(self)?)), + (_,StateVarType::Fixed14_4) => Ok(StateValue::Fixed14_4(f64::try_from(self)?)), + (_,StateVarType::R4) => Ok(StateValue::R4(f32::try_from(self)?)), + + // --- Pas encore implémenté pour les autres types --- + (val, target) => Err(StateValueError::TypeError(format!( + "Cannot cast {:?} to {:?}", + val, target + ))), + } + } +} diff --git a/pmoupnp/src/variable_types/default_value.rs b/pmoupnp/src/variable_types/default_value.rs new file mode 100644 index 00000000..93ef4eb4 --- /dev/null +++ b/pmoupnp/src/variable_types/default_value.rs @@ -0,0 +1,43 @@ +use chrono::{DateTime, FixedOffset, NaiveDate, NaiveTime}; +use url::Url; +use uuid::Uuid; + +use crate::variable_types::{StateValue, StateVarType}; + +impl StateVarType { + pub fn default_value(&self) -> StateValue { + match self { + StateVarType::UI1 => StateValue::UI1(0), + StateVarType::UI2 => StateValue::UI2(0), + StateVarType::UI4 => StateValue::UI4(0), + StateVarType::I1 => StateValue::I1(0), + StateVarType::I2 => StateValue::I2(0), + StateVarType::I4 => StateValue::I4(0), + StateVarType::Int => StateValue::Int(0), + StateVarType::R4 => StateValue::R4(0.0), + StateVarType::R8 => StateValue::R8(0.0), + StateVarType::Number => StateValue::Number(0.0), + StateVarType::Fixed14_4 => StateValue::Fixed14_4(0.0), + StateVarType::Char => StateValue::Char('\0'), + StateVarType::String => StateValue::String(String::new()), + StateVarType::Boolean => StateValue::Boolean(false), + StateVarType::BinBase64 => StateValue::BinBase64(String::new()), + StateVarType::BinHex => StateValue::BinHex(String::new()), + StateVarType::Date => StateValue::Date(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()), + StateVarType::DateTime => StateValue::DateTime(DateTime::from_timestamp(0, 0).unwrap().naive_utc().into()), + StateVarType::DateTimeTZ => StateValue::DateTimeTZ( + DateTime::from_naive_utc_and_offset( + DateTime::from_timestamp(0, 0).unwrap().naive_utc(), + FixedOffset::east_opt(0).unwrap()) + ), + StateVarType::Time => StateValue::Time(NaiveTime::from_hms_opt(0, 0, 0).unwrap()), + StateVarType::TimeTZ => StateValue::TimeTZ( + DateTime::from_naive_utc_and_offset( + DateTime::from_timestamp(0, 0).unwrap().naive_utc(), + FixedOffset::east_opt(0).unwrap()) + ), + StateVarType::UUID => StateValue::UUID(Uuid::nil()), + StateVarType::URI => StateValue::URI(Url::parse("http://localhost").unwrap()), + } + } +} \ No newline at end of file diff --git a/pmoupnp/src/variable_types/display_type.rs b/pmoupnp/src/variable_types/display_type.rs new file mode 100644 index 00000000..954af6c6 --- /dev/null +++ b/pmoupnp/src/variable_types/display_type.rs @@ -0,0 +1,35 @@ +use std::fmt; + +use crate::variable_types::StateVarType; + + +impl fmt::Display for StateVarType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + StateVarType::UI1 => "ui1", + StateVarType::UI2 => "ui2", + StateVarType::UI4 => "ui4", + StateVarType::I1 => "i1", + StateVarType::I2 => "i2", + StateVarType::I4 => "i4", + StateVarType::Int => "int", + StateVarType::R4 => "r4", + StateVarType::R8 => "r8", + StateVarType::Number => "number", + StateVarType::Fixed14_4 => "fixed.14.4", + StateVarType::Char => "char", + StateVarType::String => "string", + StateVarType::Boolean => "boolean", + StateVarType::BinBase64 => "bin.base64", + StateVarType::BinHex => "bin.hex", + StateVarType::Date => "date", + StateVarType::DateTime => "dateTime", + StateVarType::DateTimeTZ => "dateTime.tz", + StateVarType::Time => "time", + StateVarType::TimeTZ => "time.tz", + StateVarType::UUID => "uuid", + StateVarType::URI => "uri", + }; + write!(f, "{}", s) + } +} diff --git a/pmoupnp/src/variable_types/display_value.rs b/pmoupnp/src/variable_types/display_value.rs new file mode 100644 index 00000000..41bedfc9 --- /dev/null +++ b/pmoupnp/src/variable_types/display_value.rs @@ -0,0 +1,45 @@ +use std::fmt; +use base64::engine::general_purpose; +use base64::Engine; + +use crate::variable_types::StateValue; + + +impl fmt::Display for StateValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + // Numériques + StateValue::UI1(v) => write!(f, "{}", v), + StateValue::UI2(v) => write!(f, "{}", v), + StateValue::UI4(v) => write!(f, "{}", v), + StateValue::I1(v) => write!(f, "{}", v), + StateValue::I2(v) => write!(f, "{}", v), + StateValue::I4(v) => write!(f, "{}", v), + StateValue::Int(v) => write!(f, "{}", v), + StateValue::R4(v) => write!(f, "{}", v), + StateValue::R8(v) => write!(f, "{}", v), + StateValue::Number(v) => write!(f, "{}", v), + StateValue::Fixed14_4(v) => write!(f, "{}", v), + + // Types déjà Display + StateValue::Char(v) => write!(f, "{}", v), + StateValue::String(v) => write!(f, "{}", v), + StateValue::UUID(v) => write!(f, "{}", v), + StateValue::URI(v) => write!(f, "{}", v), + + // Booléen : 1 ou 0 + StateValue::Boolean(v) => write!(f, "{}", if *v { "1" } else { "0" }), + + // Encodages binaires + StateValue::BinBase64(v) => write!(f, "{}", general_purpose::URL_SAFE.encode(v)), + StateValue::BinHex(v) => write!(f, "{}", hex::encode(v)), + + // Dates et temps + StateValue::Date(v) => write!(f, "{}", v.format("%Y-%m-%d")), + StateValue::DateTime(v) => write!(f, "{}", v.format("%Y-%m-%dT%H:%M:%S")), + StateValue::DateTimeTZ(v) => write!(f, "{}", v.to_rfc3339()), + StateValue::Time(v) => write!(f, "{}", v.format("%H:%M:%S")), + StateValue::TimeTZ(v) => write!(f, "{}", v.format("%H:%M:%S%z")), + } + } +} diff --git a/pmoupnp/src/variable_types/errors.rs b/pmoupnp/src/variable_types/errors.rs new file mode 100644 index 00000000..5378cca1 --- /dev/null +++ b/pmoupnp/src/variable_types/errors.rs @@ -0,0 +1,28 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum StateValueError { + #[error("Conversion error: {0}")] + ConversionError(String), + + #[error("Validation error: {0}")] + ValidationError(String), + + #[error("Range error: {0}")] + RangeError(String), + + #[error("Type error: {0}")] + TypeError(String), + + #[error("Parse error: {0}")] + ParseError(String), + + #[error("Event condition error: {0}")] + EventConditionError(String), + + #[error("Arithmetic error: {0}")] + ArithmeticError(String), + + #[error("Unknown error: {0}")] + Unknown(String), +} diff --git a/pmoupnp/src/variable_types/fromstr.rs b/pmoupnp/src/variable_types/fromstr.rs new file mode 100644 index 00000000..7635b483 --- /dev/null +++ b/pmoupnp/src/variable_types/fromstr.rs @@ -0,0 +1,36 @@ +use std::str::FromStr; +use crate::variable_types::StateVarType; + + +impl FromStr for StateVarType { + type Err = String; // Type d'erreur personnalisé + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "ui1" => Ok(StateVarType::UI1), + "ui2" => Ok(StateVarType::UI2), + "ui4" => Ok(StateVarType::UI4), + "i1" => Ok(StateVarType::I1), + "i2" => Ok(StateVarType::I2), + "i4" => Ok(StateVarType::I4), + "int" => Ok(StateVarType::Int), + "r4" => Ok(StateVarType::R4), + "r8" => Ok(StateVarType::R8), + "number" => Ok(StateVarType::Number), + "fixed.14.4" => Ok(StateVarType::Fixed14_4), + "char" => Ok(StateVarType::Char), + "string" => Ok(StateVarType::String), + "boolean" => Ok(StateVarType::Boolean), + "bin.base64" => Ok(StateVarType::BinBase64), + "bin.hex" => Ok(StateVarType::BinHex), + "date" => Ok(StateVarType::Date), + "datetime" => Ok(StateVarType::DateTime), + "datetime.tz" => Ok(StateVarType::DateTimeTZ), + "time" => Ok(StateVarType::Time), + "time.tz" => Ok(StateVarType::TimeTZ), + "uuid" => Ok(StateVarType::UUID), + "uri" => Ok(StateVarType::URI), + _ => Err(format!("Type inconnu: {}", s)), + } + } +} diff --git a/pmoupnp/src/variable_types/mod.rs b/pmoupnp/src/variable_types/mod.rs new file mode 100644 index 00000000..4bd5e256 --- /dev/null +++ b/pmoupnp/src/variable_types/mod.rs @@ -0,0 +1,96 @@ +mod type_methods; +mod value_methods; +mod type_trait; +mod value_trait; +mod fromstr; +mod cast; +mod display_type; +mod display_value; +mod default_value; +mod errors; + +mod values_from_type; + +mod values_from_u8; +mod values_from_u16; +mod values_from_u32; +mod values_from_i8; +mod values_from_i16; +mod values_from_i32; +mod values_from_i64; + +mod values_from_f32; +mod values_from_f64; + +mod values_from_uri; +mod values_from_uuid; +mod values_from_naivedate; +mod values_from_naivedatetime; +mod values_from_naivetime; +mod values_from_datetime; +mod values_from_vec_u8; + +use std::fmt::{Debug}; + +use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; +use url::Url; +use uuid::Uuid; + +pub use errors::StateValueError; +pub use type_trait::UpnpVarType; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum StateVarType { + UI1, // Unsigned 8-bit integer + UI2, // Unsigned 16-bit integer + UI4, // Unsigned 32-bit integer + I1, // Signed 8-bit integer + I2, // Signed 16-bit integer + I4, // Signed 32-bit integer + Int, // Synonymous with i4 + R4, // 32-bit floating point + R8, // 64-bit floating point + Number, // Synonymous with r8 + Fixed14_4, // Fixed-point decimal + Char, // Single Unicode character + String, // Character string + Boolean, // Boolean value + BinBase64, // Base64-encoded binary + BinHex, // Hex-encoded binary + Date, // Date (YYYY-MM-DD) + DateTime, // DateTime without timezone + DateTimeTZ, // DateTime with timezone + Time, // Time without timezone + TimeTZ, // Time with timezone + UUID, // Universally unique identifier + URI, // Uniform Resource Identifier +} + +#[derive(Debug, Clone)] +pub enum StateValue { + UI1(u8), + UI2(u16), + UI4(u32), + I1(i8), + I2(i16), + I4(i32), + Int(i32), + R4(f32), + R8(f64), + Number(f64), + Fixed14_4(f64), + Char(char), + String(String), + Boolean(bool), + BinBase64(String), + BinHex(String), + Date(NaiveDate), + DateTime(NaiveDateTime), + DateTimeTZ(DateTime), + Time(NaiveTime), + TimeTZ(DateTime), + UUID(Uuid), + URI(Url), +} + + diff --git a/pmoupnp/src/variable_types/type_methods.rs b/pmoupnp/src/variable_types/type_methods.rs new file mode 100644 index 00000000..e484c27a --- /dev/null +++ b/pmoupnp/src/variable_types/type_methods.rs @@ -0,0 +1,109 @@ +use crate::variable_types::{type_trait::UpnpVarType, StateVarType}; + +impl UpnpVarType for StateVarType { + fn as_state_var_type(&self) -> StateVarType { + *self + } + + fn bit_size(&self) -> Option { + match self { + StateVarType::UI1 | StateVarType::I1 => Some(8), + StateVarType::UI2 | StateVarType::I2 => Some(16), + StateVarType::UI4 | StateVarType::I4 | StateVarType::Int | StateVarType::R4 => Some(32), + StateVarType::R8 | StateVarType::Number | StateVarType::Fixed14_4 => Some(64), + _ => None, + } + } + + fn is_numeric(&self) -> bool { + matches!( + self, + StateVarType::UI1 + | StateVarType::UI2 + | StateVarType::UI4 + | StateVarType::I1 + | StateVarType::I2 + | StateVarType::I4 + | StateVarType::Int + | StateVarType::R4 + | StateVarType::R8 + | StateVarType::Number + | StateVarType::Fixed14_4 + ) + } + + fn is_integer(&self) -> bool { + matches!( + self, + StateVarType::UI1 + | StateVarType::UI2 + | StateVarType::UI4 + | StateVarType::I1 + | StateVarType::I2 + | StateVarType::I4 + | StateVarType::Int + ) + } + + fn is_signed_int(&self) -> bool { + matches!( + self, + StateVarType::I1 | StateVarType::I2 | StateVarType::I4 | StateVarType::Int + ) + } + + fn is_unsigned_int(&self) -> bool { + matches!( + self, + StateVarType::UI1 | StateVarType::UI2 | StateVarType::UI4 + ) + } + + fn is_float(&self) -> bool { + matches!( + self, + StateVarType::R4 + | StateVarType::R8 + | StateVarType::Number + | StateVarType::Fixed14_4 + ) + } + + fn is_bool(&self) -> bool { + matches!(self, StateVarType::Boolean) + } + + fn is_string(&self) -> bool { + matches!(self, StateVarType::String + | StateVarType::Char + | StateVarType::BinHex + | StateVarType::BinBase64) + } + + fn is_time(&self) -> bool { + matches!( + self, + StateVarType::Date + | StateVarType::DateTime + | StateVarType::DateTimeTZ + | StateVarType::Time + | StateVarType::TimeTZ + ) + } + + fn is_uuid(&self) -> bool { + matches!(self, StateVarType::UUID) + } + + fn is_uri(&self) -> bool { + matches!(self, StateVarType::URI) + } + + fn is_binary(&self) -> bool { + matches!(self, StateVarType::BinBase64 | StateVarType::BinHex) + } + + fn is_comparable(&self) -> bool { + !self.is_binary() + } +} diff --git a/pmoupnp/src/variable_types/type_trait.rs b/pmoupnp/src/variable_types/type_trait.rs new file mode 100644 index 00000000..d72d2ea6 --- /dev/null +++ b/pmoupnp/src/variable_types/type_trait.rs @@ -0,0 +1,57 @@ +use crate::variable_types::StateVarType; + +pub trait UpnpVarType { + fn as_state_var_type(&self) -> StateVarType; + + fn bit_size(&self) -> Option { + self.as_state_var_type().bit_size() + } + + fn is_numeric(&self) -> bool { + self.as_state_var_type().is_numeric() + } + + fn is_integer(&self) -> bool { + self.as_state_var_type().is_integer() + } + + fn is_signed_int(&self) -> bool { + self.as_state_var_type().is_signed_int() + } + + fn is_unsigned_int(&self) -> bool { + self.as_state_var_type().is_unsigned_int() + } + + fn is_float(&self) -> bool { + self.as_state_var_type().is_float() + } + + fn is_bool(&self) -> bool { + self.as_state_var_type().is_bool() + } + + fn is_string(&self) -> bool { + self.as_state_var_type().is_string() + } + + fn is_time(&self) -> bool { + self.as_state_var_type().is_time() + } + + fn is_uuid(&self) -> bool { + self.as_state_var_type().is_uuid() + } + + fn is_uri(&self) -> bool { + self.as_state_var_type().is_uri() + } + + fn is_binary(&self) -> bool { + self.as_state_var_type().is_binary() + } + + fn is_comparable(&self) -> bool { + self.as_state_var_type().is_comparable() + } +} diff --git a/pmoupnp/src/variable_types/value_methods.rs b/pmoupnp/src/variable_types/value_methods.rs new file mode 100644 index 00000000..8f10bf90 --- /dev/null +++ b/pmoupnp/src/variable_types/value_methods.rs @@ -0,0 +1,89 @@ +use std::cmp::Ordering; + +use crate::variable_types::{StateValue, StateVarType, type_trait::UpnpVarType}; + + +impl UpnpVarType for StateValue { + fn as_state_var_type(&self) -> StateVarType { + StateVarType::from(self) // utilise ton From<&StateValue> existant + } +} + +impl PartialEq for StateValue { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (a, b) if a.is_integer() && b.is_integer() => { + if let (Ok(ia), Ok(ib)) = (i64::try_from(a), i64::try_from(b)) { + return ia == ib; + }; + return false; + } + (a, b) if a.is_float() && b.is_float() => { + if let (Ok(a), Ok(b)) = (f64::try_from(self), f64::try_from(other)) { + return a == b; // NaN respecte la sémantique IEEE (NaN != NaN) + } + return false; + } + (a, b) if a.is_string() && b.is_string() => { + let (a, b) = (self.to_string(), other.to_string()); + return a == b; // NaN respecte la sémantique IEEE (NaN != NaN) + } + (StateValue::Date(a), StateValue::Date(b)) => { + return a == b; + } + (StateValue::Time(a), StateValue::Time(b)) => { + return a == b; + } + (StateValue::DateTime(a), StateValue::DateTime(b)) => { + return a == b; + } + (StateValue::DateTimeTZ(a), StateValue::DateTimeTZ(b)) => { + return a == b; + } + (StateValue::TimeTZ(a), StateValue::TimeTZ(b)) => { + return a == b; + } + + (_, _) => return false, + } + } +} + +impl PartialOrd for StateValue { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (a, b) if a.is_integer() && b.is_integer() => { + if let (Ok(ia), Ok(ib)) = (i64::try_from(a), i64::try_from(b)) { + return Some(ia.cmp(&ib)); + }; + return None; + } + (a, b) if a.is_float() && b.is_float() => { + if let (Ok(a), Ok(b)) = (f64::try_from(self), f64::try_from(other)) { + return a.partial_cmp(&b); // NaN respecte la sémantique IEEE (NaN != NaN) + } + return None; + } + (a, b) if a.is_string() && b.is_string() => { + let (a, b) = (self.to_string(), other.to_string()); + return Some(a.cmp(&b)); // NaN respecte la sémantique IEEE (NaN != NaN) + } + (StateValue::Date(a), StateValue::Date(b)) => { + return Some(a.cmp(&b)); + } + (StateValue::Time(a), StateValue::Time(b)) => { + return Some(a.cmp(&b)); + } + (StateValue::DateTime(a), StateValue::DateTime(b)) => { + return Some(a.cmp(&b)); + } + (StateValue::DateTimeTZ(a), StateValue::DateTimeTZ(b)) => { + return Some(a.cmp(&b)); + } + (StateValue::TimeTZ(a), StateValue::TimeTZ(b)) => { + return Some(a.cmp(&b)); + } + (_, _) => return None, + } + } +} diff --git a/pmoupnp/src/variable_types/value_trait.rs b/pmoupnp/src/variable_types/value_trait.rs new file mode 100644 index 00000000..23b0f392 --- /dev/null +++ b/pmoupnp/src/variable_types/value_trait.rs @@ -0,0 +1,2 @@ +pub trait UpnpValue { +} \ No newline at end of file diff --git a/pmoupnp/src/variable_types/values_from_datetime.rs b/pmoupnp/src/variable_types/values_from_datetime.rs new file mode 100644 index 00000000..0a5b8a6a --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_datetime.rs @@ -0,0 +1,33 @@ +use std::convert::TryFrom; +use chrono::{DateTime,FixedOffset}; +use crate::variable_types::{StateValue, StateValueError}; + +impl TryFrom<&StateValue> for DateTime { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + StateValue::DateTimeTZ(v) => Ok(v.clone()), + StateValue::TimeTZ(v) => Ok(v.clone()), + StateValue::String(v) => DateTime::parse_from_rfc3339(v) + .map_err(|e| StateValueError::ParseError(format!("Cannot parse DateTimeTZ from string '{}': {}", v, e))), + _ => Err(StateValueError::TypeError("Cannot cast to DateTime".into())), + } + } +} + +impl TryFrom for DateTime { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + DateTime::::try_from(&value) + } +} + +impl From> for StateValue { + + fn from(value: DateTime) -> Self { + StateValue::DateTimeTZ(value) + } +} + diff --git a/pmoupnp/src/variable_types/values_from_f32.rs b/pmoupnp/src/variable_types/values_from_f32.rs new file mode 100644 index 00000000..fc292c6c --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_f32.rs @@ -0,0 +1,82 @@ +use std::convert::TryFrom; + +use crate::variable_types::{StateValue, StateValueError}; + + +impl TryFrom<&StateValue> for f32 { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + const MAX_EXACT: i32 = 1 << f32::MANTISSA_DIGITS; + match value { + // --- Signed integers --- + StateValue::I1(v) => Ok(*v as f32), + StateValue::I2(v) => Ok(*v as f32), + StateValue::I4(v) + if *v > -MAX_EXACT && + *v < MAX_EXACT => Ok(*v as f32), + StateValue::Int(v) + if *v >= -MAX_EXACT && + *v <= MAX_EXACT as i32 => Ok(*v as f32), + + // --- Unsigned integers --- + StateValue::UI1(v) => Ok(*v as f32), + StateValue::UI2(v) => Ok(*v as f32), + StateValue::UI4(v) if *v <= MAX_EXACT as u32 => Ok(*v as f32), + StateValue::UI4(_) => Err(StateValueError::TypeError( + "Cannot cast UI4 to f32: out of range".into(), + )), + + // --- Floats --- + StateValue::R4(v) => Ok(*v), // déjà un f32 + StateValue::R8(v) + if ! v.is_finite() || + (*v <= f32::MAX as f64 && + *v >= f32::MIN as f64) => Ok(*v as f32), + StateValue::R8(_) => Err(StateValueError::TypeError( + "Cannot cast R8 to f32: out of range".into(), + )), + StateValue::Number(v) + if ! v.is_finite() || + (*v <= f32::MAX as f64 && + *v >= f32::MIN as f64) => Ok(*v as f32), + StateValue::Number(_) => Err(StateValueError::TypeError( + "Cannot cast Number to f32: out of range".into(), + )), + StateValue::Fixed14_4(v) + if ! v.is_finite() || + (*v <= f32::MAX as f64 && + *v >= f32::MIN as f64) => Ok(*v as f32), + StateValue::Fixed14_4(_) => Err(StateValueError::TypeError( + "Cannot cast Fixed14_4 to f32: out of range".into(), + )), + + StateValue::Boolean(v) => Ok((*v as i32) as Self), + + StateValue::String(s) => s.parse::().map_err(|_| { + StateValueError::TypeError(format!("Cannot parse '{}' as f32", s)) + }), + + // --- Par défaut : erreur --- + _ => Err(StateValueError::TypeError( + "Cannot cast to f32".into(), + )), + } + } +} + +impl TryFrom for f32 { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + f32::try_from(&value) + } +} + +impl From for StateValue { + + fn from(value: f32) -> Self { + StateValue::R4(value) + } +} + diff --git a/pmoupnp/src/variable_types/values_from_f64.rs b/pmoupnp/src/variable_types/values_from_f64.rs new file mode 100644 index 00000000..015621b9 --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_f64.rs @@ -0,0 +1,53 @@ +use std::convert::TryFrom; + +use crate::variable_types::{StateValue, StateValueError}; + +impl TryFrom<&StateValue> for f64 { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + // --- Signed integers --- + StateValue::I1(v) => Ok(*v as f64), + StateValue::I2(v) => Ok(*v as f64), + StateValue::I4(v) => Ok(*v as f64), + StateValue::Int(v) => Ok(*v as f64), + + // --- Unsigned integers --- + StateValue::UI1(v) => Ok(*v as f64), + StateValue::UI2(v) => Ok(*v as f64), + StateValue::UI4(v) => Ok(*v as f64), + + // --- Floats --- + StateValue::R4(v) => Ok(*v as f64), + StateValue::R8(v) => Ok(*v), + StateValue::Number(v) => Ok(*v), + StateValue::Fixed14_4(v) => Ok(*v), + + StateValue::Boolean(v) => Ok((*v as i32) as Self), + + StateValue::String(s) => s.parse::().map_err(|_| { + StateValueError::TypeError(format!("Cannot parse '{}' as f64", s)) + }), + + // --- Par défaut : erreur --- + _ => Err(StateValueError::TypeError( + "Cannot cast to f64".into(), + )), + } + } +} + +impl TryFrom for f64 { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + f64::try_from(&value) + } +} + +impl From for StateValue { + fn from(value: f64) -> Self { + StateValue::R8(value) + } +} diff --git a/pmoupnp/src/variable_types/values_from_i16.rs b/pmoupnp/src/variable_types/values_from_i16.rs new file mode 100644 index 00000000..5bc396fe --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_i16.rs @@ -0,0 +1,46 @@ +use std::convert::TryFrom; + + +use crate::variable_types::{StateValue, StateValueError}; + +// Implémentations TryFrom pour types numériques + +impl TryFrom<&StateValue> for i16 { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + StateValue::I1(v) => Ok(*v as i16), + StateValue::I2(v) => Ok(*v), + StateValue::I4(v) if *v <= i16::MAX as i32 && *v >= i16::MIN as i32 => Ok(*v as i16), + StateValue::Int(v) if *v <= i16::MAX as i32 && *v >= i16::MIN as i32 => Ok(*v as i16), + + StateValue::UI1(v) => Ok(*v as i16), + StateValue::UI2(v) if *v <= i16::MAX as u16 => Ok(*v as i16), + StateValue::UI4(v) if *v <= i16::MAX as u32 => Ok(*v as i16), + StateValue::Boolean(v) => Ok(*v as Self), + + StateValue::String(s) => s.parse::().map_err(|_| { + StateValueError::TypeError(format!("Cannot parse '{}' as i16", s)) + }), + + _ => Err(StateValueError::TypeError("Cannot cast to i32".into())), + } + } +} + +impl TryFrom for i16 { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + i16::try_from(&value) + } +} + +impl From for StateValue { + + fn from(value: i16) -> Self { + StateValue::I2(value) + } +} + diff --git a/pmoupnp/src/variable_types/values_from_i32.rs b/pmoupnp/src/variable_types/values_from_i32.rs new file mode 100644 index 00000000..ac944497 --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_i32.rs @@ -0,0 +1,46 @@ +use std::convert::TryFrom; + +use crate::variable_types::{StateValue, StateValueError}; + + +impl TryFrom<&StateValue> for i32 { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + // signés + StateValue::I1(v) => Ok(*v as i32), + StateValue::I2(v) => Ok(*v as i32), + StateValue::I4(v) => Ok(*v), + StateValue::Int(v) => Ok(*v), + + // non signés + StateValue::UI1(v) => Ok(*v as i32), + StateValue::UI2(v) => Ok(*v as i32), + StateValue::UI4(v) if *v <= i32::MAX as u32 =>Ok(*v as i32), + StateValue::Boolean(v) => Ok(*v as Self), + + StateValue::String(s) => s.parse::().map_err(|_| { + StateValueError::TypeError(format!("Cannot parse '{}' as i32", s)) + }), + + _ => Err(StateValueError::TypeError("Cannot cast to i32".into())), + } + } +} + +impl TryFrom for i32 { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + i32::try_from(&value) + } +} + +impl From for StateValue { + + fn from(value: i32) -> Self { + StateValue::I4(value) + } +} + diff --git a/pmoupnp/src/variable_types/values_from_i64.rs b/pmoupnp/src/variable_types/values_from_i64.rs new file mode 100644 index 00000000..b4a92480 --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_i64.rs @@ -0,0 +1,45 @@ +use std::convert::TryFrom; +use crate::variable_types::{StateValue, StateValueError}; + +impl TryFrom<&StateValue> for i64 { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + // signés + StateValue::I1(v) => Ok(*v as i64), + StateValue::I2(v) => Ok(*v as i64), + StateValue::I4(v) => Ok(*v as i64), + StateValue::Int(v) => Ok(*v as i64), + + // non signés + StateValue::UI1(v) => Ok(*v as i64), + StateValue::UI2(v) => Ok(*v as i64), + StateValue::UI4(v) => Ok(*v as i64), // toujours dans l'intervalle d'un i64 + + // booléen + StateValue::Boolean(v) => Ok(*v as i64), + + // chaîne → i64 + StateValue::String(s) => s.parse::().map_err(|_| { + StateValueError::TypeError(format!("Cannot parse '{}' as i64", s)) + }), + + _ => Err(StateValueError::TypeError("Cannot cast to i64".into())), + } + } +} + +impl TryFrom for i64 { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + i64::try_from(&value) + } +} + +impl From for StateValue { + fn from(value: i64) -> Self { + StateValue::Int(value as i32) // ⚠️ choix à discuter : Int est i32, pas i64 + } +} diff --git a/pmoupnp/src/variable_types/values_from_i8.rs b/pmoupnp/src/variable_types/values_from_i8.rs new file mode 100644 index 00000000..8831f507 --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_i8.rs @@ -0,0 +1,46 @@ +use std::convert::TryFrom; + + +use crate::variable_types::{StateValue, StateValueError}; + +// Implémentations TryFrom pour types numériques + +impl TryFrom<&StateValue> for i8 { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + StateValue::I1(v) if *v >= 0 => Ok(*v as i8), + StateValue::I2(v) if *v <= i8::MAX as i16 && *v >= i8::MIN as i16 => Ok(*v as i8), + StateValue::I4(v) if *v <= i8::MAX as i32 && *v >= i8::MIN as i32=> Ok(*v as i8), + StateValue::Int(v) if *v <= i8::MAX as i32 && *v >= i8::MIN as i32=> Ok(*v as i8), + + StateValue::UI1(v) if *v <= i8::MAX as u8 => Ok(*v as i8), + StateValue::UI2(v) if *v <= i8::MAX as u16 => Ok(*v as i8), + StateValue::UI4(v) if *v <= i8::MAX as u32 => Ok(*v as i8), + StateValue::Boolean(v) => Ok(*v as Self), + + StateValue::String(s) => s.parse::().map_err(|_| { + StateValueError::TypeError(format!("Cannot parse '{}' as i8", s)) + }), + + _ => Err(StateValueError::TypeError("Cannot cast to i8".into())), + } + } +} + +impl TryFrom for i8 { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + i8::try_from(&value) + } +} + +impl From for StateValue { + + fn from(value: i8) -> Self { + StateValue::I1(value) + } +} + diff --git a/pmoupnp/src/variable_types/values_from_naivedate.rs b/pmoupnp/src/variable_types/values_from_naivedate.rs new file mode 100644 index 00000000..992a3931 --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_naivedate.rs @@ -0,0 +1,32 @@ +use std::convert::TryFrom; +use chrono::NaiveDate; +use crate::variable_types::{StateValue, StateValueError}; + +impl TryFrom<&StateValue> for NaiveDate { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + StateValue::Date(v) => Ok(v.clone()), + StateValue::String(v) => NaiveDate::parse_from_str(v, "%Y-%m-%d") + .map_err(|e| StateValueError::ParseError(format!("Cannot parse Date from string '{}': {}", v, e))), + _ => Err(StateValueError::TypeError("Cannot cast to NaiveDate".into())), + } + } +} + +impl TryFrom for NaiveDate { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + NaiveDate::try_from(&value) + } +} + +impl From for StateValue { + + fn from(value: NaiveDate) -> Self { + StateValue::Date(value) + } +} + diff --git a/pmoupnp/src/variable_types/values_from_naivedatetime.rs b/pmoupnp/src/variable_types/values_from_naivedatetime.rs new file mode 100644 index 00000000..19dcba67 --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_naivedatetime.rs @@ -0,0 +1,31 @@ +use std::convert::TryFrom; +use chrono::NaiveDateTime; +use crate::variable_types::{StateValue, StateValueError}; + +impl TryFrom<&StateValue> for NaiveDateTime { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + StateValue::DateTime(v) => Ok(v.clone()), + StateValue::String(v) => NaiveDateTime::parse_from_str(&v, "%Y-%m-%dT%H:%M:%S") + .map_err(|e| StateValueError::ParseError(format!("Cannot parse DateTime from string '{}': {}", v, e))), + _ => Err(StateValueError::TypeError("Cannot cast to NaiveDateTime".into())), + } + } +} + +impl TryFrom for NaiveDateTime { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + NaiveDateTime::try_from(&value) + } +} + +impl From for StateValue { + + fn from(value: NaiveDateTime) -> Self { + StateValue::DateTime(value) + } +} diff --git a/pmoupnp/src/variable_types/values_from_naivetime.rs b/pmoupnp/src/variable_types/values_from_naivetime.rs new file mode 100644 index 00000000..a8de2594 --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_naivetime.rs @@ -0,0 +1,31 @@ +use std::convert::TryFrom; +use chrono::NaiveTime; +use crate::variable_types::{StateValue, StateValueError}; + +impl TryFrom<&StateValue> for NaiveTime { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + StateValue::Time(v) => Ok(v.clone()), + StateValue::String(v) => NaiveTime::parse_from_str(&v, "%H:%M:%S") + .map_err(|e| StateValueError::ParseError(format!("Cannot parse Time from string '{}': {}", v, e))), + _ => Err(StateValueError::TypeError("Cannot cast to NaiveTime".into())), + } + } +} + +impl TryFrom for NaiveTime { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + NaiveTime::try_from(&value) + } +} + +impl From for StateValue { + + fn from(value: NaiveTime) -> Self { + StateValue::Time(value) + } +} diff --git a/pmoupnp/src/variable_types/values_from_type.rs b/pmoupnp/src/variable_types/values_from_type.rs new file mode 100644 index 00000000..f28a3789 --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_type.rs @@ -0,0 +1,37 @@ +use crate::variable_types::{StateValue, StateVarType}; + +impl From<&StateValue> for StateVarType { + fn from(value: &StateValue) -> Self { + match value { + StateValue::UI1(_) => StateVarType::UI1, + StateValue::UI2(_) => StateVarType::UI2, + StateValue::UI4(_) => StateVarType::UI4, + StateValue::I1(_) => StateVarType::I1, + StateValue::I2(_) => StateVarType::I2, + StateValue::I4(_) => StateVarType::I4, + StateValue::Int(_) => StateVarType::Int, + StateValue::R4(_) => StateVarType::R4, + StateValue::R8(_) => StateVarType::R8, + StateValue::Number(_) => StateVarType::Number, + StateValue::Fixed14_4(_) => StateVarType::Fixed14_4, + StateValue::Char(_) => StateVarType::Char, + StateValue::String(_) => StateVarType::String, + StateValue::BinBase64(_) => StateVarType::BinBase64, + StateValue::BinHex(_) => StateVarType::BinHex, + StateValue::URI(_) => StateVarType::URI, + StateValue::UUID(_) => StateVarType::UUID, + StateValue::Date(_) => StateVarType::Date, + StateValue::DateTime(_) => StateVarType::DateTime, + StateValue::DateTimeTZ(_) => StateVarType::DateTimeTZ, + StateValue::Time(_) => StateVarType::Time, + StateValue::TimeTZ(_) => StateVarType::TimeTZ, + StateValue::Boolean(_) => StateVarType::Boolean, + } + } +} + +impl From for StateVarType { + fn from(value: StateValue) -> Self { + StateVarType::from(&value) + } +} diff --git a/pmoupnp/src/variable_types/values_from_u16.rs b/pmoupnp/src/variable_types/values_from_u16.rs new file mode 100644 index 00000000..d835b7ee --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_u16.rs @@ -0,0 +1,47 @@ + +use std::convert::TryFrom; + + +use crate::variable_types::{StateValue, StateValueError}; + +// Implémentations TryFrom pour types numériques + +impl TryFrom<&StateValue> for u16 { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + StateValue::UI1(v) => Ok(*v as Self), + StateValue::UI2(v) => Ok(*v), + StateValue::UI4(v) if *v <= i16::MAX as u32 => Ok(*v as Self), + StateValue::I1(v) if *v >= 0 => Ok(*v as Self), + StateValue::I2(v) if *v >= 0 => Ok(*v as Self), + StateValue::I4(v) if *v >= 0 && *v <= u16::MAX as i32 => Ok(*v as Self), + StateValue::Int(v) if *v >= 0 && *v <= u16::MAX as i32 => Ok(*v as Self), + StateValue::Boolean(v) => Ok(*v as Self), + + StateValue::String(s) => s.parse::().map_err(|_| { + StateValueError::TypeError(format!("Cannot parse '{}' as u16", s)) + }), + + _ => Err(StateValueError::TypeError("Cannot cast to u16".into())), + } + } +} + +impl TryFrom for u16 { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + u16::try_from(&value) + } +} + + +impl From for StateValue { + + fn from(value: u16) -> Self { + StateValue::UI2(value) + } +} + diff --git a/pmoupnp/src/variable_types/values_from_u32.rs b/pmoupnp/src/variable_types/values_from_u32.rs new file mode 100644 index 00000000..b94d91d3 --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_u32.rs @@ -0,0 +1,45 @@ +use std::convert::TryFrom; + + +use crate::variable_types::{StateValue, StateValueError}; + +impl TryFrom<&StateValue> for u32 { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + StateValue::UI1(v) => Ok(*v as Self), + StateValue::UI2(v) => Ok(*v as Self), + StateValue::UI4(v) => Ok(*v), + StateValue::I1(v) if *v >= 0 => Ok(*v as Self), + StateValue::I2(v) if *v >= 0 => Ok(*v as Self), + StateValue::I4(v) if *v >= 0 => Ok(*v as Self), + StateValue::Int(v) if *v >= 0 => Ok(*v as Self), + StateValue::Boolean(v) => Ok(*v as Self), + + StateValue::String(s) => s.parse::().map_err(|_| { + StateValueError::TypeError(format!("Cannot parse '{}' as u32", s)) + }), + + _ => Err(StateValueError::TypeError("Cannot cast to u32".into())), + } + } +} + +impl TryFrom for u32 { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + u32::try_from(&value) + } +} + + + +impl From for StateValue { + + fn from(value: u32) -> Self { + StateValue::UI4(value) + } +} + diff --git a/pmoupnp/src/variable_types/values_from_u8.rs b/pmoupnp/src/variable_types/values_from_u8.rs new file mode 100644 index 00000000..8899bde2 --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_u8.rs @@ -0,0 +1,45 @@ +use std::convert::TryFrom; + + +use crate::variable_types::{StateValue, StateValueError}; + +// Implémentations TryFrom pour types numériques + +impl TryFrom<&StateValue> for u8 { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + StateValue::UI1(v) => Ok(*v), + StateValue::UI2(v) if *v <= u8::MAX as u16 => Ok(*v as u8), + StateValue::UI4(v) if *v <= i8::MAX as u32 => Ok(*v as u8), + StateValue::I1(v) if *v >= 0 => Ok(*v as u8), + StateValue::I2(v) if *v >= 0 && *v <= u8::MAX as i16 => Ok(*v as u8), + StateValue::I4(v) if *v >= 0 && *v <= u8::MAX as i32 => Ok(*v as u8), + StateValue::Int(v) if *v >= 0 && *v <= u8::MAX as i32 => Ok(*v as u8), + StateValue::Boolean(v) => Ok(*v as Self), + + StateValue::String(s) => s.parse::().map_err(|_| { + StateValueError::TypeError(format!("Cannot parse '{}' as u8", s)) + }), + + _ => Err(StateValueError::TypeError("Cannot cast to u8".into())), + } + } +} + +impl TryFrom for u8 { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + u8::try_from(&value) + } +} + +impl From for StateValue { + + fn from(value: u8) -> Self { + StateValue::UI1(value) + } +} + diff --git a/pmoupnp/src/variable_types/values_from_uri.rs b/pmoupnp/src/variable_types/values_from_uri.rs new file mode 100644 index 00000000..a717862c --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_uri.rs @@ -0,0 +1,31 @@ +use std::convert::TryFrom; +use url::Url; + +use crate::variable_types::{StateValue, StateValueError}; + +impl TryFrom for Url { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + match value { + // Si déjà un URI encodé comme StateValue::URI + StateValue::URI(v) => Ok(v), + + // Si c'est une String, on tente un parse + StateValue::String(v) => { + Url::parse(&v) + .map_err(|_| StateValueError::TypeError("Invalid URI string".into())) + } + + // Autres types : erreur + _ => Err(StateValueError::TypeError("Cannot cast to Url".into())), + } + } +} + +impl From for StateValue { + + fn from(value: Url) -> Self { + StateValue::URI(value) + } +} diff --git a/pmoupnp/src/variable_types/values_from_uuid.rs b/pmoupnp/src/variable_types/values_from_uuid.rs new file mode 100644 index 00000000..3bf78032 --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_uuid.rs @@ -0,0 +1,31 @@ +use std::convert::TryFrom; +use uuid::Uuid; + +use crate::variable_types::{StateValue, StateValueError}; + +impl TryFrom for Uuid { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + match value { + // Si déjà un URI encodé comme StateValue::URI + StateValue::UUID(v) => Ok(v), + + // Si c'est une String, on tente un parse + StateValue::String(v) => { + Uuid::parse_str(&v) + .map_err(|_| StateValueError::TypeError("Invalid UUID string".into())) + } + + // Autres types : erreur + _ => Err(StateValueError::TypeError("Cannot cast to Uuid".into())), + } + } +} + +impl From for StateValue { + + fn from(value: Uuid) -> Self { + StateValue::UUID(value) + } +} diff --git a/pmoupnp/src/variable_types/values_from_vec_u8.rs b/pmoupnp/src/variable_types/values_from_vec_u8.rs new file mode 100644 index 00000000..776b395d --- /dev/null +++ b/pmoupnp/src/variable_types/values_from_vec_u8.rs @@ -0,0 +1,45 @@ +use std::convert::TryFrom; +use crate::variable_types::{StateValue, StateValueError}; +use base64::{engine::general_purpose::STANDARD, Engine as _}; + +impl TryFrom<&StateValue> for Vec { + type Error = StateValueError; + + fn try_from(value: &StateValue) -> Result { + match value { + // Déjà un vecteur binaire + StateValue::BinBase64(v) => STANDARD.decode(v).map_err( + |e| StateValueError::ParseError(format!("Base64 decode error: {}", e))), + StateValue::BinHex(v) => hex::decode(v).map_err( + |e| StateValueError::ParseError(format!("BinHex decode error: {}", e))), + + // Conversion depuis une chaîne encodée + StateValue::String(s) => { + // Essayer Base64 + if let Ok(bytes) = STANDARD.decode(s) { + return Ok(bytes); + } + // Essayer Hex + if let Ok(bytes) = hex::decode(s) { + return Ok(bytes); + } + Err(StateValueError::ParseError(format!( + "Cannot parse string '{}' as binary", + s + ))) + } + + _ => Err(StateValueError::TypeError( + "Cannot cast to binary Vec".into(), + )), + } + } +} + +impl TryFrom for Vec { + type Error = StateValueError; + + fn try_from(value: StateValue) -> Result { + Vec::::try_from(&value) + } +}