Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ default = []
contact = ["dep:once_cell", "dep:regex", "dep:url"]
finance = ["dep:rust_decimal", "dep:chrono"]
geo = []
measurement = []
net = ["dep:url"]
identifiers = []
primitives = ["dep:rust_decimal", "dep:base64"]
Expand All @@ -36,6 +37,7 @@ full = [
"finance",
"geo",
"identifiers",
"measurement",
"net",
"primitives",
"temporal",
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ let email: EmailAddress = "user@example.com".try_into()?;
| [docs/contact.md](docs/contact.md) | Reference for all `contact` module types |
| [docs/finance.md](docs/finance.md) | Reference for all `finance` module types |
| [docs/geo.md](docs/geo.md) | Reference for all `geo` module types |
| [docs/measurement.md](docs/measurement.md) | Reference for all `measurement` module types |
| [docs/net.md](docs/net.md) | Reference for all `net` module types |
| [docs/identifiers.md](docs/identifiers.md) | Reference for all `identifiers` module types |
| [docs/primitives.md](docs/primitives.md) | Reference for all `primitives` module types |
Expand All @@ -72,6 +73,7 @@ Enable only the modules you need — unused features add zero dependencies.
| `contact` | `EmailAddress`, `CountryCode`, `PhoneNumber`, `PostalAddress`, `Website` | `once_cell`, `regex`, `url` |
| `finance` | `Money`, `CurrencyCode`, `Iban`, `Bic`, `VatNumber`, `Percentage`, `ExchangeRate`, `CreditCardNumber`, `CardExpiryDate` | `rust_decimal`, `chrono` |
| `geo` | `Latitude`, `Longitude`, `Coordinate`, `BoundingBox`, `TimeZone`, `CountryRegion` | — |
| `measurement` | `Length`, `Weight`, `Temperature`, `Volume`, `Area`, `Speed`, `Pressure`, `Energy`, `Power`, `Frequency` | — |
| `net` | `Url`, `Domain`, `IpV4Address`, `IpV6Address`, `IpAddress`, `Port`, `MacAddress`, `MimeType`, `HttpStatusCode`, `ApiKey` | `url` |
| `identifiers` | `Slug`, `Ean13`, `Ean8`, `Isbn13`, `Isbn10`, `Issn`, `Vin` | — |
| `primitives` | `NonEmptyString`, `BoundedString`, `PositiveInt`, `NonNegativeInt`, `PositiveDecimal`, `NonNegativeDecimal`, `Probability`, `HexColor`, `Locale`, `Base64String` | `rust_decimal`, `base64` |
Expand Down Expand Up @@ -213,7 +215,7 @@ let parsed: EmailAddress = serde_json::from_str(r#""hello@example.com""#)?;
| `temporal` | `UnixTimestamp`, `BirthDate`, `ExpiryDate`, `TimeRange`, `BusinessHours` | 5 | 5 / 5 ✅ |
| `geo` | `Latitude`, `Longitude`, `Coordinate`, `BoundingBox`, `TimeZone`, `CountryRegion` | 6 | 6 / 6 ✅ |
| `net` | `Url`, `Domain`, `IpV4Address`, `IpV6Address`, `IpAddress`, `Port`, `MacAddress`, `MimeType`, `HttpStatusCode`, `ApiKey` | 10 | 10 / 10 ✅ |
| `measurement` | `Length`, `Weight`, `Temperature`, `Speed` ⚠️ needs unit conversion design | 10 | 0 / 10 |
| `measurement` | `Length`, `Weight`, `Temperature`, `Volume`, `Area`, `Speed`, `Pressure`, `Energy`, `Power`, `Frequency` | 10 | 10 / 10 |
| `primitives` | `NonEmptyString`, `BoundedString`, `Locale`, `HexColor` | 10 | 10 / 10 ✅ |

→ Full details and design rationale in [ROADMAP.md](ROADMAP.md)
Expand Down
24 changes: 12 additions & 12 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,16 @@

| Type | Status | Notes |
|---|---|---|
| `Length` | | non-negative `f64` with unit (m, cm, mm, in, ft) |
| `Weight` | | non-negative `f64` with unit (kg, g, lb, oz) |
| `Temperature` | | `f64` with unit (°C, °F, K); Kelvin must be ≥ 0 |
| `Volume` | | non-negative `f64` with unit (l, ml, m³, fl oz) |
| `Area` | | non-negative `f64` with unit (m², cm², ft²) |
| `Speed` | | non-negative `f64` with unit (m/s, km/h, mph) |
| `Pressure` | | non-negative `f64` with unit (Pa, bar, psi) |
| `Energy` | | non-negative `f64` with unit (J, kWh, cal) |
| `Power` | | non-negative `f64` with unit (W, kW, hp) |
| `Frequency` | | positive `f64` with unit (Hz, kHz, MHz) |
| `Length` | | non-negative `f64` with unit (mm, cm, m, km, in, ft) |
| `Weight` | | non-negative `f64` with unit (mg, g, kg, t, oz, lb) |
| `Temperature` | | `f64` with unit (°C, °F, K); validated against absolute zero |
| `Volume` | | non-negative `f64` with unit (ml, l, m³, fl oz, gal) |
| `Area` | | non-negative `f64` with unit (mm², cm², m², km², in², ft², ha) |
| `Speed` | | non-negative `f64` with unit (m/s, km/h, mph, kn) |
| `Pressure` | | non-negative `f64` with unit (Pa, kPa, MPa, bar, psi, atm) |
| `Energy` | | non-negative `f64` with unit (J, kJ, MJ, kWh, cal, kcal) |
| `Power` | | non-negative `f64` with unit (W, kW, MW, hp) |
| `Frequency` | | positive `f64` with unit (Hz, kHz, MHz, GHz) |

---

Expand Down Expand Up @@ -140,6 +140,6 @@
| `temporal` | 5 | 5 | 0 |
| `geo` | 6 | 6 | 0 |
| `net` | 10 | 10 | 0 |
| `measurement` | 10 | 0 | 10 |
| `measurement` | 10 | 10 | 0 |
| `primitives` | 10 | 10 | 0 |
| **Total** | **62** | **52** | **10** |
| **Total** | **62** | **62** | **0** |
163 changes: 163 additions & 0 deletions docs/measurement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# measurement module

Feature flag: `measurement`

```toml
[dependencies]
arvo = { version = "0.9", features = ["measurement"] }
```

All measurement types share the same pattern: `XxxInput { value: f64, unit: XxxUnit }`.
`value()` returns a canonical string like `"75 kg"`. No unit conversion is provided — the unit is metadata.

---

## Length

**Validation:** finite, non-negative. **Units:** `Mm`, `Cm`, `M`, `Km`, `In`, `Ft`.

```rust,ignore
use arvo::measurement::{Length, LengthInput, LengthUnit};
use arvo::traits::ValueObject;

let len = Length::new(LengthInput { value: 1.80, unit: LengthUnit::M })?;
assert_eq!(len.value(), "1.8 m");
assert_eq!(len.amount(), 1.80);
```

| Method | Returns |
|---|---|
| `value()` | `&str` — e.g. `"1.8 m"` |
| `amount()` | `f64` |
| `unit()` | `&LengthUnit` |

---

## Weight

**Validation:** finite, non-negative. **Units:** `Mg`, `G`, `Kg`, `T`, `Oz`, `Lb`.

```rust,ignore
use arvo::measurement::{Weight, WeightInput, WeightUnit};
use arvo::traits::ValueObject;

let w = Weight::new(WeightInput { value: 75.0, unit: WeightUnit::Kg })?;
assert_eq!(w.value(), "75 kg");
```

---

## Temperature

**Validation:** finite; minimum depends on unit — Kelvin ≥ 0, Celsius ≥ −273.15, Fahrenheit ≥ −459.67.
**Units:** `Celsius`, `Fahrenheit`, `Kelvin`.

```rust,ignore
use arvo::measurement::{Temperature, TemperatureInput, TemperatureUnit};
use arvo::traits::ValueObject;

let t = Temperature::new(TemperatureInput { value: 100.0, unit: TemperatureUnit::Celsius })?;
assert_eq!(t.value(), "100 °C");

assert!(Temperature::new(TemperatureInput { value: -274.0, unit: TemperatureUnit::Celsius }).is_err());
```

---

## Volume

**Validation:** finite, non-negative. **Units:** `Ml`, `L`, `M3`, `FlOz`, `Gal`.

```rust,ignore
use arvo::measurement::{Volume, VolumeInput, VolumeUnit};
use arvo::traits::ValueObject;

let v = Volume::new(VolumeInput { value: 1.5, unit: VolumeUnit::L })?;
assert_eq!(v.value(), "1.5 l");
```

---

## Area

**Validation:** finite, non-negative. **Units:** `Mm2`, `Cm2`, `M2`, `Km2`, `In2`, `Ft2`, `Ha`.

```rust,ignore
use arvo::measurement::{Area, AreaInput, AreaUnit};
use arvo::traits::ValueObject;

let a = Area::new(AreaInput { value: 50.0, unit: AreaUnit::M2 })?;
assert_eq!(a.value(), "50 m²");
```

---

## Speed

**Validation:** finite, non-negative. **Units:** `Ms` (m/s), `Kmh` (km/h), `Mph`, `Kn` (knots).

```rust,ignore
use arvo::measurement::{Speed, SpeedInput, SpeedUnit};
use arvo::traits::ValueObject;

let s = Speed::new(SpeedInput { value: 120.0, unit: SpeedUnit::Kmh })?;
assert_eq!(s.value(), "120 km/h");
```

---

## Pressure

**Validation:** finite, non-negative. **Units:** `Pa`, `KPa`, `MPa`, `Bar`, `Psi`, `Atm`.

```rust,ignore
use arvo::measurement::{Pressure, PressureInput, PressureUnit};
use arvo::traits::ValueObject;

let p = Pressure::new(PressureInput { value: 101.325, unit: PressureUnit::KPa })?;
assert_eq!(p.value(), "101.325 kPa");
```

---

## Energy

**Validation:** finite, non-negative. **Units:** `J`, `KJ`, `MJ`, `KWh`, `Cal`, `Kcal`.

```rust,ignore
use arvo::measurement::{Energy, EnergyInput, EnergyUnit};
use arvo::traits::ValueObject;

let e = Energy::new(EnergyInput { value: 500.0, unit: EnergyUnit::Kcal })?;
assert_eq!(e.value(), "500 kcal");
```

---

## Power

**Validation:** finite, non-negative. **Units:** `W`, `KW`, `MW`, `Hp`.

```rust,ignore
use arvo::measurement::{Power, PowerInput, PowerUnit};
use arvo::traits::ValueObject;

let p = Power::new(PowerInput { value: 3.7, unit: PowerUnit::KW })?;
assert_eq!(p.value(), "3.7 kW");
```

---

## Frequency

**Validation:** finite, strictly positive (> 0). **Units:** `Hz`, `KHz`, `MHz`, `GHz`.

```rust,ignore
use arvo::measurement::{Frequency, FrequencyInput, FrequencyUnit};
use arvo::traits::ValueObject;

let f = Frequency::new(FrequencyInput { value: 2.4, unit: FrequencyUnit::GHz })?;
assert_eq!(f.value(), "2.4 GHz");

assert!(Frequency::new(FrequencyInput { value: 0.0, unit: FrequencyUnit::Hz }).is_err());
```
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ pub mod finance;
#[cfg(feature = "geo")]
pub mod geo;

#[cfg(feature = "measurement")]
pub mod measurement;

#[cfg(feature = "net")]
pub mod net;

Expand Down
Loading
Loading