Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#108] application.properties 구현 #141

Merged
merged 8 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading