Skip to content

Commit

Permalink
Merge pull request #5 from nao1215/nchika/server-mode
Browse files Browse the repository at this point in the history
Add server mode
  • Loading branch information
nao1215 authored Jul 20, 2024
2 parents e43ca05 + 133384a commit 6c74d3e
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 52 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Build
- name: Test
run: cargo build --verbose
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ clap = "4.5.9"
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.120"
plotters = "0.3.4"
actix-web = "4.8.0"
45 changes: 43 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[![Unit tests](https://github.com/nao1215/cic/actions/workflows/test.yml/badge.svg)](https://github.com/nao1215/cic/actions/workflows/test.yml)

# cic - compound interest calculator
The cic command calculates compound interest. The results of the calculation are output as either a bar graph (`plot.png`) or in JSON format.
The cic command calculates compound interest. The results of the calculation are output as either a bar graph (`plot.png`) or in JSON format. The cic command has a mode for starting as a server.

The cis calculates the total final amount of the investment based on the input values for the principal, monthly contribution, annual interest rate, and the number of years of contribution.

Expand All @@ -22,7 +22,11 @@ $ cic --help
cis - Calculates Compound Interest.
Output the results of compound interest calculations as either a line graph image or JSON.

Usage: cic [OPTIONS]
Usage: cic [OPTIONS] [COMMAND]

Commands:
server Starts the server mode
help Print this message or the help of the given subcommand(s)

Options:
-p, --principal <PRINCIPAL> The principal at the time you started investing. Defaults to 0
Expand Down Expand Up @@ -95,6 +99,43 @@ $ ./target/debug/cic --principal 1000000 --contribution 100000 --rate 10 --years
]
```

### Sever mode

```shell
$ cic server
Starting server, port: 8080
POST /compound-interests
```

```shell
$ curl -X POST "http://localhost:8080/compound-interests" \
-H "Content-Type: application/json" \
-d '{"principal": 1000000, "contribution": 100000, "rate": 10, "years": 2}' | jq .
[
{
"annual_contribution": 1200000.0,
"annual_interest": 100000.0,
"principal": 1000000.0,
"total_amount": 2300000.0,
"total_contribution": 1200000.0,
"total_interest": 100000.0,
"year": 1
},
{
"annual_contribution": 1200000.0,
"annual_interest": 230000.0,
"principal": 1000000.0,
"total_amount": 3730000.0,
"total_contribution": 2400000.0,
"total_interest": 330000.0,
"year": 2
}
]
```




## License
MIT

Expand Down
78 changes: 78 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use clap::{Arg, ArgMatches, Command};

/// Builds the CLI command structure for the Compound Interest Calculator.
///
/// This function defines the main command and its arguments, as well as a subcommand for server mode.
///
/// # Returns
///
/// A `Command` instance configured with the necessary arguments and subcommands.
pub fn build_cli() -> Command {
Command::new("Compound Interest Calculator")
.about("cis - Calculates Compound Interest.\nOutput the results of compound interest calculations as either a line graph image or JSON.")
.arg(
Arg::new("principal")
.short('p')
.long("principal")
.value_name("PRINCIPAL")
.help("The principal at the time you started investing. Defaults to 0"),
)
.arg(
Arg::new("contribution")
.short('c')
.long("contribution")
.value_name("CONTRIBUTION")
.help("The monthly contribution amount. Defaults to 1"),
)
.arg(
Arg::new("rate")
.short('r')
.long("rate")
.value_name("RATE")
.help("The annual interest rate (in %). Defaults to 5"),
)
.arg(
Arg::new("years")
.short('y')
.long("years")
.value_name("YEARS")
.help("The number of years for contributions. Defaults to 5"),
)
.arg(
Arg::new("json")
.short('j')
.long("json")
.help("Output as JSON. Defaults to false")
.action(clap::ArgAction::SetTrue),
)
.subcommand(
Command::new("server")
.about("Starts the server mode")
.arg(
Arg::new("port")
.short('p')
.long("port")
.value_name("PORT")
.help("The port to run the server on. Defaults to 8080"),
),
)
}

/// Retrieves the port number from the CLI matches.
///
/// This function extracts and parses the port number from the subcommand matches. If no port is
/// specified, it returns the default port 8080.
///
/// # Arguments
///
/// * `matches` - The `ArgMatches` instance containing the parsed CLI arguments and subcommands.
///
/// # Returns
///
/// The port number as a `u16`.
pub fn get_port(matches: &ArgMatches) -> u16 {
matches
.get_one::<String>("port")
.and_then(|s| s.parse().ok())
.unwrap_or(8080)
}
60 changes: 60 additions & 0 deletions src/calculations.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::server;
use plotters::prelude::*;
use serde::Serialize;

Expand Down Expand Up @@ -59,6 +60,65 @@ impl Investment {
}
}

/// Creates an `Investment` instance from the provided `InvestmentParams`.
///
/// # Arguments
///
/// * `params` - An instance of `InvestmentParams` containing the parameters for the investment.
/// This includes the principal amount, monthly contribution, annual interest rate, and duration in years.
///
/// # Returns
///
/// Returns a `Result<Self, &'static str>`. On success, returns an `Investment` instance initialized with
/// the provided parameters. If any of the values are negative, returns an error with a message indicating
/// that negative values are not allowed.
///
/// # Errors
///
/// Returns an error if any of the following conditions are met:
/// - `params.principal` is less than 0.0
/// - `params.contribution` is less than 0.0
/// - `params.rate` is less than 0.0
/// - `params.years` is less than 0
///
/// # Example
///
/// ```
/// use cic::server::InvestmentParams;
/// use cic::calculations::Investment;
///
/// let params = InvestmentParams {
/// principal: 1000.0,
/// contribution: 100.0,
/// rate: 5.0,
/// years: 10,
/// };
///
/// match Investment::from_params(params) {
/// Ok(investment) => println!("Investment created: {:?}", investment),
/// Err(e) => eprintln!("Error creating investment: {}", e),
/// }
/// ```
///
/// # Panics
///
/// This function does not panic but returns an error if invalid values are provided.
pub fn from_params(params: server::InvestmentParams) -> Result<Self, &'static str> {
if params.principal < 0.0
|| params.contribution < 0.0
|| params.rate < 0.0
|| params.years < 0
{
return Err("Negative values are not allowed");
}
Ok(Self {
principal: params.principal,
contribution: params.contribution,
rate: params.rate,
years: params.years,
})
}

