travail sur les actions notion de service stateless
This commit is contained in:
@@ -41,42 +41,39 @@
|
|||||||
|
|
||||||
use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc};
|
use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc};
|
||||||
|
|
||||||
use crate::variable_types::StateValue;
|
use bevy_reflect::Reflect;
|
||||||
|
|
||||||
/// Données d'une action UPnP.
|
/// Données d'une action UPnP (entrée/sortie unifiées).
|
||||||
///
|
///
|
||||||
/// Représente un ensemble de paramètres clé-valeur pour une action UPnP,
|
/// Représente un ensemble de paramètres clé-valeur pour une action UPnP,
|
||||||
/// partagé via `Arc` pour permettre un clonage efficace.
|
/// utilisant des valeurs Reflect pour la flexibilité de typage.
|
||||||
///
|
///
|
||||||
/// # Structure
|
/// # Structure
|
||||||
///
|
///
|
||||||
/// - **Clé** : Nom du paramètre (ex: "InstanceID", "TransportURI")
|
/// - **Clé** : Nom du paramètre (ex: "InstanceID", "TransportURI")
|
||||||
/// - **Valeur** : Valeur typée du paramètre ([`StateValue`])
|
/// - **Valeur** : Valeur dynamique via `Box<dyn Reflect>`
|
||||||
///
|
///
|
||||||
/// # Exemples
|
/// # Exemples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use pmoupnp::actions::ActionData;
|
/// use pmoupnp::actions::ActionData;
|
||||||
/// use pmoupnp::variable_types::StateValue;
|
|
||||||
/// use std::collections::HashMap;
|
/// use std::collections::HashMap;
|
||||||
/// use std::sync::Arc;
|
/// use bevy_reflect::Reflect;
|
||||||
///
|
///
|
||||||
/// let mut data = HashMap::new();
|
/// let mut data: ActionData = HashMap::new();
|
||||||
/// data.insert("InstanceID".to_string(), StateValue::UI4(0));
|
/// data.insert("InstanceID".to_string(), Box::new(0u32));
|
||||||
/// data.insert("Speed".to_string(), StateValue::String("1".to_string()));
|
/// data.insert("Speed".to_string(), Box::new("1".to_string()));
|
||||||
///
|
///
|
||||||
/// let action_data: ActionData = Arc::new(data);
|
/// // Les valeurs peuvent être modifiées
|
||||||
///
|
/// data.insert("InstanceID".to_string(), Box::new(1u32));
|
||||||
/// // Le Arc permet un clonage efficace
|
|
||||||
/// let cloned = action_data.clone();
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Notes
|
/// # Notes
|
||||||
///
|
///
|
||||||
/// - Utilise `Arc` pour éviter les copies coûteuses
|
/// - Utilise `Box<dyn Reflect>` pour la flexibilité de typage
|
||||||
/// - Thread-safe grâce à `Arc`
|
/// - Même type pour les entrées et sorties du handler
|
||||||
/// - Les valeurs sont immuables une fois créées
|
/// - Le handler peut modifier directement les données
|
||||||
pub type ActionData = Arc<HashMap<String, StateValue>>;
|
pub type ActionData = HashMap<String, Box<dyn Reflect>>;
|
||||||
|
|
||||||
/// Future retourné par un [`ActionHandler`].
|
/// Future retourné par un [`ActionHandler`].
|
||||||
///
|
///
|
||||||
@@ -87,53 +84,54 @@ pub type ActionData = Arc<HashMap<String, StateValue>>;
|
|||||||
/// # Type complet
|
/// # Type complet
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// Pin<Box<dyn Future<Output = Result<(), ActionError>> + Send>>
|
/// Pin<Box<dyn Future<Output = Result<ActionData, ActionError>> + Send>>
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Composants
|
/// # Composants
|
||||||
///
|
///
|
||||||
/// - `Pin<Box<...>>` : Permet de déplacer le future en mémoire sans invalidation
|
/// - `Pin<Box<...>>` : Permet de déplacer le future en mémoire sans invalidation
|
||||||
/// - `dyn Future<Output = Result<(), ActionError>>` : Future retournant un Result
|
/// - `dyn Future<Output = Result<ActionData, ActionError>>` : Future retournant un Result avec les données modifiées
|
||||||
/// - `+ Send` : Le future peut être envoyé entre threads
|
/// - `+ Send` : Le future peut être envoyé entre threads
|
||||||
///
|
///
|
||||||
/// # Notes
|
/// # Notes
|
||||||
///
|
///
|
||||||
/// - Les handlers retournent `Ok(())` en cas de succès ou `Err(ActionError)` en cas d'erreur
|
/// - Les handlers retournent `Ok(ActionData)` en cas de succès ou `Err(ActionError)` en cas d'erreur
|
||||||
/// - Ils modifient les variables d'instance et [`ActionInstance::run()`](crate::actions::ActionInstance::run)
|
/// - Le handler retourne les données modifiées (ActionData unifié pour entrée/sortie)
|
||||||
/// collecte automatiquement les valeurs OUT si le handler réussit
|
|
||||||
/// - Rarement utilisé directement (la macro `action_handler!` s'en charge)
|
/// - Rarement utilisé directement (la macro `action_handler!` s'en charge)
|
||||||
/// - Nécessaire pour la compatibilité avec les trait objects
|
/// - Nécessaire pour la compatibilité avec les trait objects
|
||||||
pub type ActionFuture = Pin<Box<dyn Future<Output = Result<(), crate::actions::ActionError>> + Send>>;
|
pub type ActionFuture = Pin<Box<dyn Future<Output = Result<ActionData, crate::actions::ActionError>> + Send>>;
|
||||||
|
|
||||||
/// Handler d'action UPnP asynchrone.
|
/// Handler d'action UPnP asynchrone.
|
||||||
///
|
///
|
||||||
/// Un `ActionHandler` est une fonction asynchrone partageable qui exécute
|
/// Un `ActionHandler` est une fonction asynchrone partageable qui exécute
|
||||||
/// la logique métier d'une action sans retourner de valeur.
|
/// la logique métier d'une action et retourne les données modifiées.
|
||||||
///
|
///
|
||||||
/// # Signature
|
/// # Signature
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// Fn(Arc<ActionInstance>) -> ActionFuture
|
/// Fn(ActionData) -> ActionFuture
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Prend :
|
/// Prend :
|
||||||
/// - [`Arc<ActionInstance>`](crate::actions::ActionInstance) : L'instance de l'action avec accès aux variables liées
|
/// - [`ActionData`] : HashMap contenant les valeurs des arguments (Box<dyn Reflect>)
|
||||||
/// qui contiennent déjà les valeurs des arguments IN
|
|
||||||
///
|
///
|
||||||
/// Retourne un [`ActionFuture`] qui se résout en `Result<(), ActionError>`.
|
/// Retourne un [`ActionFuture`] qui se résout en `Result<ActionData, ActionError>`.
|
||||||
///
|
///
|
||||||
/// # Responsabilités
|
/// # Responsabilités
|
||||||
///
|
///
|
||||||
/// Le handler est responsable de :
|
/// Le handler est responsable de :
|
||||||
/// - Lire les arguments d'entrée depuis les variables liées à l'instance
|
/// - Lire les arguments d'entrée depuis ActionData
|
||||||
/// - Exécuter la logique métier
|
/// - Exécuter la logique métier
|
||||||
/// - Modifier les variables d'instance selon les besoins
|
/// - Modifier les données selon les besoins
|
||||||
/// - Retourner `Ok(())` en cas de succès ou `Err(ActionError)` en cas d'erreur
|
/// - Retourner `Ok(ActionData)` avec les données modifiées ou `Err(ActionError)` en cas d'erreur
|
||||||
///
|
///
|
||||||
/// La méthode [`ActionInstance::run()`](crate::actions::ActionInstance::run) s'occupe
|
/// La méthode [`ActionInstance::run()`](crate::actions::ActionInstance::run) s'occupe
|
||||||
/// automatiquement de :
|
/// automatiquement de :
|
||||||
/// 1. Stocker les valeurs IN dans les variables liées avant d'appeler le handler
|
/// 1. Construire ActionData depuis les StateVarInstance
|
||||||
/// 2. Collecter les valeurs OUT si le handler retourne `Ok(())`
|
/// 2. Merger les valeurs IN du SOAP
|
||||||
|
/// 3. (Si stateful) Sauver les IN dans les StateVarInstance avant le handler
|
||||||
|
/// 4. Exécuter le handler
|
||||||
|
/// 5. (Si stateful) Sauver les OUT dans les StateVarInstance après le handler
|
||||||
///
|
///
|
||||||
/// # Traits requis
|
/// # Traits requis
|
||||||
///
|
///
|
||||||
@@ -149,34 +147,34 @@ pub type ActionFuture = Pin<Box<dyn Future<Output = Result<(), crate::actions::A
|
|||||||
/// use pmoupnp::action_handler;
|
/// use pmoupnp::action_handler;
|
||||||
/// use pmoupnp::actions::ActionError;
|
/// use pmoupnp::actions::ActionError;
|
||||||
///
|
///
|
||||||
/// let handler = action_handler!(|instance| {
|
/// let handler = action_handler!(|data| {
|
||||||
/// // Logique métier - les valeurs IN sont déjà dans les variables
|
/// // Logique métier avec ActionData
|
||||||
/// Ok::<(), ActionError>(())
|
/// Ok(data)
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Manuellement
|
/// ## Manuellement
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use pmoupnp::actions::{ActionHandler, ActionInstance, ActionError};
|
/// use pmoupnp::actions::{ActionHandler, ActionData, ActionError};
|
||||||
/// use std::sync::Arc;
|
/// use std::sync::Arc;
|
||||||
///
|
///
|
||||||
/// let handler: ActionHandler = Arc::new(|instance| {
|
/// let handler: ActionHandler = Arc::new(|data| {
|
||||||
/// Box::pin(async move {
|
/// Box::pin(async move {
|
||||||
/// // Votre logique async
|
/// // Votre logique async
|
||||||
/// Ok::<(), ActionError>(())
|
/// Ok(data)
|
||||||
/// })
|
/// })
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Notes d'implémentation
|
/// # Notes d'implémentation
|
||||||
///
|
///
|
||||||
/// - Le handler ne retourne rien - il modifie les variables d'instance
|
/// - Le handler reçoit et retourne ActionData (type unifié entrée/sortie)
|
||||||
/// - [`ActionInstance::run()`](crate::actions::ActionInstance::run) collecte automatiquement les OUT
|
/// - Le handler peut modifier directement les données reçues
|
||||||
/// - Le handler capture les variables par `move`
|
/// - Le handler capture les variables par `move`
|
||||||
/// - Le future est automatiquement `Send` si les captures le sont
|
/// - Le future est automatiquement `Send` si les captures le sont
|
||||||
/// - Utilisez la macro `action_handler!` pour simplifier la création
|
/// - Utilisez la macro `action_handler!` pour simplifier la création
|
||||||
pub type ActionHandler = Arc<dyn Fn(Arc<crate::actions::ActionInstance>) -> ActionFuture + Send + Sync>;
|
pub type ActionHandler = Arc<dyn Fn(ActionData) -> ActionFuture + Send + Sync>;
|
||||||
|
|
||||||
/// Macro pour créer facilement un ActionHandler.
|
/// Macro pour créer facilement un ActionHandler.
|
||||||
///
|
///
|
||||||
@@ -186,16 +184,16 @@ pub type ActionHandler = Arc<dyn Fn(Arc<crate::actions::ActionInstance>) -> Acti
|
|||||||
/// # Syntaxe
|
/// # Syntaxe
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// action_handler!(|instance| {
|
/// action_handler!(|data| {
|
||||||
/// // votre logique async (automatiquement dans un bloc async move)
|
/// // votre logique async avec ActionData
|
||||||
/// // Les valeurs IN sont déjà disponibles dans les variables liées
|
/// // Modifier les données et les retourner
|
||||||
|
/// Ok(data)
|
||||||
/// })
|
/// })
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// - `instance` : Paramètre de type `Arc<`[`ActionInstance`](crate::actions::ActionInstance)`>` - L'instance de l'action
|
/// - `data` : Paramètre de type [`ActionData`] - HashMap contenant les valeurs des arguments
|
||||||
/// avec les valeurs IN déjà stockées dans les variables liées
|
|
||||||
/// - Le corps du bloc peut contenir du code asynchrone (`.await`)
|
/// - Le corps du bloc peut contenir du code asynchrone (`.await`)
|
||||||
///
|
///
|
||||||
/// # Type de retour
|
/// # Type de retour
|
||||||
@@ -204,108 +202,87 @@ pub type ActionHandler = Arc<dyn Fn(Arc<crate::actions::ActionInstance>) -> Acti
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ## Exemple 1 : Handler simple (ne fait rien)
|
/// ## Exemple 1 : Handler simple (retourne les données telles quelles)
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use pmoupnp::action_handler;
|
/// use pmoupnp::action_handler;
|
||||||
///
|
///
|
||||||
/// // Handler minimal - run() collectera automatiquement les OUT
|
/// let handler = action_handler!(|data| {
|
||||||
/// let handler = action_handler!(|instance| {
|
/// Ok(data) // Retourne les données non modifiées
|
||||||
/// Ok(()) // Succès, pas d'erreur
|
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Exemple 2 : Handler qui lit et modifie des variables
|
/// ## Exemple 2 : Handler qui calcule et modifie les données
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use pmoupnp::action_handler;
|
/// use pmoupnp::{action_handler, get, set};
|
||||||
/// use pmoupnp::actions::ActionError;
|
/// use pmoupnp::actions::ActionError;
|
||||||
///
|
///
|
||||||
/// let handler = action_handler!(|instance| {
|
/// let handler = action_handler!(|mut data| {
|
||||||
/// // Lire un argument d'entrée depuis la variable liée
|
/// // Extraire les valeurs avec la macro get!
|
||||||
/// let arg = instance.argument("DesiredVolume")
|
/// let celsius: f64 = get!(data, "Celsius", f64);
|
||||||
/// .ok_or_else(|| ActionError::ArgumentNotFound("DesiredVolume".to_string()))?;
|
|
||||||
///
|
///
|
||||||
/// let var = arg.get_variable_instance()
|
/// // Calculer
|
||||||
/// .ok_or_else(|| ActionError::VariableNotBound)?;
|
/// let fahrenheit = celsius * 9.0 / 5.0 + 32.0;
|
||||||
///
|
///
|
||||||
/// let volume = var.value();
|
/// // Insérer avec la macro set!
|
||||||
|
/// set!(data, "Fahrenheit", fahrenheit);
|
||||||
///
|
///
|
||||||
/// // Modifier une autre variable d'instance
|
/// Ok(data) // Retourner les données modifiées
|
||||||
/// let current_volume = instance.argument("CurrentVolume")
|
|
||||||
/// .ok_or_else(|| ActionError::ArgumentNotFound("CurrentVolume".to_string()))?
|
|
||||||
/// .get_variable_instance()
|
|
||||||
/// .ok_or_else(|| ActionError::VariableNotBound)?;
|
|
||||||
///
|
|
||||||
/// current_volume.set_value(volume);
|
|
||||||
///
|
|
||||||
/// Ok(()) // Succès - run() collectera CurrentVolume dans les OUT
|
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Exemple 3 : Handler avec logique métier asynchrone
|
/// ## Exemple 3 : Handler avec logique métier asynchrone
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use pmoupnp::action_handler;
|
/// use pmoupnp::{action_handler, get, set};
|
||||||
/// use pmoupnp::actions::ActionError;
|
/// use pmoupnp::actions::ActionError;
|
||||||
///
|
///
|
||||||
/// let handler = action_handler!(|instance| {
|
/// let handler = action_handler!(|mut data| {
|
||||||
/// // Lire les paramètres depuis les variables liées
|
/// // Lire l'URI
|
||||||
/// let uri_var = instance.argument("CurrentURI")
|
/// let uri: String = get!(data, "URI", String);
|
||||||
/// .and_then(|a| a.get_variable_instance())
|
|
||||||
/// .ok_or_else(|| ActionError::VariableNotBound)?;
|
|
||||||
///
|
|
||||||
/// let uri = uri_var.value();
|
|
||||||
///
|
///
|
||||||
/// // Appel asynchrone à un service externe
|
/// // Appel asynchrone à un service externe
|
||||||
/// let response = external_service::fetch_metadata(&uri).await
|
/// let metadata = external_service::fetch_metadata(&uri).await
|
||||||
/// .map_err(|e| ActionError::ExternalError(e.to_string()))?;
|
/// .map_err(|e| ActionError::ExternalError(e.to_string()))?;
|
||||||
///
|
///
|
||||||
/// // Mettre à jour les variables selon la réponse
|
/// // Mettre à jour les données
|
||||||
/// if let Some(arg) = instance.argument("Metadata") {
|
/// set!(data, "Metadata", metadata);
|
||||||
/// if let Some(var) = arg.get_variable_instance() {
|
|
||||||
/// var.set_value(StateValue::String(response.metadata));
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
///
|
||||||
/// Ok(())
|
/// Ok(data)
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Exemple 4 : Handler avec capture de contexte et validation
|
/// ## Exemple 4 : Handler avec capture de contexte
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use pmoupnp::action_handler;
|
/// use pmoupnp::{action_handler, get, set};
|
||||||
/// use pmoupnp::actions::ActionError;
|
/// use pmoupnp::actions::ActionError;
|
||||||
/// use std::sync::Arc;
|
/// use std::sync::Arc;
|
||||||
/// use tokio::sync::Mutex;
|
/// use tokio::sync::Mutex;
|
||||||
///
|
///
|
||||||
/// // Contexte partagé (ex: état d'un lecteur média)
|
/// // Contexte partagé
|
||||||
/// let player_state = Arc::new(Mutex::new(PlayerState::Stopped));
|
/// let player_state = Arc::new(Mutex::new(PlayerState::Stopped));
|
||||||
///
|
///
|
||||||
/// let handler = action_handler!(|instance| {
|
/// let handler = action_handler!(|mut data| {
|
||||||
/// // Vérifier l'état actuel
|
/// // Vérifier l'état
|
||||||
/// {
|
/// {
|
||||||
/// let state = player_state.lock().await;
|
/// let state = player_state.lock().await;
|
||||||
/// if *state == PlayerState::Error {
|
/// if *state == PlayerState::Error {
|
||||||
/// return Err(ActionError::InvalidState("Player in error state".to_string()));
|
/// return Err(ActionError::InvalidState("Player in error state".into()));
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // Modifier l'état du lecteur
|
/// // Modifier l'état
|
||||||
/// {
|
/// {
|
||||||
/// let mut state = player_state.lock().await;
|
/// let mut state = player_state.lock().await;
|
||||||
/// *state = PlayerState::Playing;
|
/// *state = PlayerState::Playing;
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // Mettre à jour la variable TransportState
|
/// // Mettre à jour les données
|
||||||
/// if let Some(arg) = instance.argument("CurrentTransportState") {
|
/// set!(data, "TransportState", "PLAYING".to_string());
|
||||||
/// if let Some(var) = arg.get_variable_instance() {
|
|
||||||
/// var.set_value(StateValue::String("PLAYING".to_string()));
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
///
|
||||||
/// Ok(())
|
/// Ok(data)
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
@@ -314,10 +291,11 @@ pub type ActionHandler = Arc<dyn Fn(Arc<crate::actions::ActionInstance>) -> Acti
|
|||||||
/// - Le bloc est automatiquement wrappé dans `async move`
|
/// - Le bloc est automatiquement wrappé dans `async move`
|
||||||
/// - Les captures de variables sont déplacées (`move`)
|
/// - Les captures de variables sont déplacées (`move`)
|
||||||
/// - Le résultat est automatiquement boxé et arcé
|
/// - Le résultat est automatiquement boxé et arcé
|
||||||
|
/// - Utilisez les macros `get!` et `set!` pour manipuler facilement les données
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! action_handler {
|
macro_rules! action_handler {
|
||||||
(|$instance:ident| $body:block) => {
|
(|$data:ident| $body:block) => {
|
||||||
std::sync::Arc::new(|$instance: std::sync::Arc<$crate::actions::ActionInstance>| {
|
std::sync::Arc::new(|$data: $crate::actions::ActionData| {
|
||||||
Box::pin(async move $body)
|
Box::pin(async move $body)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -76,6 +76,27 @@ impl UpnpTypedInstance for ActionInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ActionInstance {
|
impl ActionInstance {
|
||||||
|
/// Retourne `true` si l'action est stateful.
|
||||||
|
///
|
||||||
|
/// Une action stateful met à jour les StateVarInstance lors de l'exécution.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `true` si l'action est stateful, `false` si stateless.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pmoupnp::actions::{Action, ActionInstance};
|
||||||
|
/// # use pmoupnp::UpnpInstance;
|
||||||
|
/// let mut action = Action::new("Play".to_string());
|
||||||
|
/// let instance = ActionInstance::new(&action);
|
||||||
|
/// assert!(instance.is_stateful()); // Stateful par défaut
|
||||||
|
/// ```
|
||||||
|
pub fn is_stateful(&self) -> bool {
|
||||||
|
self.model.is_stateful()
|
||||||
|
}
|
||||||
|
|
||||||
/// Retourne une instance d'argument par son nom.
|
/// Retourne une instance d'argument par son nom.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
|||||||
@@ -51,43 +51,33 @@ impl UpnpTyped for Action {
|
|||||||
impl Action {
|
impl Action {
|
||||||
/// Crée un handler par défaut pour une action.
|
/// Crée un handler par défaut pour une action.
|
||||||
///
|
///
|
||||||
/// Ce handler logge simplement l'appel et les arguments d'entrée.
|
/// Ce handler logge simplement l'appel et les arguments.
|
||||||
/// La méthode [`ActionInstance::run()`](crate::actions::ActionInstance::run) s'occupe
|
|
||||||
/// automatiquement de :
|
|
||||||
/// 1. Stocker les valeurs IN dans les variables liées avant d'appeler le handler
|
|
||||||
/// 2. Collecter les valeurs OUT après l'exécution
|
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// Un [`ActionHandler`] qui logge les entrées.
|
/// Un [`ActionHandler`] qui logge les entrées et retourne les données telles quelles.
|
||||||
///
|
///
|
||||||
/// # Comportement
|
/// # Comportement
|
||||||
///
|
///
|
||||||
/// - Logge le nom de l'action
|
/// - Logge les arguments avec leurs valeurs
|
||||||
/// - Logge les arguments IN avec leurs valeurs (lues depuis les variables liées)
|
|
||||||
/// - Ne fait aucune modification (handler passif)
|
/// - Ne fait aucune modification (handler passif)
|
||||||
|
/// - Retourne les données telles quelles
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
///
|
///
|
||||||
/// Ce handler est automatiquement assigné lors de la création d'une action.
|
/// Ce handler est automatiquement assigné lors de la création d'une action.
|
||||||
/// Il peut être remplacé via [`set_handler`](Self::set_handler).
|
/// Il peut être remplacé via [`set_handler`](Self::set_handler).
|
||||||
fn default_handler() -> ActionHandler {
|
fn default_handler() -> ActionHandler {
|
||||||
action_handler!(|instance| {
|
action_handler!(|data| {
|
||||||
use crate::UpnpTypedInstance;
|
info!("🎬 Action called with default handler");
|
||||||
|
|
||||||
info!("🎬 Action '{}' called", instance.get_name());
|
// Logger les arguments
|
||||||
|
for (key, value) in data.iter() {
|
||||||
// Logger les arguments d'entrée (déjà stockés dans les variables par run())
|
trace!(" {} = {:?}", key, value);
|
||||||
for arg_inst in instance.arguments_set().all() {
|
|
||||||
let arg_model = arg_inst.as_ref().get_model();
|
|
||||||
if arg_model.is_in() {
|
|
||||||
if let Some(var_inst) = arg_inst.get_variable_instance() {
|
|
||||||
trace!(" IN {} = {:?}", arg_inst.get_name(), var_inst.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(()) // Succès - handler par défaut ne fait rien d'autre
|
// Retourner les données telles quelles
|
||||||
|
Ok(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +104,7 @@ impl Action {
|
|||||||
},
|
},
|
||||||
arguments: ArgumentSet::new(),
|
arguments: ArgumentSet::new(),
|
||||||
handle: Self::default_handler(),
|
handle: Self::default_handler(),
|
||||||
|
stateful: true, // Par défaut, les actions sont stateful
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,4 +156,53 @@ impl Action {
|
|||||||
pub fn handler(&self) -> &ActionHandler {
|
pub fn handler(&self) -> &ActionHandler {
|
||||||
&self.handle
|
&self.handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Définit si l'action est stateful.
|
||||||
|
///
|
||||||
|
/// Une action stateful met à jour les StateVarInstance lors de l'exécution,
|
||||||
|
/// déclenchant ainsi les notifications d'événements UPnP.
|
||||||
|
///
|
||||||
|
/// Une action stateless n'interagit pas avec les StateVarInstance,
|
||||||
|
/// ce qui améliore les performances pour les opérations purement calculatoires.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `stateful` - `true` pour stateful (défaut), `false` pour stateless
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `&mut Self` pour permettre le chaînage
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pmoupnp::actions::Action;
|
||||||
|
/// let mut action = Action::new("Calculate".to_string());
|
||||||
|
/// action.set_stateful(false); // Action stateless
|
||||||
|
/// ```
|
||||||
|
pub fn set_stateful(&mut self, stateful: bool) -> &mut Self {
|
||||||
|
self.stateful = stateful;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retourne `true` si l'action est stateful.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `true` si l'action met à jour les StateVarInstance (stateful),
|
||||||
|
/// `false` si l'action est purement calculatoire (stateless).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pmoupnp::actions::Action;
|
||||||
|
/// let mut action = Action::new("Play".to_string());
|
||||||
|
/// assert!(action.is_stateful()); // Stateful par défaut
|
||||||
|
///
|
||||||
|
/// action.set_stateful(false);
|
||||||
|
/// assert!(!action.is_stateful()); // Maintenant stateless
|
||||||
|
/// ```
|
||||||
|
pub fn is_stateful(&self) -> bool {
|
||||||
|
self.stateful
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
202
pmoupnp/src/actions/handler_helpers.rs
Normal file
202
pmoupnp/src/actions/handler_helpers.rs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
//! Helpers et macros pour faciliter l'écriture de handlers d'actions.
|
||||||
|
//!
|
||||||
|
//! Ce module fournit des fonctions utilitaires et des macros pour simplifier
|
||||||
|
//! la manipulation de [`ActionData`](crate::actions::ActionData) dans les handlers.
|
||||||
|
//!
|
||||||
|
//! # Fonctions utilitaires
|
||||||
|
//!
|
||||||
|
//! - [`get_value`] : Extrait une valeur typée depuis ActionData
|
||||||
|
//! - [`set_value`] : Insère une valeur dans ActionData
|
||||||
|
//!
|
||||||
|
//! # Macros
|
||||||
|
//!
|
||||||
|
//! - [`get!`](crate::get) : Macro pour extraire facilement une valeur
|
||||||
|
//! - [`set!`](crate::set) : Macro pour insérer facilement une valeur
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use pmoupnp::{action_handler, get, set};
|
||||||
|
//! use pmoupnp::actions::ActionError;
|
||||||
|
//!
|
||||||
|
//! let handler = action_handler!(|mut data| {
|
||||||
|
//! // Extraction avec macro
|
||||||
|
//! let celsius: f64 = get!(data, "Celsius", f64);
|
||||||
|
//!
|
||||||
|
//! // Calcul
|
||||||
|
//! let fahrenheit = celsius * 9.0 / 5.0 + 32.0;
|
||||||
|
//!
|
||||||
|
//! // Insertion avec macro
|
||||||
|
//! set!(data, "Fahrenheit", fahrenheit);
|
||||||
|
//!
|
||||||
|
//! Ok(data)
|
||||||
|
//! });
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use bevy_reflect::Reflect;
|
||||||
|
use crate::actions::{ActionData, ActionError};
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
/// Extrait une valeur typée depuis ActionData.
|
||||||
|
///
|
||||||
|
/// Cette fonction permet d'extraire une valeur `Box<dyn Reflect>` depuis
|
||||||
|
/// ActionData et de la convertir vers le type concret attendu.
|
||||||
|
///
|
||||||
|
/// # Type Parameters
|
||||||
|
///
|
||||||
|
/// * `T` - Le type concret attendu (doit implémenter `Reflect + Clone`)
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `data` - Référence vers ActionData
|
||||||
|
/// * `key` - Clé de la valeur à extraire
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `Ok(T)` si la valeur existe et peut être convertie vers `T`,
|
||||||
|
/// `Err(ActionError)` sinon.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Retourne `ActionError::ArgumentNotFound` si :
|
||||||
|
/// - La clé n'existe pas dans ActionData
|
||||||
|
/// - La valeur ne peut pas être convertie vers le type `T`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pmoupnp::actions::{ActionData, get_value};
|
||||||
|
/// use std::collections::HashMap;
|
||||||
|
///
|
||||||
|
/// let mut data: ActionData = HashMap::new();
|
||||||
|
/// data.insert("Volume".to_string(), Box::new(50u32));
|
||||||
|
///
|
||||||
|
/// let volume: u32 = get_value(&data, "Volume").unwrap();
|
||||||
|
/// assert_eq!(volume, 50);
|
||||||
|
/// ```
|
||||||
|
pub fn get_value<T: Reflect + Clone>(
|
||||||
|
data: &ActionData,
|
||||||
|
key: &str
|
||||||
|
) -> Result<T, ActionError> {
|
||||||
|
data.get(key)
|
||||||
|
.and_then(|boxed| boxed.as_any().downcast_ref::<T>())
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| ActionError::ArgumentNotFound(key.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insère une valeur dans ActionData.
|
||||||
|
///
|
||||||
|
/// Cette fonction convertit automatiquement la valeur en `Box<dyn Reflect>`
|
||||||
|
/// et l'insère dans ActionData.
|
||||||
|
///
|
||||||
|
/// # Type Parameters
|
||||||
|
///
|
||||||
|
/// * `T` - Le type de la valeur (doit implémenter `Reflect + 'static`)
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `data` - Référence mutable vers ActionData
|
||||||
|
/// * `key` - Clé pour la valeur (convertie en `String`)
|
||||||
|
/// * `value` - Valeur à insérer
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pmoupnp::actions::{ActionData, set_value};
|
||||||
|
/// use std::collections::HashMap;
|
||||||
|
///
|
||||||
|
/// let mut data: ActionData = HashMap::new();
|
||||||
|
/// set_value(&mut data, "Volume", 75u32);
|
||||||
|
///
|
||||||
|
/// // Vérifier l'insertion
|
||||||
|
/// use pmoupnp::actions::get_value;
|
||||||
|
/// let volume: u32 = get_value(&data, "Volume").unwrap();
|
||||||
|
/// assert_eq!(volume, 75);
|
||||||
|
/// ```
|
||||||
|
pub fn set_value<T: Reflect + 'static>(
|
||||||
|
data: &mut ActionData,
|
||||||
|
key: impl Into<String>,
|
||||||
|
value: T
|
||||||
|
) {
|
||||||
|
data.insert(key.into(), Box::new(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro pour extraire facilement une valeur depuis ActionData.
|
||||||
|
///
|
||||||
|
/// Cette macro simplifie l'utilisation de [`get_value`] en gérant
|
||||||
|
/// automatiquement la propagation d'erreur avec `?`.
|
||||||
|
///
|
||||||
|
/// # Syntaxe
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// get!(data, "key", Type)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `data` - Expression évaluant à `&ActionData`
|
||||||
|
/// * `key` - Clé de la valeur (expression évaluant à `&str`)
|
||||||
|
/// * `type` - Type concret attendu
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// La valeur de type `Type` si elle existe et peut être convertie,
|
||||||
|
/// sinon propage l'erreur avec `?`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use pmoupnp::{get, action_handler};
|
||||||
|
/// use pmoupnp::actions::ActionError;
|
||||||
|
///
|
||||||
|
/// let handler = action_handler!(|data| {
|
||||||
|
/// let volume: u32 = get!(data, "Volume", u32);
|
||||||
|
/// let name: String = get!(data, "Name", String);
|
||||||
|
///
|
||||||
|
/// // Utiliser les valeurs...
|
||||||
|
///
|
||||||
|
/// Ok(data)
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! get {
|
||||||
|
($data:expr, $key:expr, $type:ty) => {
|
||||||
|
$crate::actions::get_value::<$type>($data, $key)?
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro pour insérer facilement une valeur dans ActionData.
|
||||||
|
///
|
||||||
|
/// Cette macro simplifie l'utilisation de [`set_value`] pour
|
||||||
|
/// insérer des valeurs dans ActionData.
|
||||||
|
///
|
||||||
|
/// # Syntaxe
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// set!(data, "key", value)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `data` - Expression évaluant à `&mut ActionData`
|
||||||
|
/// * `key` - Clé pour la valeur (expression évaluant vers `String`)
|
||||||
|
/// * `value` - Valeur à insérer (doit implémenter `Reflect + 'static`)
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use pmoupnp::{set, action_handler};
|
||||||
|
///
|
||||||
|
/// let handler = action_handler!(|mut data| {
|
||||||
|
/// set!(data, "Result", 42u32);
|
||||||
|
/// set!(data, "Message", "Success".to_string());
|
||||||
|
///
|
||||||
|
/// Ok(data)
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! set {
|
||||||
|
($data:expr, $key:expr, $value:expr) => {
|
||||||
|
$crate::actions::set_value($data, $key, $value)
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,3 +1,107 @@
|
|||||||
|
/// Macro pour créer facilement une action avec son handler.
|
||||||
|
///
|
||||||
|
/// Cette macro simplifie la création d'actions dynamiques en combinant
|
||||||
|
/// la création de l'action et l'assignation du handler en une seule expression.
|
||||||
|
///
|
||||||
|
/// # Syntaxe
|
||||||
|
///
|
||||||
|
/// ## Action stateful (défaut)
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// action!("ActionName", |data| {
|
||||||
|
/// // Handler code
|
||||||
|
/// Ok(data)
|
||||||
|
/// })
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Action stateless
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// action!("ActionName", stateless, |data| {
|
||||||
|
/// // Handler code
|
||||||
|
/// Ok(data)
|
||||||
|
/// })
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `name` - Nom de l'action UPnP (chaîne littérale ou expression String)
|
||||||
|
/// * `stateless` - (Optionnel) Mot-clé pour marquer l'action comme stateless
|
||||||
|
/// * `|data| { ... }` - Closure du handler (voir [`action_handler!`](crate::action_handler))
|
||||||
|
///
|
||||||
|
/// # Type de retour
|
||||||
|
///
|
||||||
|
/// Retourne une `Action` configurée avec le handler spécifié.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ## Action stateless simple
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use pmoupnp::{action, get, set};
|
||||||
|
///
|
||||||
|
/// let convert = action!("ConvertTemp", stateless, |mut data| {
|
||||||
|
/// let celsius: f64 = get!(data, "Celsius", f64);
|
||||||
|
/// let fahrenheit = celsius * 9.0 / 5.0 + 32.0;
|
||||||
|
/// set!(data, "Fahrenheit", fahrenheit);
|
||||||
|
/// Ok(data)
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Action stateful (défaut)
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use pmoupnp::{action, get, set};
|
||||||
|
///
|
||||||
|
/// let play = action!("Play", |mut data| {
|
||||||
|
/// let speed: String = get!(data, "Speed", String);
|
||||||
|
/// set!(data, "TransportState", "PLAYING".to_string());
|
||||||
|
/// Ok(data)
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Avec capture de contexte
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use pmoupnp::{action, get, set};
|
||||||
|
/// use std::sync::Arc;
|
||||||
|
/// use tokio::sync::Mutex;
|
||||||
|
///
|
||||||
|
/// let state = Arc::new(Mutex::new(PlayerState::Stopped));
|
||||||
|
/// let state_clone = state.clone();
|
||||||
|
///
|
||||||
|
/// let play = action!("Play", |mut data| {
|
||||||
|
/// let mut player = state_clone.lock().await;
|
||||||
|
/// *player = PlayerState::Playing;
|
||||||
|
/// set!(data, "TransportState", "PLAYING".to_string());
|
||||||
|
/// Ok(data)
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
///
|
||||||
|
/// - Actions stateful (défaut) : mettent à jour les StateVarInstance (notifications)
|
||||||
|
/// - Actions stateless : pas de mise à jour des StateVarInstance (performances)
|
||||||
|
/// - Le handler capture les variables par `move`
|
||||||
|
/// - Utilisez [`get!`](crate::get) et [`set!`](crate::set) pour manipuler facilement les données
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! action {
|
||||||
|
// Action stateful (défaut)
|
||||||
|
($name:expr, |$data:ident| $body:block) => {{
|
||||||
|
let mut action = $crate::actions::Action::new($name.to_string());
|
||||||
|
action.set_handler($crate::action_handler!(|$data| $body));
|
||||||
|
action
|
||||||
|
}};
|
||||||
|
|
||||||
|
// Action stateless
|
||||||
|
($name:expr, stateless, |$data:ident| $body:block) => {{
|
||||||
|
let mut action = $crate::actions::Action::new($name.to_string());
|
||||||
|
action.set_stateful(false);
|
||||||
|
action.set_handler($crate::action_handler!(|$data| $body));
|
||||||
|
action
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
/// Macro pour définir facilement une action UPnP.
|
/// Macro pour définir facilement une action UPnP.
|
||||||
///
|
///
|
||||||
/// Cette macro simplifie la création d'actions UPnP statiques en générant
|
/// Cette macro simplifie la création d'actions UPnP statiques en générant
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ mod arg_inst_set_methods;
|
|||||||
mod arg_instance_methods;
|
mod arg_instance_methods;
|
||||||
mod arg_set_methods;
|
mod arg_set_methods;
|
||||||
mod argument_methods;
|
mod argument_methods;
|
||||||
|
mod handler_helpers;
|
||||||
|
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ use std::sync::{Arc, RwLock};
|
|||||||
|
|
||||||
pub use errors::ActionError;
|
pub use errors::ActionError;
|
||||||
pub use action_handler::{ActionData, ActionFuture, ActionHandler};
|
pub use action_handler::{ActionData, ActionFuture, ActionHandler};
|
||||||
|
pub use handler_helpers::{get_value, set_value};
|
||||||
|
|
||||||
/// Action UPnP.
|
/// Action UPnP.
|
||||||
///
|
///
|
||||||
@@ -61,6 +63,7 @@ pub struct Action {
|
|||||||
object: UpnpObjectType,
|
object: UpnpObjectType,
|
||||||
arguments: ArgumentSet,
|
arguments: ArgumentSet,
|
||||||
handle: ActionHandler,
|
handle: ActionHandler,
|
||||||
|
stateful: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Action {
|
impl std::fmt::Debug for Action {
|
||||||
|
|||||||
@@ -233,4 +233,95 @@ impl StateVarInstance {
|
|||||||
|
|
||||||
Ok(arc_reflect)
|
Ok(arc_reflect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convertit la valeur actuelle en Box<dyn Reflect>
|
||||||
|
///
|
||||||
|
/// - Si type String ET parser défini : utilise le parser
|
||||||
|
/// - Sinon : utilise StateValue::to_reflect() directement
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Un `Box<dyn Reflect>` contenant la valeur actuelle
|
||||||
|
pub fn to_reflect(&self) -> Box<dyn Reflect> {
|
||||||
|
use crate::variable_types::StateVarType;
|
||||||
|
|
||||||
|
let current_value = self.value.read().unwrap().clone();
|
||||||
|
|
||||||
|
// Parser uniquement pour les String
|
||||||
|
if self.as_state_var_type() == StateVarType::String {
|
||||||
|
if let StateValue::String(ref s) = current_value {
|
||||||
|
if let Some(ref parser) = self.model.parse {
|
||||||
|
match parser(s) {
|
||||||
|
Ok(reflected) => return reflected,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"Failed to parse value '{}' for variable '{}': {:?}, using raw string",
|
||||||
|
s, self.get_name(), e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversion standard pour tous les autres types
|
||||||
|
current_value.to_reflect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Définit la valeur depuis Box<dyn Reflect>
|
||||||
|
///
|
||||||
|
/// - Si type String ET marshal défini : utilise le marshal
|
||||||
|
/// - Sinon : utilise StateValue::from_reflect() directement
|
||||||
|
///
|
||||||
|
/// Puis délègue à set_value() pour la mise à jour et les notifications
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `reflect_value` - La nouvelle valeur sous forme Reflect
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Retourne une erreur si :
|
||||||
|
/// - La conversion Reflect → StateValue échoue
|
||||||
|
/// - Le marshalling échoue
|
||||||
|
/// - La mise à jour de la valeur échoue
|
||||||
|
pub async fn set_reflect_value(&self, reflect_value: Box<dyn Reflect>) -> Result<(), StateValueError> {
|
||||||
|
use crate::variable_types::StateVarType;
|
||||||
|
|
||||||
|
// Convertir Reflect → StateValue
|
||||||
|
let state_value = if self.as_state_var_type() == StateVarType::String {
|
||||||
|
// Pour les String : essayer le marshal si défini
|
||||||
|
if let Some(ref marshal) = self.model.marshal {
|
||||||
|
// D'abord, essayer de convertir Reflect → StateValue temporaire
|
||||||
|
match StateValue::from_reflect(reflect_value.as_ref(), self.as_state_var_type()) {
|
||||||
|
Ok(temp_value) => {
|
||||||
|
// Utiliser le marshal pour obtenir la String marshallée
|
||||||
|
match marshal(&temp_value) {
|
||||||
|
Ok(marshalled_string) => {
|
||||||
|
StateValue::String(marshalled_string)
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"Failed to marshal value for variable '{}': {:?}, using standard conversion",
|
||||||
|
self.get_name(), e
|
||||||
|
);
|
||||||
|
// Fallback
|
||||||
|
temp_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pas de marshal, conversion standard
|
||||||
|
StateValue::from_reflect(reflect_value.as_ref(), self.as_state_var_type())?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pas un String, conversion standard
|
||||||
|
StateValue::from_reflect(reflect_value.as_ref(), self.as_state_var_type())?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Déléguer à set_value() pour factoriser (mise à jour + notifications)
|
||||||
|
self.set_value(state_value).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
// Ce module permet de convertir StateValue en valeurs Reflect
|
// Ce module permet de convertir StateValue en valeurs Reflect et vice-versa
|
||||||
//
|
//
|
||||||
// Étant donné que StateValue contient des types qui n'implémentent pas tous Reflect
|
// Étant donné que StateValue contient des types qui n'implémentent pas tous Reflect
|
||||||
// (comme Uuid, Url, et certains types chrono), nous fournissons des méthodes de conversion
|
// (comme Uuid, Url, et certains types chrono), nous fournissons des méthodes de conversion
|
||||||
// vers des types primitifs qui supportent Reflect.
|
// vers des types primitifs qui supportent Reflect.
|
||||||
|
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use crate::variable_types::StateValue;
|
use crate::variable_types::{StateValue, StateValueError, StateVarType};
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
impl StateValue {
|
impl StateValue {
|
||||||
/// Convertit la StateValue en une valeur Reflect.
|
/// Convertit la StateValue en une valeur Reflect.
|
||||||
@@ -41,4 +42,85 @@ impl StateValue {
|
|||||||
StateValue::URI(v) => Box::new(v.to_string()),
|
StateValue::URI(v) => Box::new(v.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convertit &dyn Reflect → StateValue selon le type attendu
|
||||||
|
///
|
||||||
|
/// Méthode statique utilisée pour reconstruire StateValue depuis Reflect
|
||||||
|
pub fn from_reflect(
|
||||||
|
value: &dyn Reflect,
|
||||||
|
expected_type: StateVarType
|
||||||
|
) -> Result<StateValue, StateValueError> {
|
||||||
|
match expected_type {
|
||||||
|
StateVarType::UI1 => {
|
||||||
|
value.as_any().downcast_ref::<u8>()
|
||||||
|
.map(|v| StateValue::UI1(*v))
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected u8".into()))
|
||||||
|
},
|
||||||
|
StateVarType::UI2 => {
|
||||||
|
value.as_any().downcast_ref::<u16>()
|
||||||
|
.map(|v| StateValue::UI2(*v))
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected u16".into()))
|
||||||
|
},
|
||||||
|
StateVarType::UI4 => {
|
||||||
|
value.as_any().downcast_ref::<u32>()
|
||||||
|
.map(|v| StateValue::UI4(*v))
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected u32".into()))
|
||||||
|
},
|
||||||
|
StateVarType::I1 => {
|
||||||
|
value.as_any().downcast_ref::<i8>()
|
||||||
|
.map(|v| StateValue::I1(*v))
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected i8".into()))
|
||||||
|
},
|
||||||
|
StateVarType::I2 => {
|
||||||
|
value.as_any().downcast_ref::<i16>()
|
||||||
|
.map(|v| StateValue::I2(*v))
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected i16".into()))
|
||||||
|
},
|
||||||
|
StateVarType::I4 | StateVarType::Int => {
|
||||||
|
value.as_any().downcast_ref::<i32>()
|
||||||
|
.map(|v| StateValue::I4(*v))
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected i32".into()))
|
||||||
|
},
|
||||||
|
StateVarType::R4 => {
|
||||||
|
value.as_any().downcast_ref::<f32>()
|
||||||
|
.map(|v| StateValue::R4(*v))
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected f32".into()))
|
||||||
|
},
|
||||||
|
StateVarType::R8 | StateVarType::Number | StateVarType::Fixed14_4 => {
|
||||||
|
value.as_any().downcast_ref::<f64>()
|
||||||
|
.map(|v| StateValue::R8(*v))
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected f64".into()))
|
||||||
|
},
|
||||||
|
StateVarType::String | StateVarType::BinBase64 | StateVarType::BinHex => {
|
||||||
|
value.as_any().downcast_ref::<String>()
|
||||||
|
.map(|v| match expected_type {
|
||||||
|
StateVarType::String => StateValue::String(v.clone()),
|
||||||
|
StateVarType::BinBase64 => StateValue::BinBase64(v.clone()),
|
||||||
|
StateVarType::BinHex => StateValue::BinHex(v.clone()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected String".into()))
|
||||||
|
},
|
||||||
|
StateVarType::Boolean => {
|
||||||
|
value.as_any().downcast_ref::<bool>()
|
||||||
|
.map(|v| StateValue::Boolean(*v))
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected bool".into()))
|
||||||
|
},
|
||||||
|
StateVarType::Char => {
|
||||||
|
value.as_any().downcast_ref::<char>()
|
||||||
|
.map(|v| StateValue::Char(*v))
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected char".into()))
|
||||||
|
},
|
||||||
|
// Pour les types complexes, on essaie de reconstruire depuis String
|
||||||
|
StateVarType::Date | StateVarType::DateTime | StateVarType::DateTimeTZ |
|
||||||
|
StateVarType::Time | StateVarType::TimeTZ | StateVarType::UUID | StateVarType::URI => {
|
||||||
|
value.as_any().downcast_ref::<String>()
|
||||||
|
.ok_or_else(|| StateValueError::TypeError("Expected String representation".into()))
|
||||||
|
.and_then(|s| {
|
||||||
|
// Utiliser les méthodes from_string existantes
|
||||||
|
StateValue::from_string(s, &expected_type)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user