Add a TransactionId type

This commit is contained in:
Jonas Platte 2022-01-14 23:46:35 +01:00
parent 9bdde6241e
commit ef6728abde
No known key found for this signature in database
GPG Key ID: 7D261D771D915378
23 changed files with 101 additions and 68 deletions

View File

@ -2,6 +2,7 @@
use ruma_api::ruma_api;
use ruma_events::AnyRoomEvent;
use ruma_identifiers::TransactionId;
use ruma_serde::Raw;
ruma_api! {
@ -19,7 +20,7 @@ ruma_api! {
///
/// Homeservers generate these IDs and they are used to ensure idempotency of results.
#[ruma_api(path)]
pub txn_id: &'a str,
pub txn_id: &'a TransactionId,
/// A list of events.
pub events: &'a [Raw<AnyRoomEvent>],
@ -31,14 +32,14 @@ ruma_api! {
impl<'a> Request<'a> {
/// Creates a new `Request` with the given transaction ID and list of events.
pub fn new(txn_id: &'a str, events: &'a [Raw<AnyRoomEvent>]) -> Self {
pub fn new(txn_id: &'a TransactionId, events: &'a [Raw<AnyRoomEvent>]) -> Self {
Self { txn_id, events }
}
}
impl IncomingRequest {
/// Creates an `IncomingRequest` with the given transaction ID and list of events.
pub fn new(txn_id: String, events: Vec<Raw<AnyRoomEvent>>) -> IncomingRequest {
pub fn new(txn_id: Box<TransactionId>, events: Vec<Raw<AnyRoomEvent>>) -> IncomingRequest {
IncomingRequest { txn_id, events }
}
@ -171,7 +172,7 @@ mod tests {
let dummy_event = Raw::new(&dummy_event).unwrap();
let events = vec![dummy_event];
let req = Request { events: &events, txn_id: "any_txn_id" }
let req = Request { events: &events, txn_id: "any_txn_id".into() }
.try_into_http_request::<Vec<u8>>(
"https://homeserver.tld",
SendAccessToken::IfRequired("auth_tok"),

View File

@ -2,7 +2,7 @@
use ruma_api::ruma_api;
use ruma_events::{AnyMessageEventContent, MessageEventContent};
use ruma_identifiers::{EventId, RoomId};
use ruma_identifiers::{EventId, RoomId, TransactionId};
use ruma_serde::Raw;
use serde_json::value::to_raw_value as to_raw_json_value;
@ -31,7 +31,7 @@ ruma_api! {
/// same access token; it will be used by the server to ensure
/// idempotency of requests.
#[ruma_api(path)]
pub txn_id: &'a str,
pub txn_id: &'a TransactionId,
/// The event content to send.
#[ruma_api(body)]
@ -55,7 +55,7 @@ impl<'a> Request<'a> {
/// [`Serialize`][serde::Serialize] implementation can fail.
pub fn new<T: MessageEventContent>(
room_id: &'a RoomId,
txn_id: &'a str,
txn_id: &'a TransactionId,
content: &'a T,
) -> serde_json::Result<Self> {
Ok(Self {
@ -70,7 +70,7 @@ impl<'a> Request<'a> {
/// content.
pub fn new_raw(
room_id: &'a RoomId,
txn_id: &'a str,
txn_id: &'a TransactionId,
event_type: &'a str,
body: Raw<AnyMessageEventContent>,
) -> Self {

View File

@ -1,7 +1,7 @@
//! [PUT /_matrix/client/r0/rooms/{roomId}/redact/{eventId}/{txnId}](https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid)
use ruma_api::ruma_api;
use ruma_identifiers::{EventId, RoomId};
use ruma_identifiers::{EventId, RoomId, TransactionId};
ruma_api! {
metadata: {
@ -27,7 +27,7 @@ ruma_api! {
/// Clients should generate a unique ID; it will be used by the server to ensure idempotency
/// of requests.
#[ruma_api(path)]
pub txn_id: &'a str,
pub txn_id: &'a TransactionId,
/// The reason for the redaction.
#[serde(skip_serializing_if = "Option::is_none")]
@ -44,7 +44,7 @@ ruma_api! {
impl<'a> Request<'a> {
/// Creates a new `Request` with the given room ID, event ID and transaction ID.
pub fn new(room_id: &'a RoomId, event_id: &'a EventId, txn_id: &'a str) -> Self {
pub fn new(room_id: &'a RoomId, event_id: &'a EventId, txn_id: &'a TransactionId) -> Self {
Self { room_id, event_id, txn_id, reason: None }
}
}

View File

@ -5,7 +5,7 @@ use std::collections::BTreeMap;
use ruma_api::ruma_api;
use ruma_common::to_device::DeviceIdOrAllDevices;
use ruma_events::AnyToDeviceEventContent;
use ruma_identifiers::UserId;
use ruma_identifiers::{TransactionId, UserId};
use ruma_serde::Raw;
ruma_api! {
@ -25,7 +25,7 @@ ruma_api! {
/// A request identifier unique to the access token used to send the request.
#[ruma_api(path)]
pub txn_id: &'a str,
pub txn_id: &'a TransactionId,
/// Messages to send.
///
@ -42,7 +42,7 @@ ruma_api! {
impl<'a> Request<'a> {
/// Creates a new `Request` with the given event type, transaction ID and raw messages.
pub fn new_raw(event_type: &'a str, txn_id: &'a str, messages: Messages) -> Self {
pub fn new_raw(event_type: &'a str, txn_id: &'a TransactionId, messages: Messages) -> Self {
Self { event_type, txn_id, messages }
}
}

View File

@ -10,7 +10,7 @@ use ruma_api::{
EndpointError, OutgoingResponse,
};
use ruma_common::thirdparty::Medium;
use ruma_identifiers::{ClientSecret, SessionId};
use ruma_identifiers::{ClientSecret, SessionId, TransactionId};
use ruma_serde::{JsonObject, Outgoing, StringEnum};
use serde::{
de::{self, DeserializeOwned},
@ -470,7 +470,7 @@ pub struct Token<'a> {
pub token: &'a str,
/// The transaction ID.
pub txn_id: &'a str,
pub txn_id: &'a TransactionId,
/// The value of the session key given by the homeserver, if any.
pub session: Option<&'a str>,
@ -478,7 +478,7 @@ pub struct Token<'a> {
impl<'a> Token<'a> {
/// Creates a new `Token` with the given token and transaction ID.
pub fn new(token: &'a str, txn_id: &'a str) -> Self {
pub fn new(token: &'a str, txn_id: &'a TransactionId) -> Self {
Self { token, txn_id, session: None }
}
}

View File

@ -30,7 +30,7 @@ fn deserialize_user_identifier() {
#[test]
fn serialize_auth_data_token() {
let auth_data = AuthData::Token(
assign!(uiaa::Token::new("mytoken", "txn123"), { session: Some("session") }),
assign!(uiaa::Token::new("mytoken", "txn123".into()), { session: Some("session") }),
);
assert_matches!(

View File

@ -5,6 +5,7 @@
use std::collections::BTreeMap;
use ruma_events_macros::EventContent;
use ruma_identifiers::TransactionId;
use ruma_serde::Base64;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
@ -25,7 +26,7 @@ pub struct ToDeviceKeyVerificationAcceptEventContent {
/// An opaque identifier for the verification process.
///
/// Must be the same as the one used for the `m.key.verification.start` message.
pub transaction_id: String,
pub transaction_id: Box<TransactionId>,
/// The method specific content.
#[serde(flatten)]
@ -35,7 +36,7 @@ pub struct ToDeviceKeyVerificationAcceptEventContent {
impl ToDeviceKeyVerificationAcceptEventContent {
/// Creates a new `ToDeviceKeyVerificationAcceptEventContent` with the given transaction ID and
/// method-specific content.
pub fn new(transaction_id: String, method: AcceptMethod) -> Self {
pub fn new(transaction_id: Box<TransactionId>, method: AcceptMethod) -> Self {
Self { transaction_id, method }
}
}

View File

@ -3,6 +3,7 @@
//! [`m.key.verification.cancel`]: https://spec.matrix.org/v1.1/client-server-api/#mkeyverificationcancel
use ruma_events_macros::EventContent;
use ruma_identifiers::TransactionId;
use ruma_serde::StringEnum;
use serde::{Deserialize, Serialize};
@ -19,7 +20,7 @@ use super::Relation;
#[ruma_event(type = "m.key.verification.cancel", kind = ToDevice)]
pub struct ToDeviceKeyVerificationCancelEventContent {
/// The opaque identifier for the verification process/request.
pub transaction_id: String,
pub transaction_id: Box<TransactionId>,
/// A human readable description of the `code`.
///
@ -33,7 +34,7 @@ pub struct ToDeviceKeyVerificationCancelEventContent {
impl ToDeviceKeyVerificationCancelEventContent {
/// Creates a new `ToDeviceKeyVerificationCancelEventContent` with the given transaction ID,
/// reason and code.
pub fn new(transaction_id: String, reason: String, code: CancelCode) -> Self {
pub fn new(transaction_id: Box<TransactionId>, reason: String, code: CancelCode) -> Self {
Self { transaction_id, reason, code }
}
}

View File

@ -3,6 +3,7 @@
//! [`m.key.verification.done`]: https://spec.matrix.org/v1.1/client-server-api/#mkeyverificationdone
use ruma_events_macros::EventContent;
use ruma_identifiers::TransactionId;
use serde::{Deserialize, Serialize};
use super::Relation;
@ -17,12 +18,12 @@ pub struct ToDeviceKeyVerificationDoneEventContent {
/// An opaque identifier for the verification process.
///
/// Must be the same as the one used for the `m.key.verification.start` message.
pub transaction_id: String,
pub transaction_id: Box<TransactionId>,
}
impl ToDeviceKeyVerificationDoneEventContent {
/// Creates a new `ToDeviceKeyVerificationDoneEventContent` with the given transaction ID.
pub fn new(transaction_id: String) -> Self {
pub fn new(transaction_id: Box<TransactionId>) -> Self {
Self { transaction_id }
}
}

View File

@ -3,6 +3,7 @@
//! [`m.key.verification.key`]: https://spec.matrix.org/v1.1/client-server-api/#mkeyverificationkey
use ruma_events_macros::EventContent;
use ruma_identifiers::TransactionId;
use ruma_serde::Base64;
use serde::{Deserialize, Serialize};
@ -19,7 +20,7 @@ pub struct ToDeviceKeyVerificationKeyEventContent {
/// An opaque identifier for the verification process.
///
/// Must be the same as the one used for the `m.key.verification.start` message.
pub transaction_id: String,
pub transaction_id: Box<TransactionId>,
/// The device's ephemeral public key, encoded as unpadded base64.
pub key: Base64,
@ -28,7 +29,7 @@ pub struct ToDeviceKeyVerificationKeyEventContent {
impl ToDeviceKeyVerificationKeyEventContent {
/// Creates a new `ToDeviceKeyVerificationKeyEventContent` with the given transaction ID and
/// key.
pub fn new(transaction_id: String, key: Base64) -> Self {
pub fn new(transaction_id: Box<TransactionId>, key: Base64) -> Self {
Self { transaction_id, key }
}
}

View File

@ -5,6 +5,7 @@
use std::collections::BTreeMap;
use ruma_events_macros::EventContent;
use ruma_identifiers::TransactionId;
use ruma_serde::Base64;
use serde::{Deserialize, Serialize};
@ -21,7 +22,7 @@ pub struct ToDeviceKeyVerificationMacEventContent {
/// An opaque identifier for the verification process.
///
/// Must be the same as the one used for the `m.key.verification.start` message.
pub transaction_id: String,
pub transaction_id: Box<TransactionId>,
/// A map of the key ID to the MAC of the key, using the algorithm in the verification process.
///
@ -36,7 +37,11 @@ pub struct ToDeviceKeyVerificationMacEventContent {
impl ToDeviceKeyVerificationMacEventContent {
/// Creates a new `ToDeviceKeyVerificationMacEventContent` with the given transaction ID, key ID
/// to MAC map and key MAC.
pub fn new(transaction_id: String, mac: BTreeMap<String, Base64>, keys: Base64) -> Self {
pub fn new(
transaction_id: Box<TransactionId>,
mac: BTreeMap<String, Base64>,
keys: Base64,
) -> Self {
Self { transaction_id, mac, keys }
}
}

View File

@ -3,7 +3,7 @@
//! [`m.key.verification.ready`]: https://spec.matrix.org/v1.1/client-server-api/#mkeyverificationready
use ruma_events_macros::EventContent;
use ruma_identifiers::DeviceId;
use ruma_identifiers::{DeviceId, TransactionId};
use serde::{Deserialize, Serialize};
use super::{Relation, VerificationMethod};
@ -26,7 +26,7 @@ pub struct ToDeviceKeyVerificationReadyEventContent {
/// Must be unique with respect to the devices involved. Must be the same as the
/// `transaction_id` given in the `m.key.verification.request` from a
/// request.
pub transaction_id: String,
pub transaction_id: Box<TransactionId>,
}
impl ToDeviceKeyVerificationReadyEventContent {
@ -35,7 +35,7 @@ impl ToDeviceKeyVerificationReadyEventContent {
pub fn new(
from_device: Box<DeviceId>,
methods: Vec<VerificationMethod>,
transaction_id: String,
transaction_id: Box<TransactionId>,
) -> Self {
Self { from_device, methods, transaction_id }
}
@ -111,7 +111,7 @@ mod tests {
let content = ToDeviceKeyVerificationReadyEventContent {
from_device: device,
transaction_id: "456".to_owned(),
transaction_id: "456".into(),
methods: vec![VerificationMethod::SasV1],
};

View File

@ -4,7 +4,7 @@
use ruma_common::MilliSecondsSinceUnixEpoch;
use ruma_events_macros::EventContent;
use ruma_identifiers::DeviceId;
use ruma_identifiers::{DeviceId, TransactionId};
use serde::{Deserialize, Serialize};
use super::VerificationMethod;
@ -20,7 +20,7 @@ pub struct ToDeviceKeyVerificationRequestEventContent {
/// An opaque identifier for the verification request.
///
/// Must be unique with respect to the devices involved.
pub transaction_id: String,
pub transaction_id: Box<TransactionId>,
/// The verification methods supported by the sender.
pub methods: Vec<VerificationMethod>,
@ -37,7 +37,7 @@ impl ToDeviceKeyVerificationRequestEventContent {
/// transaction ID, methods and timestamp.
pub fn new(
from_device: Box<DeviceId>,
transaction_id: String,
transaction_id: Box<TransactionId>,
methods: Vec<VerificationMethod>,
timestamp: MilliSecondsSinceUnixEpoch,
) -> Self {

View File

@ -5,7 +5,7 @@
use std::collections::BTreeMap;
use ruma_events_macros::EventContent;
use ruma_identifiers::DeviceId;
use ruma_identifiers::{DeviceId, TransactionId};
#[cfg(feature = "unstable-pre-spec")]
use ruma_serde::Base64;
use serde::{Deserialize, Serialize};
@ -32,7 +32,7 @@ pub struct ToDeviceKeyVerificationStartEventContent {
/// Must be unique with respect to the devices involved. Must be the same as the
/// `transaction_id` given in the `m.key.verification.request` if this process is originating
/// from a request.
pub transaction_id: String,
pub transaction_id: Box<TransactionId>,
/// Method specific content.
#[serde(flatten)]
@ -42,7 +42,11 @@ pub struct ToDeviceKeyVerificationStartEventContent {
impl ToDeviceKeyVerificationStartEventContent {
/// Creates a new `ToDeviceKeyVerificationStartEventContent` with the given device ID,
/// transaction ID and method specific content.
pub fn new(from_device: Box<DeviceId>, transaction_id: String, method: StartMethod) -> Self {
pub fn new(
from_device: Box<DeviceId>,
transaction_id: Box<TransactionId>,
method: StartMethod,
) -> Self {
Self { from_device, transaction_id, method }
}
}

View File

@ -1,4 +1,5 @@
use js_int::Int;
use ruma_identifiers::TransactionId;
use serde::{Deserialize, Serialize};
#[cfg(feature = "unstable-pre-spec")]
@ -20,7 +21,7 @@ pub struct Unsigned {
/// The client-supplied transaction ID, if the client being given the event is the same one
/// which sent it.
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_id: Option<String>,
pub transaction_id: Option<Box<TransactionId>>,
/// Server-compiled information from other events relating to this event.
#[cfg(feature = "unstable-pre-spec")]
@ -82,7 +83,7 @@ pub struct UnsignedWithPrevContent {
age: Option<Int>,
#[serde(skip_serializing_if = "Option::is_none")]
transaction_id: Option<String>,
transaction_id: Option<Box<TransactionId>>,
#[cfg(feature = "unstable-pre-spec")]
#[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]

View File

@ -4,7 +4,7 @@ use std::collections::BTreeMap;
use ruma_api::ruma_api;
use ruma_common::MilliSecondsSinceUnixEpoch;
use ruma_identifiers::{EventId, ServerName};
use ruma_identifiers::{EventId, ServerName, TransactionId};
use ruma_serde::Raw;
use serde_json::value::RawValue as RawJsonValue;
@ -23,7 +23,7 @@ ruma_api! {
request: {
/// A transaction ID unique between sending and receiving homeservers.
#[ruma_api(path)]
pub transaction_id: &'a str,
pub transaction_id: &'a TransactionId,
/// The server_name of the homeserver sending this transaction.
pub origin: &'a ServerName,
@ -58,7 +58,7 @@ impl<'a> Request<'a> {
///
/// The PDU and EDU lists will start off empty.
pub fn new(
transaction_id: &'a str,
transaction_id: &'a TransactionId,
origin: &'a ServerName,
origin_server_ts: MilliSecondsSinceUnixEpoch,
) -> Self {

View File

@ -21,19 +21,21 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["serde"]
compat = ["ruma-identifiers-validation/compat"]
rand = ["rand_crate", "uuid"]
serde = ["ruma-serde", "serde1"]
unstable-pre-spec = []
[dependencies]
either = { version = "1.6.1", optional = true }
percent-encoding = "2.1.0"
rand = { version = "0.8.3", optional = true }
rand_crate = { package = "rand", version = "0.8.3", optional = true }
ruma-identifiers-macros = { version = "=0.20.0", path = "../ruma-identifiers-macros" }
ruma-identifiers-validation = { version = "0.5.0", path = "../ruma-identifiers-validation", default-features = false }
ruma-serde = { version = "0.5.0", path = "../ruma-serde", optional = true }
ruma-serde-macros = { version = "0.5.0", path = "../ruma-serde-macros" }
# Renamed so we can have a serde feature.
serde1 = { package = "serde", version = "1.0.126", optional = true, features = ["derive"] }
uuid = { version = "0.8.2", optional = true, features = ["v4"] }
[dev-dependencies]
matches = "0.1.8"

View File

@ -8,11 +8,14 @@
#![allow(unused_qualifications)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
// Renamed in `Cargo.toml` so we can have a serde feature.
// Rename it back here because `serde1` is ugly.
// Renamed in `Cargo.toml` so we can features with the same name as the package.
// Rename them back here because the `Cargo.toml` names are ugly.
#[cfg(feature = "serde")]
extern crate serde1 as serde;
#[cfg(feature = "rand")]
extern crate rand_crate as rand;
#[cfg(feature = "serde")]
use std::convert::TryFrom;
@ -38,6 +41,7 @@ pub use crate::{
server_name::ServerName,
session_id::SessionId,
signatures::{DeviceSignatures, EntitySignatures, ServerSignatures, Signatures},
transaction_id::TransactionId,
user_id::UserId,
};
#[doc(inline)]
@ -65,6 +69,7 @@ mod room_version_id;
mod server_name;
mod session_id;
mod signatures;
mod transaction_id;
/// Generates a random identifier localpart.
#[cfg(feature = "rand")]

View File

@ -0,0 +1,25 @@
/// A Matrix transaction ID.
///
/// Transaction IDs in Matrix are opaque strings. This type is provided simply for its semantic
/// value.
///
/// You can create one from a string (using `.into()`) but the recommended way is to use
/// `TransactionId::new()` to generate a random one. If that function is not available for you, you
/// need to activate this crate's `rand` Cargo feature.
#[repr(transparent)]
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TransactionId(str);
impl TransactionId {
/// Creates a random transaction ID.
///
/// This will currently be a UUID without hyphens, but no guarantees are made about the
/// structure of transaction IDs generated from this function.
#[cfg(feature = "rand")]
pub fn new() -> Box<Self> {
let id = uuid::Uuid::new_v4();
Self::from_owned(id.to_simple().to_string().into_boxed_str())
}
}
opaque_identifier!(TransactionId);

View File

@ -275,6 +275,7 @@ fn strip_lifetimes(field_type: &mut Type) -> bool {
|| last_seg.ident == "RoomName"
|| last_seg.ident == "ServerSigningKeyId"
|| last_seg.ident == "SigningKeyId"
|| last_seg.ident == "TransactionId"
|| last_seg.ident == "UserId"
{
// The identifiers that need to be boxed `Box<T>` since they are DST's.

View File

@ -149,7 +149,7 @@ tokio-stream = { version = "0.1.1", default-features = false }
[[example]]
name = "hello_world"
required-features = ["client-api-c", "client-ext-client-api", "client-hyper-native-tls"]
required-features = ["client-api-c", "client-ext-client-api", "client-hyper-native-tls", "rand"]
[[example]]
name = "hello_isahc"

View File

@ -5,6 +5,7 @@ use ruma::{
events::room::message::RoomMessageEventContent,
RoomAliasId,
};
use ruma_identifiers::TransactionId;
type MatrixClient = ruma_client::Client<ruma_client::http_client::HyperNativeTls>;
@ -22,7 +23,7 @@ async fn hello_world(
client
.send_request(send_message_event::Request::new(
&room_id,
"1",
&TransactionId::new(),
&RoomMessageEventContent::text_plain("Hello World!"),
)?)
.await?;

View File

@ -1,10 +1,4 @@
use std::{
convert::TryInto,
error::Error,
io,
process::exit,
time::{Duration, SystemTime},
};
use std::{convert::TryInto, error::Error, io, process::exit, time::Duration};
use ruma::{
api::client::r0::{
@ -16,6 +10,7 @@ use ruma::{
room::message::{MessageType, RoomMessageEventContent},
AnySyncMessageEvent, AnySyncRoomEvent,
},
identifiers::TransactionId,
presence::PresenceState,
serde::Raw,
RoomId, UserId,
@ -154,7 +149,7 @@ async fn handle_messages(
};
let joke_content = RoomMessageEventContent::text_plain(joke);
let txn_id = generate_txn_id();
let txn_id = TransactionId::new();
let req = send_message_event::Request::new(room_id, &txn_id, &joke_content)?;
// Do nothing if we can't send the message.
let _ = matrix_client.send_request(req).await;
@ -175,23 +170,12 @@ async fn handle_invitations(
let greeting = "Hello! My name is Mr. Bot! I like to tell jokes. Like this one: ";
let joke = get_joke(http_client).await.unwrap_or_else(|_| "err... never mind.".to_owned());
let content = RoomMessageEventContent::text_plain(format!("{}\n{}", greeting, joke));
let txn_id = generate_txn_id();
let txn_id = TransactionId::new();
let message = send_message_event::Request::new(room_id, &txn_id, &content)?;
matrix_client.send_request(message).await?;
Ok(())
}
// Each message needs a unique transaction ID, otherwise the server thinks that the message is
// being retransmitted. We could use a random value, or a counter, but to avoid having to store
// the state, we'll just use the current time as a transaction ID.
fn generate_txn_id() -> String {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("current time is earlier than Unix epoch")
.as_millis()
.to_string()
}
async fn get_joke(client: &HttpClient) -> Result<String, Box<dyn Error>> {
let uri = "https://v2.jokeapi.dev/joke/Programming,Pun,Misc?safe-mode&type=single"
.parse::<hyper::Uri>()?;