twilight_http_ratelimiting/
request.rs

1//! Request parameters for ratelimiting.
2//!
3//! This module contains the type definitions for parameters
4//! relevant for ratelimiting.
5//!
6//! The [`Ratelimiter`] uses [`Path`]s and [`Method`]s to store and associate
7//! buckets with endpoints.
8//!
9//! [`Ratelimiter`]: super::Ratelimiter
10
11use std::{
12    error::Error,
13    fmt::{Display, Formatter, Result as FmtResult},
14    str::FromStr,
15};
16
17/// HTTP request [method].
18///
19/// [method]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
20#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
21#[non_exhaustive]
22pub enum Method {
23    /// Delete a resource.
24    Delete,
25    /// Retrieve a resource.
26    Get,
27    /// Update a resource.
28    Patch,
29    /// Create a resource.
30    Post,
31    /// Replace a resource.
32    Put,
33}
34
35impl Method {
36    /// Name of the method.
37    pub const fn name(self) -> &'static str {
38        match self {
39            Method::Delete => "DELETE",
40            Method::Get => "GET",
41            Method::Patch => "PATCH",
42            Method::Post => "POST",
43            Method::Put => "PUT",
44        }
45    }
46}
47
48/// Error returned when a [`Path`] could not be parsed from a string.
49#[derive(Debug)]
50pub struct PathParseError {
51    /// Detailed reason why this could not be parsed.
52    kind: PathParseErrorType,
53    /// Original error leading up to this one.
54    source: Option<Box<dyn Error + Send + Sync>>,
55}
56
57impl PathParseError {
58    /// Immutable reference to the type of error that occurred.
59    #[must_use = "retrieving the type has no effect if left unused"]
60    pub const fn kind(&self) -> &PathParseErrorType {
61        &self.kind
62    }
63
64    /// Consume the error, returning the source error if there is any.
65    #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
66    pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
67        self.source
68    }
69
70    /// Consume the error, returning the owned error type and the source error.
71    #[must_use = "consuming the error into its parts has no effect if left unused"]
72    pub fn into_parts(self) -> (PathParseErrorType, Option<Box<dyn Error + Send + Sync>>) {
73        (self.kind, self.source)
74    }
75}
76
77impl Display for PathParseError {
78    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
79        match &self.kind {
80            PathParseErrorType::IntegerParsing { .. } => {
81                f.write_str("An ID in a segment was invalid")
82            }
83            PathParseErrorType::MessageIdWithoutMethod { .. } => {
84                f.write_str("A message path was detected but the method wasn't given")
85            }
86            PathParseErrorType::NoMatch => f.write_str("There was no matched path"),
87        }
88    }
89}
90
91impl Error for PathParseError {
92    fn source(&self) -> Option<&(dyn Error + 'static)> {
93        self.source
94            .as_ref()
95            .map(|source| &**source as &(dyn Error + 'static))
96    }
97}
98
99/// Type of [`PathParseError`] specifying what failed to parse.
100#[derive(Debug)]
101#[non_exhaustive]
102pub enum PathParseErrorType {
103    /// The ID couldn't be parsed as an integer.
104    IntegerParsing,
105    /// When parsing into a [`Path::ChannelsIdMessagesId`] variant, the method
106    /// must also be specified via its `TryFrom` impl.
107    MessageIdWithoutMethod {
108        /// The ID of the channel.
109        channel_id: u64,
110    },
111    /// A static path for the provided path string wasn't found.
112    NoMatch,
113}
114
115/// An enum representing a path, most useful for ratelimiting implementations.
116// If adding to this enum, be sure to add to the `TryFrom` impl.
117#[derive(Clone, Debug, Eq, Hash, PartialEq)]
118#[non_exhaustive]
119pub enum Path {
120    /// Operating on global commands.
121    ApplicationCommand(u64),
122    /// Operating on a specific command.
123    ApplicationCommandId(u64),
124    /// Operating on application emojis.
125    ApplicationEmojis(u64),
126    /// Operating on a specific application emoji.
127    ApplicationEmoji(u64),
128    /// Operating on commands in a guild.
129    ApplicationGuildCommand(u64),
130    /// Operating on a specific command in a guild.
131    ApplicationGuildCommandId(u64),
132    /// Operating on current user application,
133    ApplicationsMe,
134    /// Operating on a channel.
135    ChannelsId(u64),
136    /// Operating on a channel's followers.
137    ChannelsIdFollowers(u64),
138    /// Operating on a channel's invites.
139    ChannelsIdInvites(u64),
140    /// Operating on a channel's messages.
141    ChannelsIdMessages(u64),
142    /// Operating on a channel's messages by bulk deleting.
143    ChannelsIdMessagesBulkDelete(u64),
144    /// Operating on an individual channel's message.
145    ChannelsIdMessagesId(Method, u64),
146    /// Crossposting an individual channel's message.
147    ChannelsIdMessagesIdCrosspost(u64),
148    /// Operating on an individual channel's message's reactions.
149    ChannelsIdMessagesIdReactions(u64),
150    /// Operating on an individual channel's message's reactions while
151    /// specifying the user ID and emoji type.
152    ChannelsIdMessagesIdReactionsUserIdType(u64),
153    /// Operating on an individual channel's message's threads.
154    ChannelsIdMessagesIdThreads(u64),
155    /// Operating on a channel's permission overwrites by ID.
156    ChannelsIdPermissionsOverwriteId(u64),
157    /// Operating on a channel's pins.
158    ChannelsIdPins(u64),
159    /// Operating on a channel's individual pinned message.
160    ChannelsIdPinsMessageId(u64),
161    /// Operating on a channel's polls.
162    ChannelsIdPolls(u64),
163    /// Operating on a group DM's recipients.
164    ChannelsIdRecipients(u64),
165    /// Operating on a thread's members.
166    ChannelsIdThreadMembers(u64),
167    /// Operating on a thread's member.
168    ChannelsIdThreadMembersId(u64),
169    /// Operating on a channel's threads.
170    ChannelsIdThreads(u64),
171    /// Operating on a channel's typing indicator.
172    ChannelsIdTyping(u64),
173    /// Operating on a channel's webhooks.
174    ChannelsIdWebhooks(u64),
175    /// Operating on an application's entitlements.
176    ApplicationIdEntitlements(u64),
177    /// Operating on an application's SKUs.
178    ApplicationIdSKUs(u64),
179    /// Operating with the gateway information.
180    Gateway,
181    /// Operating with the gateway information tailored to the current user.
182    GatewayBot,
183    /// Operating on the guild resource.
184    Guilds,
185    /// Operating on one of user's guilds.
186    GuildsId(u64),
187    /// Operating on a ban from one of the user's guilds.
188    GuildsIdAuditLogs(u64),
189    /// Operating on a guild's auto moderation rules.
190    GuildsIdAutoModerationRules(u64),
191    /// Operating on an auto moderation rule from  one of the user's guilds.
192    GuildsIdAutoModerationRulesId(u64),
193    /// Operating on one of the user's guilds' bans.
194    GuildsIdBans(u64),
195    /// Operating on a ban from one of the user's guilds.
196    GuildsIdBansId(u64),
197    /// Operating on specific member's ban from one of the user's guilds.
198    GuildsIdBansUserId(u64),
199    /// Operating on one of the user's guilds' channels.
200    GuildsIdChannels(u64),
201    /// Operating on one of the user's guilds' emojis.
202    GuildsIdEmojis(u64),
203    /// Operating on an emoji from one of the user's guilds.
204    GuildsIdEmojisId(u64),
205    /// Operating on one of the user's guilds' integrations.
206    GuildsIdIntegrations(u64),
207    /// Operating on an integration from one of the user's guilds.
208    GuildsIdIntegrationsId(u64),
209    /// Operating on an integration from one of the user's guilds by synchronizing it.
210    GuildsIdIntegrationsIdSync(u64),
211    /// Operating on one of the user's guilds' invites.
212    GuildsIdInvites(u64),
213    /// Operating on one of the user's guilds' members.
214    GuildsIdMembers(u64),
215    /// Operating on a member from one of the user's guilds.
216    GuildsIdMembersId(u64),
217    /// Operating on a role of a member from one of the user's guilds.
218    GuildsIdMembersIdRolesId(u64),
219    /// Operating on the user's nickname in one of the user's guilds.
220    GuildsIdMembersMeNick(u64),
221    /// Operating on one of the user's guilds' members by searching.
222    GuildsIdMembersSearch(u64),
223    /// Operating on one of the user's guilds' MFA level.
224    GuildsIdMfa(u64),
225    /// Operating on one of the user's guilds' onboarding.
226    GuildsIdOnboarding(u64),
227    /// Operating on one of the user's guilds' by previewing it.
228    GuildsIdPreview(u64),
229    /// Operating on one of the user's guilds' by pruning members.
230    GuildsIdPrune(u64),
231    /// Operating on the voice regions of one of the user's guilds.
232    GuildsIdRegions(u64),
233    /// Operating on the roles of one of the user's guilds.
234    GuildsIdRoles(u64),
235    /// Operating on a role of one of the user's guilds.
236    GuildsIdRolesId(u64),
237    /// Operating on the guild's scheduled events.
238    GuildsIdScheduledEvents(u64),
239    /// Operating on a particular guild's scheduled events.
240    GuildsIdScheduledEventsId(u64),
241    /// Operating on a particular guild's scheduled event users.
242    GuildsIdScheduledEventsIdUsers(u64),
243    /// Operating on one of the user's guilds' stickers.
244    GuildsIdStickers(u64),
245    /// Operating on one of the user's guilds' templates.
246    GuildsIdTemplates(u64),
247    /// Operating on a template from one of the user's guilds.
248    GuildsIdTemplatesCode(u64, String),
249    /// Operating on one of the user's guilds' threads.
250    GuildsIdThreads(u64),
251    /// Operating on one of the user's guilds' vanity URL.
252    GuildsIdVanityUrl(u64),
253    /// Operating on one of the user's guilds' voice states.
254    GuildsIdVoiceStates(u64),
255    /// Operating on one of the user's guilds' webhooks.
256    GuildsIdWebhooks(u64),
257    /// Operating on one of the user's guilds' welcome screen.
258    GuildsIdWelcomeScreen(u64),
259    /// Operating on one of the user's guild's widget settings.
260    GuildsIdWidget(u64),
261    /// Operating on one of the user's guild's widget.
262    GuildsIdWidgetJson(u64),
263    /// Operating on a guild template.
264    GuildsTemplatesCode(String),
265    /// Operating on an interaction's callback.
266    ///
267    /// This path is not bound to the application's global rate limit.
268    InteractionCallback(u64),
269    /// Operating on an invite.
270    InvitesCode,
271    /// Operating on the user's application information.
272    OauthApplicationsMe,
273    /// Operating on the current authorization's information.
274    OauthMe,
275    /// Operating on stage instances.
276    StageInstances,
277    /// Operating on sticker packs.
278    StickerPacks,
279    /// Operating on a sticker.
280    Stickers,
281    /// Operating on a sticker.
282    UsersId,
283    /// Operating on the user's private channels.
284    UsersIdChannels,
285    /// Operating on the user's connections.
286    UsersIdConnections,
287    /// Operating on the state of a guild that the user is in.
288    UsersIdGuilds,
289    /// Operating on the state of a guild that the user is in.
290    UsersIdGuildsId,
291    /// Operating on the state of a guild that the user, as a member, is in.
292    UsersIdGuildsIdMember,
293    /// Operating on the voice regions available to the current user.
294    VoiceRegions,
295    /// Operating on a webhook as a bot.
296    WebhooksId(u64),
297    /// Operating on a webhook as a webhook.
298    ///
299    /// When used with interactions, this path is not bound to the application's
300    /// global rate limit.
301    WebhooksIdToken(u64, String),
302    /// Operating on a message created by a webhook.
303    ///
304    /// When used with interactions, this path is not bound to the application's
305    /// global rate limit.
306    WebhooksIdTokenMessagesId(u64, String),
307}
308
309impl FromStr for Path {
310    type Err = PathParseError;
311
312    /// Parses a string into a path.
313    ///
314    /// The string *may* start with a slash (`/`), which will be ignored.
315    ///
316    /// # Examples
317    ///
318    /// ```
319    /// use std::str::FromStr;
320    /// use twilight_http_ratelimiting::Path;
321    ///
322    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
323    /// assert_eq!(Path::VoiceRegions, Path::from_str("/voice/regions")?);
324    /// assert_eq!(
325    ///     Path::ChannelsIdMessages(123),
326    ///     Path::from_str("channels/123/messages")?,
327    /// );
328    /// # Ok(()) }
329    /// ```
330    #[allow(clippy::enum_glob_use, clippy::too_many_lines)]
331    fn from_str(s: &str) -> Result<Self, Self::Err> {
332        use Path::*;
333
334        /// Parse a string into a Discord ID.
335        fn parse_id(id: &str) -> Result<u64, PathParseError> {
336            id.parse().map_err(|source| PathParseError {
337                kind: PathParseErrorType::IntegerParsing,
338                source: Some(Box::new(source)),
339            })
340        }
341
342        let skip = usize::from(s.starts_with('/'));
343
344        let parts = s.split('/').skip(skip).collect::<Vec<&str>>();
345
346        Ok(match parts[..] {
347            ["applications", "@me"] => ApplicationsMe,
348            ["applications", id, "commands"] => ApplicationCommand(parse_id(id)?),
349            ["applications", id, "commands", _] => ApplicationCommandId(parse_id(id)?),
350            ["applications", id, "entitlements"] => ApplicationIdEntitlements(parse_id(id)?),
351            ["applications", id, "emojis"] => ApplicationEmojis(parse_id(id)?),
352            ["applications", id, "guilds", _, "commands"]
353            | ["applications", id, "guilds", _, "commands", "permissions"] => {
354                ApplicationGuildCommand(parse_id(id)?)
355            }
356            ["applications", id, "guilds", _, "commands", _]
357            | ["applications", id, "guilds", _, "commands", _, "permissions"] => {
358                ApplicationGuildCommandId(parse_id(id)?)
359            }
360            ["applications", id, "skus"] => ApplicationIdSKUs(parse_id(id)?),
361            ["channels", id] => ChannelsId(parse_id(id)?),
362            ["channels", id, "followers"] => ChannelsIdFollowers(parse_id(id)?),
363            ["channels", id, "invites"] => ChannelsIdInvites(parse_id(id)?),
364            ["channels", id, "messages"] => ChannelsIdMessages(parse_id(id)?),
365            ["channels", id, "messages", "bulk-delete"] => {
366                ChannelsIdMessagesBulkDelete(parse_id(id)?)
367            }
368            ["channels", id, "messages", _] => {
369                // can not map to path without method since they have different ratelimits
370                return Err(PathParseError {
371                    kind: PathParseErrorType::MessageIdWithoutMethod {
372                        channel_id: parse_id(id)?,
373                    },
374                    source: None,
375                });
376            }
377            ["channels", id, "messages", _, "crosspost"] => {
378                ChannelsIdMessagesIdCrosspost(parse_id(id)?)
379            }
380            ["channels", id, "messages", _, "reactions"]
381            | ["channels", id, "messages", _, "reactions", _] => {
382                ChannelsIdMessagesIdReactions(parse_id(id)?)
383            }
384            ["channels", id, "messages", _, "reactions", _, _] => {
385                ChannelsIdMessagesIdReactionsUserIdType(parse_id(id)?)
386            }
387            ["channels", id, "messages", _, "threads"] => {
388                ChannelsIdMessagesIdThreads(parse_id(id)?)
389            }
390            ["channels", id, "permissions", _] => ChannelsIdPermissionsOverwriteId(parse_id(id)?),
391            ["channels", id, "pins"] => ChannelsIdPins(parse_id(id)?),
392            ["channels", id, "pins", _] => ChannelsIdPinsMessageId(parse_id(id)?),
393            ["channels", id, "recipients"] | ["channels", id, "recipients", _] => {
394                ChannelsIdRecipients(parse_id(id)?)
395            }
396            ["channels", id, "thread-members"] => ChannelsIdThreadMembers(parse_id(id)?),
397            ["channels", id, "thread-members", _] => ChannelsIdThreadMembersId(parse_id(id)?),
398            ["channels", id, "threads"] => ChannelsIdThreads(parse_id(id)?),
399            ["channels", id, "typing"] => ChannelsIdTyping(parse_id(id)?),
400            ["channels", id, "webhooks"] | ["channels", id, "webhooks", _] => {
401                ChannelsIdWebhooks(parse_id(id)?)
402            }
403            ["gateway"] => Gateway,
404            ["gateway", "bot"] => GatewayBot,
405            ["guilds"] => Guilds,
406            ["guilds", "templates", code] => GuildsTemplatesCode(code.to_string()),
407            ["guilds", id] => GuildsId(parse_id(id)?),
408            ["guilds", id, "audit-logs"] => GuildsIdAuditLogs(parse_id(id)?),
409            ["guilds", id, "auto-moderation", "rules"] => {
410                GuildsIdAutoModerationRules(parse_id(id)?)
411            }
412            ["guilds", id, "auto-moderation", "rules", _] => {
413                GuildsIdAutoModerationRulesId(parse_id(id)?)
414            }
415            ["guilds", id, "bans"] => GuildsIdBans(parse_id(id)?),
416            ["guilds", id, "bans", _] => GuildsIdBansUserId(parse_id(id)?),
417            ["guilds", id, "channels"] => GuildsIdChannels(parse_id(id)?),
418            ["guilds", id, "emojis"] => GuildsIdEmojis(parse_id(id)?),
419            ["guilds", id, "emojis", _] => GuildsIdEmojisId(parse_id(id)?),
420            ["guilds", id, "integrations"] => GuildsIdIntegrations(parse_id(id)?),
421            ["guilds", id, "integrations", _] => GuildsIdIntegrationsId(parse_id(id)?),
422            ["guilds", id, "integrations", _, "sync"] => GuildsIdIntegrationsIdSync(parse_id(id)?),
423            ["guilds", id, "invites"] => GuildsIdInvites(parse_id(id)?),
424            ["guilds", id, "members"] => GuildsIdMembers(parse_id(id)?),
425            ["guilds", id, "members", "search"] => GuildsIdMembersSearch(parse_id(id)?),
426            ["guilds", id, "members", _] => GuildsIdMembersId(parse_id(id)?),
427            ["guilds", id, "members", _, "roles", _] => GuildsIdMembersIdRolesId(parse_id(id)?),
428            ["guilds", id, "members", "@me", "nick"] => GuildsIdMembersMeNick(parse_id(id)?),
429            ["guilds", id, "onboarding"] => GuildsIdOnboarding(parse_id(id)?),
430            ["guilds", id, "preview"] => GuildsIdPreview(parse_id(id)?),
431            ["guilds", id, "prune"] => GuildsIdPrune(parse_id(id)?),
432            ["guilds", id, "regions"] => GuildsIdRegions(parse_id(id)?),
433            ["guilds", id, "roles"] => GuildsIdRoles(parse_id(id)?),
434            ["guilds", id, "roles", _] => GuildsIdRolesId(parse_id(id)?),
435            ["guilds", id, "scheduled-events"] => GuildsIdScheduledEvents(parse_id(id)?),
436            ["guilds", id, "scheduled-events", _] => GuildsIdScheduledEventsId(parse_id(id)?),
437            ["guilds", id, "scheduled-events", _, "users"] => {
438                GuildsIdScheduledEventsIdUsers(parse_id(id)?)
439            }
440            ["guilds", id, "stickers"] | ["guilds", id, "stickers", _] => {
441                GuildsIdStickers(parse_id(id)?)
442            }
443            ["guilds", id, "templates"] => GuildsIdTemplates(parse_id(id)?),
444            ["guilds", id, "templates", code] => {
445                GuildsIdTemplatesCode(parse_id(id)?, code.to_string())
446            }
447            ["guilds", id, "threads", _] => GuildsIdThreads(parse_id(id)?),
448            ["guilds", id, "vanity-url"] => GuildsIdVanityUrl(parse_id(id)?),
449            ["guilds", id, "voice-states", _] => GuildsIdVoiceStates(parse_id(id)?),
450            ["guilds", id, "welcome-screen"] => GuildsIdWelcomeScreen(parse_id(id)?),
451            ["guilds", id, "webhooks"] => GuildsIdWebhooks(parse_id(id)?),
452            ["guilds", id, "widget"] => GuildsIdWidget(parse_id(id)?),
453            ["guilds", id, "widget.json"] => GuildsIdWidgetJson(parse_id(id)?),
454            ["invites", _] => InvitesCode,
455            ["interactions", id, _, "callback"] => InteractionCallback(parse_id(id)?),
456            ["stage-instances", _] => StageInstances,
457            ["sticker-packs"] => StickerPacks,
458            ["stickers", _] => Stickers,
459            ["oauth2", "applications", "@me"] => OauthApplicationsMe,
460            ["oauth2", "@me"] => OauthMe,
461            ["users", _] => UsersId,
462            ["users", _, "connections"] => UsersIdConnections,
463            ["users", _, "channels"] => UsersIdChannels,
464            ["users", _, "guilds"] => UsersIdGuilds,
465            ["users", _, "guilds", _] => UsersIdGuildsId,
466            ["users", _, "guilds", _, "member"] => UsersIdGuildsIdMember,
467            ["voice", "regions"] => VoiceRegions,
468            ["webhooks", id] => WebhooksId(parse_id(id)?),
469            ["webhooks", id, token] => WebhooksIdToken(parse_id(id)?, token.to_string()),
470            ["webhooks", id, token, "messages", _] => {
471                WebhooksIdTokenMessagesId(parse_id(id)?, token.to_string())
472            }
473            _ => {
474                return Err(PathParseError {
475                    kind: PathParseErrorType::NoMatch,
476                    source: None,
477                })
478            }
479        })
480    }
481}
482
483impl TryFrom<(Method, &str)> for Path {
484    type Error = PathParseError;
485
486    fn try_from((method, s): (Method, &str)) -> Result<Self, Self::Error> {
487        match Self::from_str(s) {
488            Ok(v) => Ok(v),
489            Err(why) => {
490                if let PathParseErrorType::MessageIdWithoutMethod { channel_id } = why.kind() {
491                    Ok(Self::ChannelsIdMessagesId(method, *channel_id))
492                } else {
493                    Err(why)
494                }
495            }
496        }
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use super::{Path, PathParseError, PathParseErrorType};
503    use crate::request::Method;
504    use static_assertions::{assert_fields, assert_impl_all};
505    use std::{error::Error, fmt::Debug, hash::Hash, str::FromStr};
506
507    assert_fields!(PathParseErrorType::MessageIdWithoutMethod: channel_id);
508    assert_impl_all!(PathParseErrorType: Debug, Send, Sync);
509    assert_impl_all!(PathParseError: Error, Send, Sync);
510    assert_impl_all!(Path: Clone, Debug, Eq, Hash, PartialEq, Send, Sync);
511
512    #[test]
513    fn prefix_unimportant() -> Result<(), Box<dyn Error>> {
514        assert_eq!(Path::Guilds, Path::from_str("guilds")?);
515        assert_eq!(Path::Guilds, Path::from_str("/guilds")?);
516
517        Ok(())
518    }
519
520    #[test]
521    fn from_str() -> Result<(), Box<dyn Error>> {
522        assert_eq!(Path::ChannelsId(123), Path::from_str("/channels/123")?);
523        assert_eq!(Path::WebhooksId(123), Path::from_str("/webhooks/123")?);
524        assert_eq!(Path::InvitesCode, Path::from_str("/invites/abc")?);
525
526        Ok(())
527    }
528
529    #[test]
530    fn message_id() -> Result<(), Box<dyn Error>> {
531        assert!(matches!(
532            Path::from_str("channels/123/messages/456")
533                .unwrap_err()
534                .kind(),
535            PathParseErrorType::MessageIdWithoutMethod { channel_id: 123 },
536        ));
537        assert_eq!(
538            Path::ChannelsIdMessagesId(Method::Get, 123),
539            Path::try_from((Method::Get, "/channels/123/messages/456"))?,
540        );
541
542        Ok(())
543    }
544
545    assert_impl_all!(Method: Clone, Copy, Debug, Eq, PartialEq);
546
547    #[test]
548    fn method_conversions() {
549        assert_eq!("DELETE", Method::Delete.name());
550        assert_eq!("GET", Method::Get.name());
551        assert_eq!("PATCH", Method::Patch.name());
552        assert_eq!("POST", Method::Post.name());
553        assert_eq!("PUT", Method::Put.name());
554    }
555}