Skip to content

feat!: arvo 1.0 — PrimitiveValue trait, remove SQLx, serde validation#98

Merged
codegresscom merged 15 commits intomainfrom
chore/release-0.9.0
Apr 23, 2026
Merged

feat!: arvo 1.0 — PrimitiveValue trait, remove SQLx, serde validation#98
codegresscom merged 15 commits intomainfrom
chore/release-0.9.0

Conversation

@vhrcgcom
Copy link
Copy Markdown
Collaborator

Summary

  • Split ValueObject trait — extracted value() into a new PrimitiveValue subtrait. Simple newtypes implement both; composite types implement only ValueObject and expose data through dedicated accessors. This correctly models the semantic difference between the two kinds of value objects.
  • Remove SQLx — arvo no longer bundles database integration. Users integrate via value() / into_inner() directly, which also enables multi-column storage for composite types in ORMs (SeaORM, Diesel). Rationale: orphan rules would prevent users from doing this themselves, but keeping SQLx in arvo forced single-column TEXT storage for composites — the opposite of idiomatic ORM usage.
  • Enforce serde validation on deserialization — replaced serde(transparent) with serde(try_from = "T", into = "T") on all simple newtypes so deserialization routes through new() and domain rules are always enforced.
  • Remove XxxOutput type aliases — these were redundant with the concrete primitive types and cluttered the public API.

Breaking changes

What changed Migration
PrimitiveValue subtrait added Implement it alongside ValueObject for any custom simple newtypes
ValueObject::value() moved to PrimitiveValue Change T: ValueObject bounds to T: PrimitiveValue if calling .value() generically
type Output removed from ValueObject Replace <T as ValueObject>::Output with the concrete type
XxxOutput type aliases removed Replace e.g. EmailAddressOutput with String
sql feature removed Use .value() / .into_inner() to bind; implement sqlx traits in your own crate if needed

Test plan

  • All 689 unit tests pass (cargo test --features full,serde)
  • Build clean with no warnings
  • serde_deserialize_validates tests pass for all 62 types
  • No sqlx references remain in source

🤖 Generated with Claude Code

Václav Hrach added 15 commits April 21, 2026 20:21
… bugs

Bug fixes:
- PhoneNumber: calling_code() fallback "+0" replaced with proper error;
  construction now fails for unknown country codes instead of silently
  producing an invalid E.164 number
- Url::host(): strip port from host when URL contains an explicit port
  number (e.g. example.com:8080 → example.com); IPv6 bracket form preserved

New methods:
- TimeRange: contains(DateTime<Utc>), overlaps(TimeRange)
- BusinessHours: is_open_at(NaiveTime)
- BoundingBox: contains(Coordinate)
- BirthDate: is_minor()
- UnixTimestamp: as_datetime() → DateTime<Utc>
- Locale: language(), region()
- Money: add(), sub(), neg() (fulfils ROADMAP "immutable arithmetic helpers")
- IpV4Address: is_loopback(), is_private()
- Port: is_well_known(), is_registered(), is_ephemeral()
- Percentage: as_fraction()
- HexColor: to_rgb()
- CardExpiryDate: months_until()

