On reformate le code ;-)

This commit is contained in:
2025-10-01 07:42:16 +02:00
parent c2390893b6
commit a23d9bf845
42 changed files with 553 additions and 289 deletions

View File

@@ -1,15 +1,14 @@
mod object_trait;
pub mod variable_types;
pub mod server;
pub mod state_variables;
pub mod value_ranges;
pub mod server;
pub mod variable_types;
pub use crate::object_trait::UpnpObject;
#[derive(Clone)]
pub struct UpnpObjectType {
name: String,
object_type: String,
}
}

View File

@@ -2,12 +2,11 @@ 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 {
fn get_name(&self) -> &String {
return &self.as_upnp_object_type().name;
}
@@ -35,5 +34,4 @@ pub trait UpnpObject {
xml_string
}
}
}

View File

@@ -10,9 +10,12 @@ use std::{
};
use axum::{
extract::{Query, State},
response::{sse::{Event, KeepAlive, Sse}, IntoResponse},
Json,
extract::{Query, State},
response::{
IntoResponse,
sse::{Event, KeepAlive, Sse},
},
};
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast;
@@ -59,8 +62,6 @@ impl LogState {
}
}
/// Query params pour /log-sse
#[derive(Debug, Deserialize)]
pub struct LogQuery {
@@ -85,7 +86,7 @@ pub async fn log_sse(
Query(params): Query<LogQuery>,
) -> impl IntoResponse {
let mut rx = state.subscribe();
// Récupérer l'historique du buffer
let history = state.dump();

View File

@@ -1,6 +1,6 @@
use tracing::field::{Field, Visit};
use tracing::{Event, Subscriber};
use tracing_subscriber::{layer::Context, Layer};
use tracing::field::{Visit, Field};
use tracing_subscriber::{Layer, layer::Context};
use super::{LogEntry, LogState};
use std::time::SystemTime;
@@ -26,7 +26,8 @@ impl Visit for LogVisitor {
if !self.message.is_empty() {
self.message.push(' ');
}
self.message.push_str(&format!("{}={:?}", field.name(), value));
self.message
.push_str(&format!("{}={:?}", field.name(), value));
}
}
}

View File

