//! ## Hiérarchie des traits
//!
//! ```text
//! Clone + Debug
//! └─> UpnpObject (trait de base)
//! ├─> UpnpModel (modèles créant des instances)
//! ├─> UpnpInstance (instances concrètes)
//! ├─> UpnpTyped (objets avec nom et type)
//! │ └─> UpnpTypedObject = UpnpObject + UpnpTyped
//! │ └─> UpnpTypedInstance = UpnpTypedObject + UpnpInstance
//! └─> UpnpSet (collections) + UpnpDeepClone
//! ├─> UpnpModelSet = UpnpSet + UpnpModel
//! └─> UpnInstanceSet = UpnpSet + UpnpInstance
//!
//! UpnpDeepClone (indépendant)
//! ```
//!
//! ## Description des traits
//!
//! - **Traits de base** :
//! - [`UpnpObject`] : Trait principal avec sérialisation XML/Markdown
//! - [`UpnpDeepClone`] : Clonage profond (indépendant de la hiérarchie)
//!
//! - **Traits de spécialisation niveau 1** :
//! - [`UpnpModel`] : Modèle pouvant créer des instances
//! - [`UpnpInstance`] : Instance concrète créée depuis un modèle
//! - [`UpnpTyped`] : Ajoute les informations de type et nom
//! - [`UpnpSet`] : Marque un objet comme collection
//!
//! - **Traits combinés niveau 2** :
//! - [`UpnpTypedObject`] : Objet typé (marker trait)
//!
//! - **Traits combinés niveau 3** :
//! - [`UpnpTypedInstance`] : Instance typée (marker trait)
//! - [`UpnpModelSet`] : Collection de modèles (marker trait)
//! - [`UpnInstanceSet`] : Collection d'instances (marker trait)
use std::{fmt::Debug, sync::Arc};
use xmltree::{Element, EmitterConfig};
use crate::UpnpObjectType;
/// Trait pour le clonage profond d'objets UPnP.
///
/// Contrairement au trait standard [`Clone`] qui peut effectuer un clonage superficiel
/// (partage via `Arc`), ce trait garantit un clonage complet et indépendant de l'objet.
///
/// # Note
///
/// Ce trait est indépendant de la hiérarchie [`UpnpObject`] et peut être implémenté
/// séparément.
pub trait UpnpDeepClone {
/// Crée un clone profond de l'objet.
///
/// Tous les éléments internes sont clonés, créant un objet complètement indépendant.
fn deep_clone(&self) -> Self;
}
/// Trait de base pour tous les objets UPnP.
///
/// Ce trait fournit les fonctionnalités communes à tous les objets UPnP :
/// - Sérialisation XML
/// - Conversion en Markdown
/// - Identification du type d'objet (instance ou set)
///
/// # Traits requis
///
/// - [`Clone`] : Pour pouvoir dupliquer les objets
/// - [`Debug`] : Pour le débogage
///
/// # Hiérarchie
///
/// Ce trait est à la base de toute la hiérarchie UPnP. Voir la documentation du module
/// pour le graphe complet.
pub trait UpnpObject: Clone + Debug {
/// Convertit l'objet en élément XML.
///
/// # Returns
///
/// Un [`Element`] xmltree représentant l'objet.
async fn to_xml_element(&self) -> Element;
/// Convertit l'objet en chaîne XML formatée.
///
/// Génère une représentation XML complète avec en-tête et indentation.
///
/// # Returns
///
/// Une chaîne XML formatée avec :
/// - En-tête ``
/// - Indentation de 2 espaces
///
/// # Examples
///
/// ```ignore
/// let xml = my_object.to_xml();
/// println!("{}", xml);
/// //
/// //
/// // value
/// //
/// ```
async fn to_xml(&self) -> String {
let elem = self.to_xml_element().await;
let config = EmitterConfig::new()
.perform_indent(true)
.indent_string(" ");
let mut buf = Vec::new();
elem.write_with_config(&mut buf, config)
.expect("Failed to write XML");
let mut xml_string = "\n".to_string();
xml_string.push_str(&String::from_utf8(buf).expect("Invalid UTF-8"));
xml_string
}
/// Convertit l'objet en représentation Markdown.
///
/// Génère une vue hiérarchique de la structure XML en format Markdown,
/// avec détection automatique des URLs et images.
///
/// # Fonctionnalités
///
/// - Les URLs sont converties en liens cliquables
/// - Les URLs d'images sont affichées comme images
/// - Les attributs sont formatés comme `key=value`
/// - Structure hiérarchique avec indentation
///
/// # Returns
///
/// Une chaîne Markdown formatée.
///
/// # Examples
///
/// ```ignore
/// let md = my_object.to_markdown();
/// println!("{}", md);
/// // # UPnP XML (Markdown view)
/// //
/// // - **element**
/// // - **child**: `value`
/// ```
async fn to_markdown(&self) -> String {
let elem = self.to_xml_element().await;
let mut md = String::new();
fn is_url(s: &str) -> bool {
s.starts_with("http://") || s.starts_with("https://") || s.starts_with("urn:")
}
fn is_image_url(s: &str) -> bool {
let s = s.to_lowercase();
s.ends_with(".png")
|| s.ends_with(".jpg")
|| s.ends_with(".jpeg")
|| s.ends_with(".gif")
|| s.ends_with(".svg")
|| s.ends_with(".webp")
}
fn format_value(v: &str) -> String {
let v = v.trim().to_string();
if is_url(&v) {
if is_image_url(&v) {
format!("[{}]({})
", v, v, v)
} else {
format!("[{}]({})", v, v)
}
} else {
format!("`{}`", v)
}
}
fn recurse(elem: &xmltree::Element, md: &mut String, depth: usize) {
let indent = " ".repeat(depth);
md.push_str(&format!("{}- **{}**", indent, elem.name));
if !elem.attributes.is_empty() {
let attrs: Vec = elem
.attributes
.iter()
.map(|(k, v)| format!("{}={}", k, format_value(v)))
.collect();
md.push_str(&format!(" ({})", attrs.join(", ")));
}
if let Some(text) = elem
.get_text()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
{
md.push_str(&format!(": {}", format_value(&text)));
}
md.push('\n');
for child in &elem.children {
if let xmltree::XMLNode::Element(child_elem) = child {
recurse(child_elem, md, depth + 1);
}
}
}
md.push_str("# UPnP XML (Markdown view)\n\n");
recurse(&elem, &mut md, 0);
md
}
/// Indique si l'objet est une instance.
///
/// # Returns
///
/// `false` par défaut. Surchargé par [`UpnpInstance`] pour retourner `true`.
fn is_instance(&self) -> bool {
false
}
/// Indique si l'objet est une collection (set).
///
/// # Returns
///
/// `false` par défaut. Surchargé par [`UpnpSet`] pour retourner `true`.
fn is_set(&self) -> bool {
false
}
}
/// Trait pour les modèles UPnP qui peuvent créer des instances.
///
/// Un modèle représente la définition ou template d'un objet UPnP, tandis qu'une
/// instance est une occurrence concrète de cet objet.
///
/// # Type associé
///
/// - [`Instance`](Self::Instance) : Le type d'instance créée par ce modèle
///
/// # Méthodes
///
/// - [`create_instance`](Self::create_instance) : Crée une nouvelle instance
///
/// # Hiérarchie
///
/// ```text
/// UpnpObject
/// └─> UpnpModel
/// ```
///
/// # Relation avec UpnpInstance
///
/// `UpnpModel` et [`UpnpInstance`] sont liés via leurs types associés :
/// - Le modèle spécifie quel type d'instance il crée
/// - L'instance spécifie de quel type de modèle elle provient
///
/// # Examples
///
/// ```ignore
/// struct DeviceModel { /* ... */ }
/// struct DeviceInstance { /* ... */ }
///
/// impl UpnpModel for DeviceModel {
/// type Instance = DeviceInstance;
/// }
///
/// impl UpnpInstance for DeviceInstance {
/// type Model = DeviceModel;
///
/// fn new(model: &DeviceModel) -> Self {
/// // Création de l'instance depuis le modèle
/// }
/// }
///
/// // Utilisation
/// let model = DeviceModel::new();
/// let instance = model.create_instance(); // Arc
/// ```
pub trait UpnpModel: UpnpObject {
/// Le type d'instance créée par ce modèle.
type Instance: UpnpInstance;
/// Crée une nouvelle instance à partir de ce modèle.
///
/// # Returns
///
/// Un `Arc` contenant la nouvelle instance créée.
///
/// # Implémentation par défaut
///
/// Par défaut, appelle [`UpnpInstance::new`] avec une référence vers ce modèle
/// et encapsule le résultat dans un `Arc`.
fn create_instance(&self) -> Arc {
Arc::new(Self::Instance::new(self))
}
}
/// Trait pour les instances UPnP concrètes.
///
/// Une instance représente une occurrence concrète d'un objet UPnP, créée à partir
/// d'un modèle ([`UpnpModel`]).
///
/// # Type associé
///
/// - [`Model`](Self::Model) : Le type du modèle dont cette instance dérive
///
/// # Méthodes requises
///
/// - [`new`](Self::new) : Constructeur créant l'instance depuis un modèle
///
/// # Hiérarchie
///
/// ```text
/// UpnpObject
/// └─> UpnpInstance
/// ```
///
/// # Relation avec UpnpModel
///
/// Voir la documentation de [`UpnpModel`] pour comprendre la relation entre
/// modèles et instances.
pub trait UpnpInstance: UpnpObject {
/// Le type du modèle dont cette instance est dérivée.
type Model: UpnpModel;
/// Crée une nouvelle instance à partir d'un modèle.
///
/// # Arguments
///
/// * `model` - Référence vers le modèle à partir duquel créer l'instance
///
/// # Returns
///
/// Une nouvelle instance initialisée depuis le modèle.
fn new(model: &Self::Model) -> Self;
/// Indique que cet objet est une instance.
///
/// # Returns
///
/// Toujours `true` pour les instances.
fn is_instance(&self) -> bool {
true
}
}
/// Trait pour les objets UPnP typés.
///
/// Ajoute les informations de type et de nom aux objets UPnP.
///
/// # Méthodes requises
///
/// - [`as_upnp_object_type`](Self::as_upnp_object_type) : Accès au type de l'objet
///
/// # Méthodes fournies
///
/// - [`get_name`](Self::get_name) : Récupère le nom de l'objet
/// - [`get_object_type`](Self::get_object_type) : Récupère le type de l'objet
///
/// # Hiérarchie
///
/// ```text
/// UpnpObject
/// └─> UpnpTyped
/// ```
pub trait UpnpTyped: UpnpObject {
/// Retourne une référence vers le type de l'objet.
fn as_upnp_object_type(&self) -> &UpnpObjectType;
/// Retourne le nom de l'objet.
///
/// # Returns
///
/// Une référence vers le nom de l'objet.
fn get_name(&self) -> &String {
&self.as_upnp_object_type().name
}
/// Retourne le type de l'objet sous forme de chaîne.
///
/// # Returns
///
/// Une référence vers le type de l'objet (ex: "Device", "Service", etc.).
fn get_object_type(&self) -> &String {
&self.as_upnp_object_type().object_type
}
}
/// Trait marqueur pour les objets UPnP typés.
///
/// Combine [`UpnpObject`] et [`UpnpTyped`] pour créer un objet avec toutes
/// les fonctionnalités de base plus les informations de type.
///
/// # Hiérarchie
///
/// ```text
/// UpnpObject + UpnpTyped
/// └─> UpnpTypedObject
/// ```
///
/// # Note
///
/// C'est un *marker trait* (trait marqueur) sans méthodes supplémentaires.
pub trait UpnpTypedObject: UpnpObject + UpnpTyped {}
/// Trait marqueur pour les instances typées UPnP.
///
/// Combine [`UpnpTypedObject`] et [`UpnpInstance`] pour représenter une instance
/// concrète d'un objet typé avec toutes les fonctionnalités :
/// - Sérialisation XML/Markdown (de [`UpnpObject`])
/// - Informations de type et nom (de [`UpnpTyped`])
/// - Relation avec un modèle (de [`UpnpInstance`])
///
/// # Hiérarchie
///
/// ```text
/// UpnpTypedObject + UpnpInstance
/// └─> UpnpTypedInstance
/// ```
///
/// # Note
///
/// Ce trait ajoute la méthode [`get_model`](Self::get_model) pour accéder
/// au modèle de l'instance. Les collections d'instances ([`UpnInstanceSet`])
/// n'ont pas cette méthode car elles contiennent plusieurs instances.
pub trait UpnpTypedInstance: UpnpTypedObject + UpnpInstance
where
Self::Model: UpnpModel
{
/// Retourne une référence vers le modèle dont cette instance est dérivée.
///
/// Permet d'accéder aux métadonnées et contraintes définies dans le modèle,
/// telles que les plages de valeurs autorisées, les types, les descriptions, etc.
///
/// # Returns
///
/// Une référence immuable vers le modèle.
///
/// # Examples
///
/// ```ignore
/// let instance = model.create_instance();
///
/// // Accéder aux propriétés du modèle depuis l'instance
/// let model_ref = instance.get_model();
/// println!("Instance du modèle: {}", model_ref.get_name());
///
/// // Vérifier les contraintes définies dans le modèle
/// if let Some(range) = model_ref.get_range() {
/// println!("Plage autorisée: {:?}", range);
/// }
/// ```
///
/// # Use cases
///
/// Cette méthode est particulièrement utile pour :
/// - Valider des valeurs contre les contraintes du modèle
/// - Accéder aux métadonnées sans dupliquer les informations
/// - Afficher des informations de type ou de description
/// - Implémenter des logiques conditionnelles basées sur le modèle
///
/// # Différence avec les traits spécifiques
///
/// Pour les variables d'état, le trait [`UpnpVariable`](crate::state_variables::UpnpVariable)
/// fournit également `get_definition()` qui est sémantiquement équivalent
/// mais spécifique au domaine des variables.
fn get_model(&self) -> &Self::Model;
}
/// Trait marqueur pour les collections UPnP.
///
/// Représente un ensemble (set) d'objets UPnP.
///
/// # Super-traits requis
///
/// - [`UpnpObject`] : Fonctionnalités de base (XML, etc.)
/// - [`UpnpDeepClone`] : Permet le clonage profond des collections
///
/// # Implémentation
///
/// Ce trait surcharge [`UpnpObject::is_set`] pour retourner `true`.
///
/// # Hiérarchie
///
/// ```text
/// UpnpObject + UpnpDeepClone
/// └─> UpnpSet
/// ```
///
/// # Note sur le clonage
///
/// Les collections UPnP contiennent généralement des `Arc` vers leurs éléments.
/// Le trait [`Clone`] (via `UpnpObject`) effectue un clonage shallow des `Arc`,
/// tandis que [`UpnpDeepClone`] clone profondément les éléments contenus.
///
/// # Examples
///
/// ```ignore
/// struct ServiceSet {
/// services: HashMap>,
/// }
///
/// impl Clone for ServiceSet {
/// fn clone(&self) -> Self {
/// // Clone shallow : partage les Services via Arc
/// Self {
/// services: self.services.clone()
/// }
/// }
/// }
///
/// impl UpnpDeepClone for ServiceSet {
/// fn deep_clone(&self) -> Self {
/// // Clone profond : crée de nouveaux Services
/// let deep_services = self.services
/// .iter()
/// .map(|(k, v)| (k.clone(), Arc::new((**v).clone())))
/// .collect();
///
/// Self {
/// services: deep_services
/// }
/// }
/// }
/// ```
pub trait UpnpSet: UpnpObject + UpnpDeepClone {
/// Indique que cet objet est une collection.
///
/// # Returns
///
/// Toujours `true` pour les collections.
fn is_set(&self) -> bool {
true
}
}
/// Trait marqueur pour les collections de modèles UPnP.
///
/// Combine [`UpnpSet`] et [`UpnpModel`] pour représenter une collection
/// de modèles qui peut elle-même créer une collection d'instances.
///
/// # Cas d'usage
///
/// Ce trait est utilisé quand une collection de modèles doit pouvoir instancier
/// une collection d'instances correspondante. Par exemple :
/// - Un ensemble de modèles de services d'un device qui crée un ensemble d'instances de services
/// - Une liste de modèles d'actions qui instancie une liste d'actions actives
/// - Une collection de modèles de variables d'état qui génère une collection d'instances
///
/// # Hiérarchie
///
/// ```text
/// UpnpSet + UpnpModel
/// └─> UpnpModelSet
/// ```
///
/// # Relation avec d'autres traits
///
/// - [`UpnpSet`] : Fournit les fonctionnalités de collection
/// - [`UpnpModel`] : Fournit la capacité de créer des instances
/// - [`UpnInstanceSet`] : Représente les collections d'instances (contrepartie)
///
/// # Note
///
/// C'est un *marker trait* (trait marqueur) sans méthodes supplémentaires.
/// Il est automatiquement implémenté pour tous les types éligibles via une
/// blanket implementation.
///
/// # Examples
///
/// ```ignore
/// /// Collection de modèles de services
/// struct ServiceSetModel {
/// services: Vec>,
/// }
///
/// /// Collection d'instances de services
/// struct ServiceSetInstance {
/// model: Arc,
/// service_instances: Vec>,
/// }
///
/// impl UpnpObject for ServiceSetModel { /* ... */ }
/// impl UpnpSet for ServiceSetModel {}
///
/// impl UpnpModel for ServiceSetModel {
/// type Instance = ServiceSetInstance;
///
/// fn create_instance(&self) -> Arc {
/// // Créer des instances pour chaque service
/// let instances = self.services
/// .iter()
/// .map(|model| model.create_instance())
/// .collect();
///
/// Arc::new(ServiceSetInstance {
/// model: Arc::new(self.clone()),
/// service_instances: instances,
/// })
/// }
/// }
///
/// // UpnpModelSet est automatiquement implémenté !
///
/// // Utilisation
/// let model_set = ServiceSetModel::new();
/// let instance_set = model_set.create_instance(); // Crée toutes les instances
/// ```
pub trait UpnpModelSet: UpnpSet + UpnpModel {}
/// Trait marqueur pour les collections d'instances UPnP.
///
/// Combine [`UpnpSet`] et [`UpnpInstance`] pour représenter une collection
/// d'instances UPnP. Cela permet d'avoir des collections qui sont elles-mêmes
/// des instances créées depuis un modèle.
///
/// # Hiérarchie
///
/// ```text
/// UpnpSet + UpnpInstance
/// └─> UpnInstanceSet
/// ```
///
/// # Note
///
/// C'est un *marker trait* (trait marqueur) sans méthodes supplémentaires.
pub trait UpnInstanceSet: UpnpSet + UpnpInstance {}
/// Implémentation automatique de [`UpnInstanceSet`] pour tous les types éligibles.
///
/// Cette *blanket implementation* fournit automatiquement le trait [`UpnInstanceSet`]
/// à tout type `T` qui implémente à la fois [`UpnpSet`] et [`UpnpInstance`].
///
/// # Contraintes
///
/// - `T` doit implémenter [`UpnpSet`] (collection d'objets UPnP)
/// - `T` doit implémenter [`UpnpInstance`] (instance créée depuis un modèle)
///
/// # Pourquoi cette implémentation existe
///
/// Certaines collections UPnP sont elles-mêmes des instances (par exemple, une
/// collection de services pour un device spécifique). Ce trait marker permet
/// d'identifier ces collections qui combinent les deux aspects. La blanket
/// implementation évite d'avoir à l'implémenter manuellement pour chaque type.
///
/// # Utilisation
///
/// ```ignore
/// struct ServiceSetInstance {
/// model: Arc,
/// services: Vec>,
/// }
///
/// impl UpnpObject for ServiceSetInstance { /* ... */ }
/// impl UpnpSet for ServiceSetInstance {}
/// impl UpnpInstance for ServiceSetInstance {
/// type Model = ServiceSetModel;
/// fn new(model: &ServiceSetModel) -> Self { /* ... */ }
/// }
///
/// // UpnInstanceSet est automatiquement implémenté !
///
/// fn process_instance_set(set: &T) {
/// if set.is_set() && set.is_instance() {
/// println!("C'est une collection ET une instance");
/// }
/// }
/// ```
impl UpnInstanceSet for T
where
T: UpnpSet + UpnpInstance
{}
/// Implémentation automatique de [`UpnpTypedObject`] pour tous les types éligibles.
///
/// Cette *blanket implementation* fournit automatiquement le trait [`UpnpTypedObject`]
/// à tout type `T` qui implémente à la fois [`UpnpObject`] et [`UpnpTyped`].
///
/// # Contraintes
///
/// - `T` doit implémenter [`UpnpObject`] (fonctionnalités de base UPnP)
/// - `T` doit implémenter [`UpnpTyped`] (informations de type et nom)
///
/// # Utilisation
///
/// ```ignore
/// struct Device {
/// object_type: UpnpObjectType,
/// }
///
/// impl UpnpObject for Device { /* ... */ }
/// impl UpnpTyped for Device { /* ... */ }
///
/// // UpnpTypedObject est automatiquement implémenté !
/// fn process(obj: &T) {
/// println!("{}", obj.get_name());
/// }
/// ```
impl UpnpTypedObject for T
where
T: UpnpObject + UpnpTyped
{}
/// Implémentation automatique de [`UpnpModelSet`] pour tous les types éligibles.
///
/// Cette *blanket implementation* fournit automatiquement le trait [`UpnpModelSet`]
/// à tout type `T` qui implémente à la fois [`UpnpSet`] et [`UpnpModel`].
///
/// # Contraintes
///
/// - `T` doit implémenter [`UpnpSet`] (collection d'objets UPnP)
/// - `T` doit implémenter [`UpnpModel`] (peut créer des instances)
///
/// # Pourquoi cette implémentation existe
///
/// [`UpnpModelSet`] est un *marker trait* qui identifie les collections pouvant
/// créer des collections d'instances. Plutôt que de demander aux développeurs
/// d'écrire manuellement `impl UpnpModelSet for MyType {}`, cette blanket
/// implementation le fait automatiquement dès que les traits requis sont implémentés.
///
/// # Fonctionnement
///
/// Lorsque vous définissez une collection de modèles :
///
/// ```ignore
/// struct ActionSetModel {
/// actions: Vec>,
/// }
///
/// impl UpnpObject for ActionSetModel { /* ... */ }
/// impl UpnpSet for ActionSetModel {}
///
/// impl UpnpModel for ActionSetModel {
/// type Instance = ActionSetInstance;
/// fn create_instance(&self) -> Arc { /* ... */ }
/// }
/// ```
///
/// Le compilateur Rust vérifie automatiquement que `ActionSetModel` satisfait
/// toutes les contraintes (implémente `UpnpSet` ET `UpnpModel`) et applique
/// donc `UpnpModelSet` sans code supplémentaire.
///
/// # Utilisation dans des signatures génériques
///
/// ```ignore
/// fn process_model_set(set: &T) {
/// println!("Processing model set that can create instances");
/// let instance = set.create_instance();
/// // ...
/// }
/// ```
///
/// # Différence avec UpnInstanceSet
///
/// - [`UpnpModelSet`] : Collection de **modèles** (peut créer des instances)
/// - [`UpnInstanceSet`] : Collection d'**instances** (créée depuis un modèle)
impl UpnpModelSet for T
where
T: UpnpSet + UpnpModel
{}