On reformate le code ;-)

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

245
Cargo.lock generated
View File

@@ -54,6 +54,15 @@ version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "async-stream"
version = "0.3.6"
@@ -300,6 +309,15 @@ dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -310,6 +328,52 @@ dependencies = [
"typenum",
]
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -364,6 +428,17 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
[[package]]
name = "flate2"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
dependencies = [
"crc32fast",
"libz-rs-sys",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
@@ -770,6 +845,12 @@ dependencies = [
"zerovec",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.1.0"
@@ -799,6 +880,8 @@ checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
dependencies = [
"equivalent",
"hashbrown",
"serde",
"serde_core",
]
[[package]]
@@ -850,6 +933,15 @@ dependencies = [
"libc",
]
[[package]]
name = "libz-rs-sys"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd"
dependencies = [
"zlib-rs",
]
[[package]]
name = "litemap"
version = "0.8.0"
@@ -1052,7 +1144,10 @@ dependencies = [
"tracing",
"tracing-subscriber",
"url",
"utoipa",
"utoipa-swagger-ui",
"uuid",
"validator",
"xmltree",
]
@@ -1072,6 +1167,28 @@ dependencies = [
"zerovec",
]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.101"
@@ -1116,6 +1233,18 @@ dependencies = [
"thiserror",
]
[[package]]
name = "regex"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.11"
@@ -1314,6 +1443,12 @@ dependencies = [
"libc",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "slab"
version = "0.4.11"
@@ -1342,6 +1477,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.106"
@@ -1595,6 +1736,48 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utoipa"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993"
dependencies = [
"indexmap",
"serde",
"serde_json",
"utoipa-gen",
]
[[package]]
name = "utoipa-gen"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn",
]
[[package]]
name = "utoipa-swagger-ui"
version = "9.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55"
dependencies = [
"axum",
"base64",
"mime_guess",
"regex",
"rust-embed",
"serde",
"serde_json",
"url",
"utoipa",
"zip",
]
[[package]]
name = "uuid"
version = "1.18.1"
@@ -1606,6 +1789,36 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "validator"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa"
dependencies = [
"idna",
"once_cell",
"regex",
"serde",
"serde_derive",
"serde_json",
"url",
"validator_derive",
]
[[package]]
name = "validator_derive"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca"
dependencies = [
"darling",
"once_cell",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "valuable"
version = "0.1.1"
@@ -1980,3 +2193,35 @@ dependencies = [
"quote",
"syn",
]
[[package]]
name = "zip"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308"
dependencies = [
"arbitrary",
"crc32fast",
"flate2",
"indexmap",
"memchr",
"zopfli",
]
[[package]]
name = "zlib-rs"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2"
[[package]]
name = "zopfli"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
dependencies = [
"bumpalo",
"crc32fast",
"log",
"simd-adler32",
]

View File

@@ -16,7 +16,7 @@ const DEFAULT_CONFIG: &str = include_str!("pmomusic.yaml");
lazy_static! {
static ref CONFIG: Arc<Config> =
Arc::new(Config::load_config("").expect("Failed to load PMOMusic configuration")) ;
Arc::new(Config::load_config("").expect("Failed to load PMOMusic configuration"));
}
const ENV_CONFIG_FILE: &str = "PMOMUSIC_CONFIG";

View File

@@ -31,3 +31,6 @@ axum-server = "0.7.2"
axum-embed = "0.1.0"
rust-embed = "8.7.2"
anyhow = "1.0"
utoipa = { version = "5.4.0", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
validator = { version = "0.20.0", features = ["derive"] }

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) => {

View File

@@ -1,5 +1,5 @@
use std::net::UdpSocket;
use get_if_addrs::get_if_addrs;
use std::net::UdpSocket;
pub fn guess_local_ip() -> String {
// On tente de deviner l'IP locale
@@ -17,7 +17,6 @@ pub fn guess_local_ip() -> String {
}
}
fn list_all_ips() -> std::collections::HashMap<String, Vec<String>> {
let mut result = std::collections::HashMap::new();
@@ -28,13 +27,17 @@ fn list_all_ips() -> std::collections::HashMap<String, Vec<String>> {
continue;
}
if ip.is_ipv4() {
result.entry(iface.name)
.or_insert_with(Vec::new)
.push(ip.to_string());
result
.entry(iface.name)
.or_insert_with(Vec::new)
.push(ip.to_string());
}
}
} else {
result.insert("error".to_string(), vec!["Failed to get interfaces".to_string()]);
result.insert(
"error".to_string(),
vec!["Failed to get interfaces".to_string()],
);
}
result

View File

@@ -1,3 +1,3 @@
mod ip_utils;
pub use ip_utils::guess_local_ip;
pub use ip_utils::guess_local_ip;