Skip to main content

edifact_rs/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![deny(unsafe_code)]
3
4//! `edifact-rs` — zero-copy EDIFACT tokenizer, parser, writer, serde traits,
5//! validation engine, and extensible directory support.
6//!
7//! `edifact-rs` is the main entry point of this workspace. The core parsing,
8//! writing, and validation infrastructure is always available. Custom directory
9//! validators can be implemented by downstream crates or generated through
10//! external build tooling.
11//!
12//! # Quick start
13//! ```
14//! use edifact_rs::from_bytes;
15//! let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'UNZ+0+1'";
16//! let segments: Vec<_> = from_bytes(input).collect::<Result<_, _>>().unwrap();
17//! assert_eq!(segments[0].tag, "UNB");
18//! ```
19//!
20//! # Crate features
21//!
22//! - `derive` (enabled by default): re-exports the derive macros from
23//!   `edifact-rs-derive`.
24//! - `diagnostics` (disabled by default): enables rich diagnostic output via `miette`.
25//!   When enabled, errors implement `miette::Diagnostic` for enhanced error reporting.
26//!   This feature adds an optional dependency and has no impact on parsing performance.
27//! - `serde` (disabled by default): derives `Serialize` / `Deserialize` for
28//!   [`ValidationReport`], [`ValidationIssue`], and the envelope types, so
29//!   reports can be persisted or sent across a queue and read back.
30//!
31//! Features are additive and independent: enabling any combination changes only
32//! which trait impls and re-exports are available, never parsing or validation
33//! behaviour.
34//!
35//! The crate is expected to compile both with defaults and with
36//! `--no-default-features` for consumers who only want the core parsing and
37//! writing functionality.
38//!
39//! ## Feature matrix workflows
40//!
41//! - default features:
42//!   `cargo test -p edifact-rs`
43//! - no default features:
44//!   `cargo test -p edifact-rs --no-default-features`
45//! - all features:
46//!   `cargo test -p edifact-rs --all-features`
47//!
48//! # Diagnostic Feature
49//!
50//! When the `diagnostics` feature is enabled, [`EdifactError`] gains additional
51//! traits and methods that enable rich, human-readable error output:
52//!
53//! ```text
54//! Error: invalid delimiter byte 0xAB at offset 42
55//!
56//!  ╭─ input.edi:2:3
57//!  │
58//!  2 │ UNB+UNOA:1+....[invalid]...
59//!  │         ^^^ invalid byte here
60//!  │
61//! Error Code: E002
62//! Help: The byte 0xAB is not a valid delimiter. Check UNA configuration
63//! ```
64//!
65//! This feature is useful for CLI tools and error reporting, but is not required
66//! for applications that handle errors programmatically.
67//!
68//! # Parse And Text Contracts
69//!
70//! Parsing in `edifact-rs` is strict and deterministic:
71//!
72//! - Segment and element text must decode as UTF-8 (`E003` on failure).
73//! - Release characters must escape exactly one following byte.
74//!   A trailing `?` at end-of-input is rejected (`E019`).
75//! - Malformed delimiters and truncated segments are reported with stable
76//!   error codes rather than panicking.
77//!
78//! These contracts apply to both slice-based parsing (`from_bytes`) and
79//! reader-based parsing (`from_reader`).
80//!
81//! ```
82//! use edifact_rs::from_reader_collect;
83//! use std::io::Cursor;
84//!
85//! let input = b"UNA:;.? 'BGM;220;test?;value'";
86//! let segments = from_reader_collect(Cursor::new(&input[..])).unwrap();
87//! assert_eq!(segments.len(), 1);
88//! assert_eq!(segments[0].tag, "BGM");
89//! assert_eq!(segments[0].element_str(0), Some("220"));
90//! assert_eq!(segments[0].element_str(1), Some("test;value"));
91//! ```
92//!
93//! # Validation Quick Start
94//!
95//! The `Validator` trait and `ValidationContext` provide a flexible framework
96//! for building custom validators. Users can generate validators from official
97//! UNECE sources or implement their own.
98//!
99//! See the [`Validator`] trait documentation and the `cookbook_fixture_validation.rs`
100//! example for details on creating custom validators.
101//!
102//! # Custom Profile Packs
103//!
104//! `ProfileRulePack` is the extension point for downstream MIG/profile crates.
105//! Packs can be authored with public APIs only and plugged into a
106//! [`ValidationContext`]:
107//!
108//! ```
109//! use edifact_rs::{
110//!     from_bytes, ProfileRulePack, ValidationContext, ValidationIssue, ValidationSeverity,
111//! };
112//!
113//! let segments: Vec<_> = from_bytes(b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO123+9'UNT+3+1'")
114//!     .collect::<Result<_, _>>()?;
115//!
116//! let pack = ProfileRulePack::new("ORDERS-DEMO")
117//!     .for_message_type("ORDERS")
118//!     .with_stateless_rule_fn(|segments, issues| {
119//!         if let Some(bgm) = segments.iter().find(|segment| segment.tag == "BGM") {
120//!             if let Some(code) = bgm.get_element(0).and_then(|e| e.get_component(0)) {
121//!                 if code == "220" {
122//!                     issues.push(
123//!                         ValidationIssue::new(
124//!                             ValidationSeverity::Warning,
125//!                             "demo pack rejects BGM 220 for illustration",
126//!                         )
127//!                         .with_rule_id("DEMO-P001")
128//!                         .with_segment("BGM")
129//!                         .with_element_index(0),
130//!                     );
131//!                 }
132//!             }
133//!         }
134//!     });
135//!
136//! let report = ValidationContext::builder()
137//!     .with_profile_pack(pack)
138//!     .build()
139//!     .validate_lenient(&segments);
140//!
141//! assert!(report.has_warnings());
142//! let partner_report = report.filter_by_rule_prefix("DEMO-");
143//! assert!(partner_report.total_issues() >= 1);
144//! # Ok::<(), edifact_rs::EdifactError>(())
145//! ```
146//!
147//! # Async Usage
148//!
149//! `edifact-rs` does not provide a native `async` API.  All parsing is
150//! synchronous and driven by the standard `std::io::Read` / `std::io::BufRead`
151//! traits.  The recommended integration pattern with async runtimes is:
152//!
153//! 1. Use your async runtime's read utilities to read the entire message into a
154//!    `Vec<u8>` (e.g. `tokio::io::AsyncReadExt::read_to_end`).
155//! 2. Parse the in-memory slice with [`from_bytes`].
156//!
157//! ```rust,no_run
158//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
159//! // With tokio:
160//! // let mut buf = Vec::new();
161//! // reader.read_to_end(&mut buf).await?;
162//! // let segments: Vec<_> = edifact_rs::from_bytes(&buf).collect::<Result<_, _>>()?;
163//! # Ok(())
164//! # }
165//! ```
166// ── core modules ──────────────────────────────────────────────────────────────
167pub mod directory_validator;
168pub(crate) mod envelope;
169/// Error types and validation reporting primitives.
170pub(crate) mod error;
171pub mod group;
172/// Core zero-copy and owned EDIFACT data model types.
173pub(crate) mod model;
174pub(crate) mod parser;
175/// Validation report types: [`ValidationSeverity`], [`ValidationIssue`], [`ValidationReport`].
176///
177/// These types are also re-exported from the crate root.
178pub mod report;
179pub(crate) mod tokenizer;
180pub(crate) mod validator;
181pub(crate) mod writer;
182
183// ── typed serialization layer ─────────────────────────────────────────────────
184pub mod de;
185pub(crate) mod event;
186pub mod ser;
187
188// ── flat re-exports: core ─────────────────────────────────────────────────────
189pub use envelope::{
190    FunctionalGroupEnvelope, GroupIdentifier, InterchangeEnvelope, LenientResult, MessageEnvelope,
191    MessageIdentifier, ValidatedInterchange, parse_ung, parse_unh, validate_envelope,
192    validate_envelope_from_owned, validate_envelope_lenient, validate_envelope_lenient_from_owned,
193};
194pub use error::{EdifactError, IoError};
195pub use group::{
196    GroupDef, SegmentGroupIndexed, group_owned_segments_indexed, group_segments_indexed,
197};
198pub use model::{
199    BorrowedElement, BorrowedSegment, Element, OwnedElement, OwnedSegment, Segment, Span,
200};
201pub use parser::{
202    OwnedSegmentStream, Parser, ReaderConfig, from_bufread, from_bufread_stream,
203    from_bufread_stream_with_config, from_reader_with_config,
204};
205pub use report::{ValidationIssue, ValidationReport, ValidationSeverity};
206pub use tokenizer::{ServiceStringAdvice, Token, Tokenizer};
207pub use validator::{
208    EnvelopeValidator, ProfileRule, ProfileRulePack, ValidationContext, ValidationContextBuilder,
209    ValidationLayer, ValidationRuleContext, Validator, validate_each,
210};
211pub use writer::{AsDataElement, DataElement, MessageWriter, Writer};
212
213// ── flat re-exports: serde ────────────────────────────────────────────────────
214
215/// User-facing deserialization API.
216pub use de::{
217    CompositeElement, DispatchedMessage, EdifactCompositeDeserialize, EdifactDeserialize,
218    EdifactSegmentTag, MessageDispatch, MessageWindow, MessageWindowsIter, MessageWindowsSliceIter,
219    OwnedMessageWindow, SegmentAccessor, composite_element, contiguous_groups_by_qualifier,
220    contiguous_groups_iter, deserialize, deserialize_all_from_reader, deserialize_all_streaming,
221    deserialize_first_from_reader, deserialize_first_streaming, deserialize_messages_bytes,
222    deserialize_messages_from_reader, deserialize_str, element_str, find_qualified_segment,
223    find_qualified_segment_owned, find_segment, find_segment_owned, find_segment_typed,
224    find_segments_iter, find_segments_typed, get_components_iter,
225    groups_are_contiguous_by_qualifier, message_windows_from_reader, optional_component,
226    optional_element, qualifier_matches_pattern, required_component, required_element,
227};
228
229/// Splits a byte slice into [`MessageWindow`] views, one per `UNH`/`UNT` envelope,
230/// enabling parallel or lazy per-message processing without copying data.
231///
232/// # Example
233/// ```rust,ignore
234/// use edifact_rs::from_bytes_windows;
235/// let windows: Vec<_> = from_bytes_windows(input).collect();
236/// ```
237pub use de::message_windows_bytes as from_bytes_windows;
238
239// ── Proc-macro support ─────────────────────────────────────────────────────────
240
241pub use directory_validator::{
242    ComponentRef, DirectoryValidator, DirectoryValidatorBuilder, ElementPath, ElementRef,
243    OwnedComponentRef, OwnedElementRef, OwnedSegmentDef, SegmentDefinition, SegmentLayout, Status,
244};
245#[cfg(feature = "derive")]
246#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
247pub use edifact_rs_derive::{EdifactDeserialize, EdifactSerialize};
248pub use event::{EdifactEvent, EventEmitter, OwnedEdifactEvent, VecEmitter, WriterEmitter};
249pub use ser::{
250    DecimalFloat, DecimalFloatDisplay, EdifactCompositeSerialize, EdifactSerialize,
251    emit_sparse_segment, to_bytes, to_edifact_string,
252};
253
254// ── core free functions ───────────────────────────────────────────────────────
255
256use std::io::{Read, Write};
257
258/// Iterator returned by [`from_bytes`].
259pub struct FromBytesIter<'a> {
260    parser: Option<parser::Parser<'a>>,
261    pending_error: Option<EdifactError>,
262    /// Remaining segment allowance (`None` = unlimited).
263    segments_remaining: Option<usize>,
264    /// Maximum byte budget (`None` = unlimited).
265    bytes_remaining: Option<u64>,
266    /// Byte offset of the start of the current parse position (approximated
267    /// as the sum of previously yielded segment spans — the borrowed tokenizer
268    /// does not expose a byte counter, so we track it from `Segment::span`).
269    bytes_consumed: u64,
270}
271
272/// Iterator returned by [`from_reader`].
273pub struct FromReaderIter<R: Read> {
274    inner: parser::OwnedSegmentStream<std::io::BufReader<R>>,
275}
276
277impl<R: Read> Iterator for FromReaderIter<R> {
278    type Item = Result<OwnedSegment, EdifactError>;
279
280    fn next(&mut self) -> Option<Self::Item> {
281        self.inner.next()
282    }
283}
284
285impl<'a> Iterator for FromBytesIter<'a> {
286    type Item = Result<Segment<'a>, EdifactError>;
287
288    fn next(&mut self) -> Option<Self::Item> {
289        if let Some(err) = self.pending_error.take() {
290            return Some(Err(err));
291        }
292        // max_segments guard
293        if let Some(ref mut remaining) = self.segments_remaining {
294            if *remaining == 0 {
295                self.parser = None;
296                return None;
297            }
298        }
299        // max_input_bytes guard — uses absolute byte offset from the input start.
300        // `bytes_consumed` holds `seg.span.end` of the last yielded segment, which
301        // is an absolute position in the input slice and therefore naturally includes
302        // the 9-byte UNA header and the segment terminator character.
303        if let Some(max) = self.bytes_remaining {
304            if self.bytes_consumed >= max {
305                self.parser = None;
306                return None;
307            }
308        }
309        let item = self.parser.as_mut()?.next();
310        if let Some(Ok(ref seg)) = item {
311            // Decrement segment allowance
312            if let Some(ref mut remaining) = self.segments_remaining {
313                *remaining = remaining.saturating_sub(1);
314            }
315            // Track the absolute input position at the end of this segment.
316            // `seg.span.end` is the byte offset just past the segment terminator —
317            // a monotonically increasing absolute cursor that automatically accounts
318            // for the UNA header, element/component separators, and terminators.
319            self.bytes_consumed = seg.span.end as u64;
320            if let Some(max) = self.bytes_remaining {
321                if self.bytes_consumed >= max {
322                    self.parser = None;
323                }
324            }
325        }
326        item
327    }
328}
329
330/// Parse `input` bytes into an iterator of [`Segment`]s.
331///
332/// Borrows directly from `input` — zero allocation for segment data.
333///
334/// # Segment-size limit
335///
336/// Applies a default 64 KiB per-segment limit, matching the reader-based path.
337/// Use [`from_bytes_with_config`] to override.
338pub fn from_bytes(input: &[u8]) -> FromBytesIter<'_> {
339    from_bytes_with_config(input, parser::ReaderConfig::default())
340}
341
342/// Parse `input` bytes into an iterator of [`Segment`]s with explicit configuration.
343///
344/// All three [`ReaderConfig`] limits are enforced:
345/// - `max_segment_bytes`: returns [`EdifactError::SegmentTooLong`] if a single segment
346///   exceeds the threshold.
347/// - `max_segments`: stops the iterator after this many segments have been yielded.
348/// - `max_input_bytes`: **stop-after** limit — the iterator stops once the cumulative
349///   byte position (tracked via `Segment::span.end`) reaches or exceeds this value.
350///   The last segment whose `span.end` exceeds the limit is **still returned**;
351///   no further segments are fetched after that.  This means at most one segment
352///   worth of bytes can be processed beyond the limit, which is sufficient for a
353///   DoS guard but is not a strict hard cap.  If your use case requires that every
354///   yielded segment fits entirely within `max_input_bytes` bytes, collect and
355///   filter the output, or set the limit conservatively below the true boundary.
356///
357/// Pass `ReaderConfig::default()` to use the default 64 KiB per-segment limit with
358/// no segment-count or byte-budget cap.
359///
360/// # Example
361///
362/// ```
363/// use edifact_rs::{ReaderConfig, from_bytes_with_config};
364///
365/// let cfg = ReaderConfig::default().max_segment_bytes(128);
366/// let result: Result<Vec<_>, _> = from_bytes_with_config(b"BGM+220+1+9'", cfg).collect();
367/// assert!(result.is_ok());
368/// ```
369pub fn from_bytes_with_config(input: &[u8], config: parser::ReaderConfig) -> FromBytesIter<'_> {
370    let segments_remaining = config.max_segments;
371    let bytes_remaining = config.max_input_bytes;
372    match tokenizer::ServiceStringAdvice::from_bytes(input) {
373        Ok(ssa) => {
374            let t = tokenizer::Tokenizer::with_limit(input, ssa, config.max_segment_bytes);
375            FromBytesIter {
376                parser: Some(parser::Parser::new(t)),
377                pending_error: None,
378                segments_remaining,
379                bytes_remaining,
380                bytes_consumed: 0,
381            }
382        }
383        Err(error) => FromBytesIter {
384            parser: None,
385            pending_error: Some(error),
386            segments_remaining,
387            bytes_remaining,
388            bytes_consumed: 0,
389        },
390    }
391}
392
393/// Parse a reader into a lazy iterator of [`OwnedSegment`]s.
394///
395/// Returns a [`FromReaderIter`] that parses and yields segments on demand,
396/// keeping memory bounded. Use [`from_reader_collect`] to eagerly materialise
397/// all segments into a `Vec`.
398///
399/// # Errors
400///
401/// Each `next()` call yields `Some(Ok(segment))` for a successfully parsed
402/// segment, `Some(Err(EdifactError))` for a parse or I/O failure, and `None`
403/// when the end of the stream has been reached.
404pub fn from_reader<R: Read>(reader: R) -> FromReaderIter<R> {
405    FromReaderIter {
406        inner: parser::from_reader_stream(reader),
407    }
408}
409
410/// Parse a reader into an owned `Vec` of all segments.
411///
412/// Eagerly collects the full interchange into memory. If you only need a
413/// subset of segments, prefer [`from_reader`] (lazy iterator) to avoid
414/// unnecessary allocations.
415///
416/// # Errors
417///
418/// Returns an error if the input contains malformed EDIFACT syntax,
419/// invalid UTF-8 segment text, dangling release sequences, or underlying I/O failures.
420pub fn from_reader_collect<R: Read>(reader: R) -> Result<Vec<OwnedSegment>, EdifactError> {
421    parser::from_reader(reader)
422}
423
424/// Parse `input` bytes eagerly into an iterator of [`OwnedSegment`]s.
425///
426/// Unlike [`from_bytes`] (which yields borrowed [`Segment`]s tied to the input
427/// lifetime), every segment returned here is fully owned.  This is convenient
428/// when you need to store or return segments without retaining a reference to
429/// the original byte slice.
430///
431/// # Example
432///
433/// ```
434/// let segs: Vec<edifact_rs::OwnedSegment> = edifact_rs::from_bytes_owned(b"BGM+220+1+9'")
435///     .collect::<Result<_, _>>()
436///     .unwrap();
437/// assert_eq!(segs[0].tag, "BGM");
438/// ```
439pub fn from_bytes_owned(
440    input: &[u8],
441) -> impl Iterator<Item = Result<OwnedSegment, EdifactError>> + '_ {
442    from_bytes(input).map(|r| r.map(OwnedSegment::from))
443}
444
445/// Parse `input` bytes eagerly into an iterator of [`OwnedSegment`]s with a
446/// custom [`ReaderConfig`].
447///
448/// Identical to [`from_bytes_owned`] but applies the limits and settings from
449/// `config` (e.g. `max_segment_bytes`, `max_segments`, `max_input_bytes`).
450///
451/// # Example
452///
453/// ```
454/// use edifact_rs::ReaderConfig;
455/// let config = ReaderConfig::default().max_segments(10);
456/// let segs: Vec<edifact_rs::OwnedSegment> = edifact_rs::from_bytes_owned_with_config(
457///     b"BGM+220+1+9'",
458///     config,
459/// )
460/// .collect::<Result<_, _>>()
461/// .unwrap();
462/// assert_eq!(segs[0].tag, "BGM");
463/// ```
464pub fn from_bytes_owned_with_config(
465    input: &[u8],
466    config: ReaderConfig,
467) -> impl Iterator<Item = Result<OwnedSegment, EdifactError>> + '_ {
468    from_bytes_with_config(input, config).map(|r| r.map(OwnedSegment::from))
469}
470
471/// Serialize `segments` to an [`std::io::Write`] implementation.
472///
473/// # Errors
474///
475/// Returns an error if writing fails or if segment serialization fails.
476pub fn to_writer<'a, 'b, W, I>(w: W, segments: I) -> Result<(), EdifactError>
477where
478    'b: 'a,
479    W: Write,
480    I: IntoIterator<Item = &'a Segment<'b>>,
481{
482    let mut wr = writer::Writer::new(w);
483    for seg in segments {
484        wr.write_segment(seg)?;
485    }
486    wr.finish().map(|_| ())
487}
488
489/// Serialize `segments` to an owned `Vec<u8>`.
490///
491/// # Errors
492///
493/// Returns an error if serialization fails.
494pub fn segments_to_bytes<'a, 'b, I>(segments: I) -> Result<Vec<u8>, EdifactError>
495where
496    'b: 'a,
497    I: IntoIterator<Item = &'a Segment<'b>>,
498{
499    let mut buf = Vec::new();
500    to_writer(&mut buf, segments)?;
501    Ok(buf)
502}
503
504/// Serialize a slice of [`OwnedSegment`]s to an owned `Vec<u8>`.
505///
506/// Convenience wrapper around [`to_writer`] that accepts owned segments
507/// directly.  Each segment is converted to its borrowed form on the fly
508/// and written immediately — no intermediate `Vec<Segment<'_>>` is
509/// allocated, so peak memory stays proportional to one segment at a time
510/// rather than the full slice.
511///
512/// # Errors
513///
514/// Returns an error if serialization fails.
515pub fn segments_to_bytes_owned(segments: &[OwnedSegment]) -> Result<Vec<u8>, EdifactError> {
516    let mut buf = Vec::new();
517    let mut wr = writer::Writer::new(&mut buf);
518    for seg in segments {
519        wr.write_segment(&seg.as_borrowed())?;
520    }
521    wr.finish()?;
522    Ok(buf)
523}
524
525/// Validate the envelope structure of an owned-segment slice.
526///
527/// Convenience wrapper that accepts `&[OwnedSegment]` without requiring a
528/// manual conversion to borrowed segments.  Unlike the previous implementation,
529/// no intermediate `Vec<Segment<'_>>` is allocated — segments are read directly.
530///
531/// # Errors
532///
533/// Returns an error if the envelope is structurally invalid.
534pub fn validate_envelope_owned(
535    segments: &[OwnedSegment],
536) -> Result<ValidatedInterchange, EdifactError> {
537    envelope::validate_envelope_from_owned(segments)
538}
539
540/// Lenient envelope validation over owned segments — collects all errors.
541///
542/// Convenience wrapper around [`validate_envelope_lenient_from_owned`].
543/// Returns a [`LenientResult`] with `Some(result)` and empty errors on success.
544/// On count-only violations, returns `Some(partial)` with errors.
545/// On structural failures, returns `None` with errors.
546pub fn validate_envelope_lenient_owned(segments: &[OwnedSegment]) -> LenientResult {
547    envelope::validate_envelope_lenient_from_owned(segments)
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553
554    #[test]
555    fn from_bytes_rejects_invalid_una() {
556        let err = from_bytes(b"UNA::.? 'BGM:220'")
557            .collect::<Result<Vec<_>, _>>()
558            .expect_err("invalid UNA should fail slice parsing");
559        assert!(matches!(err, EdifactError::InvalidUna));
560    }
561}
562
563/// Compiles and runs every ```` ```rust ```` block in the `docs/` guides as a
564/// doctest.
565///
566/// The guides drifted from the API — snippets referenced private module paths
567/// and methods that did not exist — because nothing ever compiled them. Wiring
568/// them in here means a rename that breaks a guide breaks the build.
569///
570/// Blocks that genuinely cannot run (they need a live socket, a real directory
571/// file, or a downstream crate) should be marked ```` ```rust,ignore ```` or
572/// ```` ```rust,no_run ```` in the guide itself.
573#[cfg(doctest)]
574mod doc_guides {
575    macro_rules! guide {
576        ($name:ident, $path:literal) => {
577            #[doc = include_str!($path)]
578            pub struct $name;
579        };
580    }
581
582    guide!(CoreConcepts, "../../../docs/core-concepts.md");
583    guide!(Parsing, "../../../docs/parsing.md");
584    guide!(ProfilePacks, "../../../docs/profile-packs.md");
585    guide!(Validation, "../../../docs/validation.md");
586
587    // Guides whose examples use the derive macros.
588    #[cfg(feature = "derive")]
589    guide!(AsyncIntegration, "../../../docs/async-integration.md");
590    #[cfg(feature = "derive")]
591    guide!(ErrorReference, "../../../docs/error-reference.md");
592    #[cfg(feature = "derive")]
593    guide!(GettingStarted, "../../../docs/getting-started.md");
594    #[cfg(feature = "derive")]
595    guide!(Performance, "../../../docs/performance.md");
596    #[cfg(feature = "derive")]
597    guide!(Streaming, "../../../docs/streaming.md");
598    #[cfg(feature = "derive")]
599    guide!(TypedDerive, "../../../docs/typed-derive.md");
600    #[cfg(feature = "derive")]
601    guide!(Writing, "../../../docs/writing.md");
602
603    // The diagnostics guide's examples use `miette` types.
604    #[cfg(feature = "diagnostics")]
605    guide!(Diagnostics, "../../../docs/diagnostics.md");
606
607    // The README is the crate's front page on docs.rs and crates.io, and drifts
608    // for exactly the same reason the guides did.
609    #[cfg(feature = "derive")]
610    guide!(Readme, "../../../README.md");
611}