From 62920caffaf6b9ad8a61da66b80a3ab029b791a8 Mon Sep 17 00:00:00 2001 From: ianagbip1oti Date: Sun, 24 Jan 2021 04:49:29 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=A6=20NEW:=20=20Refactor=20resource=20?= =?UTF-8?q?access=20to=20builder=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/ping_bot.rs | 15 ++++---- src/http.rs | 90 ++++---------------------------------------- src/lib.rs | 17 +++++---- src/resource.rs | 59 +++++++++++++++++++++++++++++ src/smalld.rs | 64 +++++-------------------------- tests/http_test.rs | 40 ++++++++++++-------- 6 files changed, 117 insertions(+), 168 deletions(-) create mode 100644 src/resource.rs diff --git a/examples/ping_bot.rs b/examples/ping_bot.rs index 85103f4..888a0e1 100644 --- a/examples/ping_bot.rs +++ b/examples/ping_bot.rs @@ -1,4 +1,4 @@ -use log::{debug, warn}; +use log::warn; use serde_json::json; use smalld::SmallD; @@ -8,13 +8,12 @@ fn main() { let smalld = SmallD::new().expect("Failed to initialize smalld"); smalld.on_event("MESSAGE_CREATE", move |smalld, json| { - if let Some("++ping") = json.get("content").and_then(|c| c.as_str()) { - debug!("Pong!"); - if let Some(channel_id) = json.get("channel_id").and_then(|c| c.as_str()) { - if let Err(err) = smalld.post( - format!("/channels/{}/messages", channel_id), - json!({"content":"pong"}), - ) { + if let Some("++ping") = json["content"].as_str() { + if let Some(channel_id) = json["channel_id"].as_str() { + if let Err(err) = smalld + .resource(format!("/channels/{}/messages", channel_id)) + .post(json!({"content":"pong"})) + { warn!("Error sending pong response: {}", err); } } diff --git a/src/http.rs b/src/http.rs index f45ddba..3c8cb5b 100644 --- a/src/http.rs +++ b/src/http.rs @@ -24,40 +24,6 @@ impl Http { } } - pub fn get>(&self, path: S, params: QueryParameters) -> Result { - self.with_request("GET", path.as_ref(), params, |r| r.call()) - } - - pub fn post>( - &self, - path: S, - params: QueryParameters, - json: Value, - ) -> Result { - self.with_request("POST", path.as_ref(), params, |r| r.send_json(json)) - } - - pub fn put>( - &self, - path: S, - params: QueryParameters, - json: Value, - ) -> Result { - self.with_request("PUT", path.as_ref(), params, |r| r.send_json(json)) - } - - pub fn patch>(&self, path: S, json: Value) -> Result { - self.with_request("PATCH", path.as_ref(), QueryParameters::new(), |r| { - r.send_json(json) - }) - } - - pub fn delete>(&self, path: S) -> Result { - self.with_request("DELETE", path.as_ref(), QueryParameters::new(), |r| { - r.call() - }) - } - fn build_url(&self, path: &str) -> Result { let mut url: Url = self.base_url.clone(); @@ -69,23 +35,26 @@ impl Http { Ok(url) } - fn with_request( + pub(crate) fn with_request( &self, method: &str, - path: &str, - params: QueryParameters, + path: P, + params: &[(String, String)], f: F, ) -> Result where + P: AsRef, F: FnOnce(Request) -> Result, { let mut request = self .agent - .request_url(method, &self.build_url(path)?) + .request_url(method, &self.build_url(path.as_ref())?) .set("Authorization", &self.authorization) .set("User-Agent", &self.user_agent); - request = params.apply_to(request); + for (k, v) in params.iter() { + request = request.query(k, v); + } let response = f(request)?; @@ -95,46 +64,3 @@ impl Http { } } } - -#[derive(Clone, Debug)] -pub struct QueryParameters(Vec<(String, String)>); - -/// Struct for holding query parameters to be added to a url with a HTTP request. -/// Parameters may be added using the add method: -/// -/// ```no_run -/// use smalld::QueryParameters; -/// -/// let params = QueryParameters::new() -/// .add("key1", "value1") -/// .add("key2", "value2"); -/// ``` -/// -impl QueryParameters { - pub fn new() -> Self { - QueryParameters(Vec::new()) - } - - pub fn add(mut self, key: A, value: B) -> Self - where - A: Into, - B: Into, - { - self.0.push((key.into(), value.into())); - self - } - - pub(self) fn apply_to(&self, req_in: Request) -> Request { - let mut req_out = req_in; - for (k, v) in self.0.iter() { - req_out = req_out.query(k, v); - } - req_out - } -} - -impl Default for QueryParameters { - fn default() -> Self { - Self::new() - } -} diff --git a/src/lib.rs b/src/lib.rs index c9578a5..9d70a6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ //! let smalld = SmallD::new().expect("Failed to initialize smalld"); //! //! smalld.on_event("MESSAGE_CREATE", |smalld, json| { -//! if let Some("ping") = json.get("content").and_then(|c| c.as_str()) { +//! if let Some("ping") = json["content"].as_str() { //! println!("Ping Received!"); //! } //! }); @@ -74,8 +74,9 @@ //! smalld.run(); //! ``` //! -//! To send requests through Discord's resources api SmallD provides methods related to the HTTP -//! methods. For example, [`post`](smalld::SmallD#method.post) for sending a HTTP post request. +//! To send requests through Discord's resources api SmallD provies the +//! [`resource`](smallD::SmallD#method.resource) method. It accepts the path of the resource and +//! provides a builder like interface that allows adding query parameters and calling the resource. //! We can use this method to send a request to the [create //! message](https://discord.com/developers/docs/resources/channel#create-message) endpoint. //! @@ -85,10 +86,9 @@ //! # use smalld::{SmallD, Error}; //! # use serde_json::{json, Value}; //! pub fn send_pong(smalld: &SmallD, reply_to: Value) -> Result<(), Error> { -//! if let Some(channel_id) = reply_to.get("channel_id").and_then(|c| c.as_str()) { -//! smalld.post( -//! format!("/channels/{}/msesages", channel_id), -//! json!({"content" : "pong"}))?; +//! if let Some(channel_id) = reply_to["channel_id"].as_str() { +//! smalld.resource(format!("/channels/{}/msesages", channel_id)) +//! .post(json!({"content" : "pong"}))?; //! }; //! //! Ok(()) @@ -96,9 +96,9 @@ //! ``` pub use crate::error::Error; -pub use crate::http::QueryParameters; pub use crate::intents::Intent; pub use crate::payload::{Op, Payload}; +pub use crate::resource::Resource; pub use crate::smalld::{SmallD, SmallDBuilder}; mod error; @@ -109,5 +109,6 @@ mod identify; mod intents; mod listeners; mod payload; +mod resource; mod retry; mod smalld; diff --git a/src/resource.rs b/src/resource.rs new file mode 100644 index 0000000..9013a70 --- /dev/null +++ b/src/resource.rs @@ -0,0 +1,59 @@ +use crate::error::Error; +use crate::http::Http; +use serde_json::Value; +use std::sync::Arc; + +pub struct Resource { + http: Arc, + path: String, + parameters: Vec<(String, String)>, +} + +impl Resource { + pub(crate) fn new>(http: Arc, path: S) -> Resource { + Resource { + http, + path: path.into(), + parameters: Vec::new(), + } + } + + pub fn query(mut self, key: K, value: V) -> Resource + where + K: Into, + V: Into, + { + self.parameters.push((key.into(), value.into())); + self + } + + pub fn get(self) -> Result { + self.call("GET") + } + + pub fn post(self, json: Value) -> Result { + self.send_json("POST", json) + } + + pub fn put(self, json: Value) -> Result { + self.send_json("PUT", json) + } + + pub fn patch(self, json: Value) -> Result { + self.send_json("PATCH", json) + } + + pub fn delete(self) -> Result { + self.call("DELETE") + } + + fn call(self, method: &str) -> Result { + self.http + .with_request(method, self.path, &self.parameters, |r| r.call()) + } + + fn send_json(self, method: &str, json: Value) -> Result { + self.http + .with_request(method, self.path, &self.parameters, |r| r.send_json(json)) + } +} diff --git a/src/smalld.rs b/src/smalld.rs index 94eabb6..9d1327f 100644 --- a/src/smalld.rs +++ b/src/smalld.rs @@ -1,11 +1,12 @@ use crate::error::Error; use crate::gateway::{Gateway, Message}; use crate::heartbeat::Heartbeat; -use crate::http::{Http, QueryParameters}; +use crate::http::Http; use crate::identify::Identify; use crate::intents::Intent; use crate::listeners::Listeners; use crate::payload::{Op, Payload}; +use crate::resource::Resource; use crate::retry::retry; use log::warn; use serde_json::Value; @@ -31,14 +32,10 @@ const V8_URL: &str = "https://discord.com/api/v8"; /// sending is via [`send_gateway_payload`](SmallD#send_gateway_payload) /// /// * **Resources** -/// The methods for acessing Discord's rest based resource apis. These methods are -/// [`get`](SmallD#function.get), [`post`](SmallD#function.post), [`put`](SmallD#function.put), -/// [`patch`](SmallD#function.patch), and [`delete`](SmallD#function.delete). There are also -/// `_with_parameters` versions for [`get`](SmallD#function.get_with_parameters), -/// [`post`](SmallD#function.post_with_parameters), and -/// [`put`](SmallD#function.put_with_parameters) if appending query parameters to the url is -/// required. -/// +/// The method for acessing Discord's rest based resource apis. This is the +/// [`resource`](SmallD#function.resource) method, which provides a builder like interface to +/// access Discord resources. +/// #[derive(Clone)] pub struct SmallD { http: Arc, @@ -79,50 +76,8 @@ impl SmallD { self.gateway.send(payload) } - pub fn get>(&self, path: S) -> Result { - self.get_with_parameters(path, QueryParameters::new()) - } - - pub fn get_with_parameters>( - &self, - path: S, - parameters: QueryParameters, - ) -> Result { - self.http.get(path, parameters) - } - - pub fn post>(&self, path: S, json: Value) -> Result { - self.post_with_parameters(path, QueryParameters::new(), json) - } - - pub fn post_with_parameters>( - &self, - path: S, - parameters: QueryParameters, - json: Value, - ) -> Result { - self.http.post(path, parameters, json) - } - - pub fn put>(&self, path: S, json: Value) -> Result { - self.put_with_parameters(path, QueryParameters::new(), json) - } - - pub fn put_with_parameters>( - &self, - path: S, - parameters: QueryParameters, - json: Value, - ) -> Result { - self.http.put(path, parameters, json) - } - - pub fn patch>(&self, path: S, json: Value) -> Result { - self.http.patch(path, json) - } - - pub fn delete>(&self, path: S) -> Result { - self.http.delete(path) + pub fn resource>(&self, path: S) -> Resource { + Resource::new(self.http.clone(), path) } pub fn run(&self) { @@ -153,7 +108,8 @@ impl SmallD { fn get_websocket_url(&self) -> Result { let ws_url_str = self - .get("/gateway/bot")? + .resource("/gateway/bot") + .get()? .get("url") .and_then(Value::as_str) .ok_or_else(|| Error::illegal_state("Could not get websocket url"))? diff --git a/tests/http_test.rs b/tests/http_test.rs index 74930a8..ce9c6b9 100644 --- a/tests/http_test.rs +++ b/tests/http_test.rs @@ -1,5 +1,5 @@ use serde_json::json; -use smalld::{Error, QueryParameters, SmallD, SmallDBuilder}; +use smalld::{Error, SmallD, SmallDBuilder}; const DUMMY_TOKEN: &str = "DuMmY.ToKeN"; const HTTP_BIN: &str = "http://httpbin.org"; @@ -22,20 +22,19 @@ fn assert_strlike_eq, B: AsRef>(lhs: A, rhs: B) { #[test] fn it_makes_get_request() { - let rsp = subject().get("/get").unwrap(); + let rsp = subject().resource("/get").get().unwrap(); assert_strlike_eq(rsp["url"].as_str().unwrap(), http_bin("/get")); } #[test] fn it_makes_get_request_with_parameters() { let rsp = subject() - .get_with_parameters( - "/get", - QueryParameters::new() - .add("key1", "value1") - .add("key2", "value2"), - ) + .resource("/get") + .query("key1", "value1") + .query("key2", "value2") + .get() .unwrap(); + assert_strlike_eq( rsp["url"].as_str().unwrap(), http_bin("/get?key1=value1&key2=value2"), @@ -45,7 +44,10 @@ fn it_makes_get_request_with_parameters() { #[test] fn it_makes_post_request() { let json = json!({"foo": "bar"}); - let rsp = subject().post("/post", json!({"foo": "bar"})).unwrap(); + let rsp = subject() + .resource("/post") + .post(json!({"foo": "bar"})) + .unwrap(); assert_strlike_eq(rsp["url"].as_str().unwrap(), http_bin("/post")); assert_eq!(rsp["json"], json); } @@ -53,7 +55,10 @@ fn it_makes_post_request() { #[test] fn it_makes_put_request() { let json = json!({"foo": "bar"}); - let rsp = subject().put("/put", json!({"foo": "bar"})).unwrap(); + let rsp = subject() + .resource("/put") + .put(json!({"foo": "bar"})) + .unwrap(); assert_strlike_eq(rsp["url"].as_str().unwrap(), http_bin("/put")); assert_eq!(rsp["json"], json); } @@ -61,20 +66,23 @@ fn it_makes_put_request() { #[test] fn it_makes_patch_request() { let json = json!({"foo": "bar"}); - let rsp = subject().patch("/patch", json!({"foo": "bar"})).unwrap(); + let rsp = subject() + .resource("/patch") + .patch(json!({"foo": "bar"})) + .unwrap(); assert_strlike_eq(rsp["url"].as_str().unwrap(), http_bin("/patch")); assert_eq!(rsp["json"], json); } #[test] fn it_makes_delete_request() { - let rsp = subject().delete("/delete").unwrap(); + let rsp = subject().resource("/delete").delete().unwrap(); assert_strlike_eq(rsp["url"].as_str().unwrap(), http_bin("/delete")); } #[test] fn it_sends_user_agent() { - let rsp = subject().get("/user-agent").unwrap(); + let rsp = subject().resource("/user-agent").get().unwrap(); assert_strlike_eq( rsp["user-agent"].as_str().unwrap(), "DiscordBot (https://github.com/princesslana/smalld_rust, 0.0.0-dev)", @@ -83,7 +91,7 @@ fn it_sends_user_agent() { #[test] fn it_sends_auth_header() { - let rsp = subject().get("/headers").unwrap(); + let rsp = subject().resource("/headers").get().unwrap(); assert_strlike_eq( rsp["headers"]["Authorization"].as_str().unwrap(), format!("Bot {}", DUMMY_TOKEN), @@ -92,12 +100,12 @@ fn it_sends_auth_header() { #[test] fn it_handles_204_response() { - let rsp = subject().delete("/status/204").unwrap(); + let rsp = subject().resource("/status/204").delete().unwrap(); assert_eq!(rsp, json!({})); } #[test] fn it_errors_on_404_response() { - let rsp = subject().get("/status/404"); + let rsp = subject().resource("/status/404").get(); assert!(matches!(rsp, Err(Error::HttpError(_)))); }