wme-models 0.1.3

Type definitions for the Wikimedia Enterprise API
Documentation
//! Structured content types (BETA).
//!
//! This module provides types for parsed article content including infoboxes,
//! sections, and tables. These are part of the Structured Contents BETA API.
//!
//! # Structured Article Content
//!
//! Unlike the raw HTML/wikitext, structured content provides semantically parsed
//! data that can be programmatically analyzed:
//!
//! - **Infoboxes**: Structured data boxes (e.g., taxobox, person infobox)
//! - **Sections**: Article sections with paragraphs and subsections
//! - **Tables**: Data tables with headers and rows
//!
//! # BETA Status
//!
//! These types are part of the experimental Structured Contents endpoints.
//! They are not covered by SLA and may change as the API evolves.

use crate::content::Image;
use crate::reference::Citation;
use crate::Link;
use serde::{Deserialize, Serialize};

/// Infobox structured content.
///
/// Infoboxes are structured data boxes displayed on article pages.
/// Common examples include taxoboxes for species, person infoboxes for biographies,
/// and infoboxes for cities, films, etc.
///
/// Infoboxes have a tree-like structure with nested parts for complex data.
///
/// # Example
///
/// ```ignore
/// use wme_models::Infobox;
///
/// // An infobox might contain:
/// // - Name: "Automatic taxobox"
/// // - Type: Infobox
/// // - Parts: [
/// //     { name: "Kingdom:", type: "field", value: "Animalia" },
/// //     { name: "Phylum:", type: "field", value: "Chordata" }
/// //   ]
/// ```
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct Infobox {
    /// Infobox name (e.g., "Automatic taxobox")
    pub name: Option<String>,
    /// Infobox type
    #[serde(rename = "type")]
    pub infobox_type: InfoboxType,
    /// Value (for field types)
    pub value: Option<String>,
    /// Nested parts (for complex infoboxes)
    pub has_parts: Option<Vec<InfoboxPart>>,
    /// Links within infobox
    pub links: Option<Vec<Link>>,
    /// Images within infobox
    pub images: Option<Vec<Image>>,
}

/// Infobox types.
///
/// The type determines how the infobox part should be rendered and interpreted.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum InfoboxType {
    /// Infobox container (root infobox)
    #[serde(rename = "infobox")]
    Infobox,
    /// Field within infobox (name-value pair)
    #[serde(rename = "field")]
    Field,
    /// Section within infobox (group of fields)
    #[serde(rename = "section")]
    Section,
    /// Image in infobox
    #[serde(rename = "image")]
    Image,
}

/// Part of an infobox.
///
/// Infobox parts can be nested to create hierarchical data structures.
/// For example, a taxobox might have sections for classification and
/// characteristics.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct InfoboxPart {
    /// Part name
    pub name: Option<String>,
    /// Part type
    #[serde(rename = "type")]
    pub part_type: InfoboxPartType,
    /// Single value (for field types)
    pub value: Option<String>,
    /// Multiple values (for list type)
    pub values: Option<Vec<String>>,
    /// Nested parts
    pub has_parts: Option<Vec<InfoboxPart>>,
    /// Links
    pub links: Option<Vec<Link>>,
    /// Images
    pub images: Option<Vec<Image>>,
}

/// Infobox part types.
///
/// Extended types for parts within infoboxes, including lists.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum InfoboxPartType {
    /// Infobox container
    #[serde(rename = "infobox")]
    Infobox,
    /// Field (name-value pair)
    #[serde(rename = "field")]
    Field,
    /// Section (grouping of fields)
    #[serde(rename = "section")]
    Section,
    /// Image
    #[serde(rename = "image")]
    Image,
    /// List (multiple values)
    #[serde(rename = "list")]
    List,
}

