librsigstopup 0.1.0

Super safe library untuk simulasi perhitungan top-up dengan JSON API, verbose logging, dan full trace
Documentation

librsigstopup

Safe, precise, and observable top‑up loan calculator – with JSON API, structured logging, and full traceability.


Table of Contents


Installation

Add this to your Cargo.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)

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

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

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.

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.

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 & LoanResult

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.

CalculationError & ApiError

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"

Configuration

Builder Pattern

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).

Serialization

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();

Error Handling

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.


Logging & Tracing

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 – 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:

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):

{
  "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": {...} }

Development

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 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