Skip to content

Commit

Permalink
Merge branch 'release/v1.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
rousan committed May 2, 2020
2 parents e828b7f + 49f8c7a commit 02c1562
Show file tree
Hide file tree
Showing 10 changed files with 485 additions and 124 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "json-response"
version = "1.0.0"
description = "A utility library to send JSON response."
description = "A utility library to send JSON response for Routerify and hyper.rs apps."
homepage = "https://github.com/routerify/json-response"
repository = "https://github.com/routerify/json-response"
keywords = ["routerify", "hyper-rs", "json", "response"]
Expand All @@ -12,8 +12,10 @@ license = "MIT"
edition = "2018"

[dependencies]
routerify = "1.0"
hyper = "0.13"
routerify = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[dev-dependencies]
tokio = { version = "0.2", features = ["full"] }
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020 Boilerplato
Copyright (c) 2020 Rousan Ali

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
85 changes: 77 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,97 @@

# json-response

A utility library to send JSON response.
A utility library to send JSON response for [`Routerify`](https://github.com/routerify/routerify) and the Rust HTTP library [`hyper.rs`](https://hyper.rs/) apps.

In `Success` case, It generates JSON response in the following format:

```json
{
"status": "success",
"code": "<status_code>",
"data": "<data>"
}
```

In `Failed` case, It generates JSON response in the following format:

```json
{
"status": "failed",
"code": "<status_code>",
"message": "<error_message>"
}
```

[Docs](https://docs.rs/json-response)

## Usage
## Install

First add this to your `Cargo.toml`:
Add this to your `Cargo.toml`:

```toml
[dependencies]
routerify = "1.0"
json-response = "1.0"
```

An example:
## Example

```rust
use json_response;
use hyper::{Body, Request, Response, Server, StatusCode};
// Import required json_response methods.
use json_response::{json_failed_resp_with_message, json_success_resp};
use routerify::{Router, RouterService};
use std::net::SocketAddr;

async fn list_users_handler(_: Request<Body>) -> Result<Response<Body>, routerify::Error> {
// Fetch response data from somewhere.
let users = ["Alice", "John"];

// Generate a success JSON response with the data in the following format:
// { "status": "success", code: 200, data: ["Alice", "John"] }
json_success_resp(&users)
}

async fn list_books_handler(_: Request<Body>) -> Result<Response<Body>, routerify::Error> {
// Generate a failed JSON response in the following format:
// { "status": "failed", code: 500, data: "Internal Server Error: Couldn't fetch book list from database" }
json_failed_resp_with_message(
StatusCode::INTERNAL_SERVER_ERROR,
"Couldn't fetch book list from database",
)
}

// Create a router.
fn router() -> Router<Body, routerify::Error> {
Router::builder()
// Attach the handlers.
.get("/users", list_users_handler)
.get("/books", list_books_handler)
.build()
.unwrap()
}

#[tokio::main]
async fn main() {
let router = router();

// Create a Service from the router above to handle incoming requests.
let service = RouterService::new(router);

// The address on which the server will be listening.
let addr = SocketAddr::from(([127, 0, 0, 1], 3001));

// Create a server by passing the created service to `.serve` method.
let server = Server::bind(&addr).serve(service);

fn main() {
println!("{}", json_response::add(2, 3));
println!("App is running on: {}", addr);
if let Err(err) = server.await {
eprintln!("Server error: {}", err);
}
}
```

## Contributing
## Contributing

Your PRs and suggestions are always welcome.
52 changes: 52 additions & 0 deletions examples/example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use hyper::{Body, Request, Response, Server, StatusCode};
// Import required json_response methods.
use json_response::{json_failed_resp_with_message, json_success_resp};
use routerify::{Router, RouterService};
use std::net::SocketAddr;

async fn list_users_handler(_: Request<Body>) -> Result<Response<Body>, routerify::Error> {
// Fetch response data from somewhere.
let users = ["Alice", "John"];

// Generate a success JSON response with the data in the following format:
// { "status": "success", code: 200, data: ["Alice", "John"] }
json_success_resp(&users)
}

async fn list_books_handler(_: Request<Body>) -> Result<Response<Body>, routerify::Error> {
// Generate a failed JSON response in the following format:
// { "status": "failed", code: 500, data: "Internal Server Error: Couldn't fetch book list from database" }
json_failed_resp_with_message(
StatusCode::INTERNAL_SERVER_ERROR,
"Couldn't fetch book list from database",
)
}

// Create a router.
fn router() -> Router<Body, routerify::Error> {
Router::builder()
// Attach the handlers.
.get("/users", list_users_handler)
.get("/books", list_books_handler)
.build()
.unwrap()
}

#[tokio::main]
async fn main() {
let router = router();

// Create a Service from the router above to handle incoming requests.
let service = RouterService::new(router);

// The address on which the server will be listening.
let addr = SocketAddr::from(([127, 0, 0, 1], 3001));

// Create a server by passing the created service to `.serve` method.
let server = Server::bind(&addr).serve(service);

println!("App is running on: {}", addr);
if let Err(err) = server.await {
eprintln!("Server error: {}", err);
}
}
29 changes: 26 additions & 3 deletions examples/test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
use json_response;
use hyper::{Body, Request, Response, Server, StatusCode};
use json_response::{json_failed_resp, json_failed_resp_with_message, json_success_resp, json_success_resp_with_code};
use routerify::{Router, RouterService};
use std::{convert::Infallible, net::SocketAddr};

fn main() {
println!("{}", json_response::add(2, 3));
async fn home_handler(_: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(json_success_resp(&["Alice", "John"]).unwrap())
}

fn router() -> Router<Body, Infallible> {
Router::builder().get("/", home_handler).build().unwrap()
}

#[tokio::main]
async fn main() {
let router = router();

let service = RouterService::new(router);

let addr = SocketAddr::from(([127, 0, 0, 1], 3001));

let server = Server::bind(&addr).serve(service);

println!("App is running on: {}", addr);
if let Err(err) = server.await {
eprintln!("Server error: {}", err);
}
}
29 changes: 29 additions & 0 deletions examples/test_stream_body.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use hyper::{Body as HyperBody, Request, Response, Server, StatusCode};
use json_response::{json_failed_resp, json_failed_resp_with_message, json_success_resp, json_success_resp_with_code};
use routerify::{Router, RouterService};
use std::{convert::Infallible, net::SocketAddr};
use stream_body::StreamBody;

async fn home_handler(_: Request<HyperBody>) -> Result<Response<StreamBody>, Infallible> {
Ok(json_success_resp_with_code(StatusCode::ACCEPTED, &["Alice", "John"]).unwrap())
}

fn router() -> Router<StreamBody, Infallible> {
Router::builder().get("/", home_handler).build().unwrap()
}

#[tokio::main]
async fn main() {
let router = router();

let service = RouterService::new(router);

let addr = SocketAddr::from(([127, 0, 0, 1], 3001));

let server = Server::bind(&addr).serve(service);

println!("App is running on: {}", addr);
if let Err(err) = server.await {
eprintln!("Server error: {}", err);
}
}
91 changes: 91 additions & 0 deletions src/failed_resp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use crate::gen_resp::gen_response;
use hyper::{body::HttpBody, Response, StatusCode};
use serde::Serialize;

const STATUS_FAILED: &'static str = "failed";

#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
struct FailedResp {
status: &'static str,
code: u16,
message: String,
}

/// Generates a failed JSON response with the provided message and status code.
///
/// It generates JSON response in the following JSON format:
///
/// ```json
/// {
/// "status": "failed",
/// "code": "<status_code>",
/// "message": "<error_message>"
/// }
///```
///
/// # Examples
///
/// ```
/// use hyper::{Body, Request, Response, StatusCode};
/// use json_response::{json_failed_resp_with_message};
///
/// async fn list_books_handler(_: Request<Body>) -> Result<Response<Body>, routerify::Error> {
/// // Generate a failed JSON response in the following format:
/// // { "status": "failed", code: 500, data: "Internal Server Error: Couldn't fetch book list from database" }
/// json_failed_resp_with_message(
/// StatusCode::INTERNAL_SERVER_ERROR,
/// "Couldn't fetch book list from database",
/// )
/// }
/// ```
pub fn json_failed_resp_with_message<B, M>(code: StatusCode, message: M) -> routerify::Result<Response<B>>
where
B: HttpBody + From<Vec<u8>> + Send + Sync + Unpin + 'static,
M: Into<String>,
{
let resp_data = FailedResp {
status: STATUS_FAILED,
code: code.as_u16(),
message: format!("{}: {}", code.canonical_reason().unwrap(), message.into()),
};

gen_response(code, &resp_data)
}

/// Generates a failed JSON response with the status code specific message and status code.
///
/// It generates JSON response in the following JSON format:
///
/// ```json
/// {
/// "status": "failed",
/// "code": "<status_code>",
/// "message": "<status_code_message>"
/// }
///```
///
/// # Examples
///
/// ```
/// use hyper::{Body, Request, Response, StatusCode};
/// use json_response::{json_failed_resp};
///
/// async fn list_books_handler(_: Request<Body>) -> Result<Response<Body>, routerify::Error> {
/// // Generate a failed JSON response in the following format:
/// // { "status": "failed", code: 500, data: "Internal Server Error" }
/// json_failed_resp(StatusCode::INTERNAL_SERVER_ERROR)
/// }
/// ```
pub fn json_failed_resp<B>(code: StatusCode) -> routerify::Result<Response<B>>
where
B: HttpBody + From<Vec<u8>> + Send + Sync + Unpin + 'static,
{
let resp_data = FailedResp {
status: STATUS_FAILED,
code: code.as_u16(),
message: code.canonical_reason().unwrap().to_string(),
};

gen_response(code, &resp_data)
}
35 changes: 35 additions & 0 deletions src/gen_resp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use hyper::{body::HttpBody, header, Response, StatusCode};
use serde::Serialize;

pub(crate) fn gen_response<B, D>(code: StatusCode, resp_data: &D) -> routerify::Result<Response<B>>
where
B: HttpBody + From<Vec<u8>> + Send + Sync + Unpin + 'static,
D: Serialize + Send + Sync + Unpin,
{
let json_resp_data = match serde_json::to_vec(&resp_data) {
Ok(json_data) => json_data,
Err(err) => {
return Err(routerify::Error::new(format!(
"json-response: Failed to convert the response data as JSON: {}",
err
)));
}
};

let content_ln = json_resp_data.len();
let body = B::from(json_resp_data);

let resp = Response::builder()
.status(code)
.header(header::CONTENT_LENGTH, content_ln.to_string())
.header(header::CONTENT_TYPE, "application/json; charset=utf-8")
.body(body);

match resp {
Ok(resp) => Ok(resp),
Err(err) => Err(routerify::Error::new(format!(
"json-response: Failed to create response: {}",
err
))),
}
}
Loading

0 comments on commit 02c1562

Please sign in to comment.