Skip to content

Commit

Permalink
Merge pull request #92 from kana-rus/impl_response_body_for_iterator_…
Browse files Browse the repository at this point in the history
…of_response_body_json

Support `Option<_>`, `Vec<_>`, `[_; {0-32}]` as JSON `ResponseBody` and `IntoResponse`
  • Loading branch information
kanarus authored Feb 19, 2024
2 parents 609626e + bd88296 commit 05e5de5
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 82 deletions.
2 changes: 1 addition & 1 deletion benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ edition = "2021"
authors = ["kanarus <[email protected]>"]

[dependencies]
ohkami = { version = "0.14.0", path = "../ohkami", features = ["rt_tokio", "DEBUG"] }
ohkami = { version = "0.15.0", path = "../ohkami", features = ["rt_tokio", "DEBUG"] }
http = "1.0.0"
rustc-hash = "1.1"
3 changes: 2 additions & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ members = [
"hello",
"realworld",
"quick_start",
"json_response",
]

[workspace.dependencies]
# To assure "DEBUG" feature be off even if DEBUGing `../ohkami`,
# explicitly set `default-features = false`
ohkami = { version = "0.14.0", path = "../ohkami", default-features = false, features = ["rt_tokio", "utils", "testing"] }
ohkami = { version = "0.15.0", path = "../ohkami", default-features = false, features = ["rt_tokio", "utils", "testing"] }
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.7.3", features = ["runtime-tokio-native-tls", "postgres", "macros", "chrono", "uuid"] }
tracing = "0.1"
Expand Down
9 changes: 9 additions & 0 deletions examples/json_response/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "json_response"
version = "0.1.0"
edition = "2021"
authors = ["kanarus <[email protected]>"]

[dependencies]
ohkami = { workspace = true }
tokio = { workspace = true }
40 changes: 40 additions & 0 deletions examples/json_response/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use ohkami::{typed::ResponseBody, Ohkami, Route};

#[ResponseBody(JSONS)]
struct User {
id: u64,
name: String,
}

async fn single_user() -> User {
User {
id: 42,
name: String::from("ohkami"),
}
}

async fn multiple_users() -> Vec<User> {
vec![
User {
id: 42,
name: String::from("ohkami"),
},
User {
id: 1024,
name: String::from("bynari"),
}
]
}

async fn nullable_user() -> Option<User> {
None
}

#[tokio::main]
async fn main() {
Ohkami::new((
"/single" .GET(single_user),
"/multiple".GET(multiple_users),
"/nullable".GET(nullable_user),
)).howl("localhost:5000").await
}
4 changes: 2 additions & 2 deletions ohkami/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ohkami"
version = "0.14.0"
version = "0.15.0"
edition = "2021"
authors = ["kanarus <[email protected]>"]
description = "Build web app in intuitive and declarative code"
Expand All @@ -17,7 +17,7 @@ features = ["rt_tokio", "custom-header"]

