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