mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 14:32:22 -04:00

The approach in #2542 unfortunately introduced a regression, as whilst it ensured that duplicate keys are removed when downgrading, it no longer prevented the duplicates from being removed when converting to a legacy Schema11 object. This resulted in things like backend.get_notetype_legacy() returning duplicate keys, and could break syncing: https://forums.ankiweb.net/t/windows-desktop-sync-error/33128 As syncing and schema11 object usage is quite common compared to downgrading, the extra Value deserialization seemed a bit expensive, so I've switched back to explicitly removing the problem keys. To ensure we don't forget to add new keys in the future, I've added some new tests that should alert us whenever a newly-added key is missing from the reserved list.
101 lines
2.5 KiB
Rust
101 lines
2.5 KiB
Rust
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
use serde::Deserialize as DeTrait;
|
|
use serde::Deserializer;
|
|
pub(crate) use serde_aux::field_attributes::deserialize_bool_from_anything;
|
|
pub(crate) use serde_aux::field_attributes::deserialize_number_from_string;
|
|
use serde_json::Value;
|
|
|
|
use crate::timestamp::TimestampSecs;
|
|
|
|
/// Note: if you wish to cover the case where a field is missing, make sure you
|
|
/// also use the `serde(default)` flag.
|
|
pub(crate) fn default_on_invalid<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
|
where
|
|
T: Default + DeTrait<'de>,
|
|
D: Deserializer<'de>,
|
|
{
|
|
let v: Value = DeTrait::deserialize(deserializer)?;
|
|
Ok(T::deserialize(v).unwrap_or_default())
|
|
}
|
|
|
|
pub(crate) fn is_default<T: Default + PartialEq>(t: &T) -> bool {
|
|
*t == Default::default()
|
|
}
|
|
|
|
pub(crate) fn deserialize_int_from_number<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
T: serde::Deserialize<'de> + FromI64,
|
|
{
|
|
#[derive(DeTrait)]
|
|
#[serde(untagged)]
|
|
enum IntOrFloat {
|
|
Int(i64),
|
|
Float(f64),
|
|
}
|
|
|
|
match IntOrFloat::deserialize(deserializer)? {
|
|
IntOrFloat::Float(f) => Ok(T::from_i64(f as i64)),
|
|
IntOrFloat::Int(i) => Ok(T::from_i64(i)),
|
|
}
|
|
}
|
|
|
|
// It may be possible to use the num_traits crate instead in the future.
|
|
pub(crate) trait FromI64 {
|
|
fn from_i64(val: i64) -> Self;
|
|
}
|
|
|
|
impl FromI64 for i32 {
|
|
fn from_i64(val: i64) -> Self {
|
|
val as Self
|
|
}
|
|
}
|
|
|
|
impl FromI64 for u32 {
|
|
fn from_i64(val: i64) -> Self {
|
|
val.max(0) as Self
|
|
}
|
|
}
|
|
|
|
impl FromI64 for i64 {
|
|
fn from_i64(val: i64) -> Self {
|
|
val
|
|
}
|
|
}
|
|
|
|
impl FromI64 for TimestampSecs {
|
|
fn from_i64(val: i64) -> Self {
|
|
TimestampSecs(val)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use serde::Deserialize;
|
|
|
|
use super::*;
|
|
|
|
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
|
struct MaybeInvalid {
|
|
#[serde(deserialize_with = "default_on_invalid", default)]
|
|
field: Option<usize>,
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_or_missing() {
|
|
assert_eq!(
|
|
serde_json::from_str::<MaybeInvalid>(r#"{"field": 5}"#).unwrap(),
|
|
MaybeInvalid { field: Some(5) }
|
|
);
|
|
assert_eq!(
|
|
serde_json::from_str::<MaybeInvalid>(r#"{"field": "5"}"#).unwrap(),
|
|
MaybeInvalid { field: None }
|
|
);
|
|
assert_eq!(
|
|
serde_json::from_str::<MaybeInvalid>(r#"{"another": 5}"#).unwrap(),
|
|
MaybeInvalid { field: None }
|
|
);
|
|
}
|
|
}
|