Skip to main content

base_message/
field_section.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use crate::__private::IncomingName;
5use crate::field_ref::IntoField;
6use crate::{FieldError, FieldNames, FieldRef};
7
8/// A field section interpreted into a struct. It is implemented only by `#[derive(FieldSection)]`.
9///
10/// A section takes the fields it declares, refuses those it forbids, and either ignores or rejects the rest. [`Self::interpret`] discards the fields it does not take; [`Self::partition`] returns them, borrowed and with their original positions, for another section to interpret.
11///
12/// The lifetime is that of the field values, which a section with a lifetime parameter can borrow; a section that owns its values implements `FieldSection<'a>` for every `'a`.
13pub trait FieldSection<'a>: Sized {
14    /// The names this section declares and forbids, including those of sections it flattens.
15    const NAMES: FieldNames;
16
17    /// Whether the section rejects fields it neither declares nor forbids, including because a section it flattens does.
18    #[doc(hidden)]
19    const REJECTS_UNKNOWN: bool = Self::NAMES.rejects_unknown();
20
21    /// The section's type with its lifetime, if any, as `'static`, which identifies the section to its static tables.
22    #[doc(hidden)]
23    type Identity: 'static;
24
25    #[doc(hidden)]
26    type Builder;
27
28    /// An empty builder, before any field is accepted.
29    #[doc(hidden)]
30    fn builder() -> Self::Builder;
31
32    /// Offer one field by name, reporting whether a declaration took it. A forbidden name is an error.
33    #[doc(hidden)]
34    fn accept(
35        builder: &mut Self::Builder,
36        name: &str,
37        value: &'a [u8],
38        position: usize,
39    ) -> Result<bool, FieldError>;
40
41    /// Accept a field by its declaration id, as [`FieldNames::declaration`] numbers them, without comparing names.
42    #[doc(hidden)]
43    fn accept_declaration(
44        builder: &mut Self::Builder,
45        declaration: usize,
46        value: &'a [u8],
47        position: usize,
48    ) -> Result<(), FieldError>;
49
50    /// Build the section, reporting a missing required field.
51    #[doc(hidden)]
52    fn finish(builder: Self::Builder) -> Result<Self, FieldError>;
53
54    /// Interpret a section from its fields in wire order, taking those it declares and discarding the rest.
55    ///
56    /// # Errors
57    ///
58    /// The first [`FieldError`]: a duplicate singleton, an invalid value its field treats as an error, a forbidden field, an unknown field the section rejects, or a missing required field.
59    fn interpret<I>(fields: I) -> Result<Self, FieldError>
60    where
61        I: IntoIterator,
62        I::Item: IntoField<'a, Self>,
63    {
64        interpret::<Self, I, Vec<FieldRef<'a>>>(fields, None)
65    }
66
67    /// Interpret a section from its fields in wire order, taking those it declares and returning the rest, with their original positions, for another section to interpret.
68    ///
69    /// The remainder can be any collection: a `Vec<FieldRef>` borrows the fields without copying them, and [`OwnedFields`](crate::OwnedFields) copies them. A field a static table classified as undeclared for this section is passed on by name, since the next section may declare it. A forbidden field is an error, never part of the remainder.
70    ///
71    /// # Errors
72    ///
73    /// As for [`Self::interpret`].
74    fn partition<I, R>(fields: I) -> Result<(Self, R), FieldError>
75    where
76        I: IntoIterator,
77        I::Item: IntoField<'a, Self>,
78        R: Default + Extend<FieldRef<'a>>,
79    {
80        let mut remainder = R::default();
81        let section = interpret::<Self, I, R>(fields, Some(&mut remainder))?;
82        Ok((section, remainder))
83    }
84}
85
86// SECURITY: Names are matched as given, ASCII case-insensitively: a malformed name, such as one with surrounding spaces, a NUL, or a non-ASCII lookalike letter, matches no declared or forbidden name and is treated as undeclared, so it is not refused. This is not a bypass, because protocol crates validate field-name syntax before interpretation, as `design/scope.md` requires, so such a name never arrives.
87fn interpret<'a, S, I, R>(fields: I, mut remainder: Option<&mut R>) -> Result<S, FieldError>
88where
89    S: FieldSection<'a>,
90    I: IntoIterator,
91    I::Item: IntoField<'a, S>,
92    R: Extend<FieldRef<'a>>,
93{
94    let mut builder = S::builder();
95    for (index, field) in fields.into_iter().enumerate() {
96        let field = field.into_field(index);
97        let unclaimed = match field.name {
98            IncomingName::Declaration(declaration) => {
99                S::accept_declaration(
100                    &mut builder,
101                    usize::from(declaration),
102                    field.value,
103                    field.position,
104                )?;
105                None
106            }
107            IncomingName::Forbidden(name) => {
108                return Err(FieldError::forbidden(name, field.position));
109            }
110            IncomingName::Undeclared(name) => Some(name),
111            IncomingName::Text(name) => {
112                (!S::accept(&mut builder, name, field.value, field.position)?).then_some(name)
113            }
114        };
115        if let Some(name) = unclaimed {
116            if S::REJECTS_UNKNOWN {
117                return Err(FieldError::unknown(field.position));
118            }
119            if let Some(remainder) = remainder.as_deref_mut() {
120                remainder.extend([FieldRef::new(field.position, name, field.value)]);
121            }
122        }
123    }
124    S::finish(builder)
125}