wme-models 0.1.3

Type definitions for the Wikimedia Enterprise API
Documentation
//! Schema envelope for version compatibility.
//!
//! This module provides an envelope wrapper for handling schema versioning
//! in API responses. The envelope allows for forward compatibility when
//! the API introduces new schema versions.
//!
//! # Schema Versioning
//!
//! The Wikimedia Enterprise API may evolve over time. The envelope pattern
//! allows you to:
//! - Check if a schema version is supported before parsing
//! - Handle multiple schema versions in the same application
//! - Gracefully handle unknown schema versions
//!
//! # Example
//!
//! ```
//! use wme_models::ArticleEnvelope;
//! use wme_models::Article;
//!
//! // Create an envelope from JSON with valid Article payload
//! let json = r#"{
//!     "schema_version": "2024.2",
//!     "payload": {
//!         "name": "Test",
//!         "identifier": 12345,
//!         "url": "https://en.wikipedia.org/wiki/Test",
//!         "date_modified": "2024-01-15T12:00:00Z",
//!         "in_language": {"identifier": "en", "name": "English"},
//!         "is_part_of": {"identifier": "enwiki"},
//!         "license": [{"name": "CC BY-SA 4.0", "url": "https://example.com/license"}],
//!         "version": {
//!             "identifier": 999,
//!             "editor": {"identifier": 12345, "name": "TestUser"}
//!         }
//!     }
//! }"#;
//! let envelope: ArticleEnvelope = serde_json::from_str(json).unwrap();
//!
//! // Check if supported before parsing
//! if envelope.is_supported() {
//!     let article: Article = envelope.parse().unwrap();
//! }
//! ```

use crate::error::ModelError;
use serde::{Deserialize, Serialize};

/// Schema version wrapper for forward compatibility.
///
/// Wraps API responses with schema version information. This allows
/// applications to handle multiple schema versions and gracefully
/// reject unsupported versions.
///
/// # Usage
///
/// ```
/// use wme_models::ArticleEnvelope;
///
/// // Check if version is supported
/// let envelope = ArticleEnvelope::new("2024.2", serde_json::json!({"name": "Test"}));
/// assert!(envelope.is_supported());
///
/// // Parse the payload
/// let data: serde_json::Value = envelope.parse().unwrap();
/// ```
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct ArticleEnvelope {
    /// Schema version (e.g., "2024.1", "2024.2")
    pub schema_version: String,
    /// Raw JSON payload
    pub payload: serde_json::Value,
}

impl ArticleEnvelope {
    /// Create a new envelope with a specific schema version.
    ///
    /// # Arguments
    ///
    /// * `schema_version` - The schema version string
    /// * `payload` - The JSON payload to wrap
    ///
    /// # Example
    ///
    /// ```
    /// use wme_models::ArticleEnvelope;
    ///
    /// let envelope = ArticleEnvelope::new(
    ///     "2024.2",
    ///     serde_json::json!({"name": "Test"})
    /// );
    /// ```
    pub fn new(schema_version: &str, payload: serde_json::Value) -> Self {
        Self {
            schema_version: schema_version.to_string(),
            payload,
        }
    }

    /// Parse the payload into a typed struct.
    ///
    /// # Type Parameters
    ///
    /// * `T` - The target type to deserialize into
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - The schema version is unsupported
    /// - Deserialization fails
    ///
    /// # Example
    ///
    /// ```
    /// use wme_models::ArticleEnvelope;
    ///
    /// let envelope = ArticleEnvelope::new(
    ///     "2024.2",
    ///     serde_json::json!({"name": "Test", "value": 42})
    /// );
    ///
    /// #[derive(serde::Deserialize)]
    /// struct MyData { name: String, value: i32 }
    ///
    /// let data: MyData = envelope.parse().unwrap();
    /// assert_eq!(data.name, "Test");
    /// ```
    pub fn parse<T>(&self) -> Result<T, ModelError>
    where
        T: for<'de> serde::Deserialize<'de>,
    {
        match self.schema_version.as_str() {
            "2024.1" | "2024.2" => serde_json::from_value(self.payload.clone())
                .map_err(|e| ModelError::DeserializationError(e.to_string())),
            _ => Err(ModelError::UnsupportedSchemaVersion(
                self.schema_version.clone(),
            )),
        }
    }

    /// Get the schema version.
    ///
    /// # Example
    ///
    /// ```
    /// use wme_models::ArticleEnvelope;
    ///
    /// let envelope = ArticleEnvelope::new("2024.2", serde_json::json!({}));
    /// assert_eq!(envelope.schema_version(), "2024.2");
    /// ```
    pub fn schema_version(&self) -> &str {
        &self.schema_version
    }

    /// Check if this envelope can be parsed.
    ///
    /// Returns true if the schema version is in the supported list.
    ///
    /// # Example
    ///
    /// ```
    /// use wme_models::ArticleEnvelope;
    ///
    /// let supported = ArticleEnvelope::new("2024.2", serde_json::json!({}));
    /// assert!(supported.is_supported());
    ///
    /// let unsupported = ArticleEnvelope::new("1999.1", serde_json::json!({}));
    /// assert!(!unsupported.is_supported());
    /// ```
    pub fn is_supported(&self) -> bool {
        matches!(self.schema_version.as_str(), "2024.1" | "2024.2")
    }
}

/// Supported schema versions.
///
/// These are the schema versions that the current library supports.
pub const SUPPORTED_SCHEMA_VERSIONS: &[&str] = &["2024.1", "2024.2"];

/// Current default schema version.
///
/// This is the schema version used when creating new envelopes.
pub const DEFAULT_SCHEMA_VERSION: &str = "2024.2";

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

    #[test]
    fn test_envelope_creation() {
        let envelope = ArticleEnvelope::new("2024.2", serde_json::json!({"name": "Test"}));
        assert_eq!(envelope.schema_version(), "2024.2");
        assert!(envelope.is_supported());
    }

    #[test]
    fn test_envelope_parse() {
        let envelope =
            ArticleEnvelope::new("2024.2", serde_json::json!({"name": "Test", "value": 42}));

        #[derive(serde::Deserialize, Debug, PartialEq)]
        struct TestData {
            name: String,
            value: i32,
        }

        let data: TestData = envelope.parse().unwrap();
        assert_eq!(data.name, "Test");
        assert_eq!(data.value, 42);
    }

    #[test]
    fn test_unsupported_version() {
        let envelope = ArticleEnvelope::new("1999.1", serde_json::json!({}));
        assert!(!envelope.is_supported());

        let result: Result<serde_json::Value, _> = envelope.parse();
        assert!(result.is_err());
    }

    #[test]
    fn test_supported_versions() {
        assert!(SUPPORTED_SCHEMA_VERSIONS.contains(&"2024.1"));
        assert!(SUPPORTED_SCHEMA_VERSIONS.contains(&"2024.2"));
        assert_eq!(DEFAULT_SCHEMA_VERSION, "2024.2");
    }
}