serde_versioned 0.2.0

A Rust library for handling versioned serialization and deserialization with backward compatibility support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
//! # `serde_versioned`
//!
//! A library for handling versioned serialization and deserialization of Rust structs.
//! This crate provides traits and derive macros to support multiple versions of data structures
//! while maintaining backward compatibility.
//!
//! ## Usage
//!
//! ```rust,no_run
//! use serde_versioned::{Versioned, FromVersion};
//! use serde::{Serialize, Deserialize};
//!
//! #[derive(Versioned, Serialize, Deserialize, Clone)]
//! #[versioned(versions = [UserV1, UserV2])]
//! struct User {
//!     pub name: String,
//!     pub age: u32,
//! }
//!
//! #[derive(Serialize, Deserialize, Clone)]
//! pub struct UserV1 {
//!     pub name: String,
//! }
//!
//! #[derive(Serialize, Deserialize, Clone)]
//! pub struct UserV2 {
//!     pub name: String,
//!     pub age: u32,
//! }
//!
//! impl FromVersion<User> for UserV1 {
//!     fn convert(self) -> User {
//!         User { name: self.name, age: 0 }
//!     }
//! }
//!
//! impl FromVersion<User> for UserV2 {
//!     fn convert(self) -> User {
//!         User { name: self.name, age: self.age }
//!     }
//! }
//! ```

use serde::{Deserialize, Serialize};
use std::error::Error;

pub use serde_versioned_derive::Versioned;

/// Trait for converting from a versioned struct to the current struct.
///
/// This trait must be implemented for each version struct to define how it converts
/// to the current version of the struct.
///
/// # Example
///
/// ```rust,no_run
/// use serde_versioned::FromVersion;
///
/// struct User { name: String, age: u32 }
/// struct UserV1 { name: String }
///
/// impl FromVersion<User> for UserV1 {
///     fn convert(self) -> User {
///         User { name: self.name, age: 0 }
///     }
/// }
/// ```
pub trait FromVersion<T>: Sized {
    /// Converts a versioned struct instance to the current struct type.
    ///
    /// # Arguments
    ///
    /// * `self` - The versioned struct instance to convert
    ///
    /// # Returns
    ///
    /// The converted current struct instance
    fn convert(self) -> T;
}

/// Trait for handling versioned serialization and deserialization.
///
/// This trait is automatically derived using the `#[derive(Versioned)]` macro.
/// It provides methods to convert between the current struct and its versioned enum.
///
/// # Example
///
/// ```rust,no_run
/// use serde_versioned::Versioned;
/// use serde::{Deserialize, Serialize};
///
/// #[derive(Versioned, Serialize, Deserialize)]
/// #[versioned(versions = [UserV1, UserV2])]
/// struct User {
///     name: String,
///     age: u32,
/// }
///
/// #[derive(Serialize, Deserialize)]
/// struct UserV1 {
///     name: String,
/// }
///
/// #[derive(Serialize, Deserialize)]
/// struct UserV2 {
///     name: String,
///     age: u32,
/// }
///
/// impl serde_versioned::FromVersion<User> for UserV1 {
///     fn convert(self) -> User {
///         User { name: self.name, age: 0 }
///     }
/// }
///
/// impl serde_versioned::FromVersion<User> for UserV2 {
///     fn convert(self) -> User {
///         User { name: self.name, age: self.age }
///     }
/// }
///
/// // Convert to versioned enum
/// let user = User { name: "Alice".to_string(), age: 30 };
/// let version = user.to_version();
///
/// // Serialize to JSON
/// let json = serde_json::to_string(&version).unwrap();
///
/// // Deserialize and convert back
/// let version: UserVersion = serde_json::from_str(&json).unwrap();
/// let user = User::from_version(version).unwrap();
/// ```
pub trait Versioned: Sized {
    /// The version enum type that represents all versions of this struct.
    ///
    /// This enum is automatically generated by the `#[derive(Versioned)]` macro
    /// and contains variants for each version specified in the `versions` attribute.
    type VersionEnum: for<'a> Deserialize<'a> + Serialize;

    /// Converts a versioned enum instance back to the current struct.
    ///
    /// # Arguments
    ///
    /// * `version` - The versioned enum instance to convert
    ///
    /// # Returns
    ///
    /// * `Ok(Self)` - Successfully converted struct
    /// * `Err(VersionConversionError)` - Error during conversion with version information
    ///
    /// # Errors
    ///
    /// Returns a `VersionConversionError` if the conversion from the versioned struct to the current struct fails.
    /// This typically happens when the `FromVersion` implementation fails.
    /// The error includes the version number that failed to convert.
    fn from_version(version: Self::VersionEnum) -> Result<Self, VersionConversionError>;

