use crate::migration::fields::{FieldDescriptor, Role};
use crate::store::StorePath;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
pub struct PrefixMeta {
#[serde(default)]
pub versions: BTreeMap<String, u32>,
}
impl PrefixMeta {
pub fn at(version: u32) -> Self {
let mut meta = Self::default();
meta.set_version(None, version);
meta
}
pub fn version_of(&self, id: Option<&str>) -> Option<u32> {
self.versions.get(id.unwrap_or_default()).copied()
}
pub fn set_version(&mut self, id: Option<&str>, version: u32) {
self.versions
.insert(id.unwrap_or_default().to_string(), version);
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct StoredShape {
pub role: Role,
pub optional: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub children: Vec<StoredFieldEntry>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub flattened: bool,
}
impl StoredShape {
pub const fn field() -> Self {
Self {
role: Role::Field,
optional: false,
children: Vec::new(),
flattened: false,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct StoredFieldEntry {
pub name: StorePath,
pub type_name: String,
pub shape: StoredShape,
}
impl From<&FieldDescriptor> for StoredFieldEntry {
fn from(field: &FieldDescriptor) -> Self {
Self {
name: field.name.path(),
type_name: field.type_name.to_string(),
shape: StoredShape {
role: field.role,
optional: field.optional,
children: field.children.iter().map(Self::from).collect(),
flattened: field.flattened,
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct SchemaSnapshot {
pub version: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub struct_name: Option<String>,
pub fields: Vec<StoredFieldEntry>,
}
#[cfg(all(test, feature = "json"))]
mod tests {
use super::*;
use crate::migration::AppliedStep;
use serde_json::{Value, json};
fn with_a_stranger(mut record: Value) -> Value {
record["a_field_from_a_later_build"] = json!("whatever it holds");
record
}
#[test]
fn a_record_carries_a_field_this_build_has_no_name_for() {
let shape = json!({ "role": "field", "optional": false });
let entry = json!({ "name": "width", "type_name": "u32", "shape": shape });
serde_json::from_value::<PrefixMeta>(with_a_stranger(json!({ "versions": { "": 3 } })))
.unwrap();
serde_json::from_value::<StoredShape>(with_a_stranger(shape.clone())).unwrap();
serde_json::from_value::<StoredFieldEntry>(with_a_stranger(entry.clone())).unwrap();
serde_json::from_value::<SchemaSnapshot>(with_a_stranger(json!({
"version": 1,
"struct_name": "Ui",
"fields": [entry],
})))
.unwrap();
serde_json::from_value::<AppliedStep>(with_a_stranger(json!({
"prefix": "ui",
"target_version": 2,
"description": null,
"applied_at": 0,
})))
.unwrap();
}
}