Skip to main content

thisversion/
traits.rs

1//! Traits for converting between versioned schemas and application models.
2//!
3//! A schema type represents data in a persisted format; an application model
4//! is the representation the program uses. Implement [`IntoSchema`] to choose
5//! a schema representation when writing data, and implement [`IntoModel`] or
6//! [`TryIntoModel`] to read schema data into the application model.
7//!
8//! These traits consume their input so conversions can move owned fields
9//! without cloning. Container implementations for `Option` (and, with the
10//! `alloc` feature, `Vec`) apply the same conversion to each contained value.
11//! The derive macros can generate the schema and version-family conversions
12//! for common cases.
13
14//============================================================================//
15// INTO SCHEMA                                                                //
16//============================================================================//
17
18/// Converts an application model into a selected schema representation.
19///
20/// The result is a data value in the requested representation. It may be a bare
21/// schema value, a version-family value, or a container of schema values. A
22/// model may support multiple target representations.
23///
24/// The `VersionBoundary` derive constructs the latest schema value, then uses
25/// `From` to convert it into `S`. Requesting its version family selects the
26/// latest variant; requesting an older schema requires an explicit conversion
27/// from the latest schema. Such conversions may discard information, so a round
28/// trip through a schema need not preserve the original model.
29pub trait IntoSchema<S>: Sized {
30    /// Converts this model into the requested schema representation.
31    fn into_schema(self) -> S;
32}
33
34impl<T, S> IntoSchema<Option<S>> for Option<T>
35where
36    T: IntoSchema<S>,
37{
38    fn into_schema(self) -> Option<S> {
39        self.map(IntoSchema::into_schema)
40    }
41}
42
43#[cfg(feature = "alloc")]
44impl<T, S> IntoSchema<alloc::vec::Vec<S>> for alloc::vec::Vec<T>
45where
46    T: IntoSchema<S>,
47{
48    fn into_schema(self) -> alloc::vec::Vec<S> {
49        self.into_iter().map(IntoSchema::into_schema).collect()
50    }
51}
52
53//============================================================================//
54// INTO MODEL                                                                 //
55//============================================================================//
56
57/// Converts a schema value into its application model without a recoverable
58/// conversion error.
59///
60/// Implement this when every value of the schema type can be represented by the
61/// model. Use [`TryIntoModel`] when conversion can reject a value.
62pub trait IntoModel: Sized {
63    /// The application model produced from this schema type.
64    type Model;
65
66    /// Converts this schema value into its application model.
67    fn into_model(self) -> Self::Model;
68}
69
70impl<T: IntoModel> IntoModel for Option<T> {
71    type Model = Option<T::Model>;
72
73    fn into_model(self) -> Self::Model {
74        self.map(IntoModel::into_model)
75    }
76}
77
78#[cfg(feature = "alloc")]
79impl<T: IntoModel> IntoModel for alloc::vec::Vec<T> {
80    type Model = alloc::vec::Vec<T::Model>;
81
82    fn into_model(self) -> Self::Model {
83        self.into_iter().map(IntoModel::into_model).collect()
84    }
85}
86
87//============================================================================//
88// TRY INTO MODEL                                                             //
89//============================================================================//
90
91/// Converts a schema, version family, or container into an application model,
92/// allowing conversion to fail.
93///
94/// Fallible latest schemas choose the final error type. Fallible version-family
95/// derives use that same type while migrating and converting the latest schema.
96/// Recursive fields propagate their errors in the same way.
97///
98/// Deriving `VersionBoundary` without an error type also supplies
99/// `TryIntoModel` with [`core::convert::Infallible`] as its error, so both
100/// model conversion entry points remain available to adapters.
101pub trait TryIntoModel: Sized {
102    /// The application model produced from this schema type.
103    type Model;
104
105    /// The error returned when a schema value cannot be represented by the
106    /// application model.
107    type Error;
108
109    /// Converts this schema value into its application model.
110    fn try_into_model(self) -> Result<Self::Model, Self::Error>;
111}
112
113impl<T: TryIntoModel> TryIntoModel for Option<T> {
114    type Model = Option<T::Model>;
115    type Error = T::Error;
116
117    fn try_into_model(self) -> Result<Self::Model, Self::Error> {
118        self.map(TryIntoModel::try_into_model).transpose()
119    }
120}
121
122#[cfg(feature = "alloc")]
123impl<T: TryIntoModel> TryIntoModel for alloc::vec::Vec<T> {
124    type Model = alloc::vec::Vec<T::Model>;
125    type Error = T::Error;
126
127    fn try_into_model(self) -> Result<Self::Model, Self::Error> {
128        self.into_iter().map(TryIntoModel::try_into_model).collect()
129    }
130}