    /// Converts the current struct instance to its versioned enum representation.
    ///
    /// This always uses the latest version specified in the `versions` attribute.
    ///
    /// # Returns
    ///
    /// The versioned enum instance representing this struct in its latest version.
    fn to_version(&self) -> Self::VersionEnum;

    /// Deserializes from a string format and converts to the current struct.
    ///
    /// This is a convenience method that combines deserialization and version conversion.
    ///
    /// # Arguments
    ///
    /// * `input` - The string to deserialize from
    /// * `deserializer` - A function that deserializes the string into `Self::VersionEnum`
    ///
    /// # Returns
    ///
    /// * `Ok(Self)` - Successfully deserialized and converted struct
    /// * `Err(FormatError<E>)` - Error during deserialization or conversion
    ///
    /// # Errors
    ///
    /// Returns `FormatError::Deserialize` if deserialization fails, or
    /// `FormatError::VersionConversion` if version conversion fails.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use serde_versioned::Versioned;
    /// use serde::{Deserialize, Serialize};
    ///
    /// #[derive(Versioned, Serialize, Deserialize)]
    /// #[versioned(versions = [UserV1, UserV2])]
    /// struct User {
    ///     name: String,
    ///     age: u32,
    /// }
    ///
    /// #[derive(Serialize, Deserialize)]
    /// struct UserV1 {
    ///     name: String,
    /// }
    ///
    /// #[derive(Serialize, Deserialize)]
    /// struct UserV2 {
    ///     name: String,
    ///     age: u32,
    /// }
    ///
    /// impl serde_versioned::FromVersion<User> for UserV1 {
    ///     fn convert(self) -> User {
    ///         User { name: self.name, age: 0 }
    ///     }
    /// }
    ///
    /// impl serde_versioned::FromVersion<User> for UserV2 {
    ///     fn convert(self) -> User {
    ///         User { name: self.name, age: self.age }
    ///     }
    /// }
    ///
    /// let json = r#"{"version":"1","name":"Alice"}"#;
    /// let user = User::from_format(json, serde_json::from_str).unwrap();
    /// ```
    fn from_format<'a, F, E>(input: &'a str, deserializer: F) -> Result<Self, FormatError<E>>
    where
        F: FnOnce(&'a str) -> Result<Self::VersionEnum, E>,
        E: Error + Send + Sync + 'static,
    {
        deserializer(input)
            .map_err(|e| FormatError::deserialize(e, Some(input.to_string())))
            .and_then(|version| Self::from_version(version).map_err(FormatError::VersionConversion))
    }

    /// Extracts version string from the version enum for error reporting.
    ///
    /// This is a helper method that attempts to extract the version number
    /// from the version enum for use in error messages.
    ///
    /// # Arguments
    ///
    /// * `version` - The version enum instance
    ///
    /// # Returns
    ///
    /// A string representation of the version number, or "unknown" if it cannot be determined.
    #[doc(hidden)]
    fn extract_version_string(_version: &Self::VersionEnum) -> String {
        // This is a default implementation that returns "unknown".
        // The derive macro should override this with a proper implementation
        // that can extract the version from the enum.
        "unknown".to_string()
    }

    /// Serializes the current struct to a string format via its versioned enum.
    ///
    /// This is a convenience method that converts the struct to its versioned enum
    /// and then serializes it using the provided serializer function.
    ///
    /// # Arguments
    ///
    /// * `serializer` - A function that serializes `Self::VersionEnum` to the desired format
    ///
    /// # Returns
    ///
    /// * `Ok(T)` - Successfully serialized data
    /// * `Err(E)` - Error during serialization
    ///
    /// # Errors
    ///
    /// Returns an error if serialization fails.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use serde_versioned::Versioned;
    /// use serde::{Deserialize, Serialize};
    ///
    /// #[derive(Versioned, Serialize, Deserialize)]
    /// #[versioned(versions = [UserV1, UserV2])]
    /// struct User {
    ///     name: String,
    ///     age: u32,
    /// }
    ///
    /// #[derive(Serialize, Deserialize)]
    /// struct UserV1 {
    ///     name: String,
    /// }
    ///
    /// #[derive(Serialize, Deserialize)]
    /// struct UserV2 {
    ///     name: String,
    ///     age: u32,
    /// }
    ///
    /// impl serde_versioned::FromVersion<User> for UserV1 {
    ///     fn convert(self) -> User {
    ///         User { name: self.name, age: 0 }
    ///     }
    /// }
    ///
    /// impl serde_versioned::FromVersion<User> for UserV2 {
    ///     fn convert(self) -> User {
    ///         User { name: self.name, age: self.age }
    ///     }
    /// }
    ///
    /// let user = User { name: "Alice".to_string(), age: 30 };
    /// let json = user.to_format(serde_json::to_string).unwrap();
    /// ```
    fn to_format<F, T, E>(&self, serializer: F) -> Result<T, E>
    where
        F: FnOnce(&Self::VersionEnum) -> Result<T, E>,
    {
        let version = self.to_version();
        serializer(&version)
    }
}

