Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📦 NEW: Refactor resource access to builder api #54

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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