mirror of
https://github.com/Blah-IM/blahrs.git
synced 2025-05-01 00:31:09 +00:00
Simplify rich text on-wire format
This commit is contained in:
parent
c492bb2537
commit
370722731b
5 changed files with 49 additions and 73 deletions
59
Cargo.lock
generated
59
Cargo.lock
generated
|
@ -107,7 +107,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -240,8 +240,7 @@ dependencies = [
|
||||||
"serde-aux",
|
"serde-aux",
|
||||||
"serde-constant",
|
"serde-constant",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_tuple",
|
"syn",
|
||||||
"syn 2.0.76",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
@ -352,7 +351,7 @@ dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -432,7 +431,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -585,7 +584,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1051,7 +1050,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1110,7 +1109,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1432,7 +1431,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1457,27 +1456,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_tuple"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f4f025b91216f15a2a32aa39669329a475733590a015835d1783549a56d09427"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_tuple_macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_tuple_macros"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4076151d1a2b688e25aaf236997933c66e18b870d0369f8b248b8ab2be630d7e"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -1579,17 +1557,6 @@ version = "2.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0.109"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.76"
|
version = "2.0.76"
|
||||||
|
@ -1699,7 +1666,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1812,7 +1779,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1977,7 +1944,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2011,7 +1978,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
@ -2193,7 +2160,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.76",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -22,7 +22,6 @@ serde = { version = "1.0.209", features = ["derive"] }
|
||||||
serde-aux = "4.5.0"
|
serde-aux = "4.5.0"
|
||||||
serde-constant = "0.1.0"
|
serde-constant = "0.1.0"
|
||||||
serde_json = "1.0.127"
|
serde_json = "1.0.127"
|
||||||
serde_tuple = "0.5.0"
|
|
||||||
tokio = { version = "1.39.3", features = ["macros", "rt-multi-thread", "sync"] }
|
tokio = { version = "1.39.3", features = ["macros", "rt-multi-thread", "sync"] }
|
||||||
tokio-stream = { version = "0.1.15", features = ["sync"] }
|
tokio-stream = { version = "0.1.15", features = ["sync"] }
|
||||||
tower-http = { version = "0.5.2", features = ["cors", "limit"] }
|
tower-http = { version = "0.5.2", features = ["cors", "limit"] }
|
||||||
|
|
|
@ -83,7 +83,7 @@ paths:
|
||||||
payload:
|
payload:
|
||||||
typ: chat
|
typ: chat
|
||||||
room: 7ed9e067-ec37-4054-9fc2-b1bd890929bd
|
room: 7ed9e067-ec37-4054-9fc2-b1bd890929bd
|
||||||
rich_text: [["before "],["bold ",{"b":true}],["italic bold ",{"b":true,"i":true}],["end"]]
|
rich_text: ["before ",["bold ",{"b":true}],["italic bold ",{"b":true,"i":true}],"end"]
|
||||||
timestamp: 1724966284
|
timestamp: 1724966284
|
||||||
user: 83ce46ced47ec0391c64846cbb6c507250ead4985b6a044d68751edc46015dd7
|
user: 83ce46ced47ec0391c64846cbb6c507250ead4985b6a044d68751edc46015dd7
|
||||||
responses:
|
responses:
|
||||||
|
|
|
@ -122,8 +122,8 @@ async function showChatMsg(chat) {
|
||||||
|
|
||||||
function richTextToHtml(richText) {
|
function richTextToHtml(richText) {
|
||||||
let ret = ''
|
let ret = ''
|
||||||
for (let [text, attrs] of richText) {
|
for (let e of richText) {
|
||||||
if (attrs === undefined) attrs = {};
|
const [text, attrs] = typeof e === 'string' ? [e, {}] : e;
|
||||||
// Incomplete cases.
|
// Incomplete cases.
|
||||||
const tags = [
|
const tags = [
|
||||||
[attrs.b, 'b'],
|
[attrs.b, 'b'],
|
||||||
|
@ -241,7 +241,7 @@ async function postChat(text) {
|
||||||
if (text.startsWith('[')) {
|
if (text.startsWith('[')) {
|
||||||
richText = JSON.parse(text);
|
richText = JSON.parse(text);
|
||||||
} else {
|
} else {
|
||||||
richText = [[text]];
|
richText = [text];
|
||||||
}
|
}
|
||||||
const signedPayload = await signData({
|
const signedPayload = await signData({
|
||||||
typ: 'chat',
|
typ: 'chat',
|
||||||
|
|
54
src/types.rs
54
src/types.rs
|
@ -12,8 +12,7 @@ use ed25519_dalek::{
|
||||||
Signature, Signer, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH,
|
Signature, Signer, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH,
|
||||||
};
|
};
|
||||||
use rand_core::RngCore;
|
use rand_core::RngCore;
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use serde_tuple::{Deserialize_tuple, Serialize_tuple};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
const TIMESTAMP_TOLERENCE: u64 = 90;
|
const TIMESTAMP_TOLERENCE: u64 = 90;
|
||||||
|
@ -93,22 +92,31 @@ pub struct ChatPayload {
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct RichText(pub Vec<RichTextPiece>);
|
pub struct RichText(pub Vec<RichTextPiece>);
|
||||||
|
|
||||||
// NB. This field is excluded from field order check, because it has tuple representation.
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize_tuple)]
|
|
||||||
pub struct RichTextPiece {
|
pub struct RichTextPiece {
|
||||||
pub text: String,
|
|
||||||
#[serde(skip_serializing_if = "is_default::<TextAttrs>")]
|
|
||||||
pub attrs: TextAttrs,
|
pub attrs: TextAttrs,
|
||||||
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The protocol representation of `RichTextPiece` which keeps nullity of `attrs` for
|
impl Serialize for RichTextPiece {
|
||||||
/// canonicalization check.
|
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
|
||||||
// NB. This field is excluded from field order check, because it has tuple representation.
|
where
|
||||||
#[derive(Debug, Deserialize_tuple)]
|
S: Serializer,
|
||||||
struct RichTextPieceRaw {
|
{
|
||||||
pub text: String,
|
if is_default(&self.attrs) {
|
||||||
#[serde(default, skip_serializing_if = "is_default::<TextAttrs>")]
|
self.text.serialize(ser)
|
||||||
pub attrs: Option<TextAttrs>,
|
} else {
|
||||||
|
(&self.text, &self.attrs).serialize(ser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The protocol representation of `RichTextPiece`.
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum RichTextPieceRaw {
|
||||||
|
Text(String),
|
||||||
|
TextWithAttrs(String, TextAttrs),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_default<T: Default + PartialEq>(v: &T) -> bool {
|
fn is_default<T: Default + PartialEq>(v: &T) -> bool {
|
||||||
|
@ -123,16 +131,19 @@ impl<'de> Deserialize<'de> for RichText {
|
||||||
let pieces = <Vec<RichTextPieceRaw>>::deserialize(de)?;
|
let pieces = <Vec<RichTextPieceRaw>>::deserialize(de)?;
|
||||||
if pieces
|
if pieces
|
||||||
.iter()
|
.iter()
|
||||||
.any(|p| matches!(&p.attrs, Some(attrs) if is_default(attrs)))
|
.any(|p| matches!(&p, RichTextPieceRaw::TextWithAttrs(_, attrs) if is_default(attrs)))
|
||||||
{
|
{
|
||||||
return Err(de::Error::custom("not in canonical form"));
|
return Err(de::Error::custom("not in canonical form"));
|
||||||
}
|
}
|
||||||
let this = Self(
|
let this = Self(
|
||||||
pieces
|
pieces
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|RichTextPieceRaw { text, attrs }| RichTextPiece {
|
.map(|raw| {
|
||||||
text,
|
let (text, attrs) = match raw {
|
||||||
attrs: attrs.unwrap_or_default(),
|
RichTextPieceRaw::Text(text) => (text, TextAttrs::default()),
|
||||||
|
RichTextPieceRaw::TextWithAttrs(text, attrs) => (text, attrs),
|
||||||
|
};
|
||||||
|
RichTextPiece { text, attrs }
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
|
@ -144,7 +155,7 @@ impl<'de> Deserialize<'de> for RichText {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This protocol format is quite large. Could use bitflags for database.
|
// TODO: This protocol format is quite large. Could use bitflags for database.
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct TextAttrs {
|
pub struct TextAttrs {
|
||||||
#[serde(default, rename = "b", skip_serializing_if = "is_default")]
|
#[serde(default, rename = "b", skip_serializing_if = "is_default")]
|
||||||
pub bold: bool,
|
pub bold: bool,
|
||||||
|
@ -286,7 +297,7 @@ pub struct RoomMemberList(pub Vec<RoomMember>);
|
||||||
impl Serialize for RoomMemberList {
|
impl Serialize for RoomMemberList {
|
||||||
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
self.0.serialize(ser)
|
self.0.serialize(ser)
|
||||||
}
|
}
|
||||||
|
@ -465,8 +476,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rich_text_serde() {
|
fn rich_text_serde() {
|
||||||
let raw =
|
let raw = r#"["before ",["bold ",{"b":true}],["italic bold ",{"b":true,"i":true}],"end"]"#;
|
||||||
r#"[["before "],["bold ",{"b":true}],["italic bold ",{"b":true,"i":true}],["end"]]"#;
|
|
||||||
let text = serde_json::from_str::<RichText>(raw).unwrap();
|
let text = serde_json::from_str::<RichText>(raw).unwrap();
|
||||||
assert!(text.is_canonical());
|
assert!(text.is_canonical());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
Loading…
Add table
Reference in a new issue