positive/
lib.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 25/12/24
5******************************************************************************/
6
7//! # Positive
8//!
9//! [![Crates.io](https://img.shields.io/crates/v/positive.svg)](https://crates.io/crates/positive)
10//! [![Documentation](https://docs.rs/positive/badge.svg)](https://docs.rs/positive)
11//! [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
12//!
13//! A type-safe wrapper for guaranteed positive decimal values in Rust.
14//!
15//! ## Overview
16//!
17//! `Positive` is a Rust library that provides a type-safe wrapper around `Decimal` values,
18//! ensuring that the contained value is always non-negative (>= 0). This is particularly
19//! useful in financial applications where negative values would be invalid or meaningless,
20//! such as prices, quantities, volatilities, and other strictly positive metrics.
21//!
22//! ## Features
23//!
24//! - **Type Safety**: Compile-time and runtime guarantees that values are non-negative
25//! - **Decimal Precision**: Built on [`rust_decimal`](https://crates.io/crates/rust_decimal) for accurate financial calculations
26//! - **Rich API**: Comprehensive arithmetic operations, conversions, and mathematical utilities
27//! - **Serde Support**: Full serialization/deserialization support for JSON and other formats
28//! - **Approx Support**: Approximate equality comparisons for floating-point tolerance
29//! - **Checked Operations**: Safe arithmetic operations that return `Result` instead of panicking
30//! - **Optional utoipa Integration**: OpenAPI schema generation support via feature flag
31//!
32//! ## Installation
33//!
34//! Add this to your `Cargo.toml`:
35//!
36//! ```toml
37//! [dependencies]
38//! positive = "0.1"
39//! ```
40//!
41//! To enable OpenAPI schema support:
42//!
43//! ```toml
44//! [dependencies]
45//! positive = { version = "0.1", features = ["utoipa"] }
46//! ```
47//!
48//! ## Quick Start
49//!
50//! ```rust
51//! use positive::{Positive, pos, pos_or_panic};
52//!
53//! // Create a positive value using the macro (returns Result)
54//! let price = pos!(100.50).unwrap();
55//!
56//! // Or use pos_or_panic! for direct value (panics on invalid input)
57//! let price = pos_or_panic!(100.50);
58//!
59//! // Create using the constructor
60//! let quantity = Positive::new(10.0).unwrap();
61//!
62//! // Arithmetic operations
63//! let total = price * quantity;
64//!
65//! // Safe operations that return Result
66//! let discount = pos_or_panic!(5.0);
67//! let final_price = price.checked_sub(&discount).unwrap();
68//!
69//! // Saturating subtraction (returns ZERO instead of negative)
70//! let result = pos_or_panic!(3.0).saturating_sub(&pos_or_panic!(5.0));
71//! assert_eq!(result, Positive::ZERO);
72//! ```
73//!
74//! ## API Overview
75//!
76//! ### Creation
77//!
78//! ```rust
79//! use positive::{Positive, pos, pos_or_panic, spos};
80//! use rust_decimal::Decimal;
81//!
82//! // From f64
83//! let p = Positive::new(5.0).unwrap();
84//!
85//! // From Decimal
86//! let p = Positive::new_decimal(Decimal::ONE).unwrap();
87//!
88//! // Using macros
89//! let p = pos!(5.0);           // Returns Result<Positive, PositiveError>
90//! let p = pos_or_panic!(5.0);  // Panics on invalid input
91//! let p = spos!(5.0);          // Returns Option<Positive>
92//! ```
93//!
94//! ### Constants
95//!
96//! ```rust
97//! use positive::Positive;
98//!
99//! let zero = Positive::ZERO;       // 0
100//! let one = Positive::ONE;         // 1
101//! let two = Positive::TWO;         // 2
102//! let ten = Positive::TEN;         // 10
103//! let hundred = Positive::HUNDRED; // 100
104//! let thousand = Positive::THOUSAND; // 1000
105//! let pi = Positive::PI;           // π
106//! let inf = Positive::INFINITY;    // Maximum value
107//! ```
108//!
109//! ### Conversions
110//!
111//! ```rust
112//! use positive::pos_or_panic;
113//!
114//! let p = pos_or_panic!(5.5);
115//!
116//! let f: f64 = p.to_f64();              // Panics on failure
117//! let f: Option<f64> = p.to_f64_checked(); // Returns None on failure
118//! let f: f64 = p.to_f64_lossy();        // Returns 0.0 on failure
119//! let i: i64 = p.to_i64();              // To signed integer
120//! let u: u64 = p.to_u64();              // To unsigned integer
121//! let d = p.to_dec();                   // To Decimal
122//! ```
123//!
124//! ### Arithmetic Operations
125//!
126//! ```rust
127//! use positive::pos_or_panic;
128//!
129//! let a = pos_or_panic!(10.0);
130//! let b = pos_or_panic!(3.0);
131//!
132//! // Standard operations
133//! let sum = a + b;        // Addition
134//! let diff = a - b;       // Subtraction (panics if result < 0)
135//! let prod = a * b;       // Multiplication
136//! let quot = a / b;       // Division
137//!
138//! // Safe operations
139//! let safe_diff = a.checked_sub(&b);    // Returns Result
140//! let sat_diff = a.saturating_sub(&b);  // Returns ZERO if result < 0
141//! let safe_quot = a.checked_div(&b);    // Returns Result (handles div by zero)
142//! ```
143//!
144//! ### Mathematical Functions
145//!
146//! ```rust
147//! use positive::pos_or_panic;
148//!
149//! let p = pos_or_panic!(16.0);
150//!
151//! let sqrt = p.sqrt();           // Square root
152//! let ln = p.ln();               // Natural logarithm
153//! let log10 = p.log10();         // Base-10 logarithm
154//! let exp = p.exp();             // Exponential (e^x)
155//! let pow = p.pow(pos_or_panic!(2.0));    // Power with Positive exponent
156//! let powi = p.powi(2);          // Integer power
157//! let floor = p.floor();         // Floor
158//! let ceil = p.ceiling();        // Ceiling
159//! let round = p.round();         // Round to nearest integer
160//! let round2 = p.round_to(2);    // Round to 2 decimal places
161//! ```
162//!
163//! ### Utility Methods
164//!
165//! ```rust
166//! use positive::pos_or_panic;
167//!
168//! let p = pos_or_panic!(5.0);
169//!
170//! let is_zero = p.is_zero();                      // Check if zero
171//! let is_mult = p.is_multiple(2.0);               // Check if multiple of value
172//! let clamped = p.clamp(pos_or_panic!(1.0), pos_or_panic!(10.0));   // Clamp between bounds
173//! let min_val = p.min(pos_or_panic!(3.0));                 // Minimum of two values
174//! let max_val = p.max(pos_or_panic!(3.0));                 // Maximum of two values
175//! let formatted = p.format_fixed_places(2);       // Format with fixed decimals
176//! ```
177//!
178//! ## Error Handling
179//!
180//! The library provides `PositiveError` for comprehensive error handling:
181//!
182//! ```rust
183//! use positive::{Positive, PositiveError};
184//!
185//! fn example() -> Result<Positive, PositiveError> {
186//!     let value = Positive::new(-5.0)?;  // Returns Err(OutOfBounds)
187//!     Ok(value)
188//! }
189//! ```
190//!
191//! Error variants include:
192//! - `InvalidValue` - Value cannot be represented as a valid positive decimal
193//! - `ArithmeticError` - Error during mathematical operations
194//! - `ConversionError` - Error when converting between types
195//! - `OutOfBounds` - Value exceeds defined limits
196//! - `InvalidPrecision` - Invalid decimal precision settings
197//!
198//! ## Serialization
199//!
200//! `Positive` implements `Serialize` and `Deserialize`:
201//!
202//! ```rust
203//! use positive::pos_or_panic;
204//!
205//! let p = pos_or_panic!(42.5);
206//! let json = serde_json::to_string(&p).unwrap();  // "42.5"
207//! let parsed: positive::Positive = serde_json::from_str(&json).unwrap();
208//! ```
209//!
210//! ## Use Cases
211//!
212//! - **Financial Applications**: Prices, quantities, fees, rates
213//! - **Scientific Computing**: Physical quantities that cannot be negative
214//! - **Game Development**: Health points, distances, timers
215//! - **Data Validation**: Ensuring input values meet positivity constraints
216//!
217//! ## License
218//!
219//! This project is licensed under the MIT License.
220//!
221
222pub mod error;
223mod positive;
224mod tests;
225pub use error::{PositiveError, PositiveResult};
226pub use positive::*;
227
228/// Re-export rust_decimal for convenience.
229pub use rust_decimal::Decimal;