@@ -10,38 +10,8 @@
//! - ⚛️ **Applications SPA** : Support pour Vue.js/React avec `add_spa()`
//! - 🔀 **Redirections** : Redirigez des routes avec `add_redirect()`
//! - 🎯 **Handlers personnalisés** : Support SSE, WebSocket, etc. avec `add_handler_with_state()`
//! - 📚 **Documentation API** : OpenAPI/Swagger automatique avec `add_openapi()`
//! - ⚡ **Gestion gracieuse** : Arrêt propre sur Ctrl+C
//!
//! ## Exemple d'utilisation
//!
//! ```rust,no_run
//! use pmoupnp::server::{ServerBuilder, Server};
//! use rust_embed::RustEmbed;
//!
//! #[derive(RustEmbed, Clone)]
//! #[folder = "static/"]
//! struct Assets;
//!
//! #[tokio::main]
//! async fn main() {
//! let mut server = ServerBuilder::new("MyAPI", "http://localhost:3000", 3000)
//! .build();
//!
//! // Route JSON simple
//! server.add_route("/api/hello", || async {
//! serde_json::json!({"message": "Hello World"})
//! }).await;
//!
//! // Redirection
//! server.add_redirect("/", "/app").await;
//!
//! // Application Vue.js
//! server.add_spa::<Assets>("/app").await;
//!
//! server.start().await;
//! server.wait().await;
//! }
//! ```
pub mod logs;
@@ -55,13 +25,18 @@ use rust_embed::RustEmbed;
use serde::Serialize;
use std::{net::SocketAddr, sync::Arc};
use tokio::{signal, sync::RwLock, task::JoinHandle};
use tracing::{info,warn,debug,error};
use tracing::{debug, error, info, warn};
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
/// Info serveur sérialisable
#[derive(Clone, Serialize)]
#[derive(Clone, Serialize, utoipa::ToSchema)]
pub struct ServerInfo {
/// Nom du serveur
pub name: String,
/// URL de base
pub base_url: String,
/// Port HTTP
pub http_port: u16,
}
@@ -71,6 +46,7 @@ pub struct Server {
base_url: String,
http_port: u16,
router: Arc<RwLock<Router>>,
api_router: Arc<RwLock<Option<Router>>>,
join_handle: Option<JoinHandle<()>>,
}
@@ -99,6 +75,7 @@ impl Server {
base_url: base_url.into(),
http_port,
router: Arc::new(RwLock::new(Router::new())),
api_router: Arc::new(RwLock::new(None)),
join_handle: None,
}
}
@@ -111,7 +88,6 @@ impl Server {
return Self::new("PMO-Music-Server", url, port);
}
/// Ajoute une route dynamique
/// Ajoute une route JSON dynamique
///
/// Crée un endpoint qui retourne du JSON. La closure fournie sera appelée
@@ -193,9 +169,9 @@ impl Server {
E: RustEmbed + Clone + Send + Sync + 'static,
{
let serve = ServeEmbed::<E>::new();
let mut r = self.router.write().await;
if path == "/" {
*r = std::mem::take(&mut *r).fallback_service(serve);
} else {
@@ -253,9 +229,9 @@ impl Server {
axum_embed::FallbackBehavior::Ok,
Some("index.html".to_string()),
);
let mut r = self.router.write().await;
if path == "/" {
*r = std::mem::take(&mut *r).fallback_service(serve);
} else {
@@ -343,9 +319,7 @@ impl Server {
T: 'static,
S: Clone + Send + Sync + 'static,
{
let route = Router::new()
.route("/", get(handler))
.with_state(state);
let route = Router::new().route("/", get(handler)).with_state(state);
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest(path, route);
@@ -413,6 +387,54 @@ impl Server {
}
}
/// Ajoute une API documentée avec OpenAPI
///
/// Monte un routeur d'API sous `/api` et active Swagger UI sur `/swagger-ui`
///
/// # Arguments
///
/// * `api_router` - Router Axum contenant les routes API
/// * `openapi` - Spécification OpenAPI générée par utoipa
///
/// # Exemple
///
/// ```rust,no_run
/// use utoipa::OpenApi;
/// use axum::{Router, Json, routing::get};
///
/// #[derive(utoipa::OpenApi)]
/// #[openapi(
/// paths(get_users),
/// components(schemas(User))
/// )]
/// struct ApiDoc;
///
/// #[utoipa::path(
/// get,
/// path = "/users",
/// responses((status = 200, description = "List users"))
/// )]
/// async fn get_users() -> Json<Vec<User>> {
/// Json(vec![])
/// }
///
/// let api_router = Router::new()
/// .route("/users", get(get_users));
///
/// server.add_openapi(api_router, ApiDoc::openapi()).await;
/// ```
pub async fn add_openapi(&mut self, api_router: Router, openapi: utoipa::openapi::OpenApi) {
// Stocker le routeur API
let mut api_r = self.api_router.write().await;
*api_r = Some(api_router);
// Ajouter Swagger UI
let swagger = SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", openapi);
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).merge(swagger);
}
/// Démarre le serveur HTTP
///
/// Lance le serveur sur le port configuré et met en place la gestion
@@ -431,7 +453,18 @@ impl Server {
/// ```
pub async fn start(&mut self) {
let addr = SocketAddr::from(([0, 0, 0, 0], self.http_port));
info!("Server {} running at [http://{}:{}](http://{}:{})", self.name, self.base_url, self.http_port, self.base_url, self.http_port);
info!(
"Server {} running at [http://{}:{}](http://{}:{})",
self.name, self.base_url, self.http_port, self.base_url, self.http_port
);
// Merger le routeur API si présent
let api_router = self.api_router.read().await;
if let Some(api_r) = api_router.as_ref() {
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest("/api", api_r.clone());
}
drop(api_router);
let router = self.router.clone();
@@ -499,7 +532,7 @@ impl ServerBuilder {
Self {
name: "PMO-Music-Server".to_string(),
base_url: config.get_base_url(),
http_port: config.get_http_port()
http_port: config.get_http_port(),
}
}

View File

@@ -4,25 +4,25 @@ use thiserror::Error;
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),
}

View File

@@ -2,10 +2,9 @@ use chrono::{DateTime, Utc};
use xmltree::Element;
use crate::{
state_variables::{StateVarInstance, StateVariable, UpnpVariable},
variable_types::StateValue,
UpnpObject,
UpnpObjectType
UpnpObject, UpnpObjectType,
state_variables::{StateVarInstance, StateVariable, UpnpVariable},
variable_types::StateValue,
};
impl UpnpVariable for StateVarInstance {
@@ -35,7 +34,7 @@ impl StateVarInstance {
value: from.get_default(),
old_value: from.get_default(),
last_modified: Utc::now(),
last_notification: Utc::now()
last_notification: Utc::now(),
}
}
@@ -54,5 +53,4 @@ impl StateVarInstance {
pub fn last_modified(&self) -> DateTime<Utc> {
self.last_modified
}
}

View File

@@ -1,26 +1,33 @@
mod errors;
mod variable_trait;
mod variable_methods;
mod instance_methods;
mod variable_methods;
mod variable_trait;
use std::{collections::HashMap, sync::{Arc, RwLock}};
use std::{
collections::HashMap,
sync::{Arc, RwLock},
};
pub use crate::state_variables::variable_trait::UpnpVariable;
use chrono::{DateTime, Utc};
pub use errors::{StateVariableError};
pub use crate::state_variables::variable_trait::UpnpVariable;
pub use errors::StateVariableError;
use crate::{value_ranges::ValueRange, variable_types::{StateValue, StateVarType}, UpnpObjectType};
use crate::{
UpnpObjectType,
value_ranges::ValueRange,
variable_types::{StateValue, StateVarType},
};
/// Type pour les fonctions de condition d'événement
pub type StateConditionFunc = Arc<dyn Fn(&StateVarInstance) -> bool + Send + Sync>;
/// Type pour les fonctions de parsing de valeurs depuis des chaînes
pub type StringValueParser = Arc<dyn Fn(&str) -> Result<StateValue, StateVariableError> + Send + Sync>;
pub type StringValueParser =
Arc<dyn Fn(&str) -> Result<StateValue, StateVariableError> + Send + Sync>;
/// Type pour les fonctions de sérialisation de valeurs vers des chaînes
pub type ValueSerializer = Arc<dyn Fn(&StateValue) -> Result<String, StateVariableError> + Send + Sync>;
pub type ValueSerializer =
Arc<dyn Fn(&StateValue) -> Result<String, StateVariableError> + Send + Sync>;
pub struct StateVariable {
object: UpnpObjectType,
@@ -44,4 +51,4 @@ pub struct StateVarInstance {
old_value: StateValue,
last_modified: DateTime<Utc>,
last_notification: DateTime<Utc>,
}
}

View File

@@ -20,7 +20,7 @@ impl UpnpObject for StateVariable {
return &self.object;
}
fn to_xml_element(&self) -> Element {
fn to_xml_element(&self) -> Element {
// Création de l'élément racine <stateVariable>
let mut root = Element::new("stateVariable");
root.attributes.insert(
@@ -65,11 +65,15 @@ fn to_xml_element(&self) -> Element {
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()));
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()));
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 {

View File

@@ -49,7 +49,7 @@ pub trait UpnpVariable {
.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;
}

View File

@@ -1,10 +1,9 @@
mod methods;
use crate::variable_types::StateValue;
#[derive(Debug, Clone)]
pub struct ValueRange {
min: StateValue,
max: StateValue,
}
}

View File

@@ -1,9 +1,8 @@
use crate::variable_types::{StateValue, StateVarType, StateValueError};
use crate::variable_types::{StateValue, StateValueError, StateVarType};
use std::convert::TryFrom;
impl StateValue {
pub fn try_cast(&self, target: StateVarType) -> Result<StateValue, StateValueError> {
let source = StateVarType::from(self);
// Identité (même type)
@@ -14,18 +13,18 @@ impl StateValue {
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::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)?)),
(_, 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!(

View File

@@ -24,20 +24,22 @@ impl StateVarType {
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::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::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()),
}
}
}
}

View File

@@ -2,7 +2,6 @@ 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 {

View File

@@ -1,10 +1,9 @@
use std::fmt;
use base64::engine::general_purpose;
use base64::Engine;
use base64::engine::general_purpose;
use std::fmt;
use crate::variable_types::StateValue;
impl fmt::Display for StateValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {

View File

@@ -4,25 +4,25 @@ use thiserror::Error;
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),
}

View File

@@ -1,6 +1,5 @@
use std::str::FromStr;
use crate::variable_types::StateVarType;
use std::str::FromStr;
impl FromStr for StateVarType {
type Err = String; // Type d'erreur personnalisé
@@ -30,7 +29,7 @@ impl FromStr for StateVarType {
"time.tz" => Ok(StateVarType::TimeTZ),
"uuid" => Ok(StateVarType::UUID),
"uri" => Ok(StateVarType::URI),
_ => Err(format!("Type inconnu: {}", s)),
_ => Err(format!("Type inconnu: {}", s)),
}
}
}

View File

@@ -1,36 +1,36 @@
mod type_methods;
mod value_methods;
mod type_trait;
mod value_trait;
mod fromstr;
mod cast;
mod default_value;
mod display_type;
mod display_value;
mod default_value;
mod errors;
mod fromstr;
mod type_methods;
mod type_trait;
mod value_methods;
mod value_trait;
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_i8;
mod values_from_u16;
mod values_from_u32;
mod values_from_u8;
mod values_from_f32;
mod values_from_f64;
mod values_from_uri;
mod values_from_uuid;
mod values_from_datetime;
mod values_from_naivedate;
mod values_from_naivedatetime;
mod values_from_naivetime;
mod values_from_datetime;
mod values_from_uri;
mod values_from_uuid;
mod values_from_vec_u8;
use std::fmt::{Debug};
use std::fmt::Debug;
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime};
use url::Url;
@@ -92,5 +92,3 @@ pub enum StateValue {
UUID(Uuid),
URI(Url),
}

