base_message/lib.rs
1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4//! The field sections of protocol messages, interpreted into typed structs.
5//!
6//! HTTP (RFC 9110 section 6), the Internet Message Format of email (RFC 5322), and MIME parts (RFC 2045) share one message structure: header and trailer sections of named fields, around the content. This crate interprets those sections. Each [`FieldType`] says how its instances combine and how one value parses, and `#[derive(FieldSection)]` generates the interpretation of a whole section, so duplicate singletons, lists with quoted commas, forbidden fields, and unknown fields are handled in one reviewed place rather than by each consumer.
7//!
8//! ```
9//! use base_message::{FieldSection, FieldType, FieldValueError, ascii_text, combination::{List, Singleton}};
10//!
11//! /// A field type that borrows its value from the input.
12//! struct Host<'a>(&'a str);
13//!
14//! impl<'a> FieldType<'a> for Host<'a> {
15//! type Combination = Singleton;
16//! fn parse(value: &'a [u8]) -> Result<Self, FieldValueError> {
17//! ascii_text(value).map(Self)
18//! }
19//! }
20//!
21//! /// A field type that owns its value.
22//! struct Accept(String);
23//!
24//! impl FieldType<'_> for Accept {
25//! type Combination = List;
26//! fn parse(value: &[u8]) -> Result<Self, FieldValueError> {
27//! Ok(Self(ascii_text(value)?.to_owned()))
28//! }
29//! }
30//!
31//! #[derive(FieldSection)]
32//! #[fields(forbid("transfer-encoding"))]
33//! struct Request<'a> {
34//! #[field("host")]
35//! host: Host<'a>,
36//! #[field("accept")]
37//! accept: Vec<Accept>,
38//! }
39//!
40//! let fields = [("Host", "example.com"), ("Accept", "text/html, image/*"), ("User-Agent", "curl/8")];
41//! let request = Request::interpret(fields)?;
42//! assert_eq!(request.host.0, "example.com");
43//! assert_eq!(request.accept.len(), 2);
44//!
45//! let duplicate = Request::interpret([("Host", "a"), ("Host", "b")]).err().expect("a duplicate host");
46//! assert_eq!(duplicate.to_string(), "duplicate host field at position 1, first at 0");
47//! # Ok::<(), base_message::FieldError>(())
48//! ```
49//!
50//! The crate interprets field sections only. Parsing them from the wire, as well as control data, such as a request line or status, content, framing, and transport, belongs to each protocol's own crate, which passes the fields it parses as name and value pairs in wire order. The [Getting Started](https://github.com/base-rs/base-message/blob/main/context/getting-started.md) guide covers field types and sections, and the [Layering](https://github.com/base-rs/base-message/blob/main/context/layering.md) guide covers passing fields between layers, trailers, and static tables.
51
52mod accumulate;
53pub mod combination;
54mod field_error;
55mod field_name;
56mod field_names;
57mod field_ref;
58mod field_section;
59mod field_type;
60mod field_value;
61mod field_value_error;
62mod owned_fields;
63mod static_table_entry;
64
65#[doc(hidden)]
66#[path = "private.rs"]
67pub mod __private;
68
69pub use base_message_macros::FieldSection;
70pub use field_error::{FieldError, FieldErrorKind};
71pub use field_name::FieldName;
72pub use field_names::FieldNames;
73pub use field_ref::{FieldRef, IntoField};
74pub use field_section::FieldSection;
75pub use field_type::{FieldType, InvalidValue};
76pub use field_value::{ListElements, ascii_text, split_list, trim_whitespace};
77pub use field_value_error::FieldValueError;
78pub use owned_fields::{OwnedField, OwnedFields};
79pub use static_table_entry::{Classification, StaticTableEntry, static_table_entries};
80
81#[cfg(doctest)]
82#[doc = include_str!("../readme.md")]
83struct Readme;
84
85#[cfg(doctest)]
86#[doc = include_str!("../context/getting-started.md")]
87struct GettingStartedGuide;
88
89#[cfg(doctest)]
90#[doc = include_str!("../context/layering.md")]
91struct LayeringGuide;