Safe, precise, and observable top‑up loan calculator – with JSON API, structured logging, and full traceability.
- Installation
- Quick Start
- Library API
- Configuration
- Error Handling
- Logging & Tracing
- JSON API
- Development
- Contributing
- License
Add this to your Cargo.toml:
[dependencies]
librsigstopup = "0.1.0"
tokio = { version = "1", features = ["rt"] } # if you need async runtimeRust version requirement: 1.95 or later.
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
}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);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);Main entry point for calculations.
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;
}Controls insurance percentage, admin fee, and logging behaviour.
| Field | Type | Default | Description |
|---|---|---|---|
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:
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 – required fields (all Decimal except sisa_tenor):
| Field | Type | Description |
|---|---|---|
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():
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.
All errors implement std::error::Error and serialise to JSON.
pub enum CalculationError {
ValidationError { field: String, message: String },
InvalidInput(String),
CalculationFailed(String),
JsonParseError(String),
ConfigError(String),
InternalError(String),
}Convert to user‑friendly ApiError:
let api_err: ApiError = calculation_error.into();
println!("{}", api_err.error.code); // e.g. "VALIDATION_ERROR"All configuration uses a fluent builder with validation.
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).
CalculatorConfig implements Serialize/Deserialize – perfect for loading from file or environment.
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();The library distinguishes between recoverable and unrecoverable errors.
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.
The library uses the [tracing] crate with JSON formatting. Call init_logging() once at startup:
librsigstopup::init_logging();Every calculation emits structured events:
calculation_started– includesrequest_idand input.calculation_completed– includes total, duration, result.input_validated,calculate_insurance,calculate_tax, etc.
Set the log level via RUST_LOG environment variable:
RUST_LOG=debug cargo runDisable logging via CalculatorConfig::builder().enable_logging(false).build().
The library exposes a JSON‑friendly interface for easy integration.
Request (CalculationRequest):
{
"pinjaman": 10000000,
"angsuran": 500000,
"sisa_tenor": 5,
"diskon": 200000,
"pajak": 150000
}Response (CalculationResponse):
{
"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:
let health = handler.health_check().await;
// { "status": "healthy", "timestamp": "...", "version": "0.1.0", "config": {...} }git clone https://github.com/neuxdotdev/librsigstopup.git
cd librsigstopup
cargo build
cargo test.
├── 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
json-logging(default) – JSON log output.verbose– Enable verbose tracing (more spans).android– Support Android logging viaandroid_logger.ffi– Build C‑compatible static library (seecrate-type = ["staticlib"]).
- Fork the repository.
- Create a feature branch (
git checkout -b feat/your-feature). - Commit changes with clear messages.
- Run
cargo testandcargo clippyto ensure quality. - Push and open a Pull Request.
All contributions are welcome – bug reports, feature requests, documentation improvements.
MIT License – see LICENSE for details.
- Built on [
rust_decimal] for precise financial arithmetic. - Structured logging with [
tracing] andtracing-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