View File

@@ -1,8 +1,8 @@
use crate::variable_types::{type_trait::UpnpVarType, StateVarType};
use crate::variable_types::{StateVarType, type_trait::UpnpVarType};
impl UpnpVarType for StateVarType {
fn as_state_var_type(&self) -> StateVarType {
*self
*self
}
fn bit_size(&self) -> Option<usize> {
@@ -62,10 +62,7 @@ impl UpnpVarType for StateVarType {
fn is_float(&self) -> bool {
matches!(
self,
StateVarType::R4
| StateVarType::R8
| StateVarType::Number
| StateVarType::Fixed14_4
StateVarType::R4 | StateVarType::R8 | StateVarType::Number | StateVarType::Fixed14_4
)
}
@@ -74,10 +71,13 @@ impl UpnpVarType for StateVarType {
}
fn is_string(&self) -> bool {
matches!(self, StateVarType::String
| StateVarType::Char
| StateVarType::BinHex
| StateVarType::BinBase64)
matches!(
self,
StateVarType::String
| StateVarType::Char
| StateVarType::BinHex
| StateVarType::BinBase64
)
}
fn is_time(&self) -> bool {

View File

@@ -2,7 +2,6 @@ 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

View File

@@ -1,2 +1 @@
pub trait UpnpValue {
}
pub trait UpnpValue {}

View File

@@ -1,6 +1,6 @@
use std::convert::TryFrom;
use chrono::{DateTime,FixedOffset};
use crate::variable_types::{StateValue, StateValueError};
use chrono::{DateTime, FixedOffset};
use std::convert::TryFrom;
impl TryFrom<&StateValue> for DateTime<FixedOffset> {
type Error = StateValueError;
@@ -9,9 +9,15 @@ impl TryFrom<&StateValue> for DateTime<FixedOffset> {
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<FixedOffset>".into())),
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<FixedOffset>".into(),
)),
}
}
}
@@ -25,9 +31,7 @@ impl TryFrom<StateValue> for DateTime<FixedOffset> {
}
impl From<DateTime<FixedOffset>> for StateValue {
fn from(value: DateTime<FixedOffset>) -> Self {
StateValue::DateTimeTZ(value)
}
}

View File

@@ -2,7 +2,6 @@ use std::convert::TryFrom;
use crate::variable_types::{StateValue, StateValueError};
impl TryFrom<&StateValue> for f32 {
type Error = StateValueError;
@@ -12,12 +11,8 @@ impl TryFrom<&StateValue> for f32 {
// --- 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),
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),
@@ -30,37 +25,38 @@ impl TryFrom<&StateValue> for f32 {
// --- 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),
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),
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),
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::<f32>().map_err(|_| {
StateValueError::TypeError(format!("Cannot parse '{}' as f32", s))
}),
StateValue::String(s) => s
.parse::<f32>()
.map_err(|_| StateValueError::TypeError(format!("Cannot parse '{}' as f32", s))),
// --- Par défaut : erreur ---
_ => Err(StateValueError::TypeError(
"Cannot cast to f32".into(),
)),
_ => Err(StateValueError::TypeError("Cannot cast to f32".into())),
}
}
}
@@ -74,9 +70,7 @@ impl TryFrom<StateValue> for f32 {
}
impl From<f32> for StateValue {
fn from(value: f32) -> Self {
StateValue::R4(value)
}
}

