diff --git a/README.md b/README.md index 77a990e..935a9dc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # rupring -![](https://img.shields.io/badge/language-Rust-red) ![](https://img.shields.io/badge/version-0.8.2-brightgreen) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/myyrakle/rupring/blob/master/LICENSE) +![](https://img.shields.io/badge/language-Rust-red) ![](https://img.shields.io/badge/version-0.9.0-brightgreen) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/myyrakle/rupring/blob/master/LICENSE) spring on rust @@ -8,8 +8,7 @@ spring on rust required dependency list ```toml -rupring = "0.8.2" -tokio = { version = "1", features = ["full"] } +rupring = "0.9.0" serde = { version="1.0.193", features=["derive"] } ``` @@ -34,13 +33,8 @@ pub fn echo(request: rupring::Request) -> rupring::Response { rupring::Response::new().text(request.body) } -#[tokio::main] -async fn main() -> Result<(), Box> { - let app = rupring::RupringFactory::create(RootModule {}); - - app.listen(3000).await?; - - Ok(()) +fn main() { + rupring::run(RootModule {}) } ``` diff --git a/application.properties b/application.properties new file mode 100644 index 0000000..a3ac65c --- /dev/null +++ b/application.properties @@ -0,0 +1 @@ +server.port=8080 \ No newline at end of file diff --git a/rupring/Cargo.toml b/rupring/Cargo.toml index 460968b..ac1c9b0 100644 --- a/rupring/Cargo.toml +++ b/rupring/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rupring" -version = "0.8.2" +version = "0.9.0" edition = "2021" license = "MIT" authors = ["myyrakle "] diff --git a/rupring/src/application_properties.rs b/rupring/src/application_properties.rs new file mode 100644 index 0000000..27d154d --- /dev/null +++ b/rupring/src/application_properties.rs @@ -0,0 +1,220 @@ +use std::collections::HashMap; + +#[derive(Debug, PartialEq, Clone)] +pub struct ApplicationProperties { + pub server: Server, + pub environment: String, + + pub etc: HashMap, +} + +impl Default for ApplicationProperties { + fn default() -> Self { + ApplicationProperties { + server: Server::default(), + environment: "dev".to_string(), + etc: HashMap::new(), + } + } +} + +// Reference: https://docs.spring.io/spring-boot/appendix/application-properties/index.html#appendix.application-properties.server +#[derive(Debug, PartialEq, Clone)] +pub struct Server { + pub address: String, + pub port: u16, +} + +impl Default for Server { + fn default() -> Self { + Server { + address: "0.0.0.0".to_string(), + port: 3000, + } + } +} + +impl ApplicationProperties { + pub fn from_properties(text: String) -> ApplicationProperties { + let mut server = Server::default(); + let mut environment = "dev".to_string(); + let mut etc = HashMap::new(); + + for line in text.lines() { + let mut parts = line.split("="); + + let key = match parts.next() { + Some(key) => key.trim().to_owned(), + None => continue, + }; + let value = match parts.next() { + Some(value) => value.trim().to_owned(), + None => continue, + }; + + // value에 앞뒤로 ""가 있다면 제거 + let value = if value.starts_with('"') && value.ends_with('"') { + value[1..value.len() - 1].to_string() + } else { + value.to_string() + }; + + match key.as_str() { + "server.port" => { + if let Ok(value) = value.parse::() { + server.port = value; + } + } + "server.address" => { + server.address = value.to_string(); + } + "environment" => { + environment = value.to_string(); + } + _ => { + etc.insert(key, value); + } + } + } + + ApplicationProperties { + server, + etc, + environment, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from_properties() { + struct TestCase { + name: String, + input: String, + expected: ApplicationProperties, + } + + let test_cases = vec![ + TestCase { + name: "일반적인 기본 속성 바인딩".to_string(), + input: r#" + server.port=8080 + server.address=127.0.0.1 + "# + .to_string(), + expected: ApplicationProperties { + server: Server { + address: "127.0.0.1".to_string(), + port: 8080, + }, + etc: HashMap::new(), + environment: "dev".to_string(), + }, + }, + TestCase { + name: "추가 속성 바인딩".to_string(), + input: r#" + server.port=8080 + server.address=127.0.0.1 + foo.bar=hello + "# + .to_string(), + expected: ApplicationProperties { + server: Server { + address: "127.0.0.1".to_string(), + port: 8080, + }, + environment: "dev".to_string(), + etc: HashMap::from([("foo.bar".to_string(), "hello".to_string())]), + }, + }, + TestCase { + name: "따옴표로 감싸기".to_string(), + input: r#" + server.port=8080 + server.address="127.0.0.1" + "# + .to_string(), + expected: ApplicationProperties { + server: Server { + address: "127.0.0.1".to_string(), + port: 8080, + }, + environment: "dev".to_string(), + etc: HashMap::new(), + }, + }, + TestCase { + name: "중간에 띄어쓰기".to_string(), + input: r#" + server.port=8080 + server.address= 127.0.0.1 + "# + .to_string(), + expected: ApplicationProperties { + server: Server { + address: "127.0.0.1".to_string(), + port: 8080, + }, + environment: "dev".to_string(), + etc: HashMap::new(), + }, + }, + TestCase { + name: "포트 파싱 실패".to_string(), + input: r#" + server.port=80#@#@80 + server.address= 127.0.0.1 + "# + .to_string(), + expected: ApplicationProperties { + server: Server { + address: "127.0.0.1".to_string(), + port: 3000, + }, + environment: "dev".to_string(), + etc: HashMap::new(), + }, + }, + TestCase { + name: "environment 바인딩".to_string(), + input: r#" + server.port=80#@#@80 + server.address= 127.0.0.1 + environment=prod + "# + .to_string(), + expected: ApplicationProperties { + server: Server { + address: "127.0.0.1".to_string(), + port: 3000, + }, + environment: "prod".to_string(), + etc: HashMap::new(), + }, + }, + ]; + + for tc in test_cases { + let got = ApplicationProperties::from_properties(tc.input.clone()); + assert_eq!( + got, tc.expected, + "{} - input: {:?}, actual: {:?}", + tc.name, tc.input, got + ); + } + } +} + +// 알아서 모든 대상에 대해 application.properties를 읽어서 ApplicationProperties를 반환하는 함수 +pub fn load_application_properties_from_all() -> ApplicationProperties { + // 1. 현재 경로에 application.properties가 있는지 확인하고, 있다면 읽어서 반환합니다. + if let Ok(text) = std::fs::read_to_string("application.properties") { + return ApplicationProperties::from_properties(text); + } + + ApplicationProperties::default() +} diff --git a/rupring/src/boot/banner.rs b/rupring/src/core/banner.rs similarity index 100% rename from rupring/src/boot/banner.rs rename to rupring/src/core/banner.rs diff --git a/rupring/src/core/boot.rs b/rupring/src/core/boot.rs new file mode 100644 index 0000000..6eab760 --- /dev/null +++ b/rupring/src/core/boot.rs @@ -0,0 +1,26 @@ +use crate::IModule; + +/** shortcut to run the application + +```rust,ignore +use domains::root::module::RootModule; + +pub(crate) mod domains; +pub(crate) mod middlewares; + +fn main() { + rupring::run(RootModule {}) +} +``` +*/ +#[tokio::main] +pub async fn run(root_module: T) +where + T: IModule + Clone + Copy + Sync + Send + 'static, +{ + let app = crate::RupringFactory::create(root_module); + + let port = app.application_properties.server.port; + + app.listen(port).await.unwrap(); +} diff --git a/rupring/src/boot/mod.rs b/rupring/src/core/mod.rs similarity index 99% rename from rupring/src/boot/mod.rs rename to rupring/src/core/mod.rs index 7e78cef..061125d 100644 --- a/rupring/src/boot/mod.rs +++ b/rupring/src/core/mod.rs @@ -1,5 +1,6 @@ mod banner; -pub mod di; +pub mod boot; +use crate::di; mod parse; pub(crate) mod route; diff --git a/rupring/src/boot/parse.rs b/rupring/src/core/parse.rs similarity index 100% rename from rupring/src/boot/parse.rs rename to rupring/src/core/parse.rs diff --git a/rupring/src/boot/route.rs b/rupring/src/core/route.rs similarity index 100% rename from rupring/src/boot/route.rs rename to rupring/src/core/route.rs diff --git a/rupring/src/boot/di.rs b/rupring/src/di/mod.rs similarity index 100% rename from rupring/src/boot/di.rs rename to rupring/src/di/mod.rs diff --git a/rupring/src/lib.rs b/rupring/src/lib.rs index 351b7ac..187fae1 100644 --- a/rupring/src/lib.rs +++ b/rupring/src/lib.rs @@ -24,13 +24,8 @@ pub fn echo(request: rupring::Request) -> rupring::Response { rupring::Response::new().text(request.body) } -#[tokio::main] -async fn main() -> Result<(), Box> { - let app = rupring::RupringFactory::create(RootModule {}); - - app.listen(3000).await?; - - Ok(()) +fn main() { + rupring::run(RootModule {}) } ``` @@ -603,7 +598,9 @@ pub fn get_user(request: rupring::Request, _: rupring::Response) -> rupring::Res ``` */ -pub(crate) mod boot; +pub(crate) mod core; +pub use core::boot::run; +pub(crate) mod di; /// header constants pub mod header; @@ -618,7 +615,10 @@ pub mod response; pub mod swagger; use std::panic::UnwindSafe; +use std::str::FromStr; +use application_properties::load_application_properties_from_all; +use application_properties::ApplicationProperties; /** Controller Annotation ```rust #[rupring::Get(path = /)] @@ -776,9 +776,9 @@ pub type Method = hyper::Method; pub type HeaderName = hyper::header::HeaderName; /// Dependency Injection Context for entire life cycle -pub use boot::di::DIContext; +pub use di::DIContext; /// Dependency Injection Provider -pub use boot::di::IProvider; +pub use di::IProvider; /// String wrapper type for ParamStringDeserializer. pub use request::ParamString; /// ParamStringDeserializer trait @@ -791,6 +791,8 @@ use swagger::json::SwaggerOperation; use swagger::macros::SwaggerRequestBody; use swagger::SwaggerSecurity; +pub mod application_properties; + /// Module interface pub trait IModule { fn child_modules(&self) -> Vec>; @@ -845,6 +847,7 @@ pub type NextFunction = fn(Request, Response) -> Response; #[derive(Debug, Clone)] pub struct RupringFactory { root_module: T, + pub application_properties: ApplicationProperties, } impl RupringFactory { @@ -852,16 +855,21 @@ impl RupringFactory { pub fn create(module: T) -> Self { RupringFactory { root_module: module, + application_properties: load_application_properties_from_all(), } } /// It receives the port number and runs the server. pub async fn listen(self, port: u16) -> Result<(), Box> { - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use std::net::{IpAddr, SocketAddr}; + + let host = self.application_properties.server.address.clone(); + + let ip = IpAddr::from_str(host.as_str())?; - let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port); + let socket_addr = SocketAddr::new(ip, port); - let result = boot::run_server(socket_addr, self.root_module).await; + let result = core::run_server(socket_addr, self.root_module).await; return result; } diff --git a/rupring/src/swagger/context.rs b/rupring/src/swagger/context.rs index 01e708c..2f058e0 100644 --- a/rupring/src/swagger/context.rs +++ b/rupring/src/swagger/context.rs @@ -56,7 +56,7 @@ fn generate_swagger(swagger: &mut SwaggerSchema, root_module: Box Result<(), Box> { - let app = rupring::RupringFactory::create(RootModule {}); - - app.listen(3000).await?; - - Ok(()) +fn main() { + rupring::run(RootModule {}) }