Skip to main content

wme_models/
envelope.rs

1//! Schema envelope for version compatibility.
2//!
3//! This module provides an envelope wrapper for handling schema versioning
4//! in API responses. The envelope allows for forward compatibility when
5//! the API introduces new schema versions.
6//!
7//! # Schema Versioning
8//!
9//! The Wikimedia Enterprise API may evolve over time. The envelope pattern
10//! allows you to:
11//! - Check if a schema version is supported before parsing
12//! - Handle multiple schema versions in the same application
13//! - Gracefully handle unknown schema versions
14//!
15//! # Example
16//!
17//! ```
18//! use wme_models::ArticleEnvelope;
19//! use wme_models::Article;
20//!
21//! // Create an envelope from JSON with valid Article payload
22//! let json = r#"{
23//!     "schema_version": "2024.2",
24//!     "payload": {
25//!         "name": "Test",
26//!         "identifier": 12345,
27//!         "url": "https://en.wikipedia.org/wiki/Test",
28//!         "date_modified": "2024-01-15T12:00:00Z",
29//!         "in_language": {"identifier": "en", "name": "English"},
30//!         "is_part_of": {"identifier": "enwiki"},
31//!         "license": [{"name": "CC BY-SA 4.0", "url": "https://example.com/license"}],
32//!         "version": {
33//!             "identifier": 999,
34//!             "editor": {"identifier": 12345, "name": "TestUser"}
35//!         }
36//!     }
37//! }"#;
38//! let envelope: ArticleEnvelope = serde_json::from_str(json).unwrap();
39//!
40//! // Check if supported before parsing
41//! if envelope.is_supported() {
42//!     let article: Article = envelope.parse().unwrap();
43//! }
44//! ```
45
46use crate::error::ModelError;
47use serde::{Deserialize, Serialize};
48
49/// Schema version wrapper for forward compatibility.
50///
51/// Wraps API responses with schema version information. This allows
52/// applications to handle multiple schema versions and gracefully
53/// reject unsupported versions.
54///
55/// # Usage
56///
57/// ```
58/// use wme_models::ArticleEnvelope;
59///
60/// // Check if version is supported
61/// let envelope = ArticleEnvelope::new("2024.2", serde_json::json!({"name": "Test"}));
62/// assert!(envelope.is_supported());
63///
64/// // Parse the payload
65/// let data: serde_json::Value = envelope.parse().unwrap();
66/// ```
67#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
68pub struct ArticleEnvelope {
69    /// Schema version (e.g., "2024.1", "2024.2")
70    pub schema_version: String,
71    /// Raw JSON payload
72    pub payload: serde_json::Value,
73}
74
75impl ArticleEnvelope {
76    /// Create a new envelope with a specific schema version.
77    ///
78    /// # Arguments
79    ///
80    /// * `schema_version` - The schema version string
81    /// * `payload` - The JSON payload to wrap
82    ///
83    /// # Example
84    ///
85    /// ```
86    /// use wme_models::ArticleEnvelope;
87    ///
88    /// let envelope = ArticleEnvelope::new(
89    ///     "2024.2",
90    ///     serde_json::json!({"name": "Test"})
91    /// );
92    /// ```
93    pub fn new(schema_version: &str, payload: serde_json::Value) -> Self {
94        Self {
95            schema_version: schema_version.to_string(),
96            payload,
97        }
98    }
99
100    /// Parse the payload into a typed struct.
101    ///
102    /// # Type Parameters
103    ///
104    /// * `T` - The target type to deserialize into
105    ///
106    /// # Errors
107    ///
108    /// Returns an error if:
109    /// - The schema version is unsupported
110    /// - Deserialization fails
111    ///
112    /// # Example
113    ///
114    /// ```
115    /// use wme_models::ArticleEnvelope;
116    ///
117    /// let envelope = ArticleEnvelope::new(
118    ///     "2024.2",
119    ///     serde_json::json!({"name": "Test", "value": 42})
120    /// );
121    ///
122    /// #[derive(serde::Deserialize)]
123    /// struct MyData { name: String, value: i32 }
124    ///
125    /// let data: MyData = envelope.parse().unwrap();
126    /// assert_eq!(data.name, "Test");
127    /// ```
128    pub fn parse<T>(&self) -> Result<T, ModelError>
129    where
130        T: for<'de> serde::Deserialize<'de>,
131    {
132        match self.schema_version.as_str() {
133            "2024.1" | "2024.2" => serde_json::from_value(self.payload.clone())
134                .map_err(|e| ModelError::DeserializationError(e.to_string())),
135            _ => Err(ModelError::UnsupportedSchemaVersion(
136                self.schema_version.clone(),
137            )),
138        }
139    }
140
141    /// Get the schema version.
142    ///
143    /// # Example
144    ///
145    /// ```
146    /// use wme_models::ArticleEnvelope;
147    ///
148    /// let envelope = ArticleEnvelope::new("2024.2", serde_json::json!({}));
149    /// assert_eq!(envelope.schema_version(), "2024.2");
150    /// ```
151    pub fn schema_version(&self) -> &str {
152        &self.schema_version
153    }
154
155    /// Check if this envelope can be parsed.
156    ///
157    /// Returns true if the schema version is in the supported list.
158    ///
159    /// # Example
160    ///
161    /// ```
162    /// use wme_models::ArticleEnvelope;
163    ///
164    /// let supported = ArticleEnvelope::new("2024.2", serde_json::json!({}));
165    /// assert!(supported.is_supported());
166    ///
167    /// let unsupported = ArticleEnvelope::new("1999.1", serde_json::json!({}));
168    /// assert!(!unsupported.is_supported());
169    /// ```
170    pub fn is_supported(&self) -> bool {
171        matches!(self.schema_version.as_str(), "2024.1" | "2024.2")
172    }
173}
174
175/// Supported schema versions.
176///
177/// These are the schema versions that the current library supports.
178pub const SUPPORTED_SCHEMA_VERSIONS: &[&str] = &["2024.1", "2024.2"];
179
180/// Current default schema version.
181///
182/// This is the schema version used when creating new envelopes.
183pub const DEFAULT_SCHEMA_VERSION: &str = "2024.2";
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_envelope_creation() {
191        let envelope = ArticleEnvelope::new("2024.2", serde_json::json!({"name": "Test"}));
192        assert_eq!(envelope.schema_version(), "2024.2");
193        assert!(envelope.is_supported());
194    }
195
196    #[test]
197    fn test_envelope_parse() {
198        let envelope =
199            ArticleEnvelope::new("2024.2", serde_json::json!({"name": "Test", "value": 42}));
200
201        #[derive(serde::Deserialize, Debug, PartialEq)]
202        struct TestData {
203            name: String,
204            value: i32,
205        }
206
207        let data: TestData = envelope.parse().unwrap();
208        assert_eq!(data.name, "Test");
209        assert_eq!(data.value, 42);
210    }
211
212    #[test]
213    fn test_unsupported_version() {
214        let envelope = ArticleEnvelope::new("1999.1", serde_json::json!({}));
215        assert!(!envelope.is_supported());
216
217        let result: Result<serde_json::Value, _> = envelope.parse();
218        assert!(result.is_err());
219    }
220
221    #[test]
222    fn test_supported_versions() {
223        assert!(SUPPORTED_SCHEMA_VERSIONS.contains(&"2024.1"));
224        assert!(SUPPORTED_SCHEMA_VERSIONS.contains(&"2024.2"));
225        assert_eq!(DEFAULT_SCHEMA_VERSION, "2024.2");
226    }
227}