From 5352c8f533b26103aad19b3d8643db4a7c87f395 Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Sun, 4 Aug 2024 16:02:32 +0100 Subject: [PATCH 01/15] chore: set msrv to `1.75` for all crates (#131) --- crates/core/Cargo.toml | 2 +- crates/hyper/Cargo.toml | 1 + crates/macros/Cargo.toml | 1 + crates/shared/Cargo.toml | 1 + crates/shuttle/Cargo.toml | 1 + crates/swagger-macros/Cargo.toml | 1 + crates/swagger/Cargo.toml | 1 + crates/vercel/Cargo.toml | 1 + 8 files changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 66d562c6..4c3f4e49 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -4,7 +4,7 @@ version = "0.4.2" edition = "2021" description = "Modular backend framework for web applications" license = "MIT" -rust-version = "1.63" +rust-version = "1.75" [lib] path = "src/lib.rs" diff --git a/crates/hyper/Cargo.toml b/crates/hyper/Cargo.toml index 53fab346..5dd19d7a 100644 --- a/crates/hyper/Cargo.toml +++ b/crates/hyper/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" description = "Hyper Platform for ngyn web framework" license = "MIT" +rust-version = "1.75" [dependencies] http-body-util = "0.1" diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index a4410a35..2b2c928a 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -4,6 +4,7 @@ version = "0.4.2" edition = "2021" description = "Modular backend framework for web applications" license = "MIT" +rust-version = "1.75" [dependencies] http = "1.1" diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 037eda18..9c6b426f 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -4,6 +4,7 @@ version = "0.4.2" edition = "2021" description = "Modular backend framework for web applications" license = "MIT" +rust-version = "1.75" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/shuttle/Cargo.toml b/crates/shuttle/Cargo.toml index 650207ad..e02ef887 100644 --- a/crates/shuttle/Cargo.toml +++ b/crates/shuttle/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" description = "Shuttle Runtime Platform for ngyn web framework" license = "MIT" +rust-version = "1.75" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/swagger-macros/Cargo.toml b/crates/swagger-macros/Cargo.toml index 4b50a50f..b739b1a4 100644 --- a/crates/swagger-macros/Cargo.toml +++ b/crates/swagger-macros/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" description = "Shuttle Runtime Platform for ngyn web framework" license = "MIT" +rust-version = "1.75" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/swagger/Cargo.toml b/crates/swagger/Cargo.toml index cd25c1fc..88b51958 100644 --- a/crates/swagger/Cargo.toml +++ b/crates/swagger/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" description = "Shuttle Runtime Platform for ngyn web framework" license = "MIT" +rust-version = "1.75" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/vercel/Cargo.toml b/crates/vercel/Cargo.toml index 5d5adea6..ed8798ee 100644 --- a/crates/vercel/Cargo.toml +++ b/crates/vercel/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" description = "Vercel Runtime Platform for ngyn web framework" license = "MIT" +rust-version = "1.75" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From c506cd705ca2c0c7dbef8f589d375048fa34979f Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Sun, 4 Aug 2024 16:03:04 +0100 Subject: [PATCH 02/15] chore(vercel-platform): improve chunk exploration (#132) --- crates/vercel/src/lib.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/vercel/src/lib.rs b/crates/vercel/src/lib.rs index 6254c8dc..db2d408a 100644 --- a/crates/vercel/src/lib.rs +++ b/crates/vercel/src/lib.rs @@ -22,10 +22,15 @@ impl VercelApplication { let body = { let mut buf = Vec::new(); let frame = body.frame().await; - if frame.is_some() { - let chunk = frame.unwrap().unwrap(); - let d = chunk.data_ref().unwrap(); - buf.extend_from_slice(d.to_vec().as_slice()); + + match frame { + Some(frame) => { + if let Ok(chunk) = frame { + let d = chunk.data_ref().unwrap(); + buf.extend_from_slice(d.to_vec().as_slice()); + } + } + None => {} } Body::from(buf) }; From 3f6192c1219860f68f702a63dee3607d5dec32be Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Mon, 5 Aug 2024 18:58:11 +0100 Subject: [PATCH 03/15] chore(vercel-platform): clippy lints code improvements (#133) * chore(vercel-platform): clippy lints code improvements * more improvements --- crates/vercel/src/lib.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/vercel/src/lib.rs b/crates/vercel/src/lib.rs index db2d408a..fbc38814 100644 --- a/crates/vercel/src/lib.rs +++ b/crates/vercel/src/lib.rs @@ -23,14 +23,9 @@ impl VercelApplication { let mut buf = Vec::new(); let frame = body.frame().await; - match frame { - Some(frame) => { - if let Ok(chunk) = frame { - let d = chunk.data_ref().unwrap(); - buf.extend_from_slice(d.to_vec().as_slice()); - } - } - None => {} + if let Some(Ok(chunk)) = frame { + let d = chunk.data_ref().unwrap(); + buf.extend_from_slice(d.to_vec().as_slice()); } Body::from(buf) }; From a6765a8ee8e21875d3313a06bf5cc003d997af29 Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Mon, 5 Aug 2024 19:06:05 +0100 Subject: [PATCH 04/15] feat(shared): support for response interpreters (#134) * feat(shared): route response interpreters * add response interpreting * support async interpretation * reassign response * remove test parse * cleanups --- crates/shared/src/core/engine.rs | 85 ++++++++++++++++++++++--- crates/shared/src/server/response.rs | 82 +----------------------- crates/shared/src/traits/interpreter.rs | 10 +++ crates/shared/src/traits/mod.rs | 2 + 4 files changed, 89 insertions(+), 90 deletions(-) create mode 100644 crates/shared/src/traits/interpreter.rs diff --git a/crates/shared/src/core/engine.rs b/crates/shared/src/core/engine.rs index ddaa7fe1..6d2eb363 100644 --- a/crates/shared/src/core/engine.rs +++ b/crates/shared/src/core/engine.rs @@ -1,20 +1,22 @@ -use hyper::Request; +use http_body_util::Full; +use hyper::{body::Bytes, Request, Response}; use std::sync::Arc; use super::{Handler, RouteHandle}; use crate::{ server::{ context::AppState, - response::{Middlewares, ResponseBuilder, Routes}, + response::{Middlewares, Routes}, Method, NgynContext, NgynResponse, }, - traits::{NgynController, NgynMiddleware, NgynModule}, + traits::{NgynController, NgynInterpreter, NgynMiddleware, NgynModule}, }; #[derive(Default)] pub struct PlatformData { routes: Routes, middlewares: Middlewares, + interpreters: Vec>, state: Option>, } @@ -30,13 +32,44 @@ impl PlatformData { /// /// The response to the request. pub async fn respond(&self, req: Request>) -> NgynResponse { - match self.state { - Some(ref state) => { - let state = state.clone(); - NgynResponse::build_with_state(req, &self.routes, &self.middlewares, state).await - } - None => NgynResponse::build(req, &self.routes, &self.middlewares).await, + let mut cx = NgynContext::from_request(req); + let mut res = Response::new(Full::new(Bytes::default())); + + if let Some(state) = &self.state { + cx.set_state(state.clone()); + } + + let mut is_handled = false; + + self.routes + .iter() + .for_each(|(path, method, route_handler)| { + if !is_handled && cx.with(path, method).is_some() { + is_handled = true; + // trigger global middlewares + self.middlewares + .iter() + .for_each(|middlewares| middlewares.handle(&mut cx, &mut res)); + // trigger route handler + route_handler(&mut cx, &mut res); + } + }); + + // execute controlled route if it is handled + if is_handled { + cx.execute(&mut res).await; + } else { + // trigger global middlewares if no route is found + self.middlewares + .iter() + .for_each(|middlewares| middlewares.handle(&mut cx, &mut res)); } + + for interpreter in &self.interpreters { + res = interpreter.interpret(&mut res).await; + } + + res } /// Adds a route to the platform data. @@ -58,6 +91,15 @@ impl PlatformData { pub(crate) fn add_middleware(&mut self, middleware: Box) { self.middlewares.push(middleware); } + + /// Adds an interpreter to the platform data. + /// + /// # Arguments + /// + /// * `interpreter` - The interpreter to add. + pub(crate) fn add_interpreter(&mut self, interpreter: Box) { + self.interpreters.push(interpreter); + } } pub trait NgynPlatform: Default { @@ -128,16 +170,40 @@ pub trait NgynEngine: NgynPlatform { self.data_mut().add_middleware(Box::new(middleware)); } + /// Adds an interpreter to the application. + /// + /// # Arguments + /// + /// * `interpreter` - The interpreter to add. + fn use_interpreter(&mut self, interpreter: impl NgynInterpreter + 'static) { + self.data_mut().add_interpreter(Box::new(interpreter)); + } + + /// Sets the state of the application to any value that implements [`AppState`]. + /// + /// # Arguments + /// + /// * `state` - The state to set. fn set_state(&mut self, state: impl AppState + 'static) { self.data_mut().state = Some(Arc::new(state)); } + /// Loads a component which implements [`NgynModule`] into the application. + /// + /// # Arguments + /// + /// * `module` - The module to load. fn load_module(&mut self, module: impl NgynModule + 'static) { for controller in module.get_controllers() { self.load_controller(controller); } } + /// Loads a component which implements [`NgynController`] into the application. + /// + /// # Arguments + /// + /// * `controller` - The arc'd controller to load. fn load_controller(&mut self, controller: Arc>) { for (path, http_method, handler) in controller.routes() { self.route( @@ -154,6 +220,7 @@ pub trait NgynEngine: NgynPlatform { } } + /// Builds the application with the specified module. fn build() -> Self { let module = AppModule::new(); let mut server = Self::default(); diff --git a/crates/shared/src/server/response.rs b/crates/shared/src/server/response.rs index 173c76f1..c3bab18e 100644 --- a/crates/shared/src/server/response.rs +++ b/crates/shared/src/server/response.rs @@ -1,15 +1,11 @@ -use std::sync::Arc; - use http_body_util::Full; -use hyper::{body::Bytes, header::IntoHeaderName, Method, Request, Response, StatusCode}; +use hyper::{header::IntoHeaderName, Method, StatusCode}; use crate::{ core::Handler, server::{NgynContext, NgynResponse, ToBytes, Transformer}, }; -use super::context::{AppState, EmptyState}; - /// Trait representing a full response. pub trait FullResponse { /// Sets the status code of the response. @@ -103,79 +99,3 @@ impl<'a> Transformer<'a> for &'a mut NgynResponse { pub(crate) type Routes = Vec<(String, Method, Box)>; pub(crate) type Middlewares = Vec>; - -pub(crate) trait ResponseBuilder: FullResponse { - /// Creates a new response. - /// - /// # Arguments - /// - /// * `req` - The request to create the response from. - /// - /// # Returns - /// - /// A new response. - /// - /// # Examples - /// - /// ```rust ignore - /// use http_body_util::Full; - /// use hyper::StatusCode; - /// use crate::{context::NgynContext, transformer::Transformer, NgynResponse, ToBytes}; - /// - /// let response = NgynResponse::build(req, routes); - /// assert_eq!(response.status, StatusCode::OK); - /// assert_eq!(response.body.as_slice(), &[1, 2, 3]); - /// ``` - async fn build(req: Request>, routes: &Routes, middlewares: &Middlewares) -> Self; - - async fn build_with_state( - req: Request>, - routes: &Routes, - middlewares: &Middlewares, - state: Arc, - ) -> Self; -} - -impl ResponseBuilder for NgynResponse { - async fn build(req: Request>, routes: &Routes, middlewares: &Middlewares) -> Self { - Self::build_with_state(req, routes, middlewares, Arc::new(EmptyState {})).await - } - - async fn build_with_state( - req: Request>, - routes: &Routes, - middlewares: &Middlewares, - state: Arc, - ) -> Self { - let mut cx = NgynContext::from_request(req); - let mut res = Response::new(Full::new(Bytes::default())); - - cx.set_state(state); - - let mut is_handled = false; - - let _ = &routes.iter().for_each(|(path, method, route_handler)| { - if !is_handled && cx.with(path, method).is_some() { - is_handled = true; - // trigger global middlewares - middlewares - .iter() - .for_each(|middlewares| middlewares.handle(&mut cx, &mut res)); - // trigger route handler - route_handler(&mut cx, &mut res); - } - }); - - // execute controlled route if it is handled - if is_handled { - cx.execute(&mut res).await; - } else { - // trigger global middlewares if no route is found - middlewares - .iter() - .for_each(|middlewares| middlewares.handle(&mut cx, &mut res)); - } - - res - } -} diff --git a/crates/shared/src/traits/interpreter.rs b/crates/shared/src/traits/interpreter.rs new file mode 100644 index 00000000..867e5eb3 --- /dev/null +++ b/crates/shared/src/traits/interpreter.rs @@ -0,0 +1,10 @@ +use crate::server::NgynResponse; + +/// NgynInterpreter is used to interpret a response. +/// +/// Sometimes, a response may need to be interpreted before it is sent back to the client. +/// This trait provides a way to do that. +#[async_trait::async_trait] +pub trait NgynInterpreter: Send + Sync { + async fn interpret(&self, res: &mut NgynResponse) -> NgynResponse; +} diff --git a/crates/shared/src/traits/mod.rs b/crates/shared/src/traits/mod.rs index c5667c02..7a6a727e 100644 --- a/crates/shared/src/traits/mod.rs +++ b/crates/shared/src/traits/mod.rs @@ -1,11 +1,13 @@ mod controller_trait; mod gate_trait; mod injectable_trait; +mod interpreter; mod middleware_trait; mod module_trait; pub use controller_trait::*; pub use gate_trait::*; pub use injectable_trait::*; +pub use interpreter::*; pub use middleware_trait::*; pub use module_trait::*; From ea04366103075a5611bffb375c066b97fe959b28 Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Mon, 5 Aug 2024 19:09:15 +0100 Subject: [PATCH 05/15] fix: separate bytes impl from ToString trait (#135) --- crates/shared/src/server/body.rs | 80 +++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/crates/shared/src/server/body.rs b/crates/shared/src/server/body.rs index 5b408c23..d3dfe986 100644 --- a/crates/shared/src/server/body.rs +++ b/crates/shared/src/server/body.rs @@ -1,4 +1,6 @@ use hyper::body::Bytes; +use serde::Serialize; +use serde_json::{json, Value}; use std::str::FromStr; /// `ToBytes` can be used to convert a type into a `Bytes` @@ -48,8 +50,84 @@ impl ParseBytes for Bytes { } } -impl ToBytes for T { +impl ToBytes for &str { fn to_bytes(self) -> Bytes { Bytes::from(self.to_string()) } } + +impl ToBytes for String { + fn to_bytes(self) -> Bytes { + Bytes::from(self) + } +} + +impl ToBytes for Bytes { + fn to_bytes(self) -> Bytes { + self + } +} + +impl ToBytes for i32 { + fn to_bytes(self) -> Bytes { + Bytes::from(self.to_string()) + } +} + +impl ToBytes for i64 { + fn to_bytes(self) -> Bytes { + Bytes::from(self.to_string()) + } +} + +impl ToBytes for f32 { + fn to_bytes(self) -> Bytes { + Bytes::from(self.to_string()) + } +} + +impl ToBytes for f64 { + fn to_bytes(self) -> Bytes { + Bytes::from(self.to_string()) + } +} + +impl ToBytes for u32 { + fn to_bytes(self) -> Bytes { + Bytes::from(self.to_string()) + } +} + +impl ToBytes for u64 { + fn to_bytes(self) -> Bytes { + Bytes::from(self.to_string()) + } +} + +impl ToBytes for bool { + fn to_bytes(self) -> Bytes { + Bytes::from(self.to_string()) + } +} + +impl ToBytes for Value { + fn to_bytes(self) -> Bytes { + Bytes::from(self.to_string()) + } +} + +impl ToBytes for Result { + fn to_bytes(self) -> Bytes { + match self { + Ok(data) => json!({ "data": data }).to_bytes(), + Err(error) => json!({ "error": error }).to_bytes(), + } + } +} + +impl ToBytes for Vec { + fn to_bytes(self) -> Bytes { + let json = json!(self); + json.to_bytes() + } +} From 08b7945a8db22e87a709e2fcdb99ae541c154d8c Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Sat, 10 Aug 2024 23:15:14 +0100 Subject: [PATCH 06/15] chore: add support for response interpreting in examples (#138) --- Cargo.lock | 1 + examples/weather_app/Cargo.toml | 1 + examples/weather_app/src/main.rs | 3 ++ .../src/modules/weather/weather_controller.rs | 21 ++++++--- .../src/modules/weather/weather_repository.rs | 4 +- .../src/modules/weather/weather_service.rs | 6 +-- examples/weather_app/src/shared/mod.rs | 45 +++++++++++++++++++ 7 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 examples/weather_app/src/shared/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 94b19e18..5b09d596 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3080,6 +3080,7 @@ name = "weather_api" version = "0.2.0" dependencies = [ "dotenv", + "http-body-util", "ngyn 0.4.2", "ngyn-shuttle", "serde", diff --git a/examples/weather_app/Cargo.toml b/examples/weather_app/Cargo.toml index e71baa86..53ea1e10 100644 --- a/examples/weather_app/Cargo.toml +++ b/examples/weather_app/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] dotenv = "0.15.0" +http-body-util = "0.1" ngyn = { version = "0.4", path = "../../crates/core" } ngyn-shuttle = { version = "0.1", path = "../../crates/shuttle" } serde = { version = "1.0", features = ["derive"] } diff --git a/examples/weather_app/src/main.rs b/examples/weather_app/src/main.rs index be9feecd..b8a73a9a 100644 --- a/examples/weather_app/src/main.rs +++ b/examples/weather_app/src/main.rs @@ -1,5 +1,6 @@ mod middlewares; mod modules; +mod shared; use dotenv::dotenv; use modules::AppModule; @@ -12,7 +13,9 @@ use crate::middlewares::notfound_middleware::NotFoundMiddleware; async fn main() -> ShuttleNgyn { dotenv().ok(); let mut app = NgynFactory::::create::(); + app.use_middleware(NotFoundMiddleware::new()); + app.use_interpreter(shared::ResponseInterpreter {}); Ok(app.into()) } diff --git a/examples/weather_app/src/modules/weather/weather_controller.rs b/examples/weather_app/src/modules/weather/weather_controller.rs index be2ff95f..e41d2004 100644 --- a/examples/weather_app/src/modules/weather/weather_controller.rs +++ b/examples/weather_app/src/modules/weather/weather_controller.rs @@ -1,5 +1,6 @@ use ngyn::prelude::*; use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; use validator::Validate; use super::weather_gate::WeatherGate; @@ -23,16 +24,24 @@ pub struct WeatherController { impl WeatherController { #[get("//")] #[check(WeatherGate)] - async fn get_location(&self, params: Query) -> String { + async fn get_location(&self, params: Param) -> Result { println!("{:?}", "Getting location weather"); - self.weather_service - .get_location_weather(params.get("location").unwrap().as_str()) - .await + if let Some(location) = params.get("location") { + match self.weather_service.get_weather(&location).await { + Ok(r) => Ok(r), + Err(e) => Err(json!({ "status": 501, "message": e.to_string() })), + } + } else { + Err(json!({ "status": 401, "message": "please specify location param" })) + } } #[post("/")] - async fn post_location(&self, weather: WeatherDto) -> String { + async fn post_location(&self, weather: WeatherDto) -> Result { let location = weather.location; - self.weather_service.get_location_weather(&location).await + match self.weather_service.get_weather(&location).await { + Ok(r) => Ok(r), + Err(e) => Err(json!({ "status": 501, "message": e.to_string() })), + } } } diff --git a/examples/weather_app/src/modules/weather/weather_repository.rs b/examples/weather_app/src/modules/weather/weather_repository.rs index 0c2c3990..6726faf8 100644 --- a/examples/weather_app/src/modules/weather/weather_repository.rs +++ b/examples/weather_app/src/modules/weather/weather_repository.rs @@ -18,10 +18,10 @@ impl WeatherRepository { Ok(response) } - pub async fn get_location_current_weather(&self, location: &str) -> String { + pub async fn get_current_weather(&self, location: &str) -> Result { println!("Getting weather for {}", location); let url = self.build_url("current", location); println!("Sending request to {}", url); - self.send_request(&url).await.unwrap() + self.send_request(&url).await } } diff --git a/examples/weather_app/src/modules/weather/weather_service.rs b/examples/weather_app/src/modules/weather/weather_service.rs index 630d7015..951af7df 100644 --- a/examples/weather_app/src/modules/weather/weather_service.rs +++ b/examples/weather_app/src/modules/weather/weather_service.rs @@ -8,10 +8,8 @@ pub struct WeatherService { } impl WeatherService { - pub async fn get_location_weather(&self, location: &str) -> String { + pub async fn get_weather(&self, location: &str) -> Result { println!("Getting weather for {}", location); - self.weather_repository - .get_location_current_weather(location) - .await + self.weather_repository.get_current_weather(location).await } } diff --git a/examples/weather_app/src/shared/mod.rs b/examples/weather_app/src/shared/mod.rs new file mode 100644 index 00000000..c6e4fc74 --- /dev/null +++ b/examples/weather_app/src/shared/mod.rs @@ -0,0 +1,45 @@ +use http_body_util::BodyExt; +use ngyn::{ + prelude::*, + shared::{server::ParseBytes, traits::NgynInterpreter}, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Serialize, Deserialize)] +struct ErrorData { + status: Option, + message: Option, +} + +#[derive(Serialize, Deserialize)] +struct CommonResponse { + data: Option, + error: Option, +} + +pub struct ResponseInterpreter {} + +#[async_trait] +impl NgynInterpreter for ResponseInterpreter { + async fn interpret(&self, res: &mut NgynResponse) -> NgynResponse { + let (parts, mut body) = res.clone().into_parts(); + let body_str = { + let mut buf = String::new(); + let frame = body.frame().await; + + if let Some(Ok(chunk)) = frame { + buf = chunk.into_data().unwrap().parse_bytes(); + } + buf + }; + let response: CommonResponse = serde_json::from_str(&body_str).unwrap(); + let mut new_res = NgynResponse::from_parts(parts, body_str.into()); + if let Some(error) = response.error { + if let Some(status) = error.status { + new_res.set_status(status); + } + } + new_res + } +} From bb7e6b275b508c7f156290f81b519bc0942e3242 Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Sat, 10 Aug 2024 23:15:30 +0100 Subject: [PATCH 07/15] chore(workflows): update release workflow (#136) --- .github/workflows/release.yml | 54 ++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4cb73b5b..b9d3c8d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,37 +1,39 @@ -name: Gituhb release +name: Release on: push: - branches: main + branches: [main] jobs: - release: - name: Perform release + crates_io_publish: + name: Publish (crates.io) runs-on: ubuntu-latest - permissions: - contents: write + timeout-minutes: 25 steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - - name: Cocogitto release - id: release - uses: oknozor/cocogitto-action@v3 + - name: cargo-release Cache + id: cargo_release_cache + uses: actions/cache@v3 with: - release: true - check-latest-tag-only: true - git-user: ${{ github.actor}} - git-user-email: ${{ github.actor_id }}+${{ github.actor}}@users.noreply.github.com + path: ~/.cargo/bin/cargo-release + key: ${{ runner.os }}-cargo-release - - name: Print version - run: "echo '${{ steps.release.outputs.version }}'" + - run: cargo install cargo-release + if: steps.cargo_release_cache.outputs.cache-hit != 'true' - - name: Generate Changelog - run: cog changelog --at ${{ steps.release.outputs.version }} -t full_hash > GITHUB_CHANGELOG.md + - name: cargo login + run: cargo login ${{ secrets.CRATES_IO_API_TOKEN }} - - name: Upload github release - uses: softprops/action-gh-release@v1 - with: - body_path: GITHUB_CHANGELOG.md - tag_name: ${{ steps.release.outputs.version }} - token: ${{ secrets.GITHUB_TOKEN }} + # allow-branch HEAD is because GitHub actions switches + # to the tag while building, which is a detached head + - name: "cargo release publish" + run: |- + cargo release \ + publish \ + --workspace \ + --all-features \ + --allow-branch HEAD \ + --no-confirm \ + --no-verify \ + --execute From 421394b39e46013af2ab0cc689958d3fb2666ad6 Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Mon, 12 Aug 2024 22:43:24 +0100 Subject: [PATCH 08/15] chore(shared): context cleanup and error handing (#141) --- crates/shared/src/server/context.rs | 51 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/crates/shared/src/server/context.rs b/crates/shared/src/server/context.rs index 1119e885..70ed27d8 100644 --- a/crates/shared/src/server/context.rs +++ b/crates/shared/src/server/context.rs @@ -34,9 +34,6 @@ pub trait AppState: Any + Send + Sync { impl AppState for Arc {} -pub struct EmptyState; -impl AppState for EmptyState {} - /// Represents the context of a request in Ngyn pub struct NgynContext { request: Request>, @@ -125,7 +122,7 @@ impl NgynContext { /// /// # Arguments /// - /// * `key` - The key to retrieve the value for. + /// * `key` - The key (case-insensitive) to retrieve the value for. /// /// # Returns /// @@ -144,20 +141,19 @@ impl NgynContext { /// ``` pub fn get Deserialize<'a>>(&self, key: &str) -> Option { let value = self.store.get(key.to_lowercase().trim()); - match value { - Some(v) => { - let stored_cx: NgynContextValue = serde_json::from_str(v).unwrap(); - Some(stored_cx.value) + if let Some(value) = value { + if let Ok(stored_cx) = serde_json::from_str::>(value) { + return Some(stored_cx.value); } - None => None, } + None } /// Sets the value associated with the given key in the context. /// /// # Arguments /// - /// * `key` - The key to set the value for. + /// * `key` - The key (case-insensitive) to set the value for. /// * `value` - The value to associate with the key. /// /// # Examples @@ -172,17 +168,16 @@ impl NgynContext { /// assert_eq!(value, "John".to_string()); /// ``` pub fn set(&mut self, key: &str, value: V) { - self.store.insert( - key.trim().to_lowercase(), - serde_json::to_string(&NgynContextValue::create(value)).unwrap(), - ); + if let Ok(value) = serde_json::to_string(&NgynContextValue::create(value)) { + self.store.insert(key.trim().to_lowercase(), value); + } } /// Removes the value associated with the given key from the context. /// /// # Arguments /// - /// * `key` - The key to remove the value for. + /// * `key` - The key (case-insensitive) to remove the value for. /// /// # Examples /// @@ -265,7 +260,7 @@ impl NgynContext { /// /// # Arguments /// - /// * `key` - The key to check for. + /// * `key` - The key (case-insensitive) to check for. /// /// # Returns /// @@ -287,6 +282,19 @@ impl NgynContext { } } +impl NgynContext { + /// Checks if the context has a valid route. + /// A valid route is when the route information and the params are set. + /// This is great for differentiating known routes from unknown routes. + /// + /// # Returns + /// + /// `true` if the context has a valid route, `false` otherwise. + pub fn is_valid_route(&self) -> bool { + self.params.is_some() + } +} + impl NgynContext { /// Creates a new `NgynContext` from the given request. /// @@ -360,17 +368,6 @@ impl NgynContext { } } - /// Checks if the context has a valid route. - /// A valid route is when the route information and the params are set. - /// This is great for differentiating known routes from unknown routes. - /// - /// # Returns - /// - /// `true` if the context has a valid route, `false` otherwise. - pub fn is_valid_route(&self) -> bool { - self.params.is_some() - } - /// Prepares the context for execution by setting the route information. /// /// # Arguments From b5c906827654dbbd252237ee69a7198d71ad45be Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Mon, 12 Aug 2024 22:44:11 +0100 Subject: [PATCH 09/15] chore(shared): finalize response interpretation (#140) --- crates/core/src/lib.rs | 4 +- crates/shared/src/core/engine.rs | 42 +++++++---------- crates/shared/src/server/body.rs | 25 ---------- crates/shared/src/server/mod.rs | 9 ++-- crates/shared/src/server/response.rs | 22 +++++---- crates/shared/src/traits/controller_trait.rs | 17 +++++-- crates/shared/src/traits/gate_trait.rs | 35 ++++++++++++-- crates/shared/src/traits/interpreter.rs | 26 ++++++++++- .../src/middlewares/notfound_middleware.rs | 9 +++- examples/weather_app/src/shared/mod.rs | 46 +++++++++---------- 10 files changed, 134 insertions(+), 101 deletions(-) diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 3672cb4e..4162a4cf 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -18,8 +18,8 @@ pub mod prelude { pub use ngyn_shared::{ core::NgynEngine, server::{ - Body, FullResponse, NgynContext, NgynRequest, NgynResponse, Param, Query, Transducer, - Transformer, + Body, CommonResponse, FullResponse, NgynContext, NgynRequest, NgynResponse, Param, + Query, Transducer, Transformer, }, traits::{NgynGate, NgynInjectable, NgynMiddleware}, }; diff --git a/crates/shared/src/core/engine.rs b/crates/shared/src/core/engine.rs index 6d2eb363..45eda50d 100644 --- a/crates/shared/src/core/engine.rs +++ b/crates/shared/src/core/engine.rs @@ -4,11 +4,7 @@ use std::sync::Arc; use super::{Handler, RouteHandle}; use crate::{ - server::{ - context::AppState, - response::{Middlewares, Routes}, - Method, NgynContext, NgynResponse, - }, + server::{context::AppState, Method, Middlewares, NgynContext, NgynResponse, Routes}, traits::{NgynController, NgynInterpreter, NgynMiddleware, NgynModule}, }; @@ -39,34 +35,30 @@ impl PlatformData { cx.set_state(state.clone()); } - let mut is_handled = false; - - self.routes + let route_handler = self + .routes .iter() - .for_each(|(path, method, route_handler)| { - if !is_handled && cx.with(path, method).is_some() { - is_handled = true; - // trigger global middlewares - self.middlewares - .iter() - .for_each(|middlewares| middlewares.handle(&mut cx, &mut res)); - // trigger route handler - route_handler(&mut cx, &mut res); + .filter_map(|(path, method, route_handler)| { + if cx.with(path, method).is_some() { + return Some(route_handler); } - }); + None + }) + .next(); + + // trigger global middlewares + self.middlewares + .iter() + .for_each(|middlewares| middlewares.handle(&mut cx, &mut res)); // execute controlled route if it is handled - if is_handled { + if let Some(route_handler) = route_handler { + route_handler(&mut cx, &mut res); cx.execute(&mut res).await; - } else { - // trigger global middlewares if no route is found - self.middlewares - .iter() - .for_each(|middlewares| middlewares.handle(&mut cx, &mut res)); } for interpreter in &self.interpreters { - res = interpreter.interpret(&mut res).await; + interpreter.interpret(&mut res).await; } res diff --git a/crates/shared/src/server/body.rs b/crates/shared/src/server/body.rs index d3dfe986..af7fbe32 100644 --- a/crates/shared/src/server/body.rs +++ b/crates/shared/src/server/body.rs @@ -1,7 +1,6 @@ use hyper::body::Bytes; use serde::Serialize; use serde_json::{json, Value}; -use std::str::FromStr; /// `ToBytes` can be used to convert a type into a `Bytes` /// @@ -26,30 +25,6 @@ pub trait ToBytes { fn to_bytes(self) -> Bytes; } -/// `ParseBytes` can be used to parse `Bytes` into a specific type -pub trait ParseBytes { - /// Parses `Bytes` into a specific type - /// - /// # Examples - /// - /// ```rust ignore - /// use ngyn_shared::{ParseBytes, Bytes}; - /// - /// let bytes: Bytes = Bytes::from("42"); - /// let value: i32 = bytes.parse_bytes(); - /// assert_eq!(value, 42); - /// ``` - fn parse_bytes(self) -> T; -} - -impl ParseBytes for Bytes { - fn parse_bytes(self) -> T { - String::from_utf8_lossy(&self) - .parse::() - .unwrap_or_default() - } -} - impl ToBytes for &str { fn to_bytes(self) -> Bytes { Bytes::from(self.to_string()) diff --git a/crates/shared/src/server/mod.rs b/crates/shared/src/server/mod.rs index 6a1c2f33..6c926788 100644 --- a/crates/shared/src/server/mod.rs +++ b/crates/shared/src/server/mod.rs @@ -4,14 +4,15 @@ pub mod response; pub mod transformer; pub mod uri; -pub use self::response::FullResponse; -pub use body::{ParseBytes, ToBytes}; +pub use self::response::{CommonResponse, FullResponse}; +pub use body::ToBytes; pub use context::NgynContext; use http_body_util::Full; -pub use hyper::body::Bytes; +pub use hyper::{body::Bytes, http::Method}; pub use transformer::{Body, Param, Query, Transducer, Transformer}; pub type NgynRequest = hyper::Request>; pub type NgynResponse = hyper::Response>; -pub use hyper::http::Method; +pub(crate) type Routes = Vec<(String, Method, Box)>; +pub(crate) type Middlewares = Vec>; diff --git a/crates/shared/src/server/response.rs b/crates/shared/src/server/response.rs index c3bab18e..4c687f14 100644 --- a/crates/shared/src/server/response.rs +++ b/crates/shared/src/server/response.rs @@ -1,12 +1,17 @@ -use http_body_util::Full; -use hyper::{header::IntoHeaderName, Method, StatusCode}; +use hyper::{header::IntoHeaderName, StatusCode}; +use serde::{Deserialize, Serialize}; -use crate::{ - core::Handler, - server::{NgynContext, NgynResponse, ToBytes, Transformer}, -}; +use crate::server::{NgynContext, NgynResponse, ToBytes, Transformer}; + +#[derive(Serialize, Deserialize)] +pub struct CommonResponse { + pub data: Option, + pub error: Option, +} /// Trait representing a full response. +/// +/// This trait provides short methods to set the status code, headers, and body of a response. pub trait FullResponse { /// Sets the status code of the response. /// @@ -81,7 +86,7 @@ impl FullResponse for NgynResponse { } fn send(&mut self, item: impl ToBytes) { - *self.body_mut() = Full::new(item.to_bytes()); + *self.body_mut() = item.to_bytes().into(); } } @@ -96,6 +101,3 @@ impl<'a> Transformer<'a> for &'a mut NgynResponse { res } } - -pub(crate) type Routes = Vec<(String, Method, Box)>; -pub(crate) type Middlewares = Vec>; diff --git a/crates/shared/src/traits/controller_trait.rs b/crates/shared/src/traits/controller_trait.rs index 42780f4d..daea49da 100644 --- a/crates/shared/src/traits/controller_trait.rs +++ b/crates/shared/src/traits/controller_trait.rs @@ -5,7 +5,7 @@ use crate::server::FullResponse; use super::NgynInjectable; /// `NgynController` defines the basic structure of a controller in Ngyn. -/// It is designed to be thread-safe. +/// Designed for thread safety, it is implemented by controllers that are used to handle requests. #[async_trait::async_trait] pub trait NgynController: NgynInjectable + Any + Sync + Send { /// Returns a vector of routes for the controller. @@ -13,10 +13,12 @@ pub trait NgynController: NgynInjectable + Any + Sync + Send { vec![] } + /// Returns the prefix for the controller. fn prefix(&self) -> String { '/'.to_string() } + /// used internally to handle the routing logic of the controller. async fn handle( &mut self, handler: &str, @@ -29,6 +31,11 @@ pub trait NgynController: NgynInjectable + Any + Sync + Send { } } +/// In Ngyn, controllers are stored as `Arc>`. +/// This is because controllers are shared across threads and need to be cloned easily. +/// +/// Here's how we convert an `Arc>` to a `Box`. +/// This conversion allows us to mutably borrow the controller and handle routing logic. impl From>> for Box { fn from(arc: Arc>) -> Self { let arc_clone = arc.clone(); @@ -43,11 +50,9 @@ impl From>> for Box { } } -impl NgynControllerHandler for T {} - /// `NgynControllerHandler` is an internal trait that defines placeholders for routing logic of a controller. #[allow(unused)] -pub trait NgynControllerHandler { +pub trait NgynControllerHandler: NgynController { const ROUTES: &'static [(&'static str, &'static str, &'static str)] = &[]; /// This is for internal use only. It handles the routing logic of the controller. @@ -58,5 +63,9 @@ pub trait NgynControllerHandler { _cx: &mut crate::server::NgynContext, _res: &mut crate::server::NgynResponse, ) { + // do nothing } } + +/// implement for all types that implement `NgynController` +impl NgynControllerHandler for T {} diff --git a/crates/shared/src/traits/gate_trait.rs b/crates/shared/src/traits/gate_trait.rs index 6389eee2..e3409909 100644 --- a/crates/shared/src/traits/gate_trait.rs +++ b/crates/shared/src/traits/gate_trait.rs @@ -4,6 +4,35 @@ use crate::{ }; /// Trait for implementing a gate. +/// +/// Gates are how Ngyn determines if a route can activate. +/// Sometimes, a route may need to be guarded by certain conditions. +/// For instance, restricting access to a route based on the user's role, or checking if the user is authenticated. +/// Typically, gates are used for this purpose. +/// +/// # Examples +/// +/// ```rust +/// use ngyn_shared::traits::{NgynGate, NgynInjectable}; +/// use ngyn_shared::server::{NgynContext, NgynResponse}; +/// +/// pub struct AuthGate {} +/// +/// impl NgynInjectable for AuthGate { +/// fn new() -> Self { +/// AuthGate {} +/// } +/// } +/// +/// impl NgynGate for AuthGate { +/// async fn can_activate(&self, cx: &mut NgynContext, res: &mut NgynResponse) -> bool { +/// // Check if the user is authenticated +/// // If the user is authenticated, return true +/// // Otherwise, return false +/// false +/// } +/// } +/// ``` pub trait NgynGate: NgynInjectable { /// Determines if the gate can activate for the given request. /// @@ -15,8 +44,8 @@ pub trait NgynGate: NgynInjectable { /// ### Returns /// /// Returns `true` if the route can activate, `false` otherwise. - #[allow(async_fn_in_trait)] - async fn can_activate(&self, _cx: &mut NgynContext, _res: &mut NgynResponse) -> bool { - true + #[allow(async_fn_in_trait, unused_variables)] + async fn can_activate(&self, cx: &mut NgynContext, res: &mut NgynResponse) -> bool { + true // default implementation } } diff --git a/crates/shared/src/traits/interpreter.rs b/crates/shared/src/traits/interpreter.rs index 867e5eb3..8a273ceb 100644 --- a/crates/shared/src/traits/interpreter.rs +++ b/crates/shared/src/traits/interpreter.rs @@ -3,8 +3,32 @@ use crate::server::NgynResponse; /// NgynInterpreter is used to interpret a response. /// /// Sometimes, a response may need to be interpreted before it is sent back to the client. +/// Good examples of this are when a response is expected to be in a certain format, or when +/// the response needs to be modified before it is sent back to the client. +/// /// This trait provides a way to do that. +/// +/// # Examples +/// +/// ```rust +/// use ngyn_shared::traits::NgynInterpreter; +/// use ngyn_shared::server::NgynResponse; +/// +/// pub struct ResponseInterpreter {} +/// +/// #[async_trait::async_trait] +/// impl NgynInterpreter for ResponseInterpreter { +/// async fn interpret(&self, res: &mut NgynResponse) { +/// // Interpret the response here +/// } +/// } +/// ``` #[async_trait::async_trait] pub trait NgynInterpreter: Send + Sync { - async fn interpret(&self, res: &mut NgynResponse) -> NgynResponse; + /// Interprets the response. + /// + /// # Arguments + /// + /// * `res` - The response to be interpreted. + async fn interpret(&self, res: &mut NgynResponse); } diff --git a/examples/weather_app/src/middlewares/notfound_middleware.rs b/examples/weather_app/src/middlewares/notfound_middleware.rs index 5d40da3d..34dd8ebd 100644 --- a/examples/weather_app/src/middlewares/notfound_middleware.rs +++ b/examples/weather_app/src/middlewares/notfound_middleware.rs @@ -1,4 +1,5 @@ use ngyn::prelude::*; +use serde_json::json; #[injectable] pub struct NotFoundMiddleware; @@ -8,7 +9,11 @@ impl NgynMiddleware for NotFoundMiddleware { if cx.is_valid_route() { return; } - res.set_status(404); - res.send("Not Found".to_string()); + res.send(json!({ + "error": { + "status": 404, + "message": "Route not found", + } + })); } } diff --git a/examples/weather_app/src/shared/mod.rs b/examples/weather_app/src/shared/mod.rs index c6e4fc74..02a09b39 100644 --- a/examples/weather_app/src/shared/mod.rs +++ b/examples/weather_app/src/shared/mod.rs @@ -1,8 +1,5 @@ use http_body_util::BodyExt; -use ngyn::{ - prelude::*, - shared::{server::ParseBytes, traits::NgynInterpreter}, -}; +use ngyn::{prelude::*, shared::traits::NgynInterpreter}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -12,34 +9,33 @@ struct ErrorData { message: Option, } -#[derive(Serialize, Deserialize)] -struct CommonResponse { - data: Option, - error: Option, -} - pub struct ResponseInterpreter {} #[async_trait] impl NgynInterpreter for ResponseInterpreter { - async fn interpret(&self, res: &mut NgynResponse) -> NgynResponse { - let (parts, mut body) = res.clone().into_parts(); - let body_str = { - let mut buf = String::new(); - let frame = body.frame().await; + async fn interpret(&self, res: &mut NgynResponse) { + let mut body_str = String::new(); - if let Some(Ok(chunk)) = frame { - buf = chunk.into_data().unwrap().parse_bytes(); + let frame = res.frame().await; + if let Some(Ok(frame)) = frame { + if let Ok(bytes) = frame.into_data() { + // Parse the body into a string. + // This process may fail, since Ngyn allows any valid vec of bytes to be a body. + // If the body cannot be parsed into a string, we will just ignore it. + if let Ok(body) = &String::from_utf8_lossy(&bytes).parse::() { + body_str.push_str(body); + } + // body has been read, so we need to set it back + *res.body_mut() = bytes.into(); } - buf - }; - let response: CommonResponse = serde_json::from_str(&body_str).unwrap(); - let mut new_res = NgynResponse::from_parts(parts, body_str.into()); - if let Some(error) = response.error { - if let Some(status) = error.status { - new_res.set_status(status); + } + + if let Ok(response) = serde_json::from_str::>(&body_str) { + if let Some(error) = response.error { + if let Some(status) = error.status { + res.set_status(status); + } } } - new_res } } From 1f00c03dd542ab3c6d731b0596383c648612d471 Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Mon, 12 Aug 2024 22:48:33 +0100 Subject: [PATCH 10/15] chore(shared): remove name from `module` (#137) * chore: remove name from `module` * fix broken tests --- crates/macros/src/core/module.rs | 3 --- crates/shared/src/traits/module_trait.rs | 22 +++++++++++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/crates/macros/src/core/module.rs b/crates/macros/src/core/module.rs index 7ce0da58..1a6ce584 100644 --- a/crates/macros/src/core/module.rs +++ b/crates/macros/src/core/module.rs @@ -136,9 +136,6 @@ pub(crate) fn module_macro(args: TokenStream, input: TokenStream) -> TokenStream fn new() -> Self { #init_module } - fn name(&self) -> &str { - stringify!(#ident) - } fn get_controllers(&self) -> Vec>> { use ngyn::shared::traits::NgynInjectable; let mut controllers: Vec>> = vec![#(#add_controllers),*]; diff --git a/crates/shared/src/traits/module_trait.rs b/crates/shared/src/traits/module_trait.rs index b3e29663..3da42ec7 100644 --- a/crates/shared/src/traits/module_trait.rs +++ b/crates/shared/src/traits/module_trait.rs @@ -1,18 +1,30 @@ use crate::traits::NgynController; use std::sync::Arc; -/// `NgynModule` is a trait that defines the basic structure of a module in Ngyn. +/// Modules are the building blocks of an application in Ngyn. +/// They are used to group related [controllers](https://ngyn.rs/docs/foundations/controllers). +/// +/// ### Example +/// +/// ```rust +/// use ngyn_shared::traits::NgynModule; +/// +/// pub struct AppModule; +/// +/// impl NgynModule for AppModule { +/// fn new() -> Self { +/// Self {} +/// } +/// } +/// ``` pub trait NgynModule: Send + Sync { /// Creates a new instance of the module. fn new() -> Self where Self: Sized; - /// Returns the name of the module. - fn name(&self) -> &str; - /// Returns the controllers of the module. fn get_controllers(&self) -> Vec>> { - vec![] + Vec::new() } } From a023a502c0636b66ac831104e798fc9e331d02a1 Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Mon, 12 Aug 2024 23:07:04 +0100 Subject: [PATCH 11/15] chore(workflows): version bump manager workflow (#143) --- .github/workflows/version-bump.yml | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/version-bump.yml diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml new file mode 100644 index 00000000..eedd8142 --- /dev/null +++ b/.github/workflows/version-bump.yml @@ -0,0 +1,56 @@ +name: Bump Version on PR (Rust Workspace) + +on: + pull_request: + types: [opened] + branches: + - main + +jobs: + bump_version: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Extract version from PR title + id: extract_version + uses: actions/github-script@v6 + with: + script: | + const prTitle = context.payload.pull_request.title; + const versionMatch = prTitle.match(/v(\d+\.\d+\.\d+)/); + if (!versionMatch) { + core.setFailed('No valid semver version found in PR title'); + } else { + // if pr title contains "minor" or "major" we bump accordingly + if (prTitle.includes('minor')) { + core.setOutput('version', 'minor'); + } else if (prTitle.includes('major')) { + core.setOutput('version', 'major'); + } else { + core.setOutput('version', versionMatch[1]); + } + } + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: Bump workspace version + run: | + cargo install cargo-workspaces + cargo workspaces version ${{ steps.extract_version.outputs.version }} --all + + - name: Commit and push changes + uses: EndBug/add-and-commit@v9 + with: + message: "Bump version to ${{ steps.extract_version.outputs.version }}" + add: "*/Cargo.toml" + push: true + branch: dev + default_author: github_actor + github_token: ${{ secrets.GITHUB_TOKEN }} From f672d8fc80a243851d480f806982abb0000c3280 Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Mon, 12 Aug 2024 23:18:24 +0100 Subject: [PATCH 12/15] chore(workflows): workflow improvements (#145) --- .github/workflows/pr.yml | 2 +- .github/workflows/version-bump.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index cc20c78e..efc25daf 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,7 +1,7 @@ name: PR Workflow on: pull_request: - types: ready_for_review + types: [ready_for_review] push: branches: - dev diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index eedd8142..3d5566e1 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -31,7 +31,7 @@ jobs: } else if (prTitle.includes('major')) { core.setOutput('version', 'major'); } else { - core.setOutput('version', versionMatch[1]); + core.setOutput('version', `custom ${versionMatch[1]}`); } } From f0b1ef1764aed6b0ba89fcec55ed67bdeded58bb Mon Sep 17 00:00:00 2001 From: elcharitas Date: Mon, 12 Aug 2024 23:36:19 +0100 Subject: [PATCH 13/15] chore: add ref to version bump workflow --- .github/workflows/version-bump.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 3d5566e1..b00c7045 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -14,6 +14,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} - name: Extract version from PR title id: extract_version From b4a42e85854a2da5ebf3cbdceffd4a7d216f872d Mon Sep 17 00:00:00 2001 From: elcharitas Date: Mon, 12 Aug 2024 23:45:16 +0100 Subject: [PATCH 14/15] chore(workflows): allow cargo-workspaces on `dev` branch --- .github/workflows/version-bump.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index b00c7045..43d45143 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -44,7 +44,7 @@ jobs: - name: Bump workspace version run: | cargo install cargo-workspaces - cargo workspaces version ${{ steps.extract_version.outputs.version }} --all + cargo workspaces version ${{ steps.extract_version.outputs.version }} --all --allow-branch dev - name: Commit and push changes uses: EndBug/add-and-commit@v9 From 8ca81e0ad8d55b2f5794bc627f2ee2f2ce27d87e Mon Sep 17 00:00:00 2001 From: Jonathan Irhodia Date: Mon, 12 Aug 2024 23:56:45 +0100 Subject: [PATCH 15/15] chore(release): version bump - 0.4.3 (#149) --- Cargo.lock | 24 ++++++++++++------------ crates/core/Cargo.toml | 2 +- crates/macros/Cargo.toml | 2 +- crates/shared/Cargo.toml | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b09d596..2d504aa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -893,7 +893,7 @@ dependencies = [ "hyper 1.4.0", "juniper", "juniper_hyper", - "ngyn 0.4.2", + "ngyn 0.4.3", "ngyn-hyper 0.1.0", "serde", "serde_json", @@ -1482,15 +1482,15 @@ dependencies = [ [[package]] name = "ngyn" -version = "0.4.2" +version = "0.4.3" dependencies = [ "async-std", "async-trait", "http-body-util", "hyper 1.4.0", "hyper-util", - "ngyn_macros 0.4.2", - "ngyn_shared 0.4.2", + "ngyn_macros 0.4.3", + "ngyn_shared 0.4.3", "tokio", ] @@ -1518,7 +1518,7 @@ dependencies = [ "http-body-util", "hyper 1.4.0", "hyper-util", - "ngyn_shared 0.4.2", + "ngyn_shared 0.4.3", "tokio", ] @@ -1547,7 +1547,7 @@ dependencies = [ name = "ngyn-swagger" version = "0.1.0" dependencies = [ - "ngyn 0.4.2", + "ngyn 0.4.3", "ngyn-swagger-macros", "serde", "serde_json", @@ -1570,7 +1570,7 @@ version = "0.1.0" dependencies = [ "http-body-util", "hyper 1.4.0", - "ngyn_shared 0.4.2", + "ngyn_shared 0.4.3", "tokio", "vercel_runtime", ] @@ -1588,10 +1588,10 @@ dependencies = [ [[package]] name = "ngyn_macros" -version = "0.4.2" +version = "0.4.3" dependencies = [ "http 1.1.0", - "ngyn_shared 0.4.2", + "ngyn_shared 0.4.3", "quote", "syn 2.0.68", ] @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "ngyn_shared" -version = "0.4.2" +version = "0.4.3" dependencies = [ "async-trait", "http-body-util", @@ -2925,7 +2925,7 @@ checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" name = "vercel_app" version = "0.2.8" dependencies = [ - "ngyn 0.4.2", + "ngyn 0.4.3", "ngyn-swagger", "ngyn-vercel", "serde", @@ -3081,7 +3081,7 @@ version = "0.2.0" dependencies = [ "dotenv", "http-body-util", - "ngyn 0.4.2", + "ngyn 0.4.3", "ngyn-shuttle", "serde", "serde_json", diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 4c3f4e49..ff9eb655 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ngyn" -version = "0.4.2" +version = "0.4.3" edition = "2021" description = "Modular backend framework for web applications" license = "MIT" diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 2b2c928a..a01a3bbf 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ngyn_macros" -version = "0.4.2" +version = "0.4.3" edition = "2021" description = "Modular backend framework for web applications" license = "MIT" diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 9c6b426f..0af5dda2 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ngyn_shared" -version = "0.4.2" +version = "0.4.3" edition = "2021" description = "Modular backend framework for web applications" license = "MIT"