Last active
September 6, 2024 09:08
-
-
Save decatur/52428de4a019db9a9df2bf77c0ce97d7 to your computer and use it in GitHub Desktop.
Demonstrate Schema Evolution
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! # Demonstrate Schema Evolution in Rust | |
//! A structure must deserialized from several, possibly persistent, variants. | |
//! | |
//! ┌────────────────────────────────────────────┐ | |
//! │ MyStruct │ | |
//! └────────────────────────────────────────────┘ | |
//! ▲ ▲ ▲ │ | |
//! from from from into | |
//! │ │ │ ▼ | |
//! ┌───────┐ ┌───────┐ ┌───────┐ | |
//! │ V09 │ │ V10 │ │ V11 │ | |
//! └───────┘ └───────┘ └───────┘ | |
//! ▲ ▲ ▲ │ | |
//! read read read write | |
//! │ │ │ ▼ | |
//! ┌────────────────────────────────────────────┐ | |
//! │ Persistence │ | |
//! └────────────────────────────────────────────┘ | |
//! | |
//! - Initial variant may not be tagged, e.g. by oversight. | |
//! - Legacy DTOs are read-only. | |
//! - Explicit (verbose) for maintainability. | |
//! | |
use serde::{Deserialize, Serialize}; | |
type PowerKW = f64; | |
#[derive(Debug, Serialize, Deserialize)] | |
struct PowerRange { | |
min: PowerKW, | |
max: PowerKW, | |
} | |
#[derive(Debug)] | |
struct MyStruct { | |
power_range: PowerRange, | |
} | |
/// The current, non-legacy DTO for MyStruct. | |
#[derive(Serialize, Deserialize)] | |
struct DtoV11 { | |
power_range: PowerRange, | |
} | |
impl From<DtoV11> for MyStruct { | |
fn from(value: DtoV11) -> Self { | |
let DtoV11 { power_range } = value; | |
assert!(power_range.min <= power_range.max); | |
MyStruct { power_range } | |
} | |
} | |
impl From<MyStruct> for DtoV11 { | |
fn from(value: MyStruct) -> Self { | |
let MyStruct { power_range } = value; | |
DtoV11 { power_range } | |
} | |
} | |
fn read(json: &str) -> Result<MyStruct, serde_json::Error> { | |
/// Start legacy DTOs for MyStruct. | |
#[derive(Deserialize)] | |
struct DtoV10 { | |
power_min: PowerKW, | |
power_max: PowerKW, | |
} | |
impl From<DtoV10> for MyStruct { | |
fn from(value: DtoV10) -> Self { | |
let DtoV10 { | |
power_min: min, | |
power_max: max, | |
} = value; | |
MyStruct { | |
power_range: PowerRange { min, max }, | |
} | |
} | |
} | |
#[derive(Deserialize)] | |
struct DtoV09 { | |
power: PowerKW, | |
} | |
impl From<DtoV09> for MyStruct { | |
fn from(value: DtoV09) -> Self { | |
let DtoV09 { power } = value; | |
MyStruct { | |
power_range: PowerRange { | |
min: 0., | |
max: power, | |
}, | |
} | |
} | |
} | |
/// End legacy DTOs for MyStruct. | |
#[derive(Deserialize)] | |
#[serde(tag = "tag")] | |
enum Version { | |
V11(DtoV11), | |
V10(DtoV10), | |
#[serde(untagged)] | |
V09(DtoV09), | |
} | |
impl From<Version> for MyStruct { | |
fn from(value: Version) -> Self { | |
match value { | |
Version::V11(v) => v.into(), | |
Version::V10(v) => v.into(), | |
Version::V09(v) => v.into(), | |
} | |
} | |
} | |
Ok(serde_json::from_str::<Version>(json)?.into()) | |
} | |
fn write(s: MyStruct) -> Result<String, serde_json::Error> { | |
#[derive(Serialize)] | |
#[serde(tag = "tag")] | |
enum TaggingEnum { | |
V11(DtoV11), | |
} | |
serde_json::to_string(&TaggingEnum::V11(s.into())) | |
} | |
#[test] | |
fn test_main() { | |
main().unwrap(); | |
} | |
#[allow(dead_code)] | |
fn main() -> Result<(), serde_json::Error> { | |
// Wrong type, missing attribute, and we do not even have a tag! | |
let s = read(r#"{"power": 100}"#)?; | |
println!("{s:?}"); // MyStruct { power_range: PowerRange { min: 0.0, max: 100.0 } } | |
// Missing attribute | |
let s = read(r#"{"tag":"V10", "power_min": 10, "power_max":100}"#)?; | |
println!("{s:?}"); // MyStruct { power_range: PowerRange { min: 10.0, max: 100.0 } } | |
// This is trailing MyStruct | |
let s = read(r#"{"tag":"V11", "power_range":{"min":10, "max":100}}"#)?; | |
println!("{s:?}"); // MyStruct { power_range: PowerRange { min: 10.0, max: 100.0 } } | |
let json = write(s)?; | |
println!("{json:?}"); // "{\"tag\":\"V11\",\"power_range\":{\"min\":10.0,\"max\":100.0}}" | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Write via
DtoV11
, do not writeMyStruct
directly.