View File

@@ -26,14 +26,12 @@ impl TryFrom<&StateValue> for f64 {
StateValue::Boolean(v) => Ok((*v as i32) as Self),
StateValue::String(s) => s.parse::<f64>().map_err(|_| {
StateValueError::TypeError(format!("Cannot parse '{}' as f64", s))
}),
StateValue::String(s) => s
.parse::<f64>()
.map_err(|_| StateValueError::TypeError(format!("Cannot parse '{}' as f64", s))),
// --- Par défaut : erreur ---
_ => Err(StateValueError::TypeError(
"Cannot cast to f64".into(),
)),
_ => Err(StateValueError::TypeError("Cannot cast to f64".into())),
}
}
}

View File

@@ -1,6 +1,5 @@
use std::convert::TryFrom;
use crate::variable_types::{StateValue, StateValueError};
// Implémentations TryFrom<StateValue> pour types numériques
@@ -12,17 +11,17 @@ impl TryFrom<&StateValue> for i16 {
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::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::<i16>().map_err(|_| {
StateValueError::TypeError(format!("Cannot parse '{}' as i16", s))
}),
StateValue::String(s) => s
.parse::<i16>()
.map_err(|_| StateValueError::TypeError(format!("Cannot parse '{}' as i16", s))),
_ => Err(StateValueError::TypeError("Cannot cast to i32".into())),
}
@@ -38,9 +37,7 @@ impl TryFrom<StateValue> for i16 {
}
impl From<i16> for StateValue {
fn from(value: i16) -> Self {
StateValue::I2(value)
}
}

