From a13f766758493c92bf308e76593aa7cd9e71a69b Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 22 Aug 2023 14:00:39 +0200 Subject: [PATCH 1/9] Add search() and search_messages() --- src/api/channels/messages.rs | 91 ++++++++++++++++++++++++++++++++++-- src/api/guilds/messages.rs | 28 +++++++++++ src/api/guilds/mod.rs | 2 + 3 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 src/api/guilds/messages.rs diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index 9487100..833ff16 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -1,13 +1,15 @@ use http::header::CONTENT_DISPOSITION; use http::HeaderMap; use reqwest::{multipart, Client}; -use serde_json::to_string; +use serde_json::{from_str, to_string, to_value}; use crate::api::LimitType; -use crate::errors::ChorusResult; +use crate::errors::{ChorusError, ChorusResult}; use crate::instance::UserMeta; use crate::ratelimiter::ChorusRequest; -use crate::types::{Message, MessageSendSchema, Snowflake}; +use crate::types::{ + Message, MessageSearchEndpoint, MessageSearchQuery, MessageSendSchema, Snowflake, +}; impl Message { /// Sends a message in the channel with the provided channel_id. @@ -70,6 +72,89 @@ impl Message { chorus_request.deserialize_response::(user).await } } + + /// Returns messages without the reactions key that match a search query in the guild or channel. + /// The messages that are direct results will have an extra hit key set to true. + /// If operating on a guild channel, this endpoint requires the `READ_MESSAGE_HISTORY` + /// permission to be present on the current user. + /// + /// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response. + /// In this case, the method will return a [`ChorusError::InvalidResponse`] error. + /// + /// # Reference: + /// See + pub(crate) async fn search( + endpoint: MessageSearchEndpoint, + query: MessageSearchQuery, + user: &mut UserMeta, + ) -> ChorusResult> { + let limit_type = match &endpoint { + MessageSearchEndpoint::Channel(id) => LimitType::Channel(*id), + MessageSearchEndpoint::GuildChannel(id) => LimitType::Guild(*id), + }; + let request = ChorusRequest { + limit_type, + request: Client::new() + .get(format!( + "{}/{}/messages/search", + &user.belongs_to.borrow().urls.api, + endpoint + )) + .header("Authorization", user.token()) + .body(to_string(&query).unwrap()), + }; + let result = request.send_request(user).await?; + let result_text = result.text().await.unwrap(); + if let Ok(response) = from_str::>(&result_text) { + return Ok(response); + } + if to_value(result_text.clone()).is_err() { + return Err(search_error(result_text)); + } + let result_value = to_value(result_text.clone()).unwrap(); + if !result_value.is_object() { + return Err(search_error(result_text)); + } + let value_map = result_value.as_object().unwrap(); + if !value_map.contains_key("code") || !value_map.contains_key("retry_after") { + return Err(search_error(result_text)); + } + let code = value_map.get("code").unwrap().as_u64().unwrap(); + let retry_after = value_map.get("retry_after").unwrap().as_u64().unwrap(); + Err(ChorusError::NotFound { + error: format!( + "Index not yet available. Try again later. Code: {}. Retry after {}s", + code, retry_after + ), + }) + } + + /// Returns messages without the reactions key that match a search query in the channel. + /// The messages that are direct results will have an extra hit key set to true. + /// If operating on a guild channel, this endpoint requires the `READ_MESSAGE_HISTORY` + /// permission to be present on the current user. + /// + /// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response. + /// In this case, the method will return a [`ChorusError::InvalidResponse`] error. + /// + /// # Reference: + /// See + pub async fn search_messages( + channel_id: Snowflake, + query: MessageSearchQuery, + user: &mut UserMeta, + ) -> ChorusResult> { + Message::search(MessageSearchEndpoint::Channel(channel_id), query, user).await + } +} + +fn search_error(result_text: String) -> ChorusError { + ChorusError::InvalidResponse { + error: format!( + "Got unexpected Response, or Response which is not valid JSON. Response: \n{}", + result_text + ), + } } impl UserMeta { diff --git a/src/api/guilds/messages.rs b/src/api/guilds/messages.rs new file mode 100644 index 0000000..72d886c --- /dev/null +++ b/src/api/guilds/messages.rs @@ -0,0 +1,28 @@ +use crate::errors::ChorusResult; +use crate::instance::UserMeta; +use crate::types::{Guild, Message, MessageSearchQuery, Snowflake}; + +impl Guild { + /// Returns messages without the reactions key that match a search query in the guild. + /// The messages that are direct results will have an extra hit key set to true. + /// If operating on a guild channel, this endpoint requires the `READ_MESSAGE_HISTORY` + /// permission to be present on the current user. + /// + /// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response. + /// In this case, the method will return a [`ChorusError::InvalidResponse`] error. + /// + /// # Reference: + /// See + pub async fn search_messages( + guild_id: Snowflake, + query: MessageSearchQuery, + user: &mut UserMeta, + ) -> ChorusResult> { + Message::search( + crate::types::MessageSearchEndpoint::GuildChannel(guild_id), + query, + user, + ) + .await + } +} diff --git a/src/api/guilds/mod.rs b/src/api/guilds/mod.rs index 7577479..f1fa039 100644 --- a/src/api/guilds/mod.rs +++ b/src/api/guilds/mod.rs @@ -1,7 +1,9 @@ pub use guilds::*; +pub use messages::*; pub use roles::*; pub use roles::*; pub mod guilds; pub mod member; +pub mod messages; pub mod roles; From 1984f8c5daa8a7807425929e661e7da3cef3040c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 22 Aug 2023 14:01:03 +0200 Subject: [PATCH 2/9] Add MessageSearchEndpoint Enum and MessageSearchQuery schema struct --- src/types/schema/message.rs | 51 ++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/types/schema/message.rs b/src/types/schema/message.rs index e1abdf7..94a5884 100644 --- a/src/types/schema/message.rs +++ b/src/types/schema/message.rs @@ -3,8 +3,9 @@ use serde::{Deserialize, Serialize}; use crate::types::entities::{ AllowedMention, Component, Embed, MessageReference, PartialDiscordFileAttachment, }; +use crate::types::Snowflake; -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] #[serde(rename_all = "snake_case")] pub struct MessageSendSchema { #[serde(rename = "type")] @@ -19,3 +20,51 @@ pub struct MessageSendSchema { pub sticker_ids: Option>, pub attachments: Option>, } + +pub enum MessageSearchEndpoint { + GuildChannel(Snowflake), + Channel(Snowflake), +} + +impl std::fmt::Display for MessageSearchEndpoint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MessageSearchEndpoint::Channel(id) => { + write!(f, "channels/{}", &id.to_string()) + } + MessageSearchEndpoint::GuildChannel(id) => { + write!(f, "guilds/{}", &id.to_string()) + } + } + } +} + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)] +/// Represents a Message Search Query JSON Body. +/// The `channel_id` field is not applicable when using the `GET /channels/{channel.id}/messages/search` endpoint. +/// +/// # Reference: +/// See +pub struct MessageSearchQuery { + attachment_extension: Option>, + attachment_filename: Option>, + author_id: Option>, + author_type: Option>, + channel_id: Option>, + command_id: Option>, + content: Option, + embed_provider: Option>, + embed_type: Option>, + has: Option>, + include_nsfw: Option, + limit: Option, + link_hostname: Option>, + max_id: Option, + mention_everyone: Option, + mentions: Option>, + min_id: Option, + offset: Option, + pinned: Option, + sort_by: Option, + sort_order: Option, +} From 500ab0e3d0933af1f8012f3f4144d0d24a038ca5 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 22 Aug 2023 14:01:17 +0200 Subject: [PATCH 3/9] Add some missing derives --- src/types/entities/attachment.rs | 2 +- src/types/entities/message.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/types/entities/attachment.rs b/src/types/entities/attachment.rs index 75ec860..59ad53d 100644 --- a/src/types/entities/attachment.rs +++ b/src/types/entities/attachment.rs @@ -31,7 +31,7 @@ pub struct Attachment { pub content: Option>, } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct PartialDiscordFileAttachment { pub id: Option, pub filename: String, diff --git a/src/types/entities/message.rs b/src/types/entities/message.rs index d7ef01c..4b03649 100644 --- a/src/types/entities/message.rs +++ b/src/types/entities/message.rs @@ -114,7 +114,7 @@ pub struct ChannelMention { name: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Embed { title: Option, #[serde(rename = "type")] @@ -132,14 +132,14 @@ pub struct Embed { fields: Option>, } -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct EmbedFooter { text: String, icon_url: Option, proxy_icon_url: Option, } -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct EmbedImage { url: String, proxy_url: String, @@ -147,7 +147,7 @@ pub struct EmbedImage { width: Option, } -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct EmbedThumbnail { url: String, proxy_url: Option, @@ -155,7 +155,7 @@ pub struct EmbedThumbnail { width: Option, } -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] struct EmbedVideo { url: Option, proxy_url: Option, @@ -163,13 +163,13 @@ struct EmbedVideo { width: Option, } -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct EmbedProvider { name: Option, url: Option, } -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct EmbedAuthor { name: String, url: Option, @@ -177,7 +177,7 @@ pub struct EmbedAuthor { proxy_icon_url: Option, } -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct EmbedField { name: String, value: String, From 9ac5925bf9c14d7113a28099e5a7bc8290d93449 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 22 Aug 2023 17:55:29 +0200 Subject: [PATCH 4/9] Make channel_type in ChannelCreateSchema of type enum "ChannelType" --- src/types/entities/channel.rs | 16 ++++++++++++++-- src/types/schema/channel.rs | 3 ++- tests/common/mod.rs | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 361ecb5..84530c9 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -176,10 +176,22 @@ pub struct DefaultReaction { pub emoji_name: Option, } -#[derive(Default, Clone, Copy, Debug, Serialize_repr, Deserialize_repr, PartialEq, Eq, Hash)] +#[derive( + Default, + Clone, + Copy, + Debug, + Serialize_repr, + Deserialize_repr, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, +)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] -#[repr(i32)] +#[repr(u32)] /// # Reference /// See pub enum ChannelType { diff --git a/src/types/schema/channel.rs b/src/types/schema/channel.rs index dd62f88..354459c 100644 --- a/src/types/schema/channel.rs +++ b/src/types/schema/channel.rs @@ -1,6 +1,7 @@ use bitflags::bitflags; use serde::{Deserialize, Serialize}; +use crate::types::ChannelType; use crate::types::{entities::PermissionOverwrite, Snowflake}; #[derive(Debug, Deserialize, Serialize, Default, PartialEq, PartialOrd)] @@ -8,7 +9,7 @@ use crate::types::{entities::PermissionOverwrite, Snowflake}; pub struct ChannelCreateSchema { pub name: String, #[serde(rename = "type")] - pub channel_type: Option, + pub channel_type: Option, pub topic: Option, pub icon: Option, pub bitrate: Option, diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 029df2d..2dc0718 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -73,7 +73,7 @@ pub(crate) async fn setup() -> TestBundle { }; let channel_create_schema = ChannelCreateSchema { name: "testchannel".to_string(), - channel_type: Some(0), + channel_type: Some(chorus::types::ChannelType::GuildText), topic: None, icon: None, bitrate: None, From d913b2d97f31a25fc138b460b0f48d4a30bd28e7 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 22 Aug 2023 17:55:53 +0200 Subject: [PATCH 5/9] Implement Default for MessageSearchQuery --- src/types/schema/message.rs | 72 +++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/src/types/schema/message.rs b/src/types/schema/message.rs index 94a5884..e0ee804 100644 --- a/src/types/schema/message.rs +++ b/src/types/schema/message.rs @@ -39,32 +39,60 @@ impl std::fmt::Display for MessageSearchEndpoint { } } -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)] /// Represents a Message Search Query JSON Body. /// The `channel_id` field is not applicable when using the `GET /channels/{channel.id}/messages/search` endpoint. /// /// # Reference: /// See pub struct MessageSearchQuery { - attachment_extension: Option>, - attachment_filename: Option>, - author_id: Option>, - author_type: Option>, - channel_id: Option>, - command_id: Option>, - content: Option, - embed_provider: Option>, - embed_type: Option>, - has: Option>, - include_nsfw: Option, - limit: Option, - link_hostname: Option>, - max_id: Option, - mention_everyone: Option, - mentions: Option>, - min_id: Option, - offset: Option, - pinned: Option, - sort_by: Option, - sort_order: Option, + pub attachment_extension: Option>, + pub attachment_filename: Option>, + pub author_id: Option>, + pub author_type: Option>, + pub channel_id: Option>, + pub command_id: Option>, + pub content: Option, + pub embed_provider: Option>, + pub embed_type: Option>, + pub has: Option>, + pub include_nsfw: Option, + pub limit: Option, + pub link_hostname: Option>, + pub max_id: Option, + pub mention_everyone: Option, + pub mentions: Option>, + pub min_id: Option, + pub offset: Option, + pub pinned: Option, + pub sort_by: Option, + pub sort_order: Option, +} + +impl std::default::Default for MessageSearchQuery { + fn default() -> Self { + Self { + attachment_extension: Default::default(), + attachment_filename: Default::default(), + author_id: Default::default(), + author_type: Default::default(), + channel_id: Default::default(), + command_id: Default::default(), + content: Default::default(), + embed_provider: Default::default(), + embed_type: Default::default(), + has: Default::default(), + include_nsfw: Some(false), + limit: Some(25), + link_hostname: Default::default(), + max_id: Default::default(), + mention_everyone: Default::default(), + mentions: Default::default(), + min_id: Default::default(), + offset: Some(0), + pinned: Default::default(), + sort_by: Default::default(), + sort_order: Default::default(), + } + } } From 6e40568b23eb43950658cbf8b3ff4010a86d066a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 22 Aug 2023 17:56:13 +0200 Subject: [PATCH 6/9] Move search_messages to Channel impl --- src/api/channels/messages.rs | 40 +++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index 833ff16..faf6b57 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -8,7 +8,7 @@ use crate::errors::{ChorusError, ChorusResult}; use crate::instance::UserMeta; use crate::ratelimiter::ChorusRequest; use crate::types::{ - Message, MessageSearchEndpoint, MessageSearchQuery, MessageSendSchema, Snowflake, + Channel, Message, MessageSearchEndpoint, MessageSearchQuery, MessageSendSchema, Snowflake, }; impl Message { @@ -128,24 +128,6 @@ impl Message { ), }) } - - /// Returns messages without the reactions key that match a search query in the channel. - /// The messages that are direct results will have an extra hit key set to true. - /// If operating on a guild channel, this endpoint requires the `READ_MESSAGE_HISTORY` - /// permission to be present on the current user. - /// - /// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response. - /// In this case, the method will return a [`ChorusError::InvalidResponse`] error. - /// - /// # Reference: - /// See - pub async fn search_messages( - channel_id: Snowflake, - query: MessageSearchQuery, - user: &mut UserMeta, - ) -> ChorusResult> { - Message::search(MessageSearchEndpoint::Channel(channel_id), query, user).await - } } fn search_error(result_text: String) -> ChorusError { @@ -174,3 +156,23 @@ impl UserMeta { Message::send(self, channel_id, message).await } } + +impl Channel { + /// Returns messages without the reactions key that match a search query in the channel. + /// The messages that are direct results will have an extra hit key set to true. + /// If operating on a guild channel, this endpoint requires the `READ_MESSAGE_HISTORY` + /// permission to be present on the current user. + /// + /// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response. + /// In this case, the method will return a [`ChorusError::InvalidResponse`] error. + /// + /// # Reference: + /// See + pub async fn search_messages( + channel_id: Snowflake, + query: MessageSearchQuery, + user: &mut UserMeta, + ) -> ChorusResult> { + Message::search(MessageSearchEndpoint::Channel(channel_id), query, user).await + } +} From c8378083cf861710c94d6fc1829ee6057322afbd Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 22 Aug 2023 19:22:30 +0200 Subject: [PATCH 7/9] Add json feature to reqwest --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6c15102..e87e747 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ serde_json = { version = "1.0.103", features = ["raw_value"] } serde-aux = "4.2.0" serde_with = "3.0.0" serde_repr = "0.1.14" -reqwest = { version = "0.11.18", features = ["multipart"] } +reqwest = { version = "0.11.18", features = ["multipart", "json"] } url = "2.4.0" chrono = { version = "0.4.26", features = ["serde"] } regex = "1.9.1" From 71dc645210599fa191aa2ed8a8166b9527df618c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 22 Aug 2023 19:23:05 +0200 Subject: [PATCH 8/9] Add search_messages Test --- tests/messages.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/messages.rs b/tests/messages.rs index 417ca54..2854b1d 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -1,7 +1,7 @@ use std::fs::File; use std::io::{BufReader, Read}; -use chorus::types; +use chorus::types::{self, Guild, MessageSearchQuery}; mod common; @@ -53,3 +53,49 @@ async fn send_message_attachment() { bundle.user.send_message(message, channel.id).await.unwrap(); common::teardown(bundle).await } + +#[tokio::test] +async fn search_messages() { + let f = File::open("./README.md").unwrap(); + let mut reader = BufReader::new(f); + let mut buffer = Vec::new(); + let mut bundle = common::setup().await; + + reader.read_to_end(&mut buffer).unwrap(); + + let attachment = types::PartialDiscordFileAttachment { + id: None, + filename: "README.md".to_string(), + description: None, + content_type: None, + size: None, + url: None, + proxy_url: None, + width: None, + height: None, + ephemeral: None, + duration_secs: None, + waveform: None, + content: buffer, + }; + + let message = types::MessageSendSchema { + content: Some("trans rights now".to_string()), + attachments: Some(vec![attachment.clone()]), + ..Default::default() + }; + let channel = bundle.channel.read().unwrap().clone(); + let vec_attach = vec![attachment.clone()]; + let _arg = Some(&vec_attach); + let message = bundle.user.send_message(message, channel.id).await.unwrap(); + let query = MessageSearchQuery { + author_id: Some(Vec::from([bundle.user.object.read().unwrap().id])), + ..Default::default() + }; + let guild_id = bundle.guild.read().unwrap().id; + let query_result = Guild::search_messages(guild_id, query, &mut bundle.user) + .await + .unwrap(); + assert!(!query_result.is_empty()); + assert_eq!(query_result.get(0).unwrap().id, message.id); +} From 36a0730d0b7adf7867c6be1dc2720630687efeb2 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 22 Aug 2023 19:23:27 +0200 Subject: [PATCH 9/9] Correctify search_message Fixes Search Messages #258 --- src/api/channels/messages.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index faf6b57..cc5aa16 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -1,7 +1,7 @@ use http::header::CONTENT_DISPOSITION; use http::HeaderMap; use reqwest::{multipart, Client}; -use serde_json::{from_str, to_string, to_value}; +use serde_json::{from_value, to_string, Value}; use crate::api::LimitType; use crate::errors::{ChorusError, ChorusResult}; @@ -104,20 +104,20 @@ impl Message { .body(to_string(&query).unwrap()), }; let result = request.send_request(user).await?; - let result_text = result.text().await.unwrap(); - if let Ok(response) = from_str::>(&result_text) { - return Ok(response); + let result_json = result.json::().await.unwrap(); + if !result_json.is_object() { + return Err(search_error(result_json.to_string())); } - if to_value(result_text.clone()).is_err() { - return Err(search_error(result_text)); + let value_map = result_json.as_object().unwrap(); + if let Some(messages) = value_map.get("messages") { + if let Ok(response) = from_value::>>(messages.clone()) { + let result_messages: Vec = response.into_iter().flatten().collect(); + return Ok(result_messages); + } } - let result_value = to_value(result_text.clone()).unwrap(); - if !result_value.is_object() { - return Err(search_error(result_text)); - } - let value_map = result_value.as_object().unwrap(); + // The code below might be incorrect. We'll cross that bridge when we come to it if !value_map.contains_key("code") || !value_map.contains_key("retry_after") { - return Err(search_error(result_text)); + return Err(search_error(result_json.to_string())); } let code = value_map.get("code").unwrap().as_u64().unwrap(); let retry_after = value_map.get("retry_after").unwrap().as_u64().unwrap();