aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2023-03-30 10:37:08 +0000
committerFuwn <[email protected]>2023-03-30 10:37:08 +0000
commit886eacdf5b0d4d369a44bf99d25b4cfcafface47 (patch)
treef7b1151576fc704cb14d42fae99f94a4c091e4cf
parentfeat(cargo): bump version to 0.2.1 (diff)
downloadwindmark-886eacdf5b0d4d369a44bf99d25b4cfcafface47.tar.xz
windmark-886eacdf5b0d4d369a44bf99d25b4cfcafface47.zip
feat: overhaul response system
-rw-r--r--examples/windmark.rs60
-rw-r--r--src/handler.rs6
-rw-r--r--src/response.rs199
-rw-r--r--src/router.rs72
4 files changed, 146 insertions, 191 deletions
diff --git a/examples/windmark.rs b/examples/windmark.rs
index b33f686..8b7839c 100644
--- a/examples/windmark.rs
+++ b/examples/windmark.rs
@@ -21,7 +21,7 @@
#[macro_use]
extern crate log;
-use windmark::{returnable::CallbackContext, Response, Router};
+use windmark::{response::Response, returnable::CallbackContext, Router};
#[derive(Default)]
struct Clicker {
@@ -65,13 +65,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("{} errors so far", error_count);
- Response::PermanentFailure("e".into())
+ Response::permanent_failure("e")
}));
router.set_fix_path(true);
router.attach_stateless(|r| {
r.mount(
"/module",
- Box::new(|_| Response::Success("This is a module!".into())),
+ Box::new(|_| Response::success("This is a module!")),
);
});
router.attach(Clicker::default());
@@ -98,38 +98,40 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
router.mount(
"/",
Box::new(|_| {
- Response::Success(
- "# INDEX\n\nWelcome!\n\n=> /test Test Page\n=> /time Unix Epoch"
- .to_string(),
+ Response::success(
+ "# INDEX\n\nWelcome!\n\n=> /test Test Page\n=> /time Unix Epoch",
)
}),
);
router.mount(
"/specific-mime",
Box::new(|_| {
- Response::SuccessWithMime("hi".to_string(), "text/plain".to_string())
+ Response::success("hi".to_string())
+ .with_mime("text/plain")
+ .clone()
}),
);
router.mount(
"/ip",
Box::new(|context| {
- Response::Success(
- { format!("Hello, {}", context.tcp.peer_addr().unwrap().ip()) }.into(),
- )
+ Response::success(format!(
+ "Hello, {}",
+ context.tcp.peer_addr().unwrap().ip()
+ ))
}),
);
router.mount(
"/test",
- Box::new(|_| Response::Success("hi there\n=> / back".to_string())),
+ Box::new(|_| Response::success("hi there\n=> / back")),
);
router.mount(
"/temporary-failure",
- Box::new(|_| Response::TemporaryFailure("Woops, temporarily...".into())),
+ Box::new(|_| Response::temporary_failure("Woops, temporarily...")),
);
router.mount(
"/time",
Box::new(|_| {
- Response::Success(
+ Response::success(
std::time::UNIX_EPOCH
.elapsed()
.unwrap()
@@ -141,7 +143,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
router.mount(
"/query",
Box::new(|context| {
- Response::Success(format!(
+ Response::success(format!(
"queries: {:?}",
windmark::utilities::queries_from_url(&context.url)
))
@@ -150,7 +152,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
router.mount(
"/param/:lang",
Box::new(|context| {
- Response::Success(format!(
+ Response::success(format!(
"Parameter lang is {}",
context.params.get("lang").unwrap()
))
@@ -159,7 +161,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
router.mount(
"/names/:first/:last",
Box::new(|context| {
- Response::Success(format!(
+ Response::success(format!(
"{} {}",
context.params.get("first").unwrap(),
context.params.get("last").unwrap()
@@ -170,9 +172,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
"/input",
Box::new(|context| {
if let Some(name) = context.url.query() {
- Response::Success(format!("Your name is {}!", name))
+ Response::success(format!("Your name is {}!", name))
} else {
- Response::Input("What is your name?".into())
+ Response::input("What is your name?")
}
}),
);
@@ -180,37 +182,35 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
"/sensitive-input",
Box::new(|context| {
if let Some(password) = context.url.query() {
- Response::Success(format!("Your password is {}!", password))
+ Response::success(format!("Your password is {}!", password))
} else {
- Response::SensitiveInput("What is your password?".into())
+ Response::sensitive_input("What is your password?")
}
}),
);
router.mount(
"/error",
- Box::new(|_| Response::CertificateNotValid("no".into())),
+ Box::new(|_| Response::certificate_not_valid("no")),
);
router.mount(
"/redirect",
- Box::new(|_| Response::PermanentRedirect("gemini://localhost/test".into())),
+ Box::new(|_| Response::permanent_redirect("gemini://localhost/test")),
);
#[cfg(feature = "auto-deduce-mime")]
router.mount("/auto-file", {
- Box::new(|_| Response::SuccessFileAuto(include_bytes!("../LICENSE")))
+ Box::new(|_| Response::binary_success_auto(include_bytes!("../LICENSE")))
});
router.mount("/file", {
Box::new(|_| {
- Response::SuccessFile(
- include_bytes!("../LICENSE"),
- "text/plain".to_string(),
- )
+ Response::binary_success(include_bytes!("../LICENSE"), "text/plain")
+ .clone()
})
});
router.mount(
"/secret",
Box::new(|context| {
if let Some(certificate) = context.certificate {
- Response::Success(format!("Your public key: {}.", {
+ Response::success(format!("Your public key: {}.", {
(|| -> Result<String, openssl::error::ErrorStack> {
Ok(format!(
"{:?}",
@@ -220,8 +220,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.unwrap_or_else(|_| "Unknown".to_string())
},))
} else {
- Response::ClientCertificateRequired(
- "This is a secret route! Identify yourself!".to_string(),
+ Response::client_certificate_required(
+ "This is a secret route! Identify yourself!",
)
}
}),
diff --git a/src/handler.rs b/src/handler.rs
index c5e23e8..bd64f03 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -17,15 +17,15 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
+ response::Response,
returnable,
returnable::{CallbackContext, RouteContext},
- Response,
};
pub type RouteResponse =
- Box<dyn FnMut(RouteContext<'_>) -> Response<'_> + Send + Sync>;
+ Box<dyn FnMut(RouteContext<'_>) -> Response + Send + Sync>;
pub type ErrorResponse =
- Box<dyn FnMut(returnable::ErrorContext<'_>) -> Response<'_> + Send + Sync>;
+ Box<dyn FnMut(returnable::ErrorContext<'_>) -> Response + Send + Sync>;
pub type Callback = Box<dyn FnMut(CallbackContext<'_>) + Send + Sync>;
pub type CleanupCallback =
Box<dyn FnMut(CallbackContext<'_>, &mut String) + Send + Sync>;
diff --git a/src/response.rs b/src/response.rs
index 806eebb..4ab513e 100644
--- a/src/response.rs
+++ b/src/response.rs
@@ -18,149 +18,110 @@
//! Content and response handlers
+macro_rules! response {
+ ($name:ident, $status:expr) => {
+ pub fn $name<S>(content: S) -> Self
+ where S: AsRef<str> {
+ Self::new($status, content.as_ref())
+ }
+ };
+}
+
/// The content and response type a handler should reply with.
-pub enum Response<'a> {
- Input(String),
- SensitiveInput(String),
- Success(String),
- /// A successful response where the MIME type of the response is manually
- /// specific by the user
- SuccessWithMime(String, String),
- #[cfg(feature = "auto-deduce-mime")]
- /// A successful response where the MIME type of the response is
- /// automatically deduced from the provided bytes
- SuccessFileAuto(&'a [u8]),
- SuccessFile(&'a [u8], String),
- TemporaryRedirect(String),
- PermanentRedirect(String),
- TemporaryFailure(String),
- ServerUnavailable(String),
- CGIError(String),
- ProxyError(String),
- SlowDown(String),
- PermanentFailure(String),
- NotFound(String),
- Gone(String),
- ProxyRefused(String),
- BadRequest(String),
- ClientCertificateRequired(String),
- CertificateNotAuthorised(String),
- CertificateNotValid(String),
+#[derive(Clone)]
+pub struct Response {
+ pub status: i32,
+ pub mime: Option<String>,
+ pub content: String,
+ pub character_set: Option<String>,
+ pub language: Option<String>,
}
-pub(crate) fn to_value_set_status(
- response: Response<'_>,
- status: &mut i32,
- mime: &mut String,
-) -> String {
- match response {
- Response::Input(value) => {
- *status = 10;
+impl Response {
+ response!(input, 10);
- value
- }
- Response::SensitiveInput(value) => {
- *status = 11;
+ response!(sensitive_input, 11);
- value
- }
- Response::Success(value) => {
- *status = 20;
+ response!(temporary_redirect, 30);
- value
- }
- Response::SuccessWithMime(value, value_mime) => {
- *status = 23;
- *mime = value_mime;
+ response!(permanent_redirect, 31);
- value
- }
- Response::SuccessFile(value, value_mime) => {
- *status = 21; // Internal status code, not real.
- *mime = value_mime;
+ response!(temporary_failure, 40);
- String::from_utf8(value.to_vec()).unwrap()
- }
- #[cfg(feature = "auto-deduce-mime")]
- Response::SuccessFileAuto(value) => {
- *status = 22; // Internal status code, not real.
+ response!(server_unavailable, 41);
- String::from_utf8(value.to_vec()).unwrap()
- }
- Response::TemporaryRedirect(value) => {
- *status = 30;
+ response!(cgi_error, 42);
- value
- }
- Response::PermanentRedirect(value) => {
- *status = 31;
+ response!(proxy_error, 43);
- value
- }
- Response::TemporaryFailure(value) => {
- *status = 40;
+ response!(slow_down, 44);
- value
- }
- Response::ServerUnavailable(value) => {
- *status = 41;
+ response!(permanent_failure, 50);
- value
- }
- Response::CGIError(value) => {
- *status = 42;
+ response!(not_found, 51);
- value
- }
- Response::ProxyError(value) => {
- *status = 43;
+ response!(gone, 52);
- value
- }
- Response::SlowDown(value) => {
- *status = 44;
+ response!(proxy_refused, 53);
- value
- }
- Response::PermanentFailure(value) => {
- *status = 50;
+ response!(bad_request, 59);
- value
- }
- Response::NotFound(value) => {
- *status = 51;
+ response!(client_certificate_required, 60);
- value
- }
- Response::Gone(value) => {
- *status = 52;
+ response!(certificate_not_authorised, 61);
- value
- }
- Response::ProxyRefused(value) => {
- *status = 53;
+ response!(certificate_not_valid, 62);
- value
- }
- Response::BadRequest(value) => {
- *status = 59;
+ pub fn success<S>(content: S) -> Self
+ where S: AsRef<str> {
+ Self::new(20, content.as_ref())
+ .with_mime("text/gemini")
+ .with_language("en")
+ .with_character_set("utf-8")
+ .clone()
+ }
- value
- }
- Response::ClientCertificateRequired(value) => {
- *status = 60;
+ #[must_use]
+ pub fn binary_success(content: &[u8], mime: &str) -> Self {
+ Self::new(21, &String::from_utf8_lossy(content))
+ .with_mime(mime)
+ .clone()
+ }
- value
- }
- Response::CertificateNotAuthorised(value) => {
- *status = 61;
+ #[cfg(feature = "auto-deduce-mime")]
+ #[must_use]
+ pub fn binary_success_auto(content: &[u8]) -> Self {
+ Self::new(22, &String::from_utf8_lossy(content))
+ .with_mime(&tree_magic::from_u8(&*content))
+ .clone()
+ }
- value
+ #[must_use]
+ pub fn new(status: i32, content: &str) -> Self {
+ Self {
+ status,
+ mime: None,
+ content: content.to_string(),
+ character_set: None,
+ language: None,
}
- Response::CertificateNotValid(value) => {
- *status = 62;
+ }
- value
- }
+ pub fn with_mime(&mut self, mime: &str) -> &mut Self {
+ self.mime = Some(mime.to_string());
+
+ self
+ }
+
+ pub fn with_character_set(&mut self, character_set: &str) -> &mut Self {
+ self.character_set = Some(character_set.to_string());
+
+ self
+ }
+
+ pub fn with_language(&mut self, language: &str) -> &mut Self {
+ self.language = Some(language.to_string());
+
+ self
}
}
diff --git a/src/router.rs b/src/router.rs
index a28eb67..e1e7a11 100644
--- a/src/router.rs
+++ b/src/router.rs
@@ -29,7 +29,7 @@ use url::Url;
use crate::{
handler::{Callback, CleanupCallback, ErrorResponse, Partial, RouteResponse},
module::Module,
- response::{to_value_set_status, Response},
+ response::Response,
returnable::{CallbackContext, ErrorContext, RouteContext},
};
@@ -271,8 +271,6 @@ impl Router {
) -> Result<(), Box<dyn Error>> {
let mut buffer = [0u8; 1024];
let mut url = Url::parse("gemini://fuwn.me/")?;
- let mut response_status = 0;
- let mut response_mime_type = String::new();
let mut footer = String::new();
let mut header = String::new();
@@ -356,26 +354,19 @@ impl Router {
},
));
}
- to_value_set_status(
- (*route.value).lock().unwrap()(RouteContext::new(
- stream.get_ref(),
- &url,
- &route.params,
- &stream.ssl().peer_certificate(),
- )),
- &mut response_status,
- &mut response_mime_type,
- )
+
+ (*route.value).lock().unwrap()(RouteContext::new(
+ stream.get_ref(),
+ &url,
+ &route.params,
+ &stream.ssl().peer_certificate(),
+ ))
} else {
- to_value_set_status(
- (*self.error_handler).lock().unwrap()(ErrorContext::new(
- stream.get_ref(),
- &url,
- &stream.ssl().peer_certificate(),
- )),
- &mut response_status,
- &mut response_mime_type,
- )
+ (*self.error_handler).lock().unwrap()(ErrorContext::new(
+ stream.get_ref(),
+ &url,
+ &stream.ssl().peer_certificate(),
+ ))
};
for module in &mut *self.modules.lock().unwrap() {
@@ -394,36 +385,39 @@ impl Router {
route.as_ref().map_or(None, |route| Some(&route.params)),
&stream.ssl().peer_certificate(),
),
- &mut content,
+ &mut content.content,
);
stream
.write_all(
format!(
"{}{}\r\n{}",
- if response_status == 21
- || response_status == 22
- || response_status == 23
+ if content.status == 21
+ || content.status == 22
+ || content.status == 23
{
20
} else {
- response_status
+ content.status
},
- match response_status {
+ match content.status {
20 =>
format!(
- " text/gemini; charset={}; lang={}",
- self.charset, self.language
+ " {}; charset={}; lang={}",
+ content.mime.unwrap_or_else(|| "text/gemini".to_string()),
+ content
+ .character_set
+ .unwrap_or_else(|| self.charset.clone()),
+ content.language.unwrap_or_else(|| self.language.clone())
),
- 21 => response_mime_type,
+ 21 => content.mime.unwrap_or_default(),
#[cfg(feature = "auto-deduce-mime")]
- 22 => format!(" {}", tree_magic::from_u8(&*content.as_bytes())),
- 23 => response_mime_type,
- _ => format!(" {content}"),
+ 22 => format!(" {}", content.mime.unwrap_or_default()),
+ _ => format!(" {}", content.content),
},
- match response_status {
- 20 => format!("{header}{content}\n{footer}"),
- 21 | 22 | 23 => content.to_string(),
+ match content.status {
+ 20 => format!("{header}{}\n{footer}", content.content),
+ 21 | 22 => content.content,
_ => String::new(),
}
)
@@ -749,8 +743,8 @@ impl Default for Router {
Self {
routes: matchit::Router::new(),
error_handler: Arc::new(Mutex::new(Box::new(|_| {
- Response::NotFound(
- "This capsule has not implemented an error handler...".to_string(),
+ Response::not_found(
+ "This capsule has not implemented an error handler...",
)
}))),
private_key_file_name: String::new(),