Idiomatic Rust API Layer
Status
Accepted
Context
Ferroni is a 1:1 C-to-Rust port of Oniguruma (ADR-001). The internal code intentionally mirrors C structure, naming, and patterns for upstream traceability. However, the public API was also C-shaped, requiring users to:
- Pass raw pointer casts (
&OnigSyntaxOniguruma as *const OnigSyntaxType) - Interpret
i32return codes that mix match positions with error codes - Construct
OnigRegionmanually and index intoVec<i32>with-1sentinels - Pass 7 parameters to
onig_search()including redundant length arguments
This made the library difficult to use for Rust developers accustomed to APIs like regex::Regex.
Decision
Add an idiomatic Rust API layer on top of the C-ported internals:
Tier 1: Public API Types (additive, zero regression risk)
-
RegexErrorenum (src/error.rs): Groups ~100const i32error codes into 12 semantic variants (Memory,Syntax,TimeLimitOver, etc.) withstd::error::Errorimplementation andFrom<i32>conversion. -
Regexwrapper (src/api.rs): WrapsRegexTypewith methods likenew(),find(),is_match(),captures(),find_iter(). Delegates toonig_new()andonig_search()internally. -
RegexBuilder(src/api.rs): Fluent builder for compile options (case_insensitive(),dot_matches_newline(),multi_line_anchors(),extended(),syntax()). -
MatchandCaptures(src/api.rs): Type-safe result types that hideOnigRegioninternals. Users getstart(),end(),as_str(),as_bytes()instead of rawbeg/endvectors. -
Prelude (
src/prelude.rs): Re-exportsRegex,RegexBuilder,Match,Captures,RegexErrorfor convenientuse ferroni::prelude::*.
Tier 2: Targeted Internal Improvements (low risk)
-
bitflags!for option flags: Replacetype OnigOptionType = u32with abitflagsstruct for type-safe option manipulation. Old constant names preserved as aliases. -
onig_new()returnsResult<RegexType, RegexError>: The primary compilation entry point now returns typed errors instead ofi32codes. -
References instead of raw pointers:
onig_new()takes&OnigSyntaxTypeinstead of*const OnigSyntaxType.onig_get_syntax()returns&OnigSyntaxType. -
Encapsulated
RegexTypefields: All 37pubfields changed topub(crate). External access via accessor functions or the Tier 1 wrapper.
Rationale
- Zero regression risk for Tier 1: All new code, no existing code modified. The C-ported internals remain intact for upstream traceability (ADR-001).
- Low risk for Tier 2: Each change is mechanical and verified by the full 1,695-test suite.
- User-facing impact: Reduces a 7-line C-style regex creation to a 1-line idiomatic call.
- Layered architecture: Users who need C-level control can still use
onig_new()/onig_search()directly.
Consequences
- The C-ported internal code remains structurally faithful to the C original (ADR-001 unchanged).
- New users should prefer
ferroni::prelude::Regexoverferroni::regcomp::onig_new(). - The
bitflagscrate is added as a dependency. RegexTypefields are no longer directly accessible from outside the crate; use accessor functions or theRegexwrapper.- Future API additions should follow the idiomatic layer pattern: wrap C-ported internals, don't modify them.
- The Scanner API (ADR-006) extends this layer with a domain-specific multi-pattern interface for TextMate grammar tokenization, following the same principle of wrapping C-ported internals without modification.