Skip to content

Commit

Permalink
Merge pull request #141 from myyrakle/feat/#108
Browse files Browse the repository at this point in the history
[#108] application.properties 구현
  • Loading branch information
myyrakle authored Sep 2, 2024
2 parents 039e449 + 8ae986f commit 926de90
Show file tree
Hide file tree
Showing 14 changed files with 279 additions and 35 deletions.
14 changes: 4 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
# 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

## Get Started

required dependency list
```toml
rupring = "0.8.2"
tokio = { version = "1", features = ["full"] }
rupring = "0.9.0"
serde = { version="1.0.193", features=["derive"] }
```

Expand All @@ -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<dyn std::error::Error + Send + Sync>> {
let app = rupring::RupringFactory::create(RootModule {});

app.listen(3000).await?;

Ok(())
fn main() {
rupring::run(RootModule {})
}
```

Expand Down
1 change: 1 addition & 0 deletions application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
server.port=8080
2 changes: 1 addition & 1 deletion rupring/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rupring"
version = "0.8.2"
version = "0.9.0"
edition = "2021"
license = "MIT"
authors = ["myyrakle <[email protected]>"]
Expand Down
220 changes: 220 additions & 0 deletions rupring/src/application_properties.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
use std::collections::HashMap;

#[derive(Debug, PartialEq, Clone)]
pub struct ApplicationProperties {
pub server: Server,
pub environment: String,

pub etc: HashMap<String, String>,
}

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::<u16>() {
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()
}
File renamed without changes.
26 changes: 26 additions & 0 deletions rupring/src/core/boot.rs
Original file line number Diff line number Diff line change
@@ -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<T>(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();
}
3 changes: 2 additions & 1 deletion rupring/src/boot/mod.rs → rupring/src/core/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod banner;
pub mod di;
pub mod boot;
use crate::di;
mod parse;
pub(crate) mod route;

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
34 changes: 21 additions & 13 deletions rupring/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn std::error::Error + Send + Sync>> {
let app = rupring::RupringFactory::create(RootModule {});
app.listen(3000).await?;
Ok(())
fn main() {
rupring::run(RootModule {})
}
```
Expand Down Expand Up @@ -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;
Expand All @@ -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 = /)]
Expand Down Expand Up @@ -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
Expand All @@ -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<Box<dyn IModule>>;
Expand Down Expand Up @@ -845,23 +847,29 @@ pub type NextFunction = fn(Request, Response) -> Response;
#[derive(Debug, Clone)]
pub struct RupringFactory<T: IModule> {
root_module: T,
pub application_properties: ApplicationProperties,
}

impl<T: IModule + Clone + Copy + Sync + Send + 'static> RupringFactory<T> {
/// It receives the root module object and creates a factory to run the server.
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<dyn std::error::Error + Send + Sync>> {
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;
}
Expand Down
Loading

0 comments on commit 926de90

Please sign in to comment.