Skip to content

Commit

Permalink
parse json request into Type
Browse files Browse the repository at this point in the history
  • Loading branch information
Sam Bonill authored and Sam Bonill committed Oct 14, 2022
1 parent 3847381 commit c41122f
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 52 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ axum = { git= "https://github.com/tokio-rs/axum", version = "0.6.0-rc.2" }
futures = "0.3.19"
num_cpus = "1.13.1"
async-trait = "0.1.52"
serde_path_to_error = "0.1.8"
serde_json = "1.0.79"
serde = "1.0"
serde_urlencoded = "0.7"
78 changes: 77 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ use graphul::{Json, Graphul, http::{StatusCode, resource::Resource, response::Re
use async_trait::async_trait;
use serde_json::json;

type ResValue = HashMap<String, String>;

struct Article;

Expand All @@ -97,7 +98,13 @@ impl Resource for Article {
}

async fn post(_req: Request) -> Response {
(StatusCode::CREATED, "post handler").into_response()
async fn post(ctx: Context<AppState>) -> Response {
let value: Json<ResValue> = match ctx.payload().await {
Ok(data) => data,
Err(err) => return err.into_response(),
};

(StatusCode::CREATED, value).into_response()
}

// you can use put, delete, head, patch and trace
Expand Down Expand Up @@ -168,6 +175,75 @@ async fn main() {
Json(json!({"message": "hello world!"}))
});

app.run("127.0.0.1:8000").await;
}
```

## Share state

```rust
use graphul::{http::Methods, extract::State, Graphul};

#[tokio::main]
async fn main() {
#[derive(Clone)]
struct AppState {
data: String
}

let state = AppState { data: "Hello, World 👋!".to_string() };
let mut app = Graphul::share_state(state);

app.get("/", |State(state): State<AppState>| async {
state.data
}); // .middelware();

app.run("127.0.0.1:8000").await;
}
```

## Share state with Resource

```rust
use async_trait::async_trait;
use graphul::{
http::{resource::Resource, response::Response, StatusCode},
Context, Graphul, IntoResponse,
};
use serde_json::json;

struct Article;

#[derive(Clone)]
struct AppState {
data: Vec<&str>,
}

#[async_trait]
impl Resource<AppState> for Article {

async fn get(ctx: Context<AppState>) -> Response {
let article = ctx.state();

let posts = json!({
"posts": article.data,
});
(StatusCode::OK, ctx.json(posts)).into_response()
}

// you can use post, put, delete, head, patch and trace
}

#[tokio::main]
async fn main() {
let state = AppState {
data: vec!["Article 1", "Article 2", "Article 3"],
};
let mut app = Graphul::share_state(state);

app.resource("/article", Article);


app.run("127.0.0.1:8000").await;
}
```
117 changes: 84 additions & 33 deletions src/http/request.rs
Original file line number Diff line number Diff line change
@@ -1,72 +1,123 @@
use std::collections::HashMap;

use async_trait::async_trait;
use axum::extract::{
rejection::QueryRejection,
FromRef, FromRequest,
};
pub use axum::http::Request;
use axum::{
body::{Bytes, HttpBody},
extract::{rejection::{JsonRejection, FailedToDeserializeQueryString}, FromRef, FromRequest, Query, Path},
BoxError, Json, http::Extensions,
};
use hyper::{HeaderMap, Method, Uri, Version};
use serde::de::DeserializeOwned;
use serde_json::Value;

use crate::Body;

type HashMapRequest = HashMap<String, String>;

#[derive(Debug)]
pub struct Context<InnerState = (), B = Body> {
params_map: HashMapRequest,
query_map: HashMapRequest,
req: Request<B>,
state: InnerState,
pub struct Context<InnerState = ()> {
params_map: Option<HashMapRequest>,
query_map: Option<HashMapRequest>,
bytes: Bytes,
inner_state: InnerState,
headers: HeaderMap,
method: Method,
uri: Uri,
version: Version,
}

// update context to get params and query
// update context to get params and query implementar params y query genericos que no solo soporte maps si no tambien otros structs
// Json

impl<InnerState, B> Context<InnerState, B> {
pub fn params(&self, key: &'static str) -> String {
match self.params_map.get(key) {
Some(value) => value.clone(),
None => String::new(),
}
impl<InnerState> Context<InnerState> {
async fn parse_query(&mut self) {
// get query
let query = self.uri.query().unwrap_or_default();
match serde_urlencoded::from_str(query) {
Ok(value) => self.query_map = Some(value),
Err(_) => (),
};
}
pub fn request(&self) -> &Request<B> {
&self.req
async fn parse_params(&mut self) {
todo!()
}
pub fn params(&self, _key: &'static str) -> String {
todo!()
}

pub fn body(&self) -> String {
String::from_utf8(self.bytes.to_vec()).expect("")
}
pub fn bytes(&self) -> &Bytes {
&self.bytes
}
pub fn state(&self) -> &InnerState {
&self.state
&self.inner_state
}
pub fn all_params(&self) -> &HashMapRequest {
pub fn all_params(&self) -> &Option<HashMapRequest> {
&self.params_map
}
pub fn query(&self, key: &'static str) -> String {
match self.query_map.get(key) {
Some(value) => value.clone(),
None => String::new(),
}
todo!()
}
pub fn all_query(&self) -> &HashMapRequest {
pub fn all_query(&self) -> &Option<HashMapRequest> {
&self.query_map
}

pub async fn payload<T: DeserializeOwned + Default>(&self) -> Result<Json<T>, JsonRejection> {
// forse parsing
let request = Request::builder()
.header("Content-Type", "application/json")
.body(Body::from(self.bytes.clone()));

let request = match request {
Ok(value) => value,
Err(_) => Request::default(),
};

Json::from_request(request, &()).await
}

pub fn json(&self, payload: Value) -> Json<Value> {
Json(payload)
}

pub fn send(value: &str) -> &str {
value
}
}

#[async_trait]
impl<OuterState, InnerState, B> FromRequest<OuterState, B> for Context<InnerState, B>
impl<OuterState, InnerState, B> FromRequest<OuterState, B> for Context<InnerState>
where
OuterState: Send + Sync + 'static,
InnerState: FromRef<OuterState>,
B: Send + 'static
InnerState: FromRef<OuterState> + Send + Sync,
B: HttpBody + Send + 'static,
B::Data: Send,
B::Error: Into<BoxError>,
{
type Rejection = QueryRejection;
type Rejection = JsonRejection;

async fn from_request(
req: axum::http::Request<B>,
state: &OuterState,
) -> Result<Self, Self::Rejection> {
let inner_state = InnerState::from_ref(state);
let headers = req.headers().clone();
let method = req.method().clone();
let uri = req.uri().clone();
let version = req.version();
let bytes = Bytes::from_request(req, state).await?;
Ok(Context {
req: req,
state: inner_state,
params_map: HashMap::new(),
query_map: HashMap::new(),
version,
headers,
method,
uri,
bytes,
inner_state,
params_map: None,
query_map: None,
})
}
}
25 changes: 11 additions & 14 deletions src/http/resource.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
use async_trait::async_trait;
use axum::{
response::{IntoResponse, Response},
};
use axum::response::{IntoResponse, Response};
use hyper::StatusCode;

use crate::{Body, Context};
use crate::Context;

/*
Expand All @@ -22,40 +20,39 @@ expected to be called millions of times a second.
*/

#[async_trait]
pub trait Resource<S = (), B = Body>
pub trait Resource<S = ()>
where
S: Clone + Send + Sync + 'static,
B: Send + 'static
{
async fn get(_ctx: Context<S, B>) -> Response {
async fn get(_ctx: Context<S>) -> Response {
(StatusCode::NOT_IMPLEMENTED, "Method Not Allowed").into_response()
}

async fn post(_ctx: Context<S, B>) -> Response {
async fn post(_ctx: Context<S>) -> Response {
(StatusCode::NOT_IMPLEMENTED, "Method Not Allowed").into_response()
}

async fn put(_ctx: Context<S, B>) -> Response {
async fn put(_ctx: Context<S>) -> Response {
(StatusCode::NOT_IMPLEMENTED, "Method Not Allowed").into_response()
}

async fn delete(_ctx: Context<S, B>) -> Response {
async fn delete(_ctx: Context<S>) -> Response {
(StatusCode::NOT_IMPLEMENTED, "Method Not Allowed").into_response()
}

async fn patch(_ctx: Context<S, B>) -> Response {
async fn patch(_ctx: Context<S>) -> Response {
(StatusCode::NOT_IMPLEMENTED, "Method Not Allowed").into_response()
}

async fn options(_ctx: Context<S, B>) -> Response {
async fn options(_ctx: Context<S>) -> Response {
(StatusCode::NOT_IMPLEMENTED, "Method Not Allowed").into_response()
}

async fn trace(_ctx: Context<S, B>) -> Response {
async fn trace(_ctx: Context<S>) -> Response {
(StatusCode::NOT_IMPLEMENTED, "Method Not Allowed").into_response()
}

async fn head(_ctx: Context<S, B>) -> Response {
async fn head(_ctx: Context<S>) -> Response {
(StatusCode::NOT_IMPLEMENTED, "Method Not Allowed").into_response()
}
}
7 changes: 3 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use axum::Router;
pub use http::request::Context;
use http::resource::Resource;


pub type Body = axum::body::Body;

pub struct Group<'a, S = ()> {
Expand All @@ -30,7 +29,7 @@ impl<'a, S> Group<'a, S>
where
S: Clone + Send + Sync + 'static,
{
pub fn resource(&mut self, path: &str, res: impl Resource<S, Body> + 'static) {
pub fn resource(&mut self, path: &str, res: impl Resource<S> + 'static) {
let route_name = self.get_route_name(path);
self.app.resource(route_name.as_str(), res);
}
Expand Down Expand Up @@ -143,7 +142,7 @@ impl<S> Graphul<S>
where
S: Clone + Send + Sync + 'static,
{
pub fn resource<T: Resource<S, Body> + 'static>(&mut self, path: &str, _res: T) {
pub fn resource<T: Resource<S> + 'static>(&mut self, path: &str, _res: T) {
// get
self.increase_route_counter();
self.routes = self.routes.clone().route(path, get(T::get));
Expand Down Expand Up @@ -261,7 +260,7 @@ impl<S> Graphul<S>
where
S: Clone + Send + Sync + 'static,
{
pub fn with_state(state: S) -> Self {
pub fn share_state(state: S) -> Self {
Self {
routes: Router::with_state(state),
count_routes: 0,
Expand Down

0 comments on commit c41122f

Please sign in to comment.