protobuf_core/
field_number.rs

1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::ProtobufError;
16use ::std::convert::TryFrom;
17/// A validated Protocol Buffers field number.
18///
19/// Field numbers must be in the range [1, 2^29 - 1].
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub struct FieldNumber(u32);
22
23impl FieldNumber {
24    /// Minimum allowed field number (1).
25    pub const MIN: Self = Self(1);
26
27    /// Maximum allowed field number (2^29 - 1 = 536,870,911).
28    pub const MAX: Self = Self(536_870_911);
29
30    /// Maximum field number that can be encoded as a single-byte tag (15).
31    /// Field numbers 1-15 result in tags that fit in 1 byte.
32    pub const MAX_SINGLE_BYTE_TAG: u32 = 15;
33
34    /// Start of reserved field number range (19000).
35    /// Protobuf reserves field numbers 19000-19999.
36    pub const RESERVED_RANGE_START: u32 = 19000;
37
38    /// End of reserved field number range (19999).
39    /// Protobuf reserves field numbers 19000-19999.
40    pub const RESERVED_RANGE_END: u32 = 19999;
41
42    /// Creates a new field number, validating the range.
43    pub fn try_new(value: u32) -> Result<Self, ProtobufError> {
44        if !(Self::MIN.0..=Self::MAX.0).contains(&value) {
45            return Err(ProtobufError::FieldNumberOutOfRange {
46                value: value.to_string(),
47            });
48        }
49        Ok(Self(value))
50    }
51
52    /// Checks if the field number can be encoded as a single-byte tag.
53    /// Field numbers 1-15 result in tags that fit in 1 byte.
54    pub fn is_tag_single_byte(&self) -> bool {
55        self.0 <= Self::MAX_SINGLE_BYTE_TAG
56    }
57
58    /// Checks if the field number is in a reserved range.
59    /// Protobuf reserves field numbers 19000-19999, inclusive.
60    pub fn is_reserved(&self) -> bool {
61        self.0 >= Self::RESERVED_RANGE_START && self.0 <= Self::RESERVED_RANGE_END
62    }
63
64    /// Checks if the field number is in a specific range, inclusive.
65    pub fn is_in_range(&self, min: u32, max: u32) -> bool {
66        self.0 >= min && self.0 <= max
67    }
68
69    /// Returns the minimum number of bytes needed to encode the tag (field_number + wire_type) as a varint.
70    ///
71    /// The tag is encoded as: (field_number << 3) | wire_type
72    /// Since wire_type is 0-7, the maximum tag value is (field_number * 8) + 7
73    pub fn encoded_size(&self) -> usize {
74        let max_tag_value = (self.0 << 3) | 7; // field_number << 3 + max wire_type (7)
75
76        match max_tag_value {
77            0..=0x7F => 1,
78            0x80..=0x3FFF => 2,
79            0x4000..=0x1FFFFF => 3,
80            0x200000..=0xFFFFFFF => 4,
81            _ => 5,
82        }
83    }
84
85    /// Returns the field number as a `u32`.
86    pub fn as_u32(&self) -> u32 {
87        self.0
88    }
89
90    /// Returns the filed number as a `i32`
91    pub fn as_i32(&self) -> i32 {
92        self.0 as i32
93    }
94
95    /// Returns the field number as a `usize`.
96    pub fn as_usize(&self) -> usize {
97        self.0 as usize
98    }
99}
100
101impl TryFrom<u32> for FieldNumber {
102    type Error = ProtobufError;
103
104    fn try_from(value: u32) -> Result<Self, Self::Error> {
105        Self::try_new(value)
106    }
107}
108
109impl From<FieldNumber> for u32 {
110    fn from(field_number: FieldNumber) -> Self {
111        field_number.as_u32()
112    }
113}
114
115impl TryFrom<i32> for FieldNumber {
116    type Error = ProtobufError;
117
118    fn try_from(value: i32) -> Result<Self, Self::Error> {
119        Self::try_new(
120            value
121                .try_into()
122                .map_err(|_| ProtobufError::FieldNumberOutOfRange {
123                    value: value.to_string(),
124                })?,
125        )
126    }
127}
128
129impl From<FieldNumber> for i32 {
130    fn from(field_number: FieldNumber) -> Self {
131        field_number.as_i32()
132    }
133}
134
135impl From<FieldNumber> for usize {
136    fn from(field_number: FieldNumber) -> Self {
137        field_number.as_usize()
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_field_number_creation() {
147        assert!(FieldNumber::try_new(1).is_ok());
148        assert!(FieldNumber::try_new(16).is_ok());
149        assert!(FieldNumber::try_new(536_870_911).is_ok());
150
151        assert!(FieldNumber::try_new(0).is_err());
152        assert!(FieldNumber::try_new(536_870_912).is_err());
153    }
154
155    #[test]
156    fn test_field_number_conversion() {
157        let field = FieldNumber::try_new(42).unwrap();
158        assert_eq!(u32::from(field), 42);
159        assert_eq!(usize::from(field), 42);
160        assert_eq!(field.as_u32(), 42);
161        assert_eq!(field.as_usize(), 42);
162    }
163
164    #[test]
165    fn test_field_number_try_from() {
166        let result = FieldNumber::try_from(1);
167        assert!(result.is_ok());
168        let field_number = result.unwrap();
169        assert_eq!(field_number.as_u32(), 1);
170
171        let result = FieldNumber::try_from(0);
172        assert!(result.is_err());
173        if let Err(ProtobufError::FieldNumberOutOfRange { value }) = result {
174            assert_eq!(value, "0");
175        } else {
176            panic!("Expected FieldNumberOutOfRange error");
177        }
178    }
179
180    #[test]
181    fn test_helper_methods() {
182        let single_byte_field = FieldNumber::try_new(15).unwrap();
183        let multi_byte_field = FieldNumber::try_new(16).unwrap();
184        let large_field = FieldNumber::try_new(1000).unwrap();
185        let reserved_field = FieldNumber::try_new(19500).unwrap();
186
187        assert!(single_byte_field.is_tag_single_byte());
188        assert!(!multi_byte_field.is_tag_single_byte());
189        assert!(!large_field.is_tag_single_byte());
190
191        assert!(reserved_field.is_reserved());
192        assert!(!single_byte_field.is_reserved());
193
194        assert!(single_byte_field.is_in_range(1, 20));
195        assert!(!single_byte_field.is_in_range(20, 30));
196    }
197
198    #[test]
199    fn test_encoded_size() {
200        // Field 1: (1 << 3) | 7 = 8 | 7 = 15 (1 byte)
201        assert_eq!(FieldNumber::try_new(1).unwrap().encoded_size(), 1);
202
203        // Field 16: (16 << 3) | 7 = 128 | 7 = 135 (2 bytes)
204        assert_eq!(FieldNumber::try_new(16).unwrap().encoded_size(), 2);
205
206        // Field 100: (100 << 3) | 7 = 800 | 7 = 807 (2 bytes)
207        assert_eq!(FieldNumber::try_new(100).unwrap().encoded_size(), 2);
208
209        // Field 10000: (10000 << 3) | 7 = 80000 | 7 = 80007 (3 bytes)
210        assert_eq!(FieldNumber::try_new(10000).unwrap().encoded_size(), 3);
211
212        // Field 1000000: (1000000 << 3) | 7 = 8000000 | 7 = 8000007 (4 bytes)
213        assert_eq!(FieldNumber::try_new(1000000).unwrap().encoded_size(), 4);
214
215        // Field 100000000: (100000000 << 3) | 7 = 800000000 | 7 = 800000007 (5 bytes)
216        assert_eq!(FieldNumber::try_new(100000000).unwrap().encoded_size(), 5);
217    }
218
219    #[test]
220    fn test_as_u32_and_as_usize() {
221        let min_field = FieldNumber::MIN;
222        let max_field = FieldNumber::MAX;
223        let mid_field = FieldNumber::try_new(1000).unwrap();
224
225        // Test as_u32
226        assert_eq!(min_field.as_u32(), 1);
227        assert_eq!(max_field.as_u32(), 536_870_911);
228        assert_eq!(mid_field.as_u32(), 1000);
229
230        // Test as_usize
231        assert_eq!(min_field.as_usize(), 1);
232        assert_eq!(max_field.as_usize(), 536_870_911);
233        assert_eq!(mid_field.as_usize(), 1000);
234    }
235}