/// Generates a yearly summary of the investment.
///
/// # Returns
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
pub mod args;
pub mod calculations;
pub mod server;
71 changes: 22 additions & 49 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,72 +1,45 @@
mod args;
mod calculations;
mod server;

use calculations::{plot_summary, Investment};
use clap::{Arg, Command};
use serde_json::to_string_pretty;
use std::env;

fn run() {
let matches = Command::new("Compound Interest Calculator")
.about("cis - Calculates Compound Interest.\nOutput the results of compound interest calculations as either a line graph image or JSON.")
.arg(
Arg::new("principal")
.short('p')
.long("principal")
.value_name("PRINCIPAL")
.help("The principal at the time you started investing. Defaults to 0"),
)
.arg(
Arg::new("contribution")
.short('c')
.long("contribution")
.value_name("CONTRIBUTION")
.help("The monthly contribution amount. Defaults to 1"),
)
.arg(
Arg::new("rate")
.short('r')
.long("rate")
.value_name("RATE")
.help("The annual interest rate (in %). Defaults to 5"),
)
.arg(
Arg::new("years")
.short('y')
.long("years")
.value_name("YEARS")
.help("The number of years for contributions. Defaults to 5"),
)
.arg(
Arg::new("json")
.short('j')
.long("json")
.help("Output as JSON. Defaults to false")
.action(clap::ArgAction::SetTrue),
)
.get_matches();
async fn run() -> std::io::Result<()> {
let matches = args::build_cli().get_matches();

let args: Vec<String> = env::args().collect();
if args.len() == 1 {
println!("`cls --help` for usage");
return;
return Ok(());
}

if let Some(matches) = matches.subcommand_matches("server") {
let port = args::get_port(&matches);
if let Err(e) = server::start_server(port).await {
eprintln!("Failed to start server: {}", e);
}
return Ok(());
}

let investment = Investment::from_matches(&matches);
// Display the yearly summary
let summary = investment.yearly_summary();
if matches.get_flag("json") {
match to_string_pretty(&summary) {
Ok(json) => println!("{}", json),
Err(e) => eprintln!("Failed to serialize to JSON: {}", e),
}
} else {
match plot_summary(&summary) {
Ok(_) => (),
Err(e) => eprintln!("Failed to plot summary: {}", e),
}
return Ok(());
}
match plot_summary(&summary) {
Ok(_) => (),
Err(e) => eprintln!("Failed to plot summary: {}", e),
}
Ok(())
}

fn main() {
run();
#[actix_web::main]
async fn main() -> std::io::Result<()> {
run().await
}
Loading

0 comments on commit 6c74d3e

Please sign in to comment.