Skip to content

Commit

Permalink
Cleanup Query struct
Browse files Browse the repository at this point in the history
Fixes #54
  • Loading branch information
connorslade committed Dec 19, 2023
1 parent 65d0544 commit 96b518c
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 65 deletions.
8 changes: 6 additions & 2 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ Coming Soon
- Add a Stream trait to allow using different socket impls
- Allow using custom event loop
- The custom event loop with the Stream trait should allow a separate crate to add tls support to an afire server

- Cleanup Query struct
- Now stores a set of QueryParameter structs instead of [String; 2]
- Implement Debug for Query
- Rename `Query::from_body` to `Query::from_str`
- Made internal::handle::handle function public for use in custom event loops.

# 2.2.1

Expand Down Expand Up @@ -211,7 +215,7 @@ Apr 10, 2022
- Add SocketHandler struct to hold socket interacting functions
- Fix Path Traversal Exploit O_O

# 1.0.0!
# 1.0.0

Mar 14, 2022

Expand Down
2 changes: 1 addition & 1 deletion examples/old-examples/application_quote_book.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fn main() {
// Route to handle creating new quotes.
// After successful creation the user will be redirected to the new quotes page.
server.stateful_route(Method::POST, "/api/new", |app, req| {
let form = Query::from_body(&String::from_utf8_lossy(&req.body));
let form = Query::from_str(&String::from_utf8_lossy(&req.body));
let name =
url::decode(form.get("author").expect("No author supplied")).expect("Invalid author");
let body =
Expand Down
2 changes: 1 addition & 1 deletion examples/old-examples/basic/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl Example for Data {
// The body of requests is not part of the req.query
// Instead it is part of the req.body but as a string
// We will need to parse it get it as a query
let body_data = Query::from_body(&String::from_utf8_lossy(&req.body));
let body_data = Query::from_str(&String::from_utf8_lossy(&req.body));

let name = body_data.get("name").unwrap_or("Nobody");
let text = format!("<h1>Hello, {}</h1>", name);
Expand Down
2 changes: 1 addition & 1 deletion examples/paste_bin/src/routes/post_paste.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ fn post_api(ctx: &Context<App>) -> Result<(), Box<dyn error::Error>> {

fn post_form(ctx: &Context<App>) -> Result<(), Box<dyn error::Error>> {
// Parse the query from the request body
let query = Query::from_body(&ctx.req.body_str());
let query = Query::from_str(&ctx.req.body_str());
// Pull out the name and paste from the query
// (automatically url-decoded)
let name = query.get("title").context("Missing name")?;
Expand Down
2 changes: 1 addition & 1 deletion lib/extensions/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ impl Logger {
// Format Query as string
let mut query = "".to_string();
for i in req.query.iter() {
query += &format!("{}: {}, ", i[0], i[1]);
query += &format!("{}: {}, ", i.key, i.value);
}
if query.len() >= 2 {
query = query[0..query.len() - 2].to_string()
Expand Down
13 changes: 4 additions & 9 deletions lib/internal/handle.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
use std::{
cell::RefCell,
io::Read,
net::{Shutdown, TcpStream},
sync::Arc,
};
use std::{cell::RefCell, io::Read, net::Shutdown, sync::Arc};

use crate::{
context::ContextFlag,
Expand All @@ -23,19 +18,19 @@ pub(crate) type Writeable = Box<RefCell<dyn Read + Send>>;
/// Handles a socket.
///
/// <https://open.spotify.com/track/50txng2W8C9SycOXKIQP0D>
pub(crate) fn handle<State>(stream: Arc<Socket>, this: Arc<Server<State>>)
pub fn handle<State>(stream: Arc<Socket>, this: Arc<Server<State>>)
where
State: 'static + Send + Sync,
{
let mut socket = stream.force_lock();
let socket = stream.force_lock();
trace!(
Level::Debug,
"Opening socket {:?}",
LazyFmt(|| socket.peer_addr())
);
socket.set_timeout(this.socket_timeout).unwrap();
drop(socket);

'outer: loop {
let mut keep_alive = false;
let mut req = Request::from_socket(stream.clone());
Expand Down
126 changes: 77 additions & 49 deletions lib/proto/http/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,19 @@ use crate::internal::encoding::url;
/// Collection of query parameters.
/// Can be made from the query string of a URL, or the body of a POST request.
/// Similar to [`crate::header::Headers`].
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub struct Query(Vec<[String; 2]>);

impl Deref for Query {
type Target = Vec<[String; 2]>;

fn deref(&self) -> &Self::Target {
&self.0
}
#[derive(Hash, PartialEq, Eq, Clone)]
pub struct Query {
inner: Vec<QueryParameter>,
}

impl DerefMut for Query {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
/// An individual query parameter.
/// Key and value are both automatically url decoded.
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub struct QueryParameter {
/// The key of the query parameter.
pub key: String,
/// The value of the query parameter.
pub value: String,
}

impl Query {
Expand All @@ -33,15 +31,15 @@ impl Query {
/// ```rust
/// # use afire::Query;
/// # use std::str::FromStr;
/// # let query = Query::from_body("foo=bar&nose=dog");
/// # let query = Query::from_str("foo=bar&nose=dog");
/// # assert!(query.has("foo"));
/// if query.has("foo") {
/// println!("foo exists");
/// }
/// ```
pub fn has(&self, key: impl AsRef<str>) -> bool {
let key = key.as_ref().to_owned();
self.iter().any(|i| *i[0] == key)
let key = key.as_ref();
self.inner.iter().any(|i| i.key == key)
}

/// Adds a new key-value pair to the collection with the specified key and value.
Expand All @@ -54,7 +52,10 @@ impl Query {
/// query.add("foo", "bar");
/// # }
pub fn add(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.push([key.into(), value.into()]);
self.inner.push(QueryParameter {
key: key.into(),
value: value.into(),
});
}

/// Get a value from a key.
Expand All @@ -63,62 +64,67 @@ impl Query {
/// ```
/// # use afire::Query;
/// # use std::str::FromStr;
/// let query = Query::from_body("foo=bar&nose=dog");
/// let query = Query::from_str("foo=bar&nose=dog");
/// assert_eq!(query.get("foo"), Some("bar"));
/// ```
pub fn get(&self, key: impl AsRef<str>) -> Option<&str> {
let key = key.as_ref().to_owned();
self.iter()
.find(|i| *i[0] == key)
.map(|i| &i[1])
.map(|x| x.as_str())
let key = key.as_ref();
self.inner
.iter()
.find(|i| i.key == key)
.map(|i| i.value.as_ref())
}

/// Gets a value of the specified key as a mutable reference.
/// This will return None if the key does not exist.
/// See [`Query::get`] for the non-mutable version.
pub fn get_mut(&mut self, key: impl AsRef<str>) -> Option<&mut String> {
let key = key.as_ref().to_owned();
self.iter_mut().find(|i| *i[0] == key).map(|i| &mut i[1])
let key = key.as_ref();
self.inner
.iter_mut()
.find(|i| i.key == key)
.map(|i| &mut i.value)
}

/// Adds a new key-value pair to the collection from a `[String; 2]`.
/// See [`Query::add`] for adding to the collection with a key and value.
/// Adds a new parameter to the collection with a QueryParameter struct.
/// See [`Query::add`] for adding a key-value pair with string keys and values.
/// ## Example
/// ```rust
/// # use afire::Query;
/// # use afire::proto::http::query::{QueryParameter, Query};
/// # fn test(query: &mut Query) {
/// query.add_query(["foo".to_owned(), "bar".to_owned()]);
/// query.add_query(QueryParameter {
/// key: "foo".into(),
/// value: "bar".into(),
/// });
/// # }
pub fn add_query(&mut self, query: [String; 2]) {
self.push(query);
pub fn add_query(&mut self, query: QueryParameter) {
self.inner.push(query);
}

/// Gets the key-value pair of the specified key.
/// If the key does not exist, this will return None.
pub fn get_query(&self, key: impl AsRef<str>) -> Option<&[String; 2]> {
let key = key.as_ref().to_owned();
self.iter().find(|i| *i[0] == key)
pub fn get_query(&self, key: impl AsRef<str>) -> Option<&QueryParameter> {
let key = key.as_ref();
self.inner.iter().find(|i| i.key == key)
}

/// Get the key-value pair of the specified key as a mutable reference.
/// If the key does not exist, this will return None.
pub fn get_query_mut(&mut self, key: impl AsRef<str>) -> Option<&mut [String; 2]> {
let key = key.as_ref().to_owned();
self.iter_mut().find(|i| *i[0] == key)
pub fn get_query_mut(&mut self, key: impl AsRef<str>) -> Option<&mut QueryParameter> {
let key = key.as_ref();
self.inner.iter_mut().find(|i| i.key == key)
}

/// Create a new Query from a Form POST body
/// ## Example
/// ```
/// # use afire::Query;
/// let query = Query::from_body("foo=bar&nose=dog");
/// let query = Query::from_str("foo=bar&nose=dog");
/// ```
pub fn from_body(body: &str) -> Self {
pub fn from_str(body: &str) -> Self {
let mut data = Vec::new();

let parts = body.split('&');
for i in parts {
for i in body.split('&') {
let mut sub = i.splitn(2, '=');

let Some(key) = sub.next().map(url::decode) else {
Expand All @@ -129,52 +135,74 @@ impl Query {
continue;
};

data.push([key, value])
data.push(QueryParameter {
key: key.into(),
value: value.into(),
});
}

Query(data)
Query { inner: data }
}
}

impl Deref for Query {
type Target = Vec<QueryParameter>;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl DerefMut for Query {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}

// Implement fmt::Display for Query
impl fmt::Display for Query {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_empty() {
return f.write_str("");
}

let mut output = String::from("?");
for i in &self.0 {
output.push_str(&format!("{}={}&", i[0], i[1]));
for i in &self.inner {
output.push_str(&format!("{}={}&", i.key, i.value));
}

f.write_str(&output[..output.len() - 1])
}
}

impl fmt::Debug for Query {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Query").field("inner", &self.inner).finish()
}
}

#[cfg(test)]
mod test {
use super::Query;

#[test]
fn test_from_str() {
let query = Query::from_body("foo=bar&nose=dog");
let query = Query::from_str("foo=bar&nose=dog");
assert_eq!(query.get("foo"), Some("bar"));
assert_eq!(query.get("nose"), Some("dog"));
assert_eq!(query.get("bar"), None);
}

#[test]
fn test_get() {
let query = Query::from_body("foo=bar&nose=dog");
let query = Query::from_str("foo=bar&nose=dog");
assert_eq!(query.get("foo"), Some("bar"));
assert_eq!(query.get("nose"), Some("dog"));
assert_eq!(query.get("bar"), None);
}

#[test]
fn test_get_mut() {
let mut query = Query::from_body("foo=bar&nose=dog");
let mut query = Query::from_str("foo=bar&nose=dog");
assert_eq!(query.get_mut("bar"), None);
query.get_mut("foo").unwrap().push_str("bar");
assert_eq!(query.get("foo"), Some("barbar"));
Expand Down
2 changes: 1 addition & 1 deletion lib/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ pub(crate) fn parse_request_line(bytes: &[u8]) -> Result<(Method, String, Query,
}
}

let query = Query::from_body(&final_query);
let query = Query::from_str(&final_query);
let version = parts
.next()
.ok_or(Error::Parse(ParseError::NoVersion))?
Expand Down

0 comments on commit 96b518c

Please sign in to comment.