Adding a New Identifier
This crate is organized so that every identifier type — CNPJ today, ISIN and CFI planned — follows the same shape, both
in src/ and in this book. Sticking to the pattern keeps the crate predictable to use and cheap to review.
Source layout
Each identifier gets its own module directory, named after the identifier in lowercase (mirror src/cnpj/):
src/
<identifier>.rs # public type, module docs, inherent methods
<identifier>/
error.rs # the <Identifier>Error enum + Display
fmt.rs # Display/Debug + any zero-allocation formatted-string helper
parser.rs # string -> normalized candidate (formatting/case only)
validation.rs # normalized candidate -> Result<(), Error> (structural + checksum rules)
serde.rs # #[cfg(feature = "serde")]
schema.rs # #[cfg(feature = "schemars")]
arbitrary.rs # #[cfg(feature = "arbitrary")]
proptest.rs # #[cfg(any(test, feature = "proptest"))], pub
tests.rs # #[cfg(test)]
The parser vs. validation split matters: parser only knows about formatting (stripping punctuation, folding
case) and has no opinion on which characters are valid where; validation only knows about rules (character class,
checksum, degenerate input) and has no opinion on how the original string was formatted. This keeps each half testable
independently and makes it obvious where a new rule belongs.
Feature flags
Follow the same Cargo.toml convention as CNPJ — every integration is optional and additive:
[features]
serde = ["dep:serde"]
schemars = ["dep:schemars", "serde"]
arbitrary = ["dep:arbitrary"]
proptest = ["dep:proptest"]
A new identifier should gate its serde/schemars/arbitrary/proptest modules behind the same feature names as
CNPJ (not per-identifier features like cnpj-serde), so enabling serde once turns it on for every identifier type in
the crate.
lib.rs
Re-export the new public type (and its error type, and any formatted-string helper type) at the crate root, the same way CNPJ does:
mod isin;
pub use isin::{Isin, IsinError, FormattedIsin};
Forgetting this step means the error type technically exists but can never be named by downstream code — the same gap
this documentation pass caught and fixed for Cnpj.
Documentation checklist
For the module itself (src/<identifier>.rs):
- Module-level doc comment covering: what the identifier represents, its segment structure (a table works well),
format history/variants if any, the full list of validation rules mapped to error variants, relevant design notes (
allocation,
Copy, ordering/hashing semantics), feature flags, and at least one runnable example. - Every public method has a doc comment with a runnable
# Examplessection. - Every public trait impl (
FromStr,TryFrom,AsRef, …) has a one-line doc comment on the implementing method. -
# Errorssections on every fallible public function.
For this book (docs/src/identifiers/<identifier>/):
-
README.md— landing page: what the identifier is, a minimal example, a table of contents for the rest of the chapter. -
format.md— structure and any format history. -
parsing-and-validation.md— constructors and validation rules. -
formatting-and-display.md— rendering,Display/Debug. -
error-handling.md— error variant table and a matching example. -
feature-flags.md— one section per optional integration. -
examples.md— end-to-end usage beyond the quick start.
Code blocks in this book use ```rust,ignore rather than ```rust, since mdbook test isn’t currently wired up to
link against the compiled crate. The examples themselves should still mirror real, compiler-verified doctests from the
source — copy them from src/<identifier>.rs rather than writing new, untested snippets from scratch.
Wiring it into SUMMARY.md
Replace the draft placeholder for your identifier with a real link, and add the same sub-chapters CNPJ has:
- [ISIN](./identifiers/isin/README.md)
- [Structure & Formats](./identifiers/isin/format.md)
- [Parsing & Validation](./identifiers/isin/parsing-and-validation.md)
- [Formatting & Display](./identifiers/isin/formatting-and-display.md)
- [Error Handling](./identifiers/isin/error-handling.md)
- [Feature Flags](./identifiers/isin/feature-flags.md)
- [Examples](./identifiers/isin/examples.md)
A draft entry (- [ISIN](), with no link target) renders as a disabled item in the sidebar and requires no file to
exist yet — that’s how ISIN and CFI are currently listed. Only replace it once the chapter files actually exist; a link
to a missing file breaks the mdbook build.