View File

@@ -2,7 +2,6 @@ use std::convert::TryFrom;
use crate::variable_types::{StateValue, StateValueError};
impl TryFrom<&StateValue> for i32 {
type Error = StateValueError;
@@ -17,12 +16,12 @@ impl TryFrom<&StateValue> for i32 {
// 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::UI4(v) if *v <= i32::MAX as u32 => Ok(*v as i32),
StateValue::Boolean(v) => Ok(*v as Self),
StateValue::String(s) => s.parse::<i32>().map_err(|_| {
StateValueError::TypeError(format!("Cannot parse '{}' as i32", s))
}),
StateValue::String(s) => s
.parse::<i32>()
.map_err(|_| StateValueError::TypeError(format!("Cannot parse '{}' as i32", s))),
_ => Err(StateValueError::TypeError("Cannot cast to i32".into())),
}
@@ -38,9 +37,7 @@ impl TryFrom<StateValue> for i32 {
}
impl From<i32> for StateValue {
fn from(value: i32) -> Self {
StateValue::I4(value)
}
}

View File

@@ -1,5 +1,5 @@
use std::convert::TryFrom;
use crate::variable_types::{StateValue, StateValueError};
use std::convert::TryFrom;
impl TryFrom<&StateValue> for i64 {
type Error = StateValueError;
@@ -21,9 +21,9 @@ impl TryFrom<&StateValue> for i64 {
StateValue::Boolean(v) => Ok(*v as i64),
// chaîne → i64
StateValue::String(s) => s.parse::<i64>().map_err(|_| {
StateValueError::TypeError(format!("Cannot parse '{}' as i64", s))
}),
StateValue::String(s) => s
.parse::<i64>()
.map_err(|_| StateValueError::TypeError(format!("Cannot parse '{}' as i64", s))),
_ => Err(StateValueError::TypeError("Cannot cast to i64".into())),
}

View File

@@ -1,6 +1,5 @@
use std::convert::TryFrom;
use crate::variable_types::{StateValue, StateValueError};
// Implémentations TryFrom<StateValue> pour types numériques
@@ -10,19 +9,19 @@ impl TryFrom<&StateValue> for i8 {
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
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::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::<i8>().map_err(|_| {
StateValueError::TypeError(format!("Cannot parse '{}' as i8", s))
}),
StateValue::String(s) => s
.parse::<i8>()
.map_err(|_| StateValueError::TypeError(format!("Cannot parse '{}' as i8", s))),
_ => Err(StateValueError::TypeError("Cannot cast to i8".into())),
}
@@ -38,9 +37,7 @@ impl TryFrom<StateValue> for i8 {
}
impl From<i8> for StateValue {
fn from(value: i8) -> Self {
StateValue::I1(value)
}
}

View File

@@ -1,6 +1,6 @@
use std::convert::TryFrom;
use chrono::NaiveDate;
use crate::variable_types::{StateValue, StateValueError};
use chrono::NaiveDate;
use std::convert::TryFrom;
impl TryFrom<&StateValue> for NaiveDate {
type Error = StateValueError;
@@ -8,9 +8,12 @@ impl TryFrom<&StateValue> for NaiveDate {
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
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())),
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(),
)),
}
}
}
@@ -24,9 +27,7 @@ impl TryFrom<StateValue> for NaiveDate {
}
impl From<NaiveDate> for StateValue {
fn from(value: NaiveDate) -> Self {
StateValue::Date(value)
}
}

