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}