rustledger_core/lib.rs
1//! Core types for rustledger
2//!
3//! This crate provides the fundamental types used throughout the rustledger project:
4//!
5//! - [`Amount`] - A decimal number with a currency
6//! - [`Cost`] - Acquisition cost of a position (lot)
7//! - [`CostSpec`] - Specification for matching or creating costs
8//! - [`Position`] - Units held at a cost
9//! - [`Inventory`] - A collection of positions with booking support
10//! - [`BookingMethod`] - How to match lots when reducing positions
11//! - [`Directive`] - All directive types (Transaction, Balance, Open, etc.)
12//!
13//! # Example
14//!
15//! ```
16//! use rustledger_core::{Amount, Cost, Position, Inventory, BookingMethod};
17//! use rust_decimal_macros::dec;
18//!
19//! // Create an inventory
20//! let mut inv = Inventory::new();
21//!
22//! // Add a stock position with cost
23//! let cost = Cost::new(dec!(150.00), "USD")
24//! .with_date(rustledger_core::naive_date(2024, 1, 15).unwrap());
25//! inv.add(Position::with_cost(Amount::new(dec!(10), "AAPL"), cost));
26//!
27//! // Check holdings
28//! assert_eq!(inv.units("AAPL"), dec!(10));
29//!
30//! // Sell some shares using FIFO
31//! let result = inv.reduce(
32//! &Amount::new(dec!(-5), "AAPL"),
33//! None,
34//! BookingMethod::Fifo,
35//! ).unwrap();
36//!
37//! assert_eq!(inv.units("AAPL"), dec!(5));
38//! assert_eq!(result.cost_basis.unwrap().number, dec!(750.00)); // 5 * 150
39//! ```
40
41#![forbid(unsafe_code)]
42#![warn(missing_docs)]
43
44pub mod amount;
45pub mod cost;
46pub mod directive;
47pub mod display_context;
48pub mod extract;
49pub mod format;
50pub mod implicit_prices;
51pub mod intern;
52pub mod inventory;
53pub mod position;
54pub mod synthetic;
55
56// Kani formal verification proofs (only compiled with Kani)
57#[cfg(kani)]
58mod kani_proofs;
59
60pub use amount::{Amount, IncompleteAmount};
61pub use cost::{Cost, CostSpec};
62pub use directive::{
63 Balance, Close, Commodity, Custom, Directive, DirectivePriority, Document, Event, MetaValue,
64 Metadata, Note, Open, Pad, Posting, Price, PriceAnnotation, Query, Transaction,
65 parse_precision_meta, sort_directives,
66};
67pub use display_context::{DEFAULT_CURRENCY, DisplayContext, Precision};
68pub use extract::{
69 DEFAULT_CURRENCIES, extract_accounts, extract_accounts_iter, extract_currencies,
70 extract_currencies_iter, extract_payees, extract_payees_iter,
71};
72pub use format::{FormatConfig, format_directive};
73pub use implicit_prices::extract_per_unit_price;
74pub use intern::{InternedStr, StringInterner};
75pub use inventory::{
76 AccountedBookingError, BookingError, BookingMethod, BookingResult, Inventory, ReductionScope,
77};
78pub use position::Position;
79
80// Re-export commonly used external types
81/// Calendar date without timezone. Alias for `jiff::civil::Date`.
82pub type NaiveDate = jiff::civil::Date;
83pub use rust_decimal::Decimal;
84
85/// Construct a [`NaiveDate`] from `(year, month, day)` with i32/u32 arguments.
86///
87/// Wraps [`jiff::civil::date`] which takes `(i16, i8, i8)`.
88/// Returns `None` if the date is invalid.
89#[must_use]
90pub fn naive_date(year: i32, month: u32, day: u32) -> Option<NaiveDate> {
91 i16::try_from(year)
92 .ok()
93 .and_then(|y| i8::try_from(month).ok().map(|m| (y, m)))
94 .and_then(|(y, m)| i8::try_from(day).ok().map(|d| (y, m, d)))
95 .and_then(|(y, m, d)| NaiveDate::new(y, m, d).ok())
96}
97
98// Re-export rkyv wrappers when feature is enabled
99#[cfg(feature = "rkyv")]
100pub use intern::{AsDecimal, AsInternedStr, AsNaiveDate};