Skip to main content

base_message/
field_names.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4/// The lowercase field names a section declares and forbids, with those of the sections it flattens, and whether it rejects unknown fields, as a constant.
5///
6/// The derive checks at compile time that no name is declared or forbidden at two levels, so that no declaration silently never receives values, and [`static_table_entries`](crate::static_table_entries) uses these names to classify a protocol's static table. Declaration ids number a section's own declarations first, in order, then each flattened section's, in order.
7#[derive(Clone, Copy, Debug)]
8pub struct FieldNames {
9    declared: &'static [&'static str],
10    forbidden: &'static [&'static str],
11    flattened: &'static [&'static FieldNames],
12    rejects_unknown: bool,
13}
14
15impl FieldNames {
16    /// The names of one section, as the derive generates them.
17    #[doc(hidden)]
18    pub const fn new(
19        declared: &'static [&'static str],
20        forbidden: &'static [&'static str],
21        flattened: &'static [&'static FieldNames],
22        rejects_unknown: bool,
23    ) -> Self {
24        Self {
25            declared,
26            forbidden,
27            flattened,
28            rejects_unknown,
29        }
30    }
31
32    /// The number of declarations, including those of flattened sections.
33    pub const fn count(&self) -> usize {
34        let mut count = self.declared.len();
35        let mut section = 0;
36        while section < self.flattened.len() {
37            count += self.flattened[section].count();
38            section += 1;
39        }
40        count
41    }
42
43    /// The declaration id of a lowercase field name, or `None` when no declaration has it.
44    pub const fn declaration(&self, name: &str) -> Option<usize> {
45        let mut index = 0;
46        while index < self.declared.len() {
47            if equal(self.declared[index], name) {
48                return Some(index);
49            }
50            index += 1;
51        }
52        let mut offset = self.declared.len();
53        let mut section = 0;
54        while section < self.flattened.len() {
55            if let Some(index) = self.flattened[section].declaration(name) {
56                return Some(offset + index);
57            }
58            offset += self.flattened[section].count();
59            section += 1;
60        }
61        None
62    }
63
64    /// Whether this section, or a section it flattens, forbids a lowercase field name.
65    pub const fn forbids(&self, name: &str) -> bool {
66        if contains(self.forbidden, name) {
67            return true;
68        }
69        let mut section = 0;
70        while section < self.flattened.len() {
71            if self.flattened[section].forbids(name) {
72                return true;
73            }
74            section += 1;
75        }
76        false
77    }
78
79    /// Whether the section rejects fields it neither declares nor forbids: because it says so, or because a section it flattens does.
80    pub const fn rejects_unknown(&self) -> bool {
81        if self.rejects_unknown {
82            return true;
83        }
84        let mut section = 0;
85        while section < self.flattened.len() {
86            if self.flattened[section].rejects_unknown() {
87                return true;
88            }
89            section += 1;
90        }
91        false
92    }
93
94    /// Whether this section, at any level, declares or forbids a lowercase field name.
95    #[doc(hidden)]
96    pub const fn names(&self, name: &str) -> bool {
97        self.declaration(name).is_some() || self.forbids(name)
98    }
99
100    /// Whether this section, at any level, declares or forbids a name that `other` also does.
101    #[doc(hidden)]
102    pub const fn shares_names_with(&self, other: &FieldNames) -> bool {
103        let mut index = 0;
104        while index < self.declared.len() {
105            if other.names(self.declared[index]) {
106                return true;
107            }
108            index += 1;
109        }
110        let mut index = 0;
111        while index < self.forbidden.len() {
112            if other.names(self.forbidden[index]) {
113                return true;
114            }
115            index += 1;
116        }
117        let mut section = 0;
118        while section < self.flattened.len() {
119            if self.flattened[section].shares_names_with(other) {
120                return true;
121            }
122            section += 1;
123        }
124        false
125    }
126}
127
128const fn contains(names: &[&str], name: &str) -> bool {
129    let mut index = 0;
130    while index < names.len() {
131        if equal(names[index], name) {
132            return true;
133        }
134        index += 1;
135    }
136    false
137}
138
139const fn equal(left: &str, right: &str) -> bool {
140    let (left, right) = (left.as_bytes(), right.as_bytes());
141    if left.len() != right.len() {
142        return false;
143    }
144    let mut index = 0;
145    while index < left.len() {
146        if left[index] != right[index] {
147            return false;
148        }
149        index += 1;
150    }
151    true
152}