Skip to content

Commit

Permalink
feat(extractors): add query extractor
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreyErmilov committed Aug 13, 2023
1 parent 364bcfd commit 9b4dffd
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 21 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ members = [
"hitbox-redis",
"hitbox-stretto",
"hitbox-tower",
"hitbox-qs",
"examples",
]
2 changes: 1 addition & 1 deletion hitbox-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ http = "0.2"
http-body = "0.4"
hitbox = { path = "../hitbox", version = "0.1" }
hitbox-backend = { path = "../hitbox-backend", version = "0.1" }
hitbox-qs = { path = "../hitbox-qs", version = "0.1" }
serde = "1.0.144"
bytes = "1"
chrono = "0.4"
hyper = { version = "0.14", features = ["stream"] }
futures = { version = "0.3", default-features = false }
actix-router = "0.5"
serde_qs = "0.12"

[dev-dependencies]
tokio = { version = "1", features = ["test-util"], default-features = false }
Expand Down
1 change: 1 addition & 0 deletions hitbox-http/src/extractors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::CacheableHttpRequest;
pub mod header;
pub mod method;
pub mod path;
pub mod query;

pub struct NeutralExtractor<ReqBody> {
_res: PhantomData<fn(ReqBody) -> ReqBody>,
Expand Down
49 changes: 49 additions & 0 deletions hitbox-http/src/extractors/query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use async_trait::async_trait;
use hitbox::cache::{Extractor, KeyPart, KeyParts};

use crate::CacheableHttpRequest;

pub struct Query<E> {
inner: E,
name: String,
}

pub trait QueryExtractor: Sized {
fn query(self, name: String) -> Query<Self>;
}

impl<E> QueryExtractor for E
where
E: Extractor,
{
fn query(self, name: String) -> Query<Self> {
Query { inner: self, name }
}
}

#[async_trait]
impl<ReqBody, E> Extractor for Query<E>
where
ReqBody: Send + 'static,
E: Extractor<Subject = CacheableHttpRequest<ReqBody>> + Send + Sync,
{
type Subject = E::Subject;

async fn get(&self, subject: Self::Subject) -> KeyParts<Self::Subject> {
let values = subject
.parts()
.uri
.query()
.map(hitbox_qs::parse)
.map(|m| m.get(&self.name).map(hitbox_qs::Value::inner))
.flatten()
.unwrap_or_default();
let mut parts = self.inner.get(subject).await;
for value in values {
parts.push(KeyPart {
key: self.name.clone(),
value: Some(value),
});}
parts
}
}
22 changes: 5 additions & 17 deletions hitbox-http/src/predicates/query.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
use crate::CacheableHttpRequest;
use async_trait::async_trait;
use hitbox::predicates::{Operation, Predicate, PredicateResult};
use serde::Deserialize;
use std::{collections::HashMap, marker::PhantomData};

#[derive(Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum QsValue {
Scalar(String),
Array(Vec<String>),
}
use hitbox_qs::Value;

pub struct Query<P> {
pub name: String,
pub value: QsValue,
pub value: Value,
pub operation: Operation,
inner: P,
}
Expand All @@ -29,17 +21,13 @@ where
fn query(self, name: String, value: String) -> Query<P> {
Query {
name,
value: QsValue::Scalar(value),
value: Value::Scalar(value),
operation: Operation::Eq,
inner: self,
}
}
}

fn parse_query(value: &str) -> HashMap<String, QsValue> {
serde_qs::from_str(value).unwrap()
}

