Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ohkami/src/fang/builtin/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl<'req, T: SendSyncOnNative + 'static> FromRequest<'req> for Context<'req, T>
Some(d) => Some(Ok(Self(d))),
None => {
#[cfg(debug_assertions)] {
crate::warning!(
crate::WARNING!(
"Context of `{}` doesn't exist",
std::any::type_name::<T>()
)
Expand Down
2 changes: 1 addition & 1 deletion ohkami/src/fang/builtin/cors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ impl CORS {

pub fn AllowCredentials(mut self) -> Self {
if self.AllowOrigin.is_any() {
#[cfg(debug_assertions)] crate::warning!("\
#[cfg(debug_assertions)] crate::WARNING!("\
[WRANING] \
'Access-Control-Allow-Origin' header \
must not have wildcard '*' when the request's credentials mode is 'include' \
Expand Down
2 changes: 1 addition & 1 deletion ohkami/src/ohkami/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl Dir {
.collect::<std::io::Result<Vec<_>>>()?;

if path_Segments.last().unwrap().starts_with('.') {
crate::warning!("\
crate::WARNING!("\
=========\n\
[WARNING] `Route::Dir`: found `{}` in directory `{}`, \
are you sure to serve this file?\n\
Expand Down
4 changes: 2 additions & 2 deletions ohkami/src/request/from_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ pub trait FromParam<'p>: Sized {
Self::from_param(
ohkami_lib::percent_decode_utf8(raw_param)
.map_err(|_e| {
#[cfg(debug_assertions)] crate::warning!(
#[cfg(debug_assertions)] crate::WARNING!(
"Failed to decode percent encoded param `{}`: {_e}",
raw_param.escape_ascii()
);
Expand Down Expand Up @@ -155,7 +155,7 @@ const _: () = {
Cow::Owned(_) => Err({
#[cold] #[inline(never)]
fn unexpected(param: &str) -> ErrorMessage {
crate::warning!("\
crate::WARNING!("\
`&str` can't handle percent encoded parameters. \
Use `Cow<'_, str>` or `String` to handle them. \
");
Expand Down
2 changes: 1 addition & 1 deletion ohkami/src/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ impl Request {
Err(e) => return match e.kind() {
std::io::ErrorKind::ConnectionReset => Ok(None),
_ => Err((|err| {
crate::warning!("Failed to read stream: {err}");
crate::WARNING!("Failed to read stream: {err}");
Response::InternalServerError()
})(e))
},
Expand Down
6 changes: 3 additions & 3 deletions ohkami/src/request/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@ impl QueryParams {
None => {
#[cfg(debug_assertions)] {
if kv.is_empty() {
crate::warning!("skipping an invalid query param: trailing `&`");
crate::WARNING!("skipping an invalid query param: trailing `&`");
} else {
crate::warning!("skipping an invalid query param: missing `=`");
crate::WARNING!("skipping an invalid query param: missing `=`");
}
}
None
}
Some(0) => {
#[cfg(debug_assertions)] {
crate::warning!("skipping an invalid query param: empty key");
crate::WARNING!("skipping an invalid query param: empty key");
}
None
}
Expand Down
2 changes: 1 addition & 1 deletion ohkami/src/response/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ const _: () = {
setcookies.iter().filter_map(|raw| match SetCookie::from_raw(raw) {
Ok(valid) => Some(valid),
Err(_err) => {
#[cfg(debug_assertions)] crate::warning!(
#[cfg(debug_assertions)] crate::WARNING!(
"Invalid `Set-Cookie`: {_err}"
);
None
Expand Down
2 changes: 1 addition & 1 deletion ohkami/src/response/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ macro_rules! status {
415 UnsupportedMediaType : "415 Unsupported Media Type",
416 RangeNotSatisfiable : "416 Range Not Satisfiable",
417 ExceptionFailed : "417 Exception Failed",
418 Im_a_teapot : "418 I'm a teapot",
418 ImATeapot : "418 I'm a teapot",
421 MisdirectedRequest : "421 Misdirected Request",
422 UnprocessableEntity : "422 Unprocessable Entity",
423 Locked : "423 Locked",
Expand Down
10 changes: 5 additions & 5 deletions ohkami/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ impl Session {
#[cold] #[inline(never)]
fn panicking(panic: Box<dyn Any + Send>) -> Response {
if let Some(msg) = panic.downcast_ref::<String>() {
crate::warning!("[Panicked]: {msg}");
crate::WARNING!("panic: {msg}");
} else if let Some(msg) = panic.downcast_ref::<&str>() {
crate::warning!("[Panicked]: {msg}");
crate::WARNING!("panic: {msg}");
} else {
crate::warning!("[Panicked]");
crate::WARNING!("panic");
}
crate::Response::InternalServerError()
}
Expand Down Expand Up @@ -66,7 +66,7 @@ impl Session {
}
}
}).await {
None => crate::warning!("[WARNING] \
None => crate::WARNING!("\
Session timeouted. In Ohkami, Keep-Alive timeout \
is set to 42 seconds by default and is configurable \
by `OHKAMI_KEEPALIVE_TIMEOUT` environment variable.\
Expand All @@ -83,7 +83,7 @@ impl Session {
self.connection
).await;
if aborted {
crate::warning!("[WARNING] \
crate::WARNING!("\
WebSocket session aborted by timeout. In Ohkami, \
WebSocket timeout is set to 3600 seconds (1 hour) \
by default and is configurable by `OHKAMI_WEBSOCKET_TIMEOUT` \
Expand Down
121 changes: 110 additions & 11 deletions ohkami/src/typed/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,64 @@ use crate::openapi;
macro_rules! generate_statuses_as_types_containing_value {
($( $status:ident : $message:literal, )*) => {
$(
#[doc = "Type-safe `"]
/// Generate`
#[doc = $message]
#[doc = "` response.<br>"]
#[doc = "Use `()` (default) to represent an empty content."]
/// ` response type with the `body: B`.
///
/// Use `()` to represent an empty content.
///
/// This is an alias of `typed::{TheStatus}::new(body)`.
#[allow(non_snake_case)]
pub fn $status<B: IntoBody>(body: B) -> $status<B> {
$status::<B>::new(body)
}

#[allow(non_camel_case_types)]
#[doc = "Typed `"]
#[doc = $message]
#[doc = "` response.<br>"]
#[doc = "Use `()` ( default of `B` ) to represent an empty content."]
#[allow(private_bounds)]
pub struct $status<B: IntoBody = ()>(pub B);
pub struct $status<B: IntoBody = ()> {
body: B,
headers: ResponseHeaders,
}

impl<B: IntoBody> $status<B> {
pub fn new(body: B) -> Self {
Self { body, headers: ResponseHeaders::new() }
}

pub fn with_headers(mut self, set: impl FnOnce(SetHeaders)->SetHeaders) -> Self {
set(self.headers.set());
self
}
}

impl<B: IntoBody> IntoResponse for $status<B> {
#[inline]
fn into_response(self) -> Response {
let mut res = self.0.into_response();
res.status = Status::$status;
res
if const {B::CONTENT_TYPE.is_empty()} {// will be removed by optimization if it's not
return Response::OK();
}

let body = match self.body.into_body() {
Ok(body) => body,
Err(e) => {
crate::ERROR!("<{} as IntoBody>::into_body() failed: {e}", std::any::type_name::<B>());
return Response::InternalServerError();
}
};

let mut headers = self.headers;
headers.set()
.ContentType(B::CONTENT_TYPE)
.ContentLength(ohkami_lib::num::itoa(body.len()));

Response {
status: Status::$status,
headers,
content: Content::Payload(body.into())
}
}

#[cfg(feature="openapi")]
Expand Down Expand Up @@ -66,7 +109,7 @@ macro_rules! generate_statuses_as_types_containing_value {
UnsupportedMediaType : "415 Unsupported Media Type",
RangeNotSatisfiable : "416 Range Not Satisfiable",
ExceptionFailed : "417 Exception Failed",
Im_a_teapot : "418 I'm a teapot",
ImATeapot : "418 I'm a teapot",
MisdirectedRequest : "421 Misdirected Request",
UnprocessableEntity : "422 Unprocessable Entity",
Locked : "423 Locked",
Expand All @@ -93,7 +136,7 @@ macro_rules! generate_statuses_as_types_containing_value {
macro_rules! generate_statuses_as_types_with_no_value {
($( $status:ident : $message:literal, )*) => {
$(
#[doc = "Type-safe `"]
#[doc = "Typed `"]
#[doc = $message]
#[doc = "` response"]
pub struct $status;
Expand Down Expand Up @@ -133,7 +176,7 @@ macro_rules! generate_statuses_as_types_with_no_value {
macro_rules! generate_redirects {
($( $status:ident / $contructor:ident : $message:literal, )*) => {
$(
#[doc = "Type-safe `"]
#[doc = "Typed `"]
#[doc = $message]
#[doc = "` response using the `location` as `Location` header value"]
pub struct $status {
Expand Down Expand Up @@ -180,3 +223,59 @@ macro_rules! generate_redirects {
TemporaryRedirect / to : "307 Temporary Redirect",
PermanentRedirect / to : "308 Permanent Redirect",
}


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

#[test]
fn typed_success_status() {
assert_eq!(
Created("Hello, world!")
.into_response(),
Response::Created()
.with_text("Hello, world!")
);

assert_eq!(
Created("Hello, world!")
.with_headers(|h| h
.Server("ohkami")
.Vary("origin")
)
.into_response(),
Response::Created()
.with_text("Hello, world!")
.with_headers(|h| h
.Server("ohkami")
.Vary("origin")
)
);
}

#[test]
fn typed_redirect() {
assert_eq!(
Found::at("https://example.com")
.into_response(),
Response::Found()
.with_headers(|h| h
.Location("https://example.com")
)
);

assert_eq!(
Found::at("https://example.com")
.with_headers(|h| h
.Server("ohkami")
)
.into_response(),
Response::Found()
.with_headers(|h| h
.Location("https://example.com")
.Server("ohkami")
)
);
}
}
74 changes: 57 additions & 17 deletions ohkami/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,64 @@
#[doc(hidden)]
#[cold] #[inline(never)]
pub fn eprintln(error: impl std::fmt::Display) {
#[cfg(not(feature = "rt_worker"))]
std::eprintln!("{error}");

#[cfg(feature="rt_worker")]
worker::console_error!("{error}");
}

/// just `eprintln`, but working both on native / Workers.
#[macro_export]
macro_rules! eprintln {
( $( $t:tt )* ) => {
$crate::util::eprintln(
format_args!($($t)*)
);
};
}

#[doc(hidden)]
#[macro_export]
macro_rules! warning {
macro_rules! WARNING {
( $( $t:tt )* ) => {{
eprintln!( $( $t )* );
$crate::eprintln!(
"[ohkami][WARNING][{}:{}:{}] {}",
file!(),
line!(),
column!(),
format_args!($($t)*)
);
}};
}

#[doc(hidden)]
#[macro_export]
macro_rules! ERROR {
( $($t:tt)* ) => {
$crate::eprintln!(
"[ohkami][ERROR][{}:{}:{}] {}",
file!(),
line!(),
column!(),
format_args!($($t)*)
);
};
}

#[cfg(feature="rt_worker")]
worker::console_log!( $( $t )* );
#[doc(hidden)]
#[macro_export]
macro_rules! DEBUG {
( $( $t:tt )* ) => {{
#[cfg(feature="DEBUG")] {
$crate::eprintln!(
"[ohkami][DEBUG][{}:{}:{}] {}",
file!(),
line!(),
column!(),
format_args!($($t)*)
);
}
}};
}

Expand All @@ -25,19 +78,6 @@ macro_rules! push_unchecked {
};
}

#[doc(hidden)]
#[macro_export]
macro_rules! DEBUG {
( $( $t:tt )* ) => {{
#[cfg(feature="DEBUG")] {
eprintln!( $( $t )* );

#[cfg(feature="rt_worker")]
worker::console_debug!( $( $t )* );
}
}};
}

pub use crate::fang::FangAction;

pub use crate::fang::bound::{SendOnNative, SendSyncOnNative};
Expand Down