Docs: all new methods documented in docs/*.md
contact/mod.rs: export PhoneNumberInput and PhoneNumberOutput (PhoneNumberInput
is a struct required to construct PhoneNumber — its absence was a real API gap)

geo/mod.rs: export LatitudeInput/Output, LongitudeInput/Output,
TimeZoneInput/Output, CountryRegionInput/Output (all were defined in source
files but not re-exported from the module)

net/mod.rs: export Input/Output type aliases for all 10 net types
(Url, Domain, IpV4Address, IpV6Address, IpAddress, Port, MacAddress,
MimeType, HttpStatusCode, ApiKey)

lib.rs: expand prelude to cover all 8 domain modules (finance, geo,
temporal, net, measurement, contact composites) so that
`use arvo::prelude::*` actually brings all types into scope;
fix feature flag list in crate-level docstring
CountryCodeInput/Output, EmailAddressInput/Output, PostalAddressOutput,
and WebsiteOutput were defined in their source files but not re-exported
from contact/mod.rs, making them inaccessible to crate consumers.
Every Input/Output type alias and Input struct now re-exported via
prelude so that `use arvo::prelude::*` brings the complete public API
into scope — consistent across all 8 feature modules.
Every VO now implements TryFrom for its Input type, enabling ergonomic
construction with the ? operator and consistent with the existing
TryFrom<&str> pattern already present in some types.

Implemented conversions by Input type:
- TryFrom<&str>: CreditCardNumber, ApiKey
- TryFrom<f64>: Latitude, Longitude, Percentage, Probability
- TryFrom<i64>: PositiveInt, NonNegativeInt, UnixTimestamp
- TryFrom<u16>: Port, HttpStatusCode
- TryFrom<Decimal>: PositiveDecimal, NonNegativeDecimal
- TryFrom<NaiveDate>: BirthDate, ExpiryDate
- TryFrom<*Input struct>: PhoneNumber, PostalAddress, Money, ExchangeRate,
  Coordinate, BoundingBox, BusinessHours, TimeRange, Length, Weight,
  Temperature, Volume, Area, Energy, Frequency, Power, Pressure, Speed

BoundedString already had TryFrom<&str> (with const generics).
Replace redundant TryFrom<InputType> wrappers (which just delegated to
new()) with TryFrom<&str> implementations that parse the canonical string
representation into the input type before calling Self::new(). Phone and
postal address intentionally have no TryFrom due to parsing ambiguity.
Cover happy path, invalid format, and invalid value for all 29 types
that received TryFrom<&str> implementations.
All Value Objects now implement sqlx::Type + Encode + Decode for Postgres
via the opt-in `sql` feature. Simple newtypes use transparent mapping to
their native DB type; composite types (Money, Coordinate, measurements,
etc.) round-trip as TEXT via their canonical string and TryFrom<&str>.
Port and HttpStatusCode map to INT4. PhoneNumber and PostalAddress are
intentionally excluded (canonical string is not reversible).

README gains a "Parsing from strings" section documenting TryFrom<&str>
and a "SQL support" section with storage mapping table and usage example.
Replace serde(transparent) with serde(try_from = "T", into = "T") on all
simple newtypes so that deserialization goes through new() and domain
validation is enforced. Add TryFrom<T> and From<VO> for T impls for each
inner type category (String, f64, i64, u16, Decimal, NaiveDate).

All 689 tests now pass including the serde_deserialize_validates suite.
…rage

- README: clarify that serde deserialisation validates via new() (not transparent)
- implementing.md: add serde try_from pattern and sqlx checklist items
- contact.md: document PhoneNumber and PostalAddress serde/sql/TryFrom exceptions
Split the ValueObject trait into two:
- ValueObject: base trait for all VOs (new, into_inner)
- PrimitiveValue: subtrait for simple newtypes (value() -> &Primitive)

Composite types retain value() as a concrete inherent method.
All 42 simple newtypes implement PrimitiveValue with typed Primitive
(String, f64, i64, u16, Decimal, NaiveDate).

Remove SQLx support entirely — users integrate with their ORM directly
via into_inner() and individual accessors. Remove sql feature flag and
all sqlx impl blocks (~260 lines).

Remove XxxOutput type aliases from all VO files and re-exports.
Export PrimitiveValue from prelude alongside ValueObject.

All 689 tests pass.
- README: replace SQL support section with ORM integration guide;
  document PrimitiveValue trait; add 0.x → 1.0 migration table
- docs/value-objects.md: document PrimitiveValue subtrait and when to use it
- docs/implementing.md: update checklist and examples for new trait design;
  add ORM integration examples with sqlx and SeaORM
- docs/contact.md: remove SQLx notes from PhoneNumber and PostalAddress
The sql feature was removed in the SQLx refactor but the test-sql CI job
still referenced it. Drop the job and its Postgres service entirely.

once_cell::sync::Lazy was introduced in v1.3.0 but Cargo.toml pinned
version = "1", resolving to v1.0.1 under minimal-versions and breaking
the check. Replace with std::sync::LazyLock (stable since Rust 1.80,
within our 1.85 MSRV) and remove the once_cell dependency.
serde(try_from = "...") was introduced in 1.0.116; the previous
version = "1" resolved to v1.0.99 under minimal-versions, which
panicked with "unknown serde container attribute try_from".
@codegresscom codegresscom merged commit f957ac3 into main Apr 23, 2026
13 checks passed
@codegresscom codegresscom deleted the chore/release-0.9.0 branch April 23, 2026 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants