dotloom_document/
migrate.rs1use serde_json::Value;
8
9use crate::{DocError, Document, Limits};
10
11pub const SCHEMA_VERSION: u32 = 1;
13
14pub const OLDEST_SUPPORTED_SCHEMA: u32 = 1;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct MigrationNote {
20 pub from: u32,
22 pub message: String,
24}
25
26pub fn schema_of(v: &Value) -> Result<u32, DocError> {
28 let s =
29 v.get("schema").and_then(Value::as_u64).ok_or_else(|| DocError::Malformed("missing `schema` field".into()))?;
30 u32::try_from(s).map_err(|_| DocError::Malformed("schema out of range".into()))
31}
32
33type Step = fn(Value) -> Result<(Value, String), DocError>;
34
35const STEPS: &[Step] = &[];
37
38pub fn migrate(mut v: Value) -> Result<(Value, Vec<MigrationNote>), DocError> {
40 let mut schema = schema_of(&v)?;
41 if schema > SCHEMA_VERSION {
42 return Err(DocError::FutureSchema { found: schema, supported: SCHEMA_VERSION });
43 }
44 if schema < OLDEST_SUPPORTED_SCHEMA {
45 return Err(DocError::UnsupportedSchema(schema));
46 }
47 let mut notes = Vec::new();
48 while schema < SCHEMA_VERSION {
49 let idx = (schema - OLDEST_SUPPORTED_SCHEMA) as usize;
50 let step = STEPS.get(idx).ok_or(DocError::UnsupportedSchema(schema))?;
51 let (nv, message) = step(v)?;
52 notes.push(MigrationNote { from: schema, message });
53 v = nv;
54 schema += 1;
55 if let Some(obj) = v.as_object_mut() {
56 obj.insert("schema".into(), Value::from(schema));
57 }
58 }
59 Ok((v, notes))
60}
61
62pub const MAX_JSON_DEPTH: usize = 128;
64
65fn depth(v: &Value, d: usize) -> usize {
66 if d > MAX_JSON_DEPTH {
67 return d;
68 }
69 match v {
70 Value::Array(a) => a.iter().map(|x| depth(x, d + 1)).max().unwrap_or(d + 1),
71 Value::Object(o) => o.values().map(|x| depth(x, d + 1)).max().unwrap_or(d + 1),
72 _ => d,
73 }
74}
75
76impl Document {
77 pub fn from_json_str(s: &str, limits: &Limits) -> Result<(Self, Vec<MigrationNote>), DocError> {
84 Self::from_json_slice(s.as_bytes(), limits)
85 }
86
87 pub fn from_json_slice(bytes: &[u8], limits: &Limits) -> Result<(Self, Vec<MigrationNote>), DocError> {
89 #[derive(serde::Deserialize)]
90 struct Peek {
91 schema: Option<u64>,
92 }
93 let peek: Peek = serde_json::from_slice(bytes).map_err(|e| DocError::Malformed(e.to_string()))?;
94 if peek.schema == Some(u64::from(SCHEMA_VERSION)) {
95 let doc: Self = serde_json::from_slice(bytes).map_err(|e| DocError::Malformed(e.to_string()))?;
96 if let Some(first) = doc.validate_all(limits).into_iter().next() {
97 return Err(first);
98 }
99 return Ok((doc, Vec::new()));
100 }
101 let v: Value = serde_json::from_slice(bytes).map_err(|e| DocError::Malformed(e.to_string()))?;
102 Self::from_json_value(v, limits)
103 }
104
105 pub fn from_json_value(v: Value, limits: &Limits) -> Result<(Self, Vec<MigrationNote>), DocError> {
107 if depth(&v, 0) > MAX_JSON_DEPTH {
108 return Err(DocError::LimitExceeded(format!("JSON nesting deeper than {MAX_JSON_DEPTH}")));
109 }
110 let (v, notes) = migrate(v)?;
111 let doc: Self = serde_json::from_value(v).map_err(|e| DocError::Malformed(e.to_string()))?;
112 if let Some(first) = doc.validate_all(limits).into_iter().next() {
113 return Err(first);
114 }
115 Ok((doc, notes))
116 }
117
118 pub fn to_json_string(&self) -> Result<String, DocError> {
120 serde_json::to_string_pretty(self).map_err(|e| DocError::Malformed(e.to_string()))
121 }
122}