# librsigstopup
> **Safe, precise, and observable top‑up loan calculator** – with JSON API, structured logging, and full traceability.
<p align="center">
<a href="https://crates.io/crates/librsigstopup"><img src="https://img.shields.io/crates/v/librsigstopup.svg?style=flat-square&logo=rust" alt="crates.io"></a>
<a href="https://docs.rs/librsigstopup"><img src="https://img.shields.io/docsrs/librsigstopup?style=flat-square&logo=rust" alt="docs.rs"></a>
<a href="https://github.com/neuxdotdev/librsigstopup/blob/main/LICENSE"><img src="https://img.shields.io/github/license/neuxdotdev/librsigstopup?style=flat-square" alt="license"></a>
<a href="https://github.com/neuxdotdev/librsigstopup/actions"><img src="https://img.shields.io/github/actions/workflow/status/neuxdotdev/librsigstopup/ci.yml?branch=main&style=flat-square&logo=github" alt="CI"></a>
<a href="https://rust-lang.org"><img src="https://img.shields.io/badge/rust-1.95+-blue?style=flat-square&logo=rust" alt="Rust"></a>
<a href="https://tracing.rs"><img src="https://img.shields.io/badge/tracing-json-green?style=flat-square&logo=rust" alt="tracing"></a>
<a href="https://docs.rs/rust_decimal"><img src="https://img.shields.io/badge/decimal-precision-orange?style=flat-square" alt="rust_decimal"></a>
<a href="http://makeapullrequest.com"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square" alt="PRs welcome"></a>
</p>
---
## Table of Contents
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Library API](#library-api)
- [`TopUpHandler`](#topuphandler)
- [`CalculatorConfig`](#calculatorconfig)
- [`LoanInput` & `LoanResult`](#loaninput--loanresult)
- [`CalculationError` & `ApiError`](#calculationerror--apierror)
- [Configuration](#configuration)
- [Builder Pattern](#builder-pattern)
- [Serialization](#serialization)
- [Error Handling](#error-handling)
- [Logging & Tracing](#logging--tracing)
- [JSON API](#json-api)
- [Development](#development)
- [Project Structure](#project-structure)
- [Scripts](#scripts)
- [Contributing](#contributing)
- [License](#license)
---
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
librsigstopup = "0.1.0"
tokio = { version = "1", features = ["rt"] } # if you need async runtime
```
**Rust version requirement:** 1.95 or later.
---
## Quick Start
### Basic synchronous usage (async wrapper)
```rust
use librsigstopup::{TopUpHandler, LoanInput};
#[tokio::main]
async fn main() {
let handler = TopUpHandler::new();
let input = LoanInput::builder()
.pinjaman(10_000_000.0) // loan amount
.angsuran(500_000.0) // monthly installment
.sisa_tenor(5) // remaining months
.diskon(200_000.0) // discount
.pajak(150_000.0) // tax (will be added to admin fee)
.build()
.unwrap();
let result = handler.calculate(input).await.unwrap();
println!("Total diterima nasabah: Rp {}", result.total_didapat);
// Output: Total diterima nasabah: Rp 7280000.00
}
```
### Using JSON input
```rust
let json = r#"{
"pinjaman": 10000000,
"angsuran": 500000,
"sisa_tenor": 5,
"diskon": 200000,
"pajak": 150000
}"#;
let response = handler.calculate_from_json(json).await.unwrap();
println!("{}", response);
```
### Custom configuration
```rust
use librsigstopup::{CalculatorConfig, TopUpHandler};
let config = CalculatorConfig::builder()
.insurance_percentage(2.0) // 2% insurance
.admin_fee(100_000.0) // Rp 100k admin fee
.enable_logging(true)
.log_level("debug")
.build();
let handler = TopUpHandler::new().with_config(config);
```
---
## Library API
### `TopUpHandler`
Main entry point for calculations.
```rust
pub struct TopUpHandler { /* ... */ }
impl TopUpHandler {
pub fn new() -> Self;
pub fn init_verbose() -> Self; // enables verbose JSON logging
pub fn with_config(self, config: CalculatorConfig) -> Self;
pub async fn calculate(&self, input: LoanInput) -> Result<LoanResult, CalculationError>;
pub async fn calculate_from_json(&self, json: &str) -> Result<String, CalculationError>;
pub async fn health_check(&self) -> serde_json::Value;
}
```
### `CalculatorConfig`
Controls insurance percentage, admin fee, and logging behaviour.
| `insurance` | `InsuranceConfig` | `percentage: 0.015` (1.5%) | Insurance rate applied to principal. |
| `tax` | `TaxConfig` | `admin_fee: 120_000` | Fixed administrative fee. |
| `enable_logging` | `bool` | `true` | Enable structured JSON logs. |
| `log_level` | `String` | `"info"` | Log level filter. |
Use the **builder** to create a custom config:
```rust
let config = CalculatorConfig::builder()
.insurance_percentage(2.5) // 2.5% (converted to 0.025 internally)
.admin_fee(150_000.0)
.enable_logging(false)
.log_level("warn")
.build();
```
### `LoanInput` & `LoanResult`
**LoanInput** – required fields (all `Decimal` except `sisa_tenor`):
| `pinjaman` | `Decimal` | Loan principal (must be >0) |
| `angsuran` | `Decimal` | Monthly installment (≥0) |
| `sisa_tenor` | `u32` | Remaining months |
| `diskon` | `Decimal` | Discount (default 0) |
| `pajak` | `Decimal` | Input tax (default 0) |
Builder with fallible `build()`:
```rust
let input = LoanInput::builder()
.pinjaman(10_000_000.0)
.angsuran(500_000.0)
.sisa_tenor(5)
.build()?; // returns Result<LoanInput, ValidationError>
```
**LoanResult** contains:
- `total_didapat` – final amount the customer receives.
- `calculations` – intermediate values (`sisa_pokok`, `asuransi`, `pajak`, etc.)
- `metadata` – calculation time, formula, warnings.
### `CalculationError` & `ApiError`
All errors implement `std::error::Error` and serialise to JSON.
```rust
pub enum CalculationError {
ValidationError { field: String, message: String },
InvalidInput(String),
CalculationFailed(String),
JsonParseError(String),
ConfigError(String),
InternalError(String),
}
```
Convert to user‑friendly `ApiError`:
```rust
let api_err: ApiError = calculation_error.into();
println!("{}", api_err.error.code); // e.g. "VALIDATION_ERROR"
```
---
## Configuration
### Builder Pattern
All configuration uses a fluent builder with validation.
```rust
let config = CalculatorConfig::builder()
.insurance_percentage(1.75) // 1.75%
.admin_fee(125_000.0) // Rp 125,000
.enable_logging(true)
.log_level("debug")
.build();
```
Missing fields fall back to defaults. Percentages are divided by 100 internally (2.5 → 0.025).
### Serialization
`CalculatorConfig` implements `Serialize`/`Deserialize` – perfect for loading from file or environment.
```rust
let json = r#"{
"insurance": { "percentage": "0.02" },
"tax": { "base_amount": "0", "admin_fee": "150000" },
"enable_logging": false,
"log_level": "error"
}"#;
let config: CalculatorConfig = serde_json::from_str(json).unwrap();
```
---
## Error Handling
The library distinguishes between recoverable and unrecoverable errors.
```rust
match handler.calculate(input).await {
Ok(result) => println!("Success: {}", result.total_didapat),
Err(CalculationError::ValidationError { field, message }) => {
eprintln!("Input error in {}: {}", field, message);
}
Err(CalculationError::JsonParseError(e)) => {
eprintln!("Invalid JSON: {}", e);
}
Err(e) => eprintln!("Unexpected error: {}", e),
}
```
All errors provide `.to_json()` for structured logging.
---
## Logging & Tracing
The library uses the [`tracing`] crate with **JSON formatting**. Call `init_logging()` once at startup:
```rust
librsigstopup::init_logging();
```
Every calculation emits structured events:
- `calculation_started` – includes `request_id` and input.
- `calculation_completed` – includes total, duration, result.
- `input_validated`, `calculate_insurance`, `calculate_tax`, etc.
Set the log level via `RUST_LOG` environment variable:
```bash
RUST_LOG=debug cargo run
```
Disable logging via `CalculatorConfig::builder().enable_logging(false).build()`.
---
## JSON API
The library exposes a JSON‑friendly interface for easy integration.
**Request** (`CalculationRequest`):
```json
{
"pinjaman": 10000000,
"angsuran": 500000,
"sisa_tenor": 5,
"diskon": 200000,
"pajak": 150000
}
```
**Response** (`CalculationResponse`):
```json
{
"success": true,
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2025-04-20T12:34:56Z",
"data": {
"input": {
"pinjaman": "10000000",
"angsuran": "500000",
"sisa_tenor": 5,
"diskon": "200000",
"pajak": "150000"
},
"calculations": {
"sisa_pokok": "7500000",
"asuransi": "150000",
"pajak": "270000",
"diskon": "200000"
},
"total_didapat": "7280000.00"
},
"metadata": {
"calculation_time_ms": 42,
"formula_used": "(E4-(E5*E6))-E8-E9+E7",
"warnings": []
}
}
```
Health check endpoint:
```rust
let health = handler.health_check().await;
// { "status": "healthy", "timestamp": "...", "version": "0.1.0", "config": {...} }
```
---
## Development
```bash
git clone https://github.com/neuxdotdev/librsigstopup.git
cd librsigstopup
cargo build
cargo test
```
### Project Structure
```
.
├── Cargo.toml
├── src/
│ ├── core/ # Pure calculation logic
│ │ ├── loan.rs # LoanInput, builder, validation
│ │ ├── insurance_or_cp.rs
│ │ ├── tax.rs
│ │ ├── remaining_installments.rs
│ │ ├── total.rs
│ │ └── ...
│ ├── handler/ # API handlers, config, errors
│ │ ├── config.rs # CalculatorConfig & builder
│ │ ├── error.rs # Error types & conversions
│ │ ├── types.rs # Request/response DTOs
│ │ └── mod.rs # TopUpHandler implementation
│ └── lib.rs # Public exports & logging init
├── tests/ # Integration tests
├── benches/ # Benchmarks (Criterion)
└── examples/ # Usage examples
```
### Features
- **`json-logging`** (default) – JSON log output.
- **`verbose`** – Enable verbose tracing (more spans).
- **`android`** – Support Android logging via `android_logger`.
- **`ffi`** – Build C‑compatible static library (see `crate-type = ["staticlib"]`).
---
## Contributing
1. Fork the repository.
2. Create a feature branch (`git checkout -b feat/your-feature`).
3. Commit changes with clear messages.
4. Run `cargo test` and `cargo clippy` to ensure quality.
5. Push and open a Pull Request.
All contributions are welcome – bug reports, feature requests, documentation improvements.
---
## License
**MIT License** – see [LICENSE](https://github.com/neuxdotdev/librsigstopup/blob/main/LICENSE) for details.
---
## Acknowledgements
- Built on [`rust_decimal`] for precise financial arithmetic.
- Structured logging with [`tracing`] and `tracing-subscriber`.
- Inspired by real‑world top‑up loan workflows in Indonesian fintech.
---
> **Repository**: https://github.com/neuxdotdev/librsigstopup
> **Crates.io**: https://crates.io/crates/librsigstopup