/// Error type for version conversion operations.
///
/// This error provides detailed information about failures during version conversion,
/// including which version was being converted from.
#[derive(Debug)]
pub struct VersionConversionError {
    /// The version number that failed to convert (e.g., "1", "2")
    pub version: String,
    /// The underlying error that occurred during conversion
    pub source: Box<dyn Error + Send + Sync + 'static>,
    /// Additional context about the conversion failure
    pub context: Option<String>,
}

impl VersionConversionError {
    /// Creates a new `VersionConversionError` with the specified version and source error.
    ///
    /// # Arguments
    ///
    /// * `version` - The version number that failed to convert
    /// * `source` - The underlying error that occurred
    pub fn new(version: impl Into<String>, source: Box<dyn Error + Send + Sync + 'static>) -> Self {
        Self {
            version: version.into(),
            source,
            context: None,
        }
    }

    /// Creates a new `VersionConversionError` with additional context.
    ///
    /// # Arguments
    ///
    /// * `version` - The version number that failed to convert
    /// * `source` - The underlying error that occurred
    /// * `context` - Additional context about the failure
    pub fn with_context(
        version: impl Into<String>,
        source: Box<dyn Error + Send + Sync + 'static>,
        context: impl Into<String>,
    ) -> Self {
        Self {
            version: version.into(),
            source,
            context: Some(context.into()),
        }
    }

    /// Returns the version number that failed to convert.
    #[must_use]
    pub fn version(&self) -> &str {
        &self.version
    }
}

impl Error for VersionConversionError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        Some(self.source.as_ref())
    }
}

impl std::fmt::Display for VersionConversionError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Failed to convert from version {}: {}",
            self.version, self.source
        )?;
        if let Some(ref context) = self.context {
            write!(f, " ({context})")?;
        }
        Ok(())
    }
}

/// Error type for format conversion operations.
///
/// This enum represents errors that can occur when using `from_format` or `to_format` methods.
#[derive(Debug)]
pub enum FormatError<E> {
    /// Error occurred during deserialization of the input string.
    ///
    /// This variant contains the original deserialization error from the format parser.
    Deserialize {
        /// The original deserialization error
        error: E,
        /// The input string that failed to deserialize (if available)
        input: Option<String>,
    },
    /// Error occurred during conversion from a versioned struct to the current struct.
    ///
    /// This variant contains detailed information about the version conversion failure.
    VersionConversion(VersionConversionError),
}

impl<E: Error + Send + Sync + 'static> FormatError<E> {
    /// Creates a new `Deserialize` variant with the error and optional input.
    pub const fn deserialize(error: E, input: Option<String>) -> Self {
        Self::Deserialize { error, input }
    }

    /// Creates a new `VersionConversion` variant from a version conversion error.
    pub fn version_conversion(
        version: impl Into<String>,
        source: Box<dyn Error + Send + Sync + 'static>,
    ) -> Self {
        Self::VersionConversion(VersionConversionError::new(version, source))
    }

    /// Returns `true` if this is a deserialization error.
    pub const fn is_deserialize(&self) -> bool {
        matches!(self, Self::Deserialize { .. })
    }

    /// Returns `true` if this is a version conversion error.
    pub const fn is_version_conversion(&self) -> bool {
        matches!(self, Self::VersionConversion(_))
    }
}

impl<E: Error + Send + Sync + 'static> Error for FormatError<E> {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::Deserialize { error, .. } => Some(error),
            Self::VersionConversion(e) => e.source(),
        }
    }
}

impl<E: Error + Send + Sync + 'static> std::fmt::Display for FormatError<E> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Deserialize { error, input } => {
                write!(f, "Deserialization error: {error}")?;
                if let Some(input_str) = input {
                    // Truncate long inputs for readability
                    if input_str.len() > 100 {
                        write!(f, " (input: {:?}...)", &input_str[..100])?;
                    } else {
                        write!(f, " (input: {input_str:?})")?;
                    }
                }
                Ok(())
            }
            Self::VersionConversion(e) => {
                write!(f, "Version conversion error: {e}")
            }
        }
    }
}