idna_adapter
A pinned Unicode back end abstraction that lets Cargo choose ICU4X, unicode-rs, or a no-op stub for the idna crate.
Repository Health
Technical Analysis
idna_adapter is a small, no_std Rust crate that works around a specific Cargo limitation: the lack of global, workspace-wide feature selection. It sits between the idna crate and one of several interchangeable Unicode back ends, letting a top-level application’s Cargo.lock pin exactly which back end gets compiled in via cargo update -p idna_adapter --precise <version>, without idna itself needing to expose that choice as a public feature flag.
Because IDNA (Internationalized Domain Name) processing depends on Unicode normalization, bidi classification, and joining-type data, any implementation needs a concrete data source. idna_adapter exposes a minimal Adapter type with normalization and Unicode property lookups, backed by whichever version stream is pinned: 1.2.x uses ICU4X, 1.1.x uses unicode-rs, and 1.0.x is a stub with no Unicode data at all, trading off compile time, binary size, and runtime performance depending on the choice.
What You Get
- Version-pinned back end selection - Cargo.lock pins to the 1.2.x (ICU4X), 1.1.x (unicode-rs), or 1.0.x (no-op stub) stream without
idnaneeding a public feature flag. - UTS 46 normalization -
map_normalizeandnormalize_validatedelegate directly toicu_normalizer’s UTS 46 mapper for domain label normalization. - Unicode property masks - precomputed bitmasks for bidi classes and joining types used by IDNA’s bidi-domain and Arabic joining-type validation rules.
- no_std compatibility - zero std dependency, keeping the crate usable anywhere
idnaitself needs to run.
Common Use Cases
- Domain name validation - libraries like
idnaandurluse it internally to normalize and validate internationalized domain names per UTS 46. - Binary-size-conscious builds - projects needing a smaller footprint pin the unicode-rs or stub version streams instead of ICU4X.
- Reproducible Unicode data versions - teams that need deterministic domain-name behavior pin an exact idna_adapter version to lock in a specific Unicode Consortium data snapshot.
- Opting out of IDNA support - applications that intentionally want to reject non-ASCII domains pin to the 1.0.x stub.
Under The Hood
Architecture
The crate is a single 279-line lib.rs acting as a thin facade: an Adapter struct wraps icu_normalizer’s Uts46MapperBorrowed plus three icu_properties::CodePointMapDataBorrowed lookups (GeneralCategory, BidiClass, JoiningType), and newtype wrappers (BidiClass, JoiningType, and their *Mask counterparts) constrain callers to the exact operations idna needs — intersecting precomputed masks rather than exposing raw ICU4X enums. Because idna is the sole consumer and the public surface is deliberately minimal, a change to Adapter’s methods would break idna directly, but the facade’s narrowness is what makes the version-stream-swap trick (Cargo.lock pinning a different idna_adapter version to change the underlying implementation) work at all.
Tech Stack
Pure Rust, no_std, targeting the 2024 edition with an MSRV of 1.86. It depends on icu_normalizer 2.2 and icu_properties 2.2 with default-features = false, gated behind an optional compiled_data feature that embeds Unicode data directly into the binary via a const fn constructor. There is no build tooling beyond Cargo and no external services or integrations — it is published to crates.io as a dependency-only crate.
Code Quality
No test files, tests/ directory, or CI configuration are present in the repository; correctness is presumably validated indirectly through idna’s own test suite and downstream users of the url crate. What the crate does lean on is Rust’s type system: #[repr(transparent)] newtypes and mask types prevent bidi-class and joining-type values from being conflated with raw integers, and const fn mask construction pushes some validation to compile time. Naming is consistent and closely mirrors Unicode/UTS 46 terminology throughout.
API Design
The public API is deliberately tiny — six methods on Adapter (is_virama, is_mark, bidi_class, joining_type, map_normalize, normalize_validate) that map one-to-one onto what idna needs, each documented with a link to the underlying icu_normalizer/icu_properties method it wraps. Getting started requires zero configuration when the default compiled_data feature is enabled (Adapter::new() is a const fn). The unusual part of the DX is that most consumers never call this API directly at all — the primary interaction is cargo update -p idna_adapter --precise <version> to select a back end, a pattern explained only in the top-level README rather than in any inline documentation.