Skip to main content

ruma_client_api/push/
set_pushrule.rs

1//! `PUT /_matrix/client/*/pushrules/global/{kind}/{ruleId}`
2//!
3//! This endpoint allows the creation and modification of push rules for this user ID.
4
5pub mod v3 {
6    //! `/v3/` ([spec])
7    //!
8    //! [spec]: https://spec.matrix.org/v1.19/client-server-api/#put_matrixclientv3pushrulesglobalkindruleid
9
10    use ruma_common::{
11        api::{auth_scheme::AccessToken, error::Error, response},
12        metadata,
13        push::{Action, NewPushRule, PushCondition},
14    };
15
16    metadata! {
17        method: PUT,
18        rate_limited: true,
19        authentication: AccessToken,
20        history: {
21            1.0 => "/_matrix/client/r0/pushrules/global/{kind}/{rule_id}",
22            1.1 => "/_matrix/client/v3/pushrules/global/{kind}/{rule_id}",
23        }
24    }
25
26    /// Request type for the `set_pushrule` endpoint.
27    #[derive(Clone, Debug)]
28    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
29    pub struct Request {
30        /// The rule.
31        pub rule: NewPushRule,
32
33        /// Use 'before' with a rule_id as its value to make the new rule the next-most important
34        /// rule with respect to the given user defined rule.
35        pub before: Option<String>,
36
37        /// This makes the new rule the next-less important rule relative to the given user defined
38        /// rule.
39        pub after: Option<String>,
40    }
41
42    /// Response type for the `set_pushrule` endpoint.
43    #[response]
44    #[derive(Default)]
45    pub struct Response {}
46
47    impl Request {
48        /// Creates a new `Request` with the given rule.
49        pub fn new(rule: NewPushRule) -> Self {
50            Self { rule, before: None, after: None }
51        }
52    }
53
54    impl Response {
55        /// Creates an empty `Response`.
56        pub fn new() -> Self {
57            Self {}
58        }
59    }
60
61    #[doc(hidden)]
62    // attribute will go away when we update IncomingRequest to also use RequestBody
63    #[cfg_attr(not(feature = "client"), expect(dead_code))]
64    pub struct RequestBody(NewPushRule);
65
66    #[cfg(feature = "client")]
67    impl ruma_common::api::OutgoingBody for RequestBody {
68        type Error = serde_json::Error;
69
70        fn try_into_buf<T: Default + bytes::BufMut + AsRef<[u8]>>(self) -> serde_json::Result<T> {
71            match self.0 {
72                NewPushRule::Override(r) | NewPushRule::Underride(r) => {
73                    let body =
74                        ConditionalRequestBody { actions: r.actions, conditions: r.conditions };
75                    ruma_common::serde::json_to_buf(&body)
76                }
77                NewPushRule::Content(r) => {
78                    let body = PatternedRequestBody { actions: r.actions, pattern: r.pattern };
79                    ruma_common::serde::json_to_buf(&body)
80                }
81                NewPushRule::Room(r) => {
82                    let body = SimpleRequestBody { actions: r.actions };
83                    ruma_common::serde::json_to_buf(&body)
84                }
85                NewPushRule::Sender(r) => {
86                    let body = SimpleRequestBody { actions: r.actions };
87                    ruma_common::serde::json_to_buf(&body)
88                }
89                #[cfg(not(ruma_unstable_exhaustive_types))]
90                _ => unreachable!("variant added to NewPushRule not serializable to request body"),
91            }
92        }
93    }
94
95    #[cfg(feature = "client")]
96    impl ruma_common::api::OutgoingRequest for Request {
97        type Body = RequestBody;
98        type EndpointError = Error;
99        type IncomingResponse = Response;
100
101        fn try_into_http_request_inner(
102            self,
103            base_url: &str,
104            considering: std::borrow::Cow<'_, ruma_common::api::SupportedVersions>,
105        ) -> Result<http::Request<RequestBody>, ruma_common::api::error::IntoHttpError> {
106            use ruma_common::api::Metadata;
107
108            let query_string = serde_html_form::to_string(RequestQuery {
109                before: self.before,
110                after: self.after,
111            })?;
112
113            let url = Self::make_endpoint_url(
114                considering,
115                base_url,
116                &[&self.rule.kind(), &self.rule.rule_id()],
117                &query_string,
118            )?;
119
120            let http_request = http::Request::builder()
121                .method(Self::METHOD)
122                .uri(url)
123                .header(http::header::CONTENT_TYPE, ruma_common::http_headers::APPLICATION_JSON)
124                .body(RequestBody(self.rule))?;
125
126            Ok(http_request)
127        }
128    }
129
130    #[cfg(feature = "server")]
131    impl ruma_common::api::IncomingRequest for Request {
132        type EndpointError = Error;
133        type OutgoingResponse = Response;
134
135        fn try_from_http_request<B, S>(
136            request: http::Request<B>,
137            path_args: &[S],
138        ) -> Result<Self, ruma_common::api::error::FromHttpRequestError>
139        where
140            B: AsRef<[u8]>,
141            S: AsRef<str>,
142        {
143            use ruma_common::push::{
144                NewConditionalPushRule, NewPatternedPushRule, NewSimplePushRule,
145            };
146
147            // Exhaustive enum to fail deserialization on unknown variants.
148            #[derive(Debug, serde::Deserialize)]
149            #[serde(rename_all = "lowercase")]
150            enum RuleKind {
151                Override,
152                Underride,
153                Sender,
154                Room,
155                Content,
156            }
157
158            Self::check_request_method(request.method())?;
159
160            let (kind, rule_id): (RuleKind, String) =
161                serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
162                    _,
163                    serde::de::value::Error,
164                >::new(
165                    path_args.iter().map(::std::convert::AsRef::as_ref),
166                ))?;
167
168            let RequestQuery { before, after } =
169                serde_html_form::from_str(request.uri().query().unwrap_or(""))?;
170
171            let rule = match kind {
172                RuleKind::Override => {
173                    let ConditionalRequestBody { actions, conditions } =
174                        serde_json::from_slice(request.body().as_ref())?;
175                    NewPushRule::Override(NewConditionalPushRule::new(rule_id, conditions, actions))
176                }
177                RuleKind::Underride => {
178                    let ConditionalRequestBody { actions, conditions } =
179                        serde_json::from_slice(request.body().as_ref())?;
180                    NewPushRule::Underride(NewConditionalPushRule::new(
181                        rule_id, conditions, actions,
182                    ))
183                }
184                RuleKind::Sender => {
185                    let SimpleRequestBody { actions } =
186                        serde_json::from_slice(request.body().as_ref())?;
187                    let rule_id = rule_id.try_into()?;
188                    NewPushRule::Sender(NewSimplePushRule::new(rule_id, actions))
189                }
190                RuleKind::Room => {
191                    let SimpleRequestBody { actions } =
192                        serde_json::from_slice(request.body().as_ref())?;
193                    let rule_id = rule_id.try_into()?;
194                    NewPushRule::Room(NewSimplePushRule::new(rule_id, actions))
195                }
196                RuleKind::Content => {
197                    let PatternedRequestBody { actions, pattern } =
198                        serde_json::from_slice(request.body().as_ref())?;
199                    NewPushRule::Content(NewPatternedPushRule::new(rule_id, pattern, actions))
200                }
201            };
202
203            Ok(Self { rule, before, after })
204        }
205    }
206
207    #[derive(Debug)]
208    #[cfg_attr(feature = "client", derive(serde::Serialize))]
209    #[cfg_attr(feature = "server", derive(serde::Deserialize))]
210    struct RequestQuery {
211        #[serde(skip_serializing_if = "Option::is_none")]
212        before: Option<String>,
213
214        #[serde(skip_serializing_if = "Option::is_none")]
215        after: Option<String>,
216    }
217
218    #[derive(Debug)]
219    #[cfg_attr(feature = "client", derive(serde::Serialize))]
220    #[cfg_attr(feature = "server", derive(serde::Deserialize))]
221    struct SimpleRequestBody {
222        actions: Vec<Action>,
223    }
224
225    #[derive(Debug)]
226    #[cfg_attr(feature = "client", derive(serde::Serialize))]
227    #[cfg_attr(feature = "server", derive(serde::Deserialize))]
228    struct PatternedRequestBody {
229        actions: Vec<Action>,
230
231        pattern: String,
232    }
233
234    #[derive(Debug)]
235    #[cfg_attr(feature = "client", derive(serde::Serialize))]
236    #[cfg_attr(feature = "server", derive(serde::Deserialize))]
237    struct ConditionalRequestBody {
238        actions: Vec<Action>,
239
240        conditions: Vec<PushCondition>,
241    }
242}