events: Parse TagInfo::order
as a f64 or a stringified f64
This commit is contained in:
parent
4ac9e9a979
commit
2c8ece6bf2
@ -4,6 +4,8 @@ Bug fixes:
|
|||||||
|
|
||||||
- Set the predefined server-default `.m.rule.tombstone` push rule as enabled by default, as defined
|
- Set the predefined server-default `.m.rule.tombstone` push rule as enabled by default, as defined
|
||||||
in the spec.
|
in the spec.
|
||||||
|
- Parse `m.tag` `order` as a f64 value or a stringified f64 value, if the `compat-tag-info` feature
|
||||||
|
is enabled.
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
|
||||||
|
@ -69,6 +69,9 @@ compat-null = []
|
|||||||
# mandatory. Deserialization will yield a default value like an empty string.
|
# mandatory. Deserialization will yield a default value like an empty string.
|
||||||
compat-optional = []
|
compat-optional = []
|
||||||
|
|
||||||
|
# Allow TagInfo to contain a stringified floating-point value for the `order` field.
|
||||||
|
compat-tag-info = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
bytes = "1.0.1"
|
bytes = "1.0.1"
|
||||||
|
@ -9,6 +9,9 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{serde::deserialize_cow_str, PrivOwnedStr};
|
use crate::{serde::deserialize_cow_str, PrivOwnedStr};
|
||||||
|
|
||||||
|
#[cfg(feature = "compat-tag-info")]
|
||||||
|
use crate::serde::deserialize_as_optional_f64_or_string;
|
||||||
|
|
||||||
/// Map of tag names to tag info.
|
/// Map of tag names to tag info.
|
||||||
pub type Tags = BTreeMap<TagName, TagInfo>;
|
pub type Tags = BTreeMap<TagName, TagInfo>;
|
||||||
|
|
||||||
@ -167,11 +170,18 @@ impl Serialize for TagName {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Information about a tag.
|
/// Information about a tag.
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
pub struct TagInfo {
|
pub struct TagInfo {
|
||||||
/// Value to use for lexicographically ordering rooms with this tag.
|
/// Value to use for lexicographically ordering rooms with this tag.
|
||||||
|
///
|
||||||
|
/// If you activate the `compat-tag-info` feature, this field can be decoded as a stringified
|
||||||
|
/// floating-point value, instead of a number as it should be according to the specification.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "compat-tag-info",
|
||||||
|
serde(default, deserialize_with = "deserialize_as_optional_f64_or_string")
|
||||||
|
)]
|
||||||
pub order: Option<f64>,
|
pub order: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +195,7 @@ impl TagInfo {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use maplit::btreemap;
|
use maplit::btreemap;
|
||||||
use serde_json::{json, to_value as to_json_value};
|
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||||
|
|
||||||
use super::{TagEventContent, TagInfo, TagName};
|
use super::{TagEventContent, TagInfo, TagName};
|
||||||
|
|
||||||
@ -215,6 +225,33 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_tag_info() {
|
||||||
|
let json = json!({});
|
||||||
|
assert_eq!(from_json_value::<TagInfo>(json).unwrap(), TagInfo::default());
|
||||||
|
|
||||||
|
let json = json!({ "order": null });
|
||||||
|
assert_eq!(from_json_value::<TagInfo>(json).unwrap(), TagInfo::default());
|
||||||
|
|
||||||
|
let json = json!({ "order": 0.42 });
|
||||||
|
assert_eq!(from_json_value::<TagInfo>(json).unwrap(), TagInfo { order: Some(0.42) });
|
||||||
|
|
||||||
|
#[cfg(feature = "compat-tag-info")]
|
||||||
|
{
|
||||||
|
let json = json!({ "order": "0.5" });
|
||||||
|
assert_eq!(from_json_value::<TagInfo>(json).unwrap(), TagInfo { order: Some(0.5) });
|
||||||
|
|
||||||
|
let json = json!({ "order": ".5" });
|
||||||
|
assert_eq!(from_json_value::<TagInfo>(json).unwrap(), TagInfo { order: Some(0.5) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "compat-tag-info"))]
|
||||||
|
{
|
||||||
|
let json = json!({ "order": "0.5" });
|
||||||
|
assert!(from_json_value::<TagInfo>(json).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_name() {
|
fn display_name() {
|
||||||
assert_eq!(TagName::Favorite.display_name(), "favourite");
|
assert_eq!(TagName::Favorite.display_name(), "favourite");
|
||||||
|
@ -26,7 +26,8 @@ pub use self::{
|
|||||||
cow::deserialize_cow_str,
|
cow::deserialize_cow_str,
|
||||||
raw::Raw,
|
raw::Raw,
|
||||||
strings::{
|
strings::{
|
||||||
btreemap_deserialize_v1_powerlevel_values, deserialize_v1_powerlevel, empty_string_as_none,
|
btreemap_deserialize_v1_powerlevel_values, deserialize_as_f64_or_string,
|
||||||
|
deserialize_as_optional_f64_or_string, deserialize_v1_powerlevel, empty_string_as_none,
|
||||||
none_as_empty_string,
|
none_as_empty_string,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -49,6 +49,58 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take either a floating point number or a string and deserialize to an floating-point number.
|
||||||
|
///
|
||||||
|
/// To be used like this:
|
||||||
|
/// `#[serde(deserialize_with = "deserialize_as_f64_or_string")]`
|
||||||
|
pub fn deserialize_as_f64_or_string<'de, D>(de: D) -> Result<f64, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct F64OrStringVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for F64OrStringVisitor {
|
||||||
|
type Value = f64;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
formatter.write_str("a double or a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
Ok(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||||
|
v.parse().map_err(E::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
de.deserialize_any(F64OrStringVisitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct F64OrStringWrapper(#[serde(deserialize_with = "deserialize_as_f64_or_string")] f64);
|
||||||
|
|
||||||
|
/// Deserializes an `Option<f64>` as encoded as a f64 or a string.
|
||||||
|
pub fn deserialize_as_optional_f64_or_string<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Option<f64>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Ok(Option::<F64OrStringWrapper>::deserialize(deserializer)?.map(|w| w.0))
|
||||||
|
}
|
||||||
|
|
||||||
/// Take either an integer number or a string and deserialize to an integer number.
|
/// Take either an integer number or a string and deserialize to an integer number.
|
||||||
///
|
///
|
||||||
/// To be used like this:
|
/// To be used like this:
|
||||||
@ -160,7 +212,7 @@ where
|
|||||||
type Value = BTreeMap<T, Int>;
|
type Value = BTreeMap<T, Int>;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
formatter.write_str("a map with integers or stings as values")
|
formatter.write_str("a map with integers or strings as values")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
|
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
|
||||||
|
@ -98,6 +98,7 @@ compat = [
|
|||||||
"compat-unset-avatar",
|
"compat-unset-avatar",
|
||||||
"compat-get-3pids",
|
"compat-get-3pids",
|
||||||
"compat-signature-id",
|
"compat-signature-id",
|
||||||
|
"compat-tag-info",
|
||||||
]
|
]
|
||||||
# Don't validate the version part in `KeyId`.
|
# Don't validate the version part in `KeyId`.
|
||||||
compat-key-id = ["ruma-common/compat-key-id"]
|
compat-key-id = ["ruma-common/compat-key-id"]
|
||||||
@ -128,6 +129,8 @@ compat-get-3pids = ["ruma-client-api?/compat-get-3pids"]
|
|||||||
compat-upload-signatures = ["ruma-client-api?/compat-upload-signatures"]
|
compat-upload-signatures = ["ruma-client-api?/compat-upload-signatures"]
|
||||||
# Allow extra characters in signature IDs not allowed in the specification.
|
# Allow extra characters in signature IDs not allowed in the specification.
|
||||||
compat-signature-id = ["ruma-signatures?/compat-signature-id"]
|
compat-signature-id = ["ruma-signatures?/compat-signature-id"]
|
||||||
|
# Allow TagInfo to contain a stringified floating-point value for the `order` field.
|
||||||
|
compat-tag-info = ["ruma-common/compat-tag-info"]
|
||||||
|
|
||||||
# Specific compatibility for past ring public/private key documents.
|
# Specific compatibility for past ring public/private key documents.
|
||||||
ring-compat = ["dep:ruma-signatures", "ruma-signatures?/ring-compat"]
|
ring-compat = ["dep:ruma-signatures", "ruma-signatures?/ring-compat"]
|
||||||
|
@ -211,7 +211,7 @@ impl CiTask {
|
|||||||
fn test_common(&self) -> Result<()> {
|
fn test_common(&self) -> Result<()> {
|
||||||
cmd!(
|
cmd!(
|
||||||
"rustup run stable cargo test -p ruma-common
|
"rustup run stable cargo test -p ruma-common
|
||||||
--features events,compat-empty-string-null,compat-user-id compat"
|
--features events,compat-empty-string-null,compat-user-id,compat-tag-info compat"
|
||||||
)
|
)
|
||||||
.run()
|
.run()
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user