[dependencies]
ohkami_lib = { version = "=0.1.1", path = "../ohkami_lib" }
ohkami_macros = { version = "=0.5.2", path = "../ohkami_macros" }
ohkami_macros = { version = "=0.6.0", path = "../ohkami_macros" }
tokio = { version = "1", optional = true, features = ["net", "rt", "io-util", "sync"] }
async-std = { version = "1", optional = true }
byte_reader = { version = "2.0.0", features = ["text"] }
Expand Down
3 changes: 2 additions & 1 deletion ohkami/src/fang/builtin/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ impl JWT {
#[test] async fn test_jwt_verify_senario() {
use crate::prelude::*;
use crate::{testing::*, Memory};
use crate::typed::{ResponseBody, status::OK};
use crate::typed::{ResponseBody, body_type, status::OK};

use std::{sync::OnceLock, sync::Mutex, collections::HashMap, borrow::Cow};

Expand Down Expand Up @@ -405,6 +405,7 @@ impl JWT {
familly_name: String,
}
impl ResponseBody for Profile {
type Type = body_type::JSON;
fn into_response_with(self, status: Status) -> Response {
Response::with(status).json(self)
}
Expand Down
3 changes: 2 additions & 1 deletion ohkami/src/handler/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ macro_rules! Route {
use ::serde::{Serialize, Deserialize};
use super::{Handlers, Route};
use crate::{FromRequest, IntoResponse, Response, Request, Status};
use crate::typed::{ResponseBody, status::{OK, Created}};
use crate::typed::{ResponseBody, body_type, status::{OK, Created}};


enum APIError {
Expand All @@ -134,6 +134,7 @@ macro_rules! Route {
password: String,
} const _: () = {
impl ResponseBody for User {
type Type = body_type::JSON;
fn into_response_with(self, status: Status) -> Response {
Response::with(status).json(self)
}
Expand Down
40 changes: 20 additions & 20 deletions ohkami/src/response/into_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,26 @@ impl<T:IntoResponse, E:IntoResponse> IntoResponse for Result<T, E> {
}
}

impl IntoResponse for &'static str {
fn into_response(self) -> Response {
Response::with(Status::OK).text(self)
}
}
impl IntoResponse for String {
#[inline(always)] fn into_response(self) -> Response {
Response::with(Status::OK).text(self)
}
}
impl IntoResponse for &'_ String {
fn into_response(self) -> Response {
Response::with(Status::OK).text(self.clone())
}
}
impl IntoResponse for std::borrow::Cow<'static, str> {
fn into_response(self) -> Response {
Response::with(Status::OK).text(self)
}
}
// impl IntoResponse for &'static str {
// fn into_response(self) -> Response {
// Response::with(Status::OK).text(self)
// }
// }
// impl IntoResponse for String {
// #[inline(always)] fn into_response(self) -> Response {
// Response::with(Status::OK).text(self)
// }
// }
// impl IntoResponse for &'_ String {
// fn into_response(self) -> Response {
// Response::with(Status::OK).text(self.clone())
// }
// }
// impl IntoResponse for std::borrow::Cow<'static, str> {
// fn into_response(self) -> Response {
// Response::with(Status::OK).text(self)
// }
// }

impl IntoResponse for std::convert::Infallible {
fn into_response(self) -> Response {
Expand Down
3 changes: 2 additions & 1 deletion ohkami/src/typed/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
pub mod status;

mod response_body;
pub use response_body::ResponseBody;
pub use response_body::{ResponseBody, body_type};
pub use ohkami_macros::ResponseBody;

pub(crate) mod parse_payload;
#[cfg(test)] mod _test_parse_payload;
pub use parse_payload::{File};

pub use ohkami_macros::{Payload, Query};
86 changes: 79 additions & 7 deletions ohkami/src/typed/response_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,37 @@ use std::borrow::Cow;
/// }
/// ```
pub trait ResponseBody: Serialize {
/// Select from `ohkami::typed::body_type` module
type Type: BodyType;
fn into_response_with(self, status: Status) -> Response;
}

pub trait BodyType {}
macro_rules! body_type {
($( $name:ident, )*) => {
pub mod body_type {
$(
pub struct $name;
impl super::BodyType for $name {}
)*
}
};
} body_type! {
Empty,
JSON,
HTML,
Text,
Other,
}

impl<RB: ResponseBody> crate::IntoResponse for RB {
fn into_response(self) -> Response {
self.into_response_with(Status::OK)
}
}

impl ResponseBody for () {
type Type = body_type::Empty;
fn into_response_with(self, status: Status) -> Response {
Response {
status,
Expand All @@ -61,10 +89,53 @@ impl ResponseBody for () {
}
}
}

const _: (/* JSON utility impls */) = {
impl<RB: ResponseBody<Type = body_type::JSON>> ResponseBody for Option<RB> {
type Type = body_type::JSON;
fn into_response_with(self, status: Status) -> Response {
Response::with(status).json(self)
}
}

impl<RB: ResponseBody<Type = body_type::JSON>> ResponseBody for Vec<RB> {
type Type = body_type::JSON;
#[inline] fn into_response_with(self, status: Status) -> Response {
Response::with(status).json(self)
}
}

impl<RB: ResponseBody<Type = body_type::JSON>> ResponseBody for &[RB] {
type Type = body_type::JSON;
fn into_response_with(self, status: Status) -> Response {
Response::with(status).json(self)
}
}

/// `impl<RB: ResponseBody<Type = body_type::JSON>, const N: usize> ResponseBody for [RB; N]`
/// is not available becasue `serde` only provides following 33 `Serialize` impls...
macro_rules! response_body_of_json_array_of_len {
($($len:literal)*) => {
$(
impl<RB: ResponseBody<Type = body_type::JSON>> ResponseBody for [RB; $len] {
type Type = body_type::JSON;
#[inline] fn into_response_with(self, status: Status) -> Response {
Response::with(status).json(self)
}
}
)*
};
} response_body_of_json_array_of_len! {
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
}
};

macro_rules! plain_text_responsebodies {
($( $text_type:ty: $self:ident => $content:expr, )*) => {
$(
impl ResponseBody for $text_type {
type Type = body_type::Text;
#[inline] fn into_response_with(self, status: Status) -> Response {
let content = {let $self = self; $content};

Expand Down Expand Up @@ -94,12 +165,13 @@ macro_rules! plain_text_responsebodies {

#[cfg(test)]
#[test] fn assert_impls() {
fn is_reponsebody<T: ResponseBody>() {}
fn is_empty_reponsebody<T: ResponseBody<Type = body_type::Empty>>() {}
is_empty_reponsebody::<()>();

is_reponsebody::<()>();
is_reponsebody::<&'static str>();
is_reponsebody::<String>();
is_reponsebody::<&'_ String>();
is_reponsebody::<Cow<'static, str>>();
is_reponsebody::<Cow<'_, str>>();
fn is_text_reponsebody<T: ResponseBody<Type = body_type::Text>>() {}
is_text_reponsebody::<&'static str>();
is_text_reponsebody::<String>();
is_text_reponsebody::<&'_ String>();
is_text_reponsebody::<Cow<'static, str>>();
is_text_reponsebody::<Cow<'_, str>>();
}
16 changes: 4 additions & 12 deletions ohkami/src/utils/text.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#![allow(non_snake_case)]

use crate::{Response, IntoResponse, Status};
use crate::{Response, Status};
use crate::typed::{ResponseBody, body_type};
use crate::response::ResponseHeaders;
use crate::serde::Serialize;
use crate::typed::ResponseBody;
use std::borrow::Cow;


Expand All @@ -21,12 +21,8 @@ impl Serialize for Text {
self.content.serialize(serializer)
}
}
impl IntoResponse for Text {
#[inline(always)] fn into_response(self) -> Response {
self.into_response_with(Status::OK)
}
}
impl ResponseBody for Text {
type Type = body_type::Text;
#[inline] fn into_response_with(self, status: Status) -> Response {
let content = match self.content {
Cow::Borrowed(str) => Cow::Borrowed(str.as_bytes()),
Expand Down Expand Up @@ -60,12 +56,8 @@ impl Serialize for HTML {
self.content.serialize(serializer)
}
}
impl IntoResponse for HTML {
#[inline(always)] fn into_response(self) -> Response {
self.into_response_with(Status::OK)
}
}
impl ResponseBody for HTML {
type Type = body_type::HTML;
#[inline] fn into_response_with(self, status: Status) -> Response {
let content = match self.content {
Cow::Borrowed(str) => Cow::Borrowed(str.as_bytes()),
Expand Down
2 changes: 1 addition & 1 deletion ohkami_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ proc-macro = true

[package]
name = "ohkami_macros"
version = "0.5.2"
version = "0.6.0"
edition = "2021"
authors = ["kanarus <[email protected]>"]
description = "Proc macros for ohkami - intuitive and declarative web framework"
Expand Down
Loading

0 comments on commit 05e5de5

Please sign in to comment.