# primitives module
Feature flag: `primitives`
```toml
[dependencies]
arvo = { version = "1.0", features = ["primitives"] }
```
---
## NonEmptyString
A non-empty, trimmed string.
**Normalisation:** surrounding whitespace trimmed.
**Validation:** must not be empty after trimming.
```rust,ignore
use arvo::primitives::NonEmptyString;
use arvo::traits::{PrimitiveValue, ValueObject};
let s = NonEmptyString::new(" hello ".into())?;
assert_eq!(s.value(), "hello");
let s: NonEmptyString = "world".try_into()?;
```
### Accessors
| `value()` | `&String` | `"hello"` |
| `into_inner()` | `String` | `"hello"` |
### Errors
| `""` | `ValidationError::Empty` |
| `" "` | `ValidationError::Empty` |
---
## BoundedString
A string whose length (in Unicode characters) is constrained at the type level.
**Normalisation:** surrounding whitespace trimmed.
**Validation:** character count (not byte count) must be `>= MIN` and `<= MAX`.
```rust,ignore
use arvo::primitives::BoundedString;
use arvo::traits::{PrimitiveValue, ValueObject};
type Username = BoundedString<3, 32>;
let name = Username::new("Alice".into())?;
assert_eq!(name.value(), "Alice");
assert!(Username::new("Al".into()).is_err()); // too short
```
### Accessors
| `value()` | `&String` | `"Alice"` |
| `into_inner()` | `String` | `"Alice"` |
### Errors
| value shorter than `MIN` | `ValidationError::OutOfRange` |
| value longer than `MAX` | `ValidationError::OutOfRange` |
---
## PositiveInt
A strictly positive integer (`i64 > 0`).
**Normalisation:** none.
**Validation:** value must be `> 0`. Zero and negative values are rejected.
```rust,ignore
use arvo::primitives::PositiveInt;
use arvo::traits::{PrimitiveValue, ValueObject};
let n = PositiveInt::new(42)?;
assert_eq!(*n.value(), 42);
assert!(PositiveInt::new(0).is_err());
```
### Accessors
| `value()` | `&i64` | `42` |
| `into_inner()` | `i64` | `42` |
### Errors
| `0` | `ValidationError::OutOfRange` |
| `-1` | `ValidationError::OutOfRange` |
---
## NonNegativeInt
A non-negative integer (`i64 >= 0`).
**Normalisation:** none.
**Validation:** value must be `>= 0`. Negative values are rejected.
```rust,ignore
use arvo::primitives::NonNegativeInt;
use arvo::traits::{PrimitiveValue, ValueObject};
let n = NonNegativeInt::new(0)?;
assert_eq!(*n.value(), 0);
assert!(NonNegativeInt::new(-1).is_err());
```
### Accessors
| `value()` | `&i64` | `0` |
| `into_inner()` | `i64` | `0` |
### Errors
| `-1` | `ValidationError::OutOfRange` |
---
## PositiveDecimal
A strictly positive decimal (`rust_decimal::Decimal > 0`).
**Normalisation:** none.
**Validation:** value must be `> Decimal::ZERO`.
```rust,ignore
use arvo::primitives::PositiveDecimal;
use arvo::traits::{PrimitiveValue, ValueObject};
use rust_decimal::Decimal;
use std::str::FromStr;
let price = PositiveDecimal::new(Decimal::from_str("9.99").unwrap())?;
assert_eq!(price.value(), &Decimal::from_str("9.99").unwrap());
```
### Accessors
| `value()` | `&Decimal` | `9.99` |
| `into_inner()` | `Decimal` | `9.99` |
### Errors
| `Decimal::ZERO` | `ValidationError::OutOfRange` |
| negative value | `ValidationError::OutOfRange` |
---
## NonNegativeDecimal
A non-negative decimal (`rust_decimal::Decimal >= 0`).
**Normalisation:** none.
**Validation:** value must be `>= Decimal::ZERO`.
```rust,ignore
use arvo::primitives::NonNegativeDecimal;
use arvo::traits::{PrimitiveValue, ValueObject};
use rust_decimal::Decimal;
let amount = NonNegativeDecimal::new(Decimal::ZERO)?;
assert_eq!(amount.value(), &Decimal::ZERO);
```
### Accessors
| `value()` | `&Decimal` | `0` |
| `into_inner()` | `Decimal` | `0` |
### Errors
| negative value | `ValidationError::OutOfRange` |
---
## Probability
A probability value in the closed interval `[0.0, 1.0]`.
**Normalisation:** none.
**Validation:** must be finite and in `0.0..=1.0`. NaN and infinity are rejected.
```rust,ignore
use arvo::primitives::Probability;
use arvo::traits::{PrimitiveValue, ValueObject};
let p = Probability::new(0.75)?;
assert_eq!(*p.value(), 0.75);
assert!(Probability::new(1.5).is_err());
assert!(Probability::new(f64::NAN).is_err());
```
### Accessors
| `value()` | `&f64` | `0.75` |
| `into_inner()` | `f64` | `0.75` |
### Errors
| `1.5` | `ValidationError::OutOfRange` |
| `-0.1` | `ValidationError::OutOfRange` |
| `NaN` | `ValidationError::OutOfRange` |
| `∞` | `ValidationError::OutOfRange` |
---
## HexColor
A CSS hex color in canonical `#RRGGBB` form.
**Normalisation:** trimmed; uppercased; 3-digit shorthand expanded to 6-digit form (`#F0A` → `#FF00AA`).
**Validation:** must start with `#`; remaining chars must be exactly 3 or 6 hexadecimal digits.
```rust,ignore
use arvo::primitives::HexColor;
use arvo::traits::{PrimitiveValue, ValueObject};
let red = HexColor::new("#f00".into())?;
assert_eq!(red.value(), "#FF0000");
assert_eq!(red.r(), 255);
assert_eq!(red.g(), 0);
assert_eq!(red.b(), 0);
let c: HexColor = "#1A2B3C".try_into()?;
```
### Accessors
| `value()` | `&String` | `"#FF0000"` |
| `r()` | `u8` | `255` |
| `g()` | `u8` | `0` |
| `b()` | `u8` | `0` |
| `to_rgb()` | `(u8, u8, u8)` | `(255, 0, 0)` |
| `into_inner()` | `String` | `"#FF0000"` |
### Errors
| `""` | `ValidationError::Empty` |
| `"FF0000"` | `ValidationError::InvalidFormat` (missing `#`) |
| `"#GGGGGG"` | `ValidationError::InvalidFormat` |
| `"#FFFF"` | `ValidationError::InvalidFormat` (wrong length) |
---
## Locale
A BCP 47 language tag (e.g. `"en-US"`, `"cs-CZ"`, `"fr"`).
**Normalisation:** trimmed; `_` separator normalised to `-`; language subtag lowercased; region subtag uppercased.
**Validation (MVP):** language subtag must be 2–3 ASCII letters; optional region subtag must be 2 ASCII letters or 3 digits.
```rust,ignore
use arvo::primitives::Locale;
use arvo::traits::{PrimitiveValue, ValueObject};
let locale = Locale::new("en_us".into())?;
assert_eq!(locale.value(), "en-US");
let fr: Locale = "fr".try_into()?;
assert_eq!(fr.value(), "fr");
```
### Accessors
| `value()` | `&String` | `"en-US"` |
| `language()` | `&str` | `"en"` (language subtag) |
| `region()` | `Option<&str>` | `Some("US")` / `None` for language-only tags |
| `into_inner()` | `String` | `"en-US"` |
### Errors
| `""` | `ValidationError::Empty` |
| `"e"` | `ValidationError::InvalidFormat` (language too short) |
| `"engl"` | `ValidationError::InvalidFormat` (language too long) |
| `"en-X1"` | `ValidationError::InvalidFormat` (invalid region) |
---
## Base64String
A validated standard Base64-encoded string.
**Normalisation:** surrounding whitespace trimmed.
**Validation:** must decode successfully using the standard Base64 alphabet (`A–Z`, `a–z`, `0–9`, `+`, `/`) with correct `=` padding.
```rust,ignore
use arvo::primitives::Base64String;
use arvo::traits::{PrimitiveValue, ValueObject};
let b = Base64String::new("aGVsbG8=".into())?;
assert_eq!(b.decode(), b"hello");
let b: Base64String = "aGVsbG8=".try_into()?;
```
### Accessors
| `value()` | `&String` | `"aGVsbG8="` |
| `decode()` | `Vec<u8>` | `[104, 101, 108, 108, 111]` |
| `into_inner()` | `String` | `"aGVsbG8="` |
### Errors
| `""` | `ValidationError::Empty` |
| `"not!!valid"` | `ValidationError::InvalidFormat` |
| `"aGVsbG8"` | `ValidationError::InvalidFormat` (invalid padding) |