#[async_trait]
impl<ReqBody, P> Predicate for Query<P>
where
Expand All @@ -52,11 +40,11 @@ where
match self.inner.check(request).await {
PredicateResult::Cacheable(request) => {
let op = match self.operation {
Operation::Eq => QsValue::eq,
Operation::Eq => Value::eq,
Operation::In => unimplemented!(),
};
match request.parts().uri.query() {
Some(query_string) => match parse_query(query_string).get(&self.name) {
Some(query_string) => match hitbox_qs::parse(query_string).get(&self.name) {
Some(value) if op(value, &self.value) => {
PredicateResult::Cacheable(request)
}
Expand Down
2 changes: 1 addition & 1 deletion hitbox-http/tests/cache_policy/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
mod request;
//mod request;
1 change: 1 addition & 0 deletions hitbox-http/tests/extractors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod header;
mod query;
mod method;
mod multiple;
mod path;
44 changes: 44 additions & 0 deletions hitbox-http/tests/extractors/query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use hitbox::cache::Extractor;
use hitbox_http::extractors::{query::QueryExtractor, NeutralExtractor};
use hitbox_http::CacheableHttpRequest;
use http::Request;
use hyper::Body;

#[tokio::test]
async fn test_request_query_extractor_some() {
let uri = http::uri::Uri::builder()
.path_and_query("test-path?key=value")
.build()
.unwrap();
let request = Request::builder().uri(uri).body(Body::empty()).unwrap();
let request = CacheableHttpRequest::from_request(request);
let extractor = NeutralExtractor::new().query("key".to_owned());
let parts = extractor.get(request).await;
dbg!(parts);
}

#[tokio::test]
async fn test_request_query_extractor_none() {
let uri = http::uri::Uri::builder()
.path_and_query("test-path?key=value")
.build()
.unwrap();
let request = Request::builder().uri(uri).body(Body::empty()).unwrap();
let request = CacheableHttpRequest::from_request(request);
let extractor = NeutralExtractor::new().query("non-existent-key".to_owned());
let parts = extractor.get(request).await;
dbg!(parts);
}

#[tokio::test]
async fn test_request_query_extractor_multiple() {
let uri = http::uri::Uri::builder()
.path_and_query("test-path?cars[]=Saab&cars[]=Audi")
.build()
.unwrap();
let request = Request::builder().uri(uri).body(Body::empty()).unwrap();
let request = CacheableHttpRequest::from_request(request);
let extractor = NeutralExtractor::new().query("cars".to_owned());
let parts = extractor.get(request).await;
dbg!(parts);
}
4 changes: 2 additions & 2 deletions hitbox-http/tests/predicates/request/query.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use hitbox::predicates::{Operation, Predicate};
use hitbox_http::predicates::query::{QsValue, QueryPredicate};
use hitbox::predicates::Predicate;
use hitbox_http::predicates::query::QueryPredicate;
use hitbox_http::predicates::NeutralPredicate;
use hitbox_http::CacheableHttpRequest;
use http::Request;
Expand Down
10 changes: 10 additions & 0 deletions hitbox-qs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "hitbox-qs"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde_qs = "0.12"
serde = { version = "1", features = ["derive"] }
51 changes: 51 additions & 0 deletions hitbox-qs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::collections::HashMap;
use serde::Deserialize;

#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum Value {
Scalar(String),
Array(Vec<String>),
}

impl Value {
pub fn inner(&self) -> Vec<String> {
match self {
Value::Scalar(value) => vec![value.to_owned()],
Value::Array(values) => values.to_owned(),
}
}
}

pub fn parse(value: &str) -> HashMap<String, Value> {
serde_qs::from_str(value).expect("Unreachable branch reached")
}

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

#[test]
fn test_parse_valid_one() {
let hash_map = parse("key=value");
let value = hash_map.get("key").unwrap();
assert_eq!(value.inner(), vec!["value"]);
}

#[test]
fn test_parse_valid_multiple() {
let hash_map = parse("key-one=value-one&key-two=value-two&key-three=value-three");
let value = hash_map.get("key-one").unwrap();
assert_eq!(value.inner(), vec!["value-one"]);
let value = hash_map.get("key-two").unwrap();
assert_eq!(value.inner(), vec!["value-two"]);
let value = hash_map.get("key-three").unwrap();
assert_eq!(value.inner(), vec!["value-three"]);
}

#[test]
fn test_parse_not_valid() {
let hash_map = parse(" wrong ");
assert_eq!(hash_map.len(), 1);
}
}

0 comments on commit 9b4dffd

Please sign in to comment.