/// Section with structured content.
///
/// Articles are organized into sections. Each section can contain paragraphs,
/// links, citations, and nested subsections. Sections form a tree structure
/// with the article root.
///
/// # Example
///
/// ```ignore
/// Section {
///     name: Some("Personal life".to_string()),
///     section_type: SectionType::Section,
///     value: None,
///     has_parts: Some(vec![
///         Section { name: Some("Relationships".to_string()), ... },
///         Section { type: Paragraph, value: Some("Baker's first marriage...".to_string()), ... },
///     ]),
/// }
/// ```
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct Section {
    /// Section name (from header h2-h6)
    pub name: Option<String>,
    /// Section type
    #[serde(rename = "type")]
    pub section_type: SectionType,
    /// Content value (plain text)
    pub value: Option<String>,
    /// Links within section
    pub links: Option<Vec<Link>>,
    /// Citations within section
    pub citations: Option<Vec<Citation>>,
    /// Nested sections
    pub has_parts: Option<Vec<Section>>,
    /// Table references (tables live at article root)
    pub table_references: Option<Vec<TableReference>>,
}

/// Section types.
///
/// Sections can be containers (with subsections) or paragraphs (with text).
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum SectionType {
    /// Section container (has subsections)
    #[serde(rename = "section")]
    Section,
    /// Paragraph (has text content)
    #[serde(rename = "paragraph")]
    Paragraph,
}

/// Table reference (links to table at article root).
///
/// Sections don't contain tables directly. Instead, they reference tables
/// that are stored at the article root level. This allows the same table
/// to be referenced from multiple locations.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct TableReference {
    /// Table identifier (unique within article)
    pub identifier: String,
    /// Confidence score (parser confidence in extraction)
    pub confidence_score: f64,
}

/// Table structured content.
///
/// Tables extracted from article content with headers and rows.
/// Tables are stored at the article root level and referenced from sections.
///
/// # Example
///
/// ```ignore
/// Table {
///     identifier: "demographics_table1".to_string(),
///     headers: vec![vec![
///         TableCell { value: "Year".to_string() },
///         TableCell { value: "Pop.".to_string() },
///     ]],
///     rows: vec![
///         vec![
///             TableCell { value: "1666".to_string() },
///             TableCell { value: "625".to_string() },
///         ],
///     ],
///     confidence_score: 0.9,
/// }
/// ```
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct Table {
    /// Table identifier (unique within article)
    pub identifier: String,
    /// Table headers (array of arrays for multi-row headers)
    pub headers: Vec<Vec<TableCell>>,
    /// Table rows (array of arrays of cells)
    pub rows: Vec<Vec<TableCell>>,
    /// Confidence score (parser confidence)
    pub confidence_score: f64,
}

/// Table cell.
///
/// A single cell within a table row.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct TableCell {
    /// Cell value (text content)
    pub value: String,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_infobox_creation() {
        let infobox = Infobox {
            name: Some("Automatic taxobox".to_string()),
            infobox_type: InfoboxType::Infobox,
            value: None,
            has_parts: Some(vec![InfoboxPart {
                name: Some("Kingdom:".to_string()),
                part_type: InfoboxPartType::Field,
                value: Some("Animalia".to_string()),
                values: None,
                has_parts: None,
                links: None,
                images: None,
            }]),
            links: None,
            images: None,
        };

        assert_eq!(infobox.name, Some("Automatic taxobox".to_string()));
        assert!(!infobox.has_parts.as_ref().unwrap().is_empty());
    }

    #[test]
    fn test_section_creation() {
        let section = Section {
            name: Some("Personal life".to_string()),
            section_type: SectionType::Section,
            value: None,
            links: None,
            citations: None,
            has_parts: Some(vec![Section {
                name: Some("Relationships".to_string()),
                section_type: SectionType::Section,
                value: Some("Baker's first marriage...".to_string()),
                links: None,
                citations: None,
                has_parts: None,
                table_references: None,
            }]),
            table_references: None,
        };

        assert_eq!(section.name, Some("Personal life".to_string()));
    }

    #[test]
    fn test_table_creation() {
        let table = Table {
            identifier: "demographics_table1".to_string(),
            headers: vec![vec![
                TableCell {
                    value: "Year".to_string(),
                },
                TableCell {
                    value: "Pop.".to_string(),
                },
            ]],
            rows: vec![vec![
                TableCell {
                    value: "1666".to_string(),
                },
                TableCell {
                    value: "625".to_string(),
                },
            ]],
            confidence_score: 0.9,
        };

        assert_eq!(table.identifier, "demographics_table1");
        assert_eq!(table.rows.len(), 1);
    }

    #[test]
    fn test_table_cell() {
        let cell = TableCell {
            value: "Test value".to_string(),
        };

        assert_eq!(cell.value, "Test value");
    }
}