Skip to content

Commit

Permalink
📦 NEW: Refactor resource access to builder api
Browse files Browse the repository at this point in the history
  • Loading branch information
ianagbip1oti committed Jan 24, 2021
1 parent 7d8a788 commit 578d70d
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 168 deletions.
15 changes: 7 additions & 8 deletions examples/ping_bot.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use log::{debug, warn};
use log::warn;
use serde_json::json;
use smalld::SmallD;

Expand All @@ -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);
}
}
Expand Down
90 changes: 8 additions & 82 deletions src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,6 @@ impl Http {
}
}

pub fn get<S: AsRef<str>>(&self, path: S, params: QueryParameters) -> Result<Value, Error> {
self.with_request("GET", path.as_ref(), params, |r| r.call())
}

pub fn post<S: AsRef<str>>(
&self,
path: S,
params: QueryParameters,
json: Value,
) -> Result<Value, Error> {
self.with_request("POST", path.as_ref(), params, |r| r.send_json(json))
}

pub fn put<S: AsRef<str>>(
&self,
path: S,
params: QueryParameters,
json: Value,
) -> Result<Value, Error> {
self.with_request("PUT", path.as_ref(), params, |r| r.send_json(json))
}

pub fn patch<S: AsRef<str>>(&self, path: S, json: Value) -> Result<Value, Error> {
self.with_request("PATCH", path.as_ref(), QueryParameters::new(), |r| {
r.send_json(json)
})
}

pub fn delete<S: AsRef<str>>(&self, path: S) -> Result<Value, Error> {
self.with_request("DELETE", path.as_ref(), QueryParameters::new(), |r| {
r.call()
})
}

fn build_url(&self, path: &str) -> Result<Url, Error> {
let mut url: Url = self.base_url.clone();

Expand All @@ -69,23 +35,26 @@ impl Http {
Ok(url)
}

fn with_request<F>(
pub(crate) fn with_request<P, F>(
&self,
method: &str,
path: &str,
params: QueryParameters,
path: P,
params: &[(String, String)],
f: F,
) -> Result<Value, Error>
where
P: AsRef<str>,
F: FnOnce(Request) -> Result<Response, ureq::Error>,
{
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)?;

Expand All @@ -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<A, B>(mut self, key: A, value: B) -> Self
where
A: Into<String>,
B: Into<String>,
{
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()
}
}
17 changes: 9 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,17 @@
//! 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!");
//! }
//! });
//!
//! 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.
//!
Expand All @@ -85,20 +86,19 @@
//! # 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(())
//! }
//! ```
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;
Expand All @@ -109,5 +109,6 @@ mod identify;
mod intents;
mod listeners;
mod payload;
mod resource;
mod retry;
mod smalld;
59 changes: 59 additions & 0 deletions src/resource.rs
Original file line number Diff line number Diff line change
@@ -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<Http>,
path: String,
parameters: Vec<(String, String)>,
}

impl Resource {
pub(crate) fn new<S: Into<String>>(http: Arc<Http>, path: S) -> Resource {
Resource {
http,
path: path.into(),
parameters: Vec::new(),
}
}

pub fn query<K, V>(mut self, key: K, value: V) -> Resource
where
K: Into<String>,
V: Into<String>,
{
self.parameters.push((key.into(), value.into()));
self
}

pub fn get(self) -> Result<Value, Error> {
self.call("GET")
}

pub fn post(self, json: Value) -> Result<Value, Error> {
self.send_json("POST", json)
}

pub fn put(self, json: Value) -> Result<Value, Error> {
self.send_json("PUT", json)
}

pub fn patch(self, json: Value) -> Result<Value, Error> {
self.send_json("PATCH", json)
}

pub fn delete(self) -> Result<Value, Error> {
self.call("DELETE")
}

fn call(self, method: &str) -> Result<Value, Error> {
self.http
.with_request(method, self.path, &self.parameters, |r| r.call())
}

fn send_json(self, method: &str, json: Value) -> Result<Value, Error> {
self.http
.with_request(method, self.path, &self.parameters, |r| r.send_json(json))
}
}
64 changes: 10 additions & 54 deletions src/smalld.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Http>,
Expand Down Expand Up @@ -79,50 +76,8 @@ impl SmallD {
self.gateway.send(payload)
}

pub fn get<S: AsRef<str>>(&self, path: S) -> Result<Value, Error> {
self.get_with_parameters(path, QueryParameters::new())
}

pub fn get_with_parameters<S: AsRef<str>>(
&self,
path: S,
parameters: QueryParameters,
) -> Result<Value, Error> {
self.http.get(path, parameters)
}

pub fn post<S: AsRef<str>>(&self, path: S, json: Value) -> Result<Value, Error> {
self.post_with_parameters(path, QueryParameters::new(), json)
}

pub fn post_with_parameters<S: AsRef<str>>(
&self,
path: S,
parameters: QueryParameters,
json: Value,
) -> Result<Value, Error> {
self.http.post(path, parameters, json)
}

pub fn put<S: AsRef<str>>(&self, path: S, json: Value) -> Result<Value, Error> {
self.put_with_parameters(path, QueryParameters::new(), json)
}

pub fn put_with_parameters<S: AsRef<str>>(
&self,
path: S,
parameters: QueryParameters,
json: Value,
) -> Result<Value, Error> {
self.http.put(path, parameters, json)
}

pub fn patch<S: AsRef<str>>(&self, path: S, json: Value) -> Result<Value, Error> {
self.http.patch(path, json)
}

pub fn delete<S: AsRef<str>>(&self, path: S) -> Result<Value, Error> {
self.http.delete(path)
pub fn resource<S: Into<String>>(&self, path: S) -> Resource {
Resource::new(self.http.clone(), path)
}

pub fn run(&self) {
Expand Down Expand Up @@ -153,7 +108,8 @@ impl SmallD {

fn get_websocket_url(&self) -> Result<Url, Error> {
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"))?
Expand Down
Loading

0 comments on commit 578d70d

Please sign in to comment.