Page MenuHomeDevCentral

D3716.id9615.diff
No OneTemporary

D3716.id9615.diff

diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,7 @@
[workspace]
resolver = "3"
members = [
+ "axum",
+ "core",
"rocket-legacy",
]
diff --git a/axum/Cargo.toml b/axum/Cargo.toml
new file mode 100644
--- /dev/null
+++ b/axum/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "limiting-factor-axum"
+version = "0.1.0"
+authors = [
+ "Sébastien Santoro <dereckson@espace-win.org>",
+]
+description = "Library to create a REST API with Axum"
+readme = "README.md"
+keywords = [
+ "API",
+ "Axum",
+ "REST",
+]
+categories = [
+ "web-programming",
+]
+license = "BSD-2-Clause"
+repository = "https://devcentral.nasqueron.org/source/limiting-factor/"
+edition = "2024"
+
+[dependencies]
+axum = "0.8.4"
+limiting-factor-core = { path="../core" }
+serde = { version = "^1.0.226", features = [ "derive" ], optional = true }
+http-body-util = "0.1.3"
+tokio = "1.47.1"
+
+[features]
+default = ["minimal"]
+
+minimal = ["serialization"]
+full = ["serialization"]
+
+serialization = ["serde"]
diff --git a/axum/README.md b/axum/README.md
new file mode 100644
--- /dev/null
+++ b/axum/README.md
@@ -0,0 +1,29 @@
+# Limiting Factor for axum
+
+This crate helps to build REST API with Axum, with less boilerplate code.
+
+## Implemented features
+### Extractors
+
+The extractor for request body is a port of the Rocket 0.4 guard added in Limiting Factor 0.8.0.
+
+If you need to read the body of the HTTP request "as is", the AxumRequestBody
+extractor allows you to read it as a string:
+
+ async fn deploy(
+ Path(site_name): Path<String>,
+ State(config): State<AlkaneConfig>,
+ body: AxumRequestBody,
+ ) -> ApiResult<Json<RecipeStatus>> {
+ let context = body.into_optional_string(); // Option<String>
+ // ...
+ }
+
+## Development
+
+Current focus is to port features used by REST API from Rocket 0.4 to Axum 0.8.4+.
+
+New features:
+ - may be added to the axum crate
+ - should be added to the core crate for the abstract part
+ - are not expected to be implemented to the rocket-legacy crate
diff --git a/axum/src/api/guards.rs b/axum/src/api/guards.rs
new file mode 100644
--- /dev/null
+++ b/axum/src/api/guards.rs
@@ -0,0 +1,152 @@
+/* -------------------------------------------------------------
+ Limiting Factor :: Axum :: API :: Guards
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ Project: Nasqueron
+ License: BSD-2-Clause
+ ------------------------------------------------------------- */
+
+//! # API extractors
+//!
+//! This module provides reusable extractors to use with Axum.
+
+use axum::{
+ extract::{FromRequest, Request},
+ http::StatusCode,
+ response::{IntoResponse, Response},
+};
+use http_body_util::BodyExt;
+
+use limiting_factor_core::api::guards::{RequestBody, REQUEST_BODY_LIMIT};
+
+// New-type wrapper for Axum-specific implementations
+#[derive(Debug, Clone)]
+pub struct AxumRequestBody(pub RequestBody);
+
+impl AxumRequestBody {
+ pub fn new() -> Self {
+ Self(RequestBody::new())
+ }
+
+ // Delegate methods
+ pub fn into_string(self) -> String {
+ self.0.into_string()
+ }
+
+ pub fn into_optional_string(self) -> Option<String> {
+ self.0.into_optional_string()
+ }
+}
+
+impl From<RequestBody> for AxumRequestBody {
+ fn from(data: RequestBody) -> Self {
+ Self(data)
+ }
+}
+
+impl From<AxumRequestBody> for RequestBody {
+ fn from(body: AxumRequestBody) -> Self {
+ body.0
+ }
+}
+
+/// Error type during a request body extraction
+#[derive(Debug)]
+pub enum RequestBodyError {
+ /// Body size is greater than REQUEST_BODY_LIMIT (DoS risk)
+ TooLarge,
+
+ /// Not in UTF-8 encoding
+ InvalidEncoding,
+
+ /// I/O error
+ ReadError(String),
+}
+
+impl IntoResponse for RequestBodyError {
+ fn into_response(self) -> Response {
+ let (status, message) = match self {
+ RequestBodyError::TooLarge => (
+ StatusCode::PAYLOAD_TOO_LARGE,
+ "Request body too large".to_string(),
+ ),
+
+ RequestBodyError::InvalidEncoding => (
+ StatusCode::BAD_REQUEST,
+ "Request body contains invalid characters when trying to decode as UTF-8".to_string(),
+ ),
+
+ RequestBodyError::ReadError(err) => (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ format!("Failed to read request body: {}", err),
+ ),
+ };
+
+ (status, message).into_response()
+ }
+}
+
+impl<S> FromRequest<S> for AxumRequestBody
+where
+ S: Send + Sync,
+{
+ type Rejection = RequestBodyError;
+
+ async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
+ // Extract the body from the request
+ let body = req.into_body();
+
+ // Collect the body with size limit
+ let collected = match body.collect().await {
+ Ok(collected) => collected,
+ Err(e) => return Err(RequestBodyError::ReadError(e.to_string())),
+ };
+
+ let bytes = collected.to_bytes();
+
+ // Check size limit
+ if bytes.len() > REQUEST_BODY_LIMIT {
+ return Err(RequestBodyError::TooLarge);
+ }
+
+ // Convert to UTF-8 string
+ let content = match String::from_utf8(bytes.to_vec()) {
+ Ok(content) => content,
+ Err(_) => return Err(RequestBodyError::InvalidEncoding),
+ };
+
+ Ok(Self(RequestBody { content }))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[tokio::test]
+ async fn test_empty_body_extraction() {
+ use axum::body::Body;
+ use axum::http::Request;
+
+ let req = Request::builder()
+ .body(Body::empty())
+ .unwrap();
+
+ let body = AxumRequestBody::from_request(req, &()).await.unwrap();
+ assert_eq!("", body.0.content);
+ assert_eq!(None, body.into_optional_string());
+ }
+
+ #[tokio::test]
+ async fn test_body_extraction() {
+ use axum::body::Body;
+ use axum::http::Request;
+
+ let req = Request::builder()
+ .body(Body::from("lorem ipsum dolor"))
+ .unwrap();
+
+ let body = AxumRequestBody::from_request(req, &()).await.unwrap();
+ assert_eq!("lorem ipsum dolor", body.0.content);
+ assert_eq!(Some("lorem ipsum dolor".to_string()), body.into_optional_string());
+ }
+}
diff --git a/axum/src/api/mod.rs b/axum/src/api/mod.rs
new file mode 100644
--- /dev/null
+++ b/axum/src/api/mod.rs
@@ -0,0 +1,16 @@
+/* -------------------------------------------------------------
+ Limiting Factor :: Axum :: API
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ Project: Nasqueron
+ License: BSD-2-Clause
+ ------------------------------------------------------------- */
+
+//! # Utilities for API.
+//!
+//! This module provides useful code to create easily APIs.
+
+/* -------------------------------------------------------------
+ Public submodules offered by this module
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+pub mod guards;
diff --git a/axum/src/lib.rs b/axum/src/lib.rs
new file mode 100644
--- /dev/null
+++ b/axum/src/lib.rs
@@ -0,0 +1,10 @@
+/* -------------------------------------------------------------
+ Limiting Factor :: Axum
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ Project: Nasqueron
+ Crate name: limiting-factor-axum
+ Description: Limiting Factor features for Axum
+ License: BSD-2-Clause
+ ------------------------------------------------------------- */
+
+pub mod api;
diff --git a/core/Cargo.toml b/core/Cargo.toml
new file mode 100644
--- /dev/null
+++ b/core/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "limiting-factor-core"
+version = "1.0.0"
+authors = [
+ "Sébastien Santoro <dereckson@espace-win.org>",
+]
+description = "Library to create a REST API with web frameworks"
+readme = "../README.md"
+keywords = [
+ "API",
+ "REST",
+]
+categories = [
+ "web-programming",
+]
+license = "BSD-2-Clause"
+repository = "https://devcentral.nasqueron.org/source/limiting-factor/"
+edition = "2024"
+
+[dependencies]
+serde = { version = "1.0.226", features = ["derive"] }
diff --git a/rocket-legacy/src/api/guards.rs b/core/src/api/guards.rs
copy from rocket-legacy/src/api/guards.rs
copy to core/src/api/guards.rs
--- a/rocket-legacy/src/api/guards.rs
+++ b/core/src/api/guards.rs
@@ -1,20 +1,23 @@
+/* -------------------------------------------------------------
+ Limiting Factor :: Core :: API :: Guards
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ Project: Nasqueron
+ License: BSD-2-Clause
+ ------------------------------------------------------------- */
+
//! # API guards
//!
-//! This module provides reusable guards to use with Rocket.
+//! This module provides common element for:
+//! - reusable guards to use with Rocket
+//! - reusable extractors to use with Axum
-use rocket::data::{FromDataSimple, Outcome};
-use rocket::{Data, Request};
-use rocket::http::Status;
-use rocket::Outcome::{Failure, Success};
use serde::{Deserialize, Serialize};
-use std::io::Read;
-
/// The maximum number of characters to read, to avoid DoS
-const REQUEST_BODY_LIMIT: u64 = 1_000_000;
+pub const REQUEST_BODY_LIMIT: usize = 1_000_000;
/// A String representation of the request body. Useful when you need to pass it through as is.
-#[derive(Serialize, Deserialize, PartialOrd, PartialEq, Eq, Ord)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialOrd, PartialEq, Eq, Ord)]
pub struct RequestBody {
/// The UTF-8 content of the request body
pub content: String,
@@ -42,20 +45,6 @@
}
}
-impl FromDataSimple for RequestBody {
- type Error = String;
-
- fn from_data(_request: &Request, data: Data) -> Outcome<Self, Self::Error> {
- let mut content = String::new();
-
- if let Err(e) = data.open().take(REQUEST_BODY_LIMIT).read_to_string(&mut content) {
- return Failure((Status::InternalServerError, format!("{:?}", e)));
- }
-
- Success(Self { content })
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
diff --git a/core/src/api/mod.rs b/core/src/api/mod.rs
new file mode 100644
--- /dev/null
+++ b/core/src/api/mod.rs
@@ -0,0 +1,16 @@
+/* -------------------------------------------------------------
+ Limiting Factor :: Core :: API
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ Project: Nasqueron
+ License: BSD-2-Clause
+ ------------------------------------------------------------- */
+
+//! # Utilities for API.
+//!
+//! This module provides useful code to create easily APIs.
+
+/* -------------------------------------------------------------
+ Public submodules offered by this module
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+pub mod guards;
diff --git a/core/src/lib.rs b/core/src/lib.rs
new file mode 100644
--- /dev/null
+++ b/core/src/lib.rs
@@ -0,0 +1,11 @@
+/* -------------------------------------------------------------
+ Limiting Factor :: core features
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ Project: Nasqueron
+ Crate name: limiting-factor-core
+ Description: Abstract code and non-framework-dependent features
+ for Limiting Factor framework-flavoured crates.
+ License: BSD-2-Clause
+ ------------------------------------------------------------- */
+
+pub mod api;
diff --git a/rocket-legacy/Cargo.toml b/rocket-legacy/Cargo.toml
--- a/rocket-legacy/Cargo.toml
+++ b/rocket-legacy/Cargo.toml
@@ -22,6 +22,7 @@
[dependencies]
diesel = { version = "^1.4.8", features = ["postgres", "r2d2", "chrono"], optional = true }
dotenv = "^0.15.0"
+limiting-factor-core = { path="../core" }
log = "^0.4.28"
r2d2 = { version = "^0.8.10", optional = true }
rocket = "^0.4.11"
diff --git a/rocket-legacy/src/api/guards.rs b/rocket-legacy/src/api/guards.rs
--- a/rocket-legacy/src/api/guards.rs
+++ b/rocket-legacy/src/api/guards.rs
@@ -6,87 +6,42 @@
use rocket::{Data, Request};
use rocket::http::Status;
use rocket::Outcome::{Failure, Success};
-use serde::{Deserialize, Serialize};
use std::io::Read;
-/// The maximum number of characters to read, to avoid DoS
-const REQUEST_BODY_LIMIT: u64 = 1_000_000;
+use limiting_factor_core::api::guards::{RequestBody, REQUEST_BODY_LIMIT};
-/// A String representation of the request body. Useful when you need to pass it through as is.
-#[derive(Serialize, Deserialize, PartialOrd, PartialEq, Eq, Ord)]
-pub struct RequestBody {
- /// The UTF-8 content of the request body
- pub content: String,
-}
+// New-type wrapper for Rocket-specific implementations
+#[derive(Debug, Clone)]
+pub struct RocketRequestBody(pub RequestBody);
-impl RequestBody {
- pub fn new () -> Self {
- Self {
- content: String::new(),
- }
+impl RocketRequestBody {
+ pub fn new() -> Self {
+ Self(RequestBody::new())
}
- /// Convert the request body into a string
- pub fn into_string (self) -> String {
- self.content
+ // Delegate methods
+ pub fn into_string(self) -> String {
+ self.0.into_string()
}
- /// Convert the request body into a string, or None if it's empty
- pub fn into_optional_string (self) -> Option<String> {
- if self.content.is_empty() {
- None
- } else {
- Some(self.content)
- }
+ pub fn into_optional_string(self) -> Option<String> {
+ self.0.into_optional_string()
}
}
-impl FromDataSimple for RequestBody {
+const ROCKET_REQUEST_BODY_LIMIT: u64 = REQUEST_BODY_LIMIT as u64;
+
+impl FromDataSimple for RocketRequestBody {
type Error = String;
fn from_data(_request: &Request, data: Data) -> Outcome<Self, Self::Error> {
let mut content = String::new();
- if let Err(e) = data.open().take(REQUEST_BODY_LIMIT).read_to_string(&mut content) {
+ if let Err(e) = data.open().take(ROCKET_REQUEST_BODY_LIMIT).read_to_string(&mut content) {
return Failure((Status::InternalServerError, format!("{:?}", e)));
}
- Success(Self { content })
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_request_body_new () {
- let body = RequestBody::new();
- assert_eq!(0, body.content.len(), "Content should be empty");
- }
-
- #[test]
- fn test_request_body_into_string () {
- let body = RequestBody { content: "quux".to_string() };
- assert_eq!(String::from("quux"), body.into_string());
- }
-
- #[test]
- fn test_request_body_into_string_when_empty () {
- let body = RequestBody::new();
- assert_eq!(String::new(), body.into_string(), "Content should be empty");
- }
-
- #[test]
- fn test_request_body_into_optional_string () {
- let body = RequestBody { content: "quux".to_string() };
- assert_eq!(Some(String::from("quux")), body.into_optional_string());
- }
-
- #[test]
- fn test_request_body_into_optional_string_when_empty () {
- let body = RequestBody::new();
- assert_eq!(None, body.into_optional_string());
+ Success(Self(RequestBody { content }))
}
}
diff --git a/rocket-legacy/src/lib.rs b/rocket-legacy/src/lib.rs
--- a/rocket-legacy/src/lib.rs
+++ b/rocket-legacy/src/lib.rs
@@ -44,6 +44,8 @@
#[cfg(feature = "serialization")]
extern crate serde;
+extern crate limiting_factor_core;
+
/* -------------------------------------------------------------
Public modules offered by this crate
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

File Metadata

Mime Type
text/plain
Expires
Thu, Sep 25, 18:33 (17 h, 58 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3016350
Default Alt Text
D3716.id9615.diff (15 KB)

Event Timeline