View File

@@ -1,6 +1,6 @@
use std::convert::TryFrom;
use chrono::NaiveDateTime;
use crate::variable_types::{StateValue, StateValueError};
use chrono::NaiveDateTime;
use std::convert::TryFrom;
impl TryFrom<&StateValue> for NaiveDateTime {
type Error = StateValueError;
@@ -9,8 +9,15 @@ impl TryFrom<&StateValue> for NaiveDateTime {
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())),
.map_err(|e| {
StateValueError::ParseError(format!(
"Cannot parse DateTime from string '{}': {}",
v, e
))
}),
_ => Err(StateValueError::TypeError(
"Cannot cast to NaiveDateTime".into(),
)),
}
}
}
@@ -24,7 +31,6 @@ impl TryFrom<StateValue> for NaiveDateTime {
}
impl From<NaiveDateTime> for StateValue {
fn from(value: NaiveDateTime) -> Self {
StateValue::DateTime(value)
}

View File

@@ -1,6 +1,6 @@
use std::convert::TryFrom;
use chrono::NaiveTime;
use crate::variable_types::{StateValue, StateValueError};
use chrono::NaiveTime;
use std::convert::TryFrom;
impl TryFrom<&StateValue> for NaiveTime {
type Error = StateValueError;
@@ -8,9 +8,12 @@ impl TryFrom<&StateValue> for NaiveTime {
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
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())),
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(),
)),
}
}
}
@@ -24,7 +27,6 @@ impl TryFrom<StateValue> for NaiveTime {
}
impl From<NaiveTime> for StateValue {
fn from(value: NaiveTime) -> Self {
StateValue::Time(value)
}

View File

@@ -1,7 +1,5 @@
use std::convert::TryFrom;
use crate::variable_types::{StateValue, StateValueError};
// Implémentations TryFrom<StateValue> pour types numériques
@@ -13,16 +11,16 @@ impl TryFrom<&StateValue> for u16 {
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::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::<u16>().map_err(|_| {
StateValueError::TypeError(format!("Cannot parse '{}' as u16", s))
}),
StateValue::String(s) => s
.parse::<u16>()
.map_err(|_| StateValueError::TypeError(format!("Cannot parse '{}' as u16", s))),
_ => Err(StateValueError::TypeError("Cannot cast to u16".into())),
}
@@ -37,11 +35,8 @@ impl TryFrom<StateValue> for u16 {
}
}
impl From<u16> for StateValue {
fn from(value: u16) -> Self {
StateValue::UI2(value)
}
}

View File

@@ -1,6 +1,5 @@
use std::convert::TryFrom;
use crate::variable_types::{StateValue, StateValueError};
impl TryFrom<&StateValue> for u32 {
@@ -10,16 +9,16 @@ impl TryFrom<&StateValue> for u32 {
match value {
StateValue::UI1(v) => Ok(*v as Self),
StateValue::UI2(v) => Ok(*v as Self),
StateValue::UI4(v) => Ok(*v),
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::<u32>().map_err(|_| {
StateValueError::TypeError(format!("Cannot parse '{}' as u32", s))
}),
StateValue::String(s) => s
.parse::<u32>()
.map_err(|_| StateValueError::TypeError(format!("Cannot parse '{}' as u32", s))),
_ => Err(StateValueError::TypeError("Cannot cast to u32".into())),
}
@@ -34,12 +33,8 @@ impl TryFrom<StateValue> for u32 {
}
}
impl From<u32> for StateValue {
fn from(value: u32) -> Self {
StateValue::UI4(value)
}
}

View File

@@ -1,6 +1,5 @@
use std::convert::TryFrom;
use crate::variable_types::{StateValue, StateValueError};
// Implémentations TryFrom<StateValue> pour types numériques
@@ -12,16 +11,16 @@ impl TryFrom<&StateValue> for u8 {
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::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::<u8>().map_err(|_| {
StateValueError::TypeError(format!("Cannot parse '{}' as u8", s))
}),
StateValue::String(s) => s
.parse::<u8>()
.map_err(|_| StateValueError::TypeError(format!("Cannot parse '{}' as u8", s))),
_ => Err(StateValueError::TypeError("Cannot cast to u8".into())),
}
@@ -37,9 +36,7 @@ impl TryFrom<StateValue> for u8 {
}
impl From<u8> for StateValue {
fn from(value: u8) -> Self {
StateValue::UI1(value)
}
}

View File

@@ -13,8 +13,7 @@ impl TryFrom<StateValue> for Url {
// Si c'est une String, on tente un parse
StateValue::String(v) => {
Url::parse(&v)
.map_err(|_| StateValueError::TypeError("Invalid URI string".into()))
Url::parse(&v).map_err(|_| StateValueError::TypeError("Invalid URI string".into()))
}
// Autres types : erreur
@@ -24,7 +23,6 @@ impl TryFrom<StateValue> for Url {
}
impl From<Url> for StateValue {
fn from(value: Url) -> Self {
StateValue::URI(value)
}

View File

@@ -12,10 +12,8 @@ impl TryFrom<StateValue> for Uuid {
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()))
}
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())),
@@ -24,7 +22,6 @@ impl TryFrom<StateValue> for Uuid {
}
impl From<Uuid> for StateValue {
fn from(value: Uuid) -> Self {
StateValue::UUID(value)
}

View File

@@ -1,6 +1,6 @@
use std::convert::TryFrom;
use crate::variable_types::{StateValue, StateValueError};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use base64::{Engine as _, engine::general_purpose::STANDARD};
use std::convert::TryFrom;
impl TryFrom<&StateValue> for Vec<u8> {
type Error = StateValueError;
@@ -8,10 +8,11 @@ impl TryFrom<&StateValue> for Vec<u8> {
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
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))),
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) => {