mise à jours des handlers d'actions
This commit is contained in:
@@ -113,25 +113,27 @@ pub type ActionFuture = Pin<Box<dyn Future<Output = Result<(), crate::actions::A
|
||||
/// # Signature
|
||||
///
|
||||
/// ```ignore
|
||||
/// Fn(Arc<ActionInstance>, ActionData) -> ActionFuture
|
||||
/// Fn(Arc<ActionInstance>) -> ActionFuture
|
||||
/// ```
|
||||
///
|
||||
/// Prend :
|
||||
/// - [`Arc<ActionInstance>`](crate::actions::ActionInstance) : L'instance de l'action avec accès aux variables liées
|
||||
/// - [`ActionData`] : Les données d'entrée (arguments IN)
|
||||
/// qui contiennent déjà les valeurs des arguments IN
|
||||
///
|
||||
/// Retourne un [`ActionFuture`] qui se résout en `Result<(), ActionError>`.
|
||||
///
|
||||
/// # Responsabilités
|
||||
///
|
||||
/// Le handler est responsable de :
|
||||
/// - Lire les arguments d'entrée depuis `data`
|
||||
/// - Lire les arguments d'entrée depuis les variables liées à l'instance
|
||||
/// - Exécuter la logique métier
|
||||
/// - Modifier les variables d'instance selon les besoins
|
||||
/// - Retourner `Ok(())` en cas de succès ou `Err(ActionError)` en cas d'erreur
|
||||
///
|
||||
/// La méthode [`ActionInstance::run()`](crate::actions::ActionInstance::run) s'occupe
|
||||
/// automatiquement de collecter les valeurs OUT si le handler retourne `Ok(())`.
|
||||
/// automatiquement de :
|
||||
/// 1. Stocker les valeurs IN dans les variables liées avant d'appeler le handler
|
||||
/// 2. Collecter les valeurs OUT si le handler retourne `Ok(())`
|
||||
///
|
||||
/// # Traits requis
|
||||
///
|
||||
@@ -147,8 +149,8 @@ pub type ActionFuture = Pin<Box<dyn Future<Output = Result<(), crate::actions::A
|
||||
/// use pmoupnp::action_handler;
|
||||
/// use pmoupnp::actions::ActionError;
|
||||
///
|
||||
/// let handler = action_handler!(|instance, data| {
|
||||
/// // Logique métier
|
||||
/// let handler = action_handler!(|instance| {
|
||||
/// // Logique métier - les valeurs IN sont déjà dans les variables
|
||||
/// Ok::<(), ActionError>(())
|
||||
/// });
|
||||
/// ```
|
||||
@@ -156,10 +158,10 @@ pub type ActionFuture = Pin<Box<dyn Future<Output = Result<(), crate::actions::A
|
||||
/// ## Manuellement
|
||||
///
|
||||
/// ```rust
|
||||
/// use pmoupnp::actions::{ActionData, ActionHandler, ActionInstance, ActionError};
|
||||
/// use pmoupnp::actions::{ActionHandler, ActionInstance, ActionError};
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// let handler: ActionHandler = Arc::new(|instance, data| {
|
||||
/// let handler: ActionHandler = Arc::new(|instance| {
|
||||
/// Box::pin(async move {
|
||||
/// // Votre logique async
|
||||
/// Ok::<(), ActionError>(())
|
||||
@@ -174,7 +176,7 @@ pub type ActionFuture = Pin<Box<dyn Future<Output = Result<(), crate::actions::A
|
||||
/// - Le handler capture les variables par `move`
|
||||
/// - Le future est automatiquement `Send` si les captures le sont
|
||||
/// - Utilisez la macro `action_handler!` pour simplifier la création
|
||||
pub type ActionHandler = Arc<dyn Fn(Arc<crate::actions::ActionInstance>, ActionData) -> ActionFuture + Send + Sync>;
|
||||
pub type ActionHandler = Arc<dyn Fn(Arc<crate::actions::ActionInstance>) -> ActionFuture + Send + Sync>;
|
||||
|
||||
/// Macro pour créer facilement un ActionHandler.
|
||||
///
|
||||
@@ -184,16 +186,16 @@ pub type ActionHandler = Arc<dyn Fn(Arc<crate::actions::ActionInstance>, ActionD
|
||||
/// # Syntaxe
|
||||
///
|
||||
/// ```ignore
|
||||
/// action_handler!(|instance, data| {
|
||||
/// action_handler!(|instance| {
|
||||
/// // votre logique async (automatiquement dans un bloc async move)
|
||||
/// data
|
||||
/// // Les valeurs IN sont déjà disponibles dans les variables liées
|
||||
/// })
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `instance` : Paramètre de type `Arc<`[`ActionInstance`](crate::actions::ActionInstance)`>` - L'instance de l'action
|
||||
/// - `data` : Paramètre de type [`ActionData`] (Arc<HashMap<String, StateValue>>) - Les données d'entrée
|
||||
/// avec les valeurs IN déjà stockées dans les variables liées
|
||||
/// - Le corps du bloc peut contenir du code asynchrone (`.await`)
|
||||
///
|
||||
/// # Type de retour
|
||||
@@ -208,56 +210,61 @@ pub type ActionHandler = Arc<dyn Fn(Arc<crate::actions::ActionInstance>, ActionD
|
||||
/// use pmoupnp::action_handler;
|
||||
///
|
||||
/// // Handler minimal - run() collectera automatiquement les OUT
|
||||
/// let handler = action_handler!(|instance, data| {
|
||||
/// let handler = action_handler!(|instance| {
|
||||
/// Ok(()) // Succès, pas d'erreur
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// ## Exemple 2 : Handler qui modifie une variable avec gestion d'erreur
|
||||
/// ## Exemple 2 : Handler qui lit et modifie des variables
|
||||
///
|
||||
/// ```ignore
|
||||
/// use pmoupnp::action_handler;
|
||||
/// use pmoupnp::actions::ActionError;
|
||||
///
|
||||
/// let handler = action_handler!(|instance, data| {
|
||||
/// // Lire un argument d'entrée
|
||||
/// let volume = data.get("DesiredVolume")
|
||||
/// .ok_or_else(|| ActionError::MissingArgument("DesiredVolume".to_string()))?;
|
||||
///
|
||||
/// // Modifier la variable d'instance
|
||||
/// let arg = instance.argument("CurrentVolume")
|
||||
/// .ok_or_else(|| ActionError::ArgumentNotFound("CurrentVolume".to_string()))?;
|
||||
/// let handler = action_handler!(|instance| {
|
||||
/// // Lire un argument d'entrée depuis la variable liée
|
||||
/// let arg = instance.argument("DesiredVolume")
|
||||
/// .ok_or_else(|| ActionError::ArgumentNotFound("DesiredVolume".to_string()))?;
|
||||
///
|
||||
/// let var = arg.get_variable_instance()
|
||||
/// .ok_or_else(|| ActionError::VariableNotBound)?;
|
||||
///
|
||||
/// var.set_value(volume.clone());
|
||||
/// let volume = var.value();
|
||||
///
|
||||
/// // Modifier une autre variable d'instance
|
||||
/// 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 et gestion d'erreur
|
||||
/// ## Exemple 3 : Handler avec logique métier asynchrone
|
||||
///
|
||||
/// ```ignore
|
||||
/// use pmoupnp::action_handler;
|
||||
/// use pmoupnp::actions::ActionError;
|
||||
///
|
||||
/// let handler = action_handler!(|instance, data| {
|
||||
/// let handler = action_handler!(|instance| {
|
||||
/// // Lire les paramètres depuis les variables liées
|
||||
/// let uri_var = instance.argument("CurrentURI")
|
||||
/// .and_then(|a| a.get_variable_instance())
|
||||
/// .ok_or_else(|| ActionError::VariableNotBound)?;
|
||||
///
|
||||
/// let uri = uri_var.value();
|
||||
///
|
||||
/// // Appel asynchrone à un service externe
|
||||
/// let response = external_service::fetch_data().await
|
||||
/// let response = external_service::fetch_metadata(&uri).await
|
||||
/// .map_err(|e| ActionError::ExternalError(e.to_string()))?;
|
||||
///
|
||||
/// // Mettre à jour les variables selon la réponse
|
||||
/// if let Some(arg) = instance.argument("Status") {
|
||||
/// if let Some(arg) = instance.argument("Metadata") {
|
||||
/// if let Some(var) = arg.get_variable_instance() {
|
||||
/// var.set_value(StateValue::String(response.status));
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// if let Some(arg) = instance.argument("Message") {
|
||||
/// if let Some(var) = arg.get_variable_instance() {
|
||||
/// var.set_value(StateValue::String(response.message));
|
||||
/// var.set_value(StateValue::String(response.metadata));
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@@ -276,7 +283,7 @@ pub type ActionHandler = Arc<dyn Fn(Arc<crate::actions::ActionInstance>, ActionD
|
||||
/// // Contexte partagé (ex: état d'un lecteur média)
|
||||
/// let player_state = Arc::new(Mutex::new(PlayerState::Stopped));
|
||||
///
|
||||
/// let handler = action_handler!(|instance, data| {
|
||||
/// let handler = action_handler!(|instance| {
|
||||
/// // Vérifier l'état actuel
|
||||
/// {
|
||||
/// let state = player_state.lock().await;
|
||||
@@ -309,8 +316,8 @@ pub type ActionHandler = Arc<dyn Fn(Arc<crate::actions::ActionInstance>, ActionD
|
||||
/// - Le résultat est automatiquement boxé et arcé
|
||||
#[macro_export]
|
||||
macro_rules! action_handler {
|
||||
(|$instance:ident, $data:ident| $body:block) => {
|
||||
std::sync::Arc::new(|$instance: std::sync::Arc<$crate::actions::ActionInstance>, $data: $crate::actions::ActionData| {
|
||||
(|$instance:ident| $body:block) => {
|
||||
std::sync::Arc::new(|$instance: std::sync::Arc<$crate::actions::ActionInstance>| {
|
||||
Box::pin(async move $body)
|
||||
})
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tracing::debug;
|
||||
use tracing::{debug, trace};
|
||||
use xmltree::{Element, XMLNode};
|
||||
|
||||
use crate::{
|
||||
@@ -165,9 +165,10 @@ impl ActionInstance {
|
||||
/// Exécute l'action avec les données fournies.
|
||||
///
|
||||
/// Cette méthode :
|
||||
/// 1. Exécute le handler avec les données d'entrée
|
||||
/// 2. Collecte automatiquement les valeurs OUT via [`get_out_values()`](Self::get_out_values)
|
||||
/// 3. Retourne les résultats
|
||||
/// 1. Stocke les valeurs IN dans les variables liées
|
||||
/// 2. Exécute le handler (qui peut accéder aux valeurs IN via les variables)
|
||||
/// 3. Collecte automatiquement les valeurs OUT via [`get_out_values()`](Self::get_out_values)
|
||||
/// 4. Retourne les résultats
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@@ -185,9 +186,13 @@ impl ActionInstance {
|
||||
///
|
||||
/// # Fonctionnement
|
||||
///
|
||||
/// Le handler n'a pas besoin de retourner les valeurs OUT - il modifie simplement
|
||||
/// les variables d'instance et retourne `Ok(())`. La méthode `run()` collecte automatiquement
|
||||
/// toutes les valeurs des arguments marqués comme OUT si le handler réussit.
|
||||
/// 1. Pour chaque argument IN, la valeur fournie dans `data` est stockée dans la
|
||||
/// variable d'état liée à cet argument
|
||||
/// 2. Le handler est appelé avec l'instance (il peut lire les valeurs IN via
|
||||
/// `argument.get_variable_instance().value()`)
|
||||
/// 3. Le handler modifie les variables selon ses besoins et retourne `Ok(())` ou `Err(...)`
|
||||
/// 4. Si le handler réussit, `run()` collecte automatiquement toutes les valeurs
|
||||
/// des arguments marqués comme OUT
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -206,8 +211,10 @@ impl ActionInstance {
|
||||
/// pmoupnp::variable_types::StateValue::UI2(50));
|
||||
/// let input_data = Arc::new(input);
|
||||
///
|
||||
/// // Exécuter l'action - le handler modifie CurrentVolume
|
||||
/// // run() retourne automatiquement CurrentVolume dans les OUT
|
||||
/// // Exécuter l'action
|
||||
/// // 1. run() stocke DesiredVolume=50 dans la variable liée
|
||||
/// // 2. Le handler lit la valeur et fait son travail
|
||||
/// // 3. run() retourne automatiquement les valeurs OUT
|
||||
/// match instance.run(input_data).await {
|
||||
/// Ok(output_data) => {
|
||||
/// // Traiter les résultats
|
||||
@@ -224,15 +231,30 @@ impl ActionInstance {
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// - Les valeurs IN sont automatiquement stockées avant l'appel du handler
|
||||
/// - Le handler n'a plus besoin de recevoir les données en paramètre
|
||||
/// - Le handler modifie les variables et retourne `Ok(())` ou `Err(ActionError)`
|
||||
/// - `run()` collecte automatiquement les OUT si le handler retourne `Ok(())`
|
||||
/// - L'instance doit être wrappée dans un `Arc` pour être passée au handler
|
||||
pub async fn run(self: Arc<Self>, data: ActionData) -> Result<ActionData, crate::actions::ActionError> {
|
||||
// Stocker les valeurs IN dans les variables liées
|
||||
for arg_inst in self.arguments.all() {
|
||||
let arg_model = arg_inst.as_ref().get_model();
|
||||
if arg_model.is_in() {
|
||||
if let Some(value) = data.get(arg_inst.get_name()) {
|
||||
if let Some(var_inst) = arg_inst.get_variable_instance() {
|
||||
var_inst.set_value(value.clone());
|
||||
trace!(" IN {} = {:?}", arg_inst.get_name(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let handler = self.model.handler().clone();
|
||||
let instance_clone = self.clone();
|
||||
|
||||
// Exécuter le handler
|
||||
handler(instance_clone, data).await?;
|
||||
// Exécuter le handler (il peut maintenant lire les valeurs IN depuis les variables)
|
||||
handler(instance_clone).await?;
|
||||
|
||||
// Collecter automatiquement les valeurs OUT si succès
|
||||
debug!("✅ Action '{}' completed successfully, collecting outputs", self.get_name());
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tracing::{debug, trace};
|
||||
@@ -11,11 +10,9 @@ use crate::{
|
||||
UpnpObjectSetError,
|
||||
UpnpObjectType,
|
||||
UpnpTyped,
|
||||
UpnpTypedInstance,
|
||||
};
|
||||
use crate::actions::{
|
||||
Action,
|
||||
ActionData,
|
||||
ActionHandler,
|
||||
ActionInstance,
|
||||
Argument,
|
||||
@@ -56,7 +53,9 @@ impl Action {
|
||||
///
|
||||
/// Ce handler logge simplement l'appel et les arguments d'entrée.
|
||||
/// La méthode [`ActionInstance::run()`](crate::actions::ActionInstance::run) s'occupe
|
||||
/// automatiquement de collecter les valeurs OUT après l'exécution.
|
||||
/// 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
|
||||
///
|
||||
@@ -65,7 +64,7 @@ impl Action {
|
||||
/// # Comportement
|
||||
///
|
||||
/// - Logge le nom de l'action
|
||||
/// - Logge les arguments IN avec leurs valeurs
|
||||
/// - Logge les arguments IN avec leurs valeurs (lues depuis les variables liées)
|
||||
/// - Ne fait aucune modification (handler passif)
|
||||
///
|
||||
/// # Note
|
||||
@@ -73,17 +72,17 @@ impl Action {
|
||||
/// Ce handler est automatiquement assigné lors de la création d'une action.
|
||||
/// Il peut être remplacé via [`set_handler`](Self::set_handler).
|
||||
fn default_handler() -> ActionHandler {
|
||||
action_handler!(|instance, data| {
|
||||
action_handler!(|instance| {
|
||||
use crate::UpnpTypedInstance;
|
||||
|
||||
debug!("🎬 Action '{}' called", instance.get_name());
|
||||
|
||||
// Logger les arguments d'entrée
|
||||
// Logger les arguments d'entrée (déjà stockés dans les variables par run())
|
||||
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(value) = data.get(arg_inst.get_name()) {
|
||||
trace!(" IN {} = {:?}", arg_inst.get_name(), value);
|
||||
if let Some(var_inst) = arg_inst.get_variable_instance() {
|
||||
trace!(" IN {} = {:?}", arg_inst.get_name(), var_inst.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,10 +137,10 @@ impl UpnpInstance for ArgumentInstance {
|
||||
name: from.get_name().clone(),
|
||||
object_type: "ArgumentInstance".to_string(),
|
||||
},
|
||||
|
||||
|
||||
// Clone du modèle pour référence future
|
||||
model: from.clone(),
|
||||
|
||||
|
||||
// Initialisation à None - sera lié plus tard via bind_variable()
|
||||
// Arc<RwLock<...>> permet la modification thread-safe post-construction
|
||||
variable_instance: Arc::new(RwLock::new(None)),
|
||||
|
||||
@@ -106,6 +106,7 @@ pub type ArgumentSet = UpnpObjectSet<Argument>;
|
||||
/// 1. **Création** : Instanciation via [`UpnpInstance::new`] avec `variable_instance = None`
|
||||
/// 2. **Liaison** : Association à une [`StateVarInstance`] via [`bind_variable`](Self::bind_variable)
|
||||
/// 3. **Utilisation** : Accès à la valeur runtime via [`get_variable_instance`](Self::get_variable_instance)
|
||||
/// 4. **Exécution** : Les valeurs IN sont stockées dans les variables liées lors de l'appel à `run()`
|
||||
///
|
||||
/// # Pourquoi `variable_instance` est optionnel ?
|
||||
///
|
||||
@@ -114,6 +115,13 @@ pub type ArgumentSet = UpnpObjectSet<Argument>;
|
||||
/// - Les `ActionInstance` sont créées **avant** que toutes les variables soient disponibles
|
||||
/// - La validation des dépendances se fait en deux phases
|
||||
///
|
||||
/// # Stockage des valeurs IN
|
||||
///
|
||||
/// Lors de l'exécution d'une action, les valeurs des arguments IN sont automatiquement
|
||||
/// stockées dans les `StateVarInstance` liées. Les handlers peuvent ensuite y accéder
|
||||
/// via `argument.get_variable_instance().value()` sans avoir besoin de recevoir les
|
||||
/// valeurs en paramètre.
|
||||
///
|
||||
/// # Thread-safety
|
||||
///
|
||||
/// Le champ `variable_instance` est protégé par un `RwLock` pour permettre :
|
||||
|
||||
Reference in New Issue
Block a user