1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! `PUT /_matrix/client/*/rooms/{roomId}/state/{eventType}/{txnId}`
//!
//! Send a delayed state event (a scheduled state event) to a room. [MSC](https://github.com/matrix-org/matrix-spec-proposals/pull/4140)

pub mod unstable {
    //! `msc4140` ([MSC])
    //!
    //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4140

    use ruma_common::{
        api::{request, response, Metadata},
        metadata,
        serde::Raw,
        OwnedRoomId,
    };
    use ruma_events::{AnyStateEventContent, StateEventContent, StateEventType};
    use serde_json::value::to_raw_value as to_raw_json_value;

    use crate::delayed_events::DelayParameters;

    const METADATA: Metadata = metadata! {
        method: PUT,
        rate_limited: false,
        authentication: AccessToken,
        history: {
            // We use the unstable prefix for the delay query parameter but the stable v3 endpoint.
            unstable => "/_matrix/client/v3/rooms/:room_id/state/:event_type/:state_key",
        }
    };

    /// Request type for the [`delayed_state_event`](crate::delayed_events::delayed_state_event)
    /// endpoint.
    #[request(error = crate::Error)]
    pub struct Request {
        /// The room to send the event to.
        #[ruma_api(path)]
        pub room_id: OwnedRoomId,

        /// The type of event to send.
        #[ruma_api(path)]
        pub event_type: StateEventType,

        /// The state_key for the state to send.
        #[ruma_api(path)]
        pub state_key: String,

        /// Additional parameters to describe sending a delayed event.
        ///
        /// Only three combinations for `timeout` and `delay_parent_id` are possible.
        /// The enum [`DelayParameters`] enforces this.
        #[ruma_api(query_all)]
        pub delay_parameters: DelayParameters,

        /// The event content to send.
        #[ruma_api(body)]
        pub body: Raw<AnyStateEventContent>,
    }

    /// Response type for the [`delayed_state_event`](crate::delayed_events::delayed_state_event)
    /// endpoint.
    #[response(error = crate::Error)]
    pub struct Response {
        /// The `delay_id` generated for this delayed event. Used to interact with delayed events.
        pub delay_id: String,
    }

    impl Request {
        /// Creates a new `Request` with the given room id, state_key delay_parameters and
        /// event content.
        ///
        /// # Errors
        ///
        /// Since `Request` stores the request body in serialized form, this function can fail if
        /// `T`s [`::serde::Serialize`] implementation can fail.
        pub fn new<T>(
            room_id: OwnedRoomId,
            state_key: String,
            delay_parameters: DelayParameters,
            content: &T,
        ) -> serde_json::Result<Self>
        where
            T: StateEventContent,
        {
            Ok(Self {
                room_id,
                state_key,
                event_type: content.event_type(),
                delay_parameters,
                body: Raw::from_json(to_raw_json_value(content)?),
            })
        }

        /// Creates a new `Request` with the given room id, event type, state key,
        /// delay parameters and raw event content.
        pub fn new_raw(
            room_id: OwnedRoomId,
            state_key: String,
            event_type: StateEventType,
            delay_parameters: DelayParameters,
            body: Raw<AnyStateEventContent>,
        ) -> Self {
            Self { room_id, event_type, state_key, body, delay_parameters }
        }
    }

    impl Response {
        /// Creates a new `Response` with the tokens required to control the delayed event using the
        /// [`crate::delayed_events::update_delayed_event::unstable::Request`] request.
        pub fn new(delay_id: String) -> Self {
            Self { delay_id }
        }
    }

    #[cfg(all(test, feature = "client"))]
    mod tests {
        use ruma_common::{
            api::{MatrixVersion, OutgoingRequest, SendAccessToken},
            owned_room_id,
        };
        use ruma_events::room::topic::RoomTopicEventContent;
        use serde_json::{json, Value as JsonValue};
        use web_time::Duration;

        use super::Request;
        use crate::delayed_events::DelayParameters;

        fn create_delayed_event_request(
            delay_parameters: DelayParameters,
        ) -> (http::request::Parts, Vec<u8>) {
            Request::new(
                owned_room_id!("!roomid:example.org"),
                "@userAsStateKey:example.org".to_owned(),
                delay_parameters,
                &RoomTopicEventContent::new("my_topic".to_owned()),
            )
            .unwrap()
            .try_into_http_request(
                "https://homeserver.tld",
                SendAccessToken::IfRequired("auth_tok"),
                &[MatrixVersion::V1_1],
            )
            .unwrap()
            .into_parts()
        }

        #[test]
        fn serialize_delayed_state_request() {
            let (parts, body) = create_delayed_event_request(DelayParameters::Timeout {
                timeout: Duration::from_millis(1_234_321),
            });
            assert_eq!(
                "https://homeserver.tld/_matrix/client/v3/rooms/!roomid:example.org/state/m.room.topic/@userAsStateKey:example.org?org.matrix.msc4140.delay=1234321",
                parts.uri.to_string()
            );
            assert_eq!("PUT", parts.method.to_string());
            assert_eq!(
                json!({"topic": "my_topic"}),
                serde_json::from_str::<JsonValue>(std::str::from_utf8(&body).unwrap()).unwrap()
            );
        }
    }
}