Skip to main content

ixa_fips/
fips_code.rs

1//! Defines the `FIPSCode` types to represent FIPS geographic region codes (and “code fragments”) very efficiently.
2//!
3//! # Encoding Scheme
4//!
5//! A table of how FIPS Geo IDs are structured is provided in the module-level documentation for [`crate::parser`]
6//! (slightly modified from
7//! [the source table in the standard](https://www.census.gov/programs-surveys/geography/guidance/geo-identifiers.html)).
8//! The rows in the table up to and including Block (that is, all but the last five rows) form a linear order with
9//! respect to prefix inclusion ("is prefix of"). This encoding scheme is for these codes. The last four rows are
10//! treated separately.
11//!
12//! In the following table, we describe the data "fragments" and their storage requirements.
13//!
14//! |                                   | **Decimal Digits** | **Actual Max Value** | **Bits** |    **Capacity (`2^bits - 1`)** |
15//! |:--------------------------------- | ------------------:| --------------------:| --------:| ------------------------------:|
16//! | **Sate**                          |                  2 |                   56 |        7 |                            127 |
17//! | **County**                        |                  3 |                  840 |       10 |                          1,023 |
18//! | **Tract**                         |                  6 |              990,101 |       20 |                      1,048,575 |
19//! | **Subtotal**                      |                    |                      |   **37** | **Bits needed for tract code** |
20//! |                                   |                    |                      |          |                                |
21//! | **Monotonically Increasing Id's** |                    |                      |          |                                |
22//! | **homeId**                        |                  4 |                9,999 |       14 |                         16,383 |
23//! | **publicschoolId**                |                  3 |                  999 |       10 |                          1,023 |
24//! | **privateschoolId**               |                  4 |                1,722 |       11 |                          2,047 |
25//! | **workplaceId**                   |                  5 |               14,938 |       14 |                         16,383 |
26//! | **Max:**                          |                    |                      |   **14** |                                |
27//! | **Total:**                        |                    |                      |   **51** |                                |
28//!
29//! State codes for states have values <= 56, but there are "state codes" for outlying areas, some historic codes, and
30//! maritime extension codes in use in the wild. We therefore use an extra bit than strictly required to represent it.
31//! To the 51 bits apparently required to store this data we add an additional 4 bits for a category tag to distinguish
32//! between home, public school, private school, workplace, and cencus tract, a field useful for representing ASPR
33//! synthetic population data, for example. Only 2 bits are required to distinguish these 4 categories, so the additional
34//! 2 bits are left unused / for future use.
35//!
36//! We encode this data into a `u64` as follows:
37//!
38//!  | **Data**               |     **State** | **County** | **Tract** |  **Category Tag** | **Monotonically increasing ID number** | **Reserved / Unused** |
39//!  |:---------------------- | -------------:| ----------:| ---------:| -----------------:| --------------------------------------:| ---------------------:|
40//!  | **Bits**               |         63…57 |      57…47 |     46…27 |             26…23 |                                   22…9 |                   8…0 |
41//!  | **Ex. Value**          | `AK`, `AZ`, … |        258 |   223,100 | `Home`, `Work`, … |                                 12,345 |                     0 |
42//!  | **Bit Count**          |             7 |         10 |        20 |                 4 |                                     14 |                     9 |
43//!  | **Capacity**           |           128 |      1,024 | 1,048,576 |                16 |                                 16,384 |                   512 |
44//!  | **Decimal Digits**     |             2 |          3 |         6 |                 - |                                 3 to 5 |                     - |
45//!  | **Max Observed Value** |            56 |        840 |   990,101 |                 4 |                                 14,938 |                     - |
46//!
47//! Observe that:
48//!
49//!  - We give the "category tag" 4 bits to allow up to 16 distinct categories. In some applications this field might be unused.
50//!  - The least significant 9 bits is completely unused by this encoding. It may be used for application-specific storage.
51//!  - The field for ID number only requires 10 bits for `publicschoolId`, for example. That is, the storage it requires
52//!    depends on the category tag.
53//!  - The category tag is encoded after the tract code but before the ID field so that numerical ordering coincides with
54//!    the hierarchical ordering.
55//!  - Likewise, the unused 9 bits are the least significant bits so that numerical ordering coincides with the
56//!    hierarchical ordering modulo those bits.
57//!
58//! # Nonhierarchical FIPS Codes
59//!
60//! The encoding of the previous section excludes the nonhierarchical codes of the last five rows from the first table
61//! above:
62//!
63//!  - Places
64//!  - Congressional District (113th Congress)
65//!  - State Legislative District (Upper Chamber)
66//!  - State Legislative District (Lower Chamber)
67//!  - ZCTA
68//!
69//! We could easily accommodate these codes as well in a variety of ways, e.g.:
70//!  - assign each of these a category tag and store their corresponding code fragments in the ID field
71//!  - use the 14 bits of the ID field and the unused 10 least significant bits, allowing the category tag to remain
72//!    orthogonal
73//!
74//! We leave them unspecified until we have a use case for them.
75use std::cmp::Ordering;
76use std::fmt::{Debug, Display, Formatter};
77use std::num::NonZero;
78
79use crate::errors::FIPSError;
80use crate::states::USState;
81use crate::{
82    CountyCode, DataCode, IdCode, SettingCategoryCode, StateCode, TractCode, CATEGORY_OFFSET,
83    COUNTY_OFFSET, FOURTEEN_BIT_MASK, FOUR_BIT_MASK, ID_OFFSET, NINE_BIT_MASK, STATE_OFFSET,
84    TEN_BIT_MASK, TRACT_OFFSET, TWENTY_BIT_MASK,
85};
86
87/// Encodes a hierarchical FIPS geographic region code in 64 bits. Excludes the nonhierarchical codes places,
88/// congressional or state legislative districts, and ZIP code tabulation areas. (See the
89/// [module level documentation](`crate::fips_code`).)
90#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
91pub struct FIPSCode(NonZero<u64>);
92
93impl FIPSCode {
94    // region Constructors
95    /// Constructs a new [`FIPSCode`] from a [`USState`]. Unlike the other constructors, this constructor is infallible.
96    #[must_use]
97    pub fn with_state(state: USState) -> Self {
98        Self::new(state.into(), 0, 0, 0, 0, 0).unwrap()
99    }
100    /// Constructs a new [`FIPSCode`].
101    /// Returns `Err(())` if the data provided is out of range.
102    pub fn with_state_code(state_code: StateCode) -> Result<Self, FIPSError> {
103        Self::new(state_code, 0, 0, 0, 0, 0)
104    }
105    /// Constructs a new [`FIPSCode`].
106    /// Returns `Err(())` if the data provided is out of range.
107    pub fn with_county(state: StateCode, county: CountyCode) -> Result<Self, FIPSError> {
108        Self::new(state, county, 0, 0, 0, 0)
109    }
110    /// Constructs a new [`FIPSCode`].
111    /// Returns `Err(())` if the data provided is out of range.
112    pub fn with_tract(
113        state: StateCode,
114        county: CountyCode,
115        tract: TractCode,
116    ) -> Result<Self, FIPSError> {
117        Self::new(state, county, tract, 0, 0, 0)
118    }
119    /// Constructs a new [`FIPSCode`].
120    /// Returns `Err(())` if the data provided is out of range.
121    pub fn with_category(
122        state: StateCode,
123        county: CountyCode,
124        tract: TractCode,
125        category: SettingCategoryCode,
126    ) -> Result<Self, FIPSError> {
127        Self::new(state, county, tract, category, 0, 0)
128    }
129
130    pub fn new(
131        state: StateCode,
132        county: CountyCode,
133        tract: TractCode,
134        category: SettingCategoryCode,
135        id: IdCode,
136        data: DataCode,
137    ) -> Result<Self, FIPSError> {
138        let encoded: u64 = Self::encode_state(state)?
139            | Self::encode_county(county)?
140            | Self::encode_tract(tract)?
141            | Self::encode_category(category)?
142            | Self::encode_id(id)?
143            | Self::encode_data(data)?;
144        // At the very least, `USState.encode()` will return a non-zero value, so this unwrapping is safe.
145        let encoded = NonZero::new(encoded).unwrap();
146        Ok(Self(encoded))
147    }
148    // endregion Constructors
149
150    // region Accessors
151
152    /// Returns the FIPS STATE as a [`USState`] enum variant.
153    /// Returns `Err(())` if [`USState`] cannot represent the state code. Use [`FIPSCode::state_code()`] to
154    /// retrieve the state code in this case.
155    #[inline(always)]
156    pub fn state(&self) -> Result<USState, FIPSError> {
157        USState::decode(self.state_code())
158    }
159
160    /// Returns the FIPS STATE code as a [`StateCode`] (a `u8`)
161    #[inline(always)]
162    #[must_use]
163    pub fn state_code(&self) -> StateCode {
164        // The state code occupies the 7 most significant bits, bits 57..63
165        (self.0.get() >> STATE_OFFSET) as StateCode
166    }
167
168    /// Returns the numeric FIPS COUNTY code
169    #[inline(always)]
170    #[must_use]
171    pub fn county_code(&self) -> CountyCode {
172        // The county code occupies the 10 bits from bits 47..56
173        ((self.0.get() >> COUNTY_OFFSET) as CountyCode) & TEN_BIT_MASK
174    }
175
176    /// Returns the numeric FIPS CENSUS TRACT code
177    #[inline(always)]
178    #[must_use]
179    pub fn census_tract_code(&self) -> TractCode {
180        // The census tract code occupies the 20 bits from bits 27..46
181        ((self.0.get() >> TRACT_OFFSET) as TractCode) & TWENTY_BIT_MASK
182    }
183
184    /// Returns the numeric SETTING CATEGORY code
185    #[inline(always)]
186    #[must_use]
187    pub fn category_code(&self) -> SettingCategoryCode {
188        // The category code occupies the 4 bits from bits 23..26
189        ((self.0.get() >> CATEGORY_OFFSET) as SettingCategoryCode) & FOUR_BIT_MASK
190    }
191
192    /// Returns the monotonically increasing ID number as an [`IdCode`]
193    #[inline(always)]
194    #[must_use]
195    pub fn id(&self) -> IdCode {
196        // The ID number occupies the 14 bits from bits 9..22
197        ((self.0.get() >> ID_OFFSET) as IdCode) & FOURTEEN_BIT_MASK
198    }
199
200    /// Returns the unused data region occupying the 9 LSB
201    #[inline(always)]
202    #[must_use]
203    pub fn data(&self) -> DataCode {
204        self.0.get() as DataCode & NINE_BIT_MASK
205    }
206    // endregion Accessors
207
208    // region Setters
209
210    /// Creates a copy of `self` with the FIPS STATE set to `state`.
211    #[must_use]
212    pub fn set_state(&self, state: USState) -> Self {
213        self.set_state_code(state.into()).unwrap()
214    }
215
216    /// Creates a copy of `self` with the FIPS STATE set to `state`.
217    pub fn set_state_code(&self, state_code: StateCode) -> Result<Self, FIPSError> {
218        let mut expanded = ExpandedFIPSCode::from_fips_code(*self);
219        expanded.state = state_code;
220        expanded.to_fips_code()
221    }
222
223    /// Creates a copy of `self` with the FIPS COUNTY set to `county`.
224    pub fn set_county(&self, county: CountyCode) -> Result<Self, FIPSError> {
225        let mut expanded = ExpandedFIPSCode::from_fips_code(*self);
226        expanded.county = county;
227        expanded.to_fips_code()
228    }
229
230    /// Creates a copy of `self` with the FIPS CENSUS TRACT set to `tract`.
231    pub fn set_tract(&self, tract: TractCode) -> Result<Self, FIPSError> {
232        let mut expanded = ExpandedFIPSCode::from_fips_code(*self);
233        expanded.tract = tract;
234        expanded.to_fips_code()
235    }
236
237    /// Creates a copy of `self` with the setting category set to `category`.
238    pub fn set_category(&self, category: SettingCategoryCode) -> Result<Self, FIPSError> {
239        let mut expanded = ExpandedFIPSCode::from_fips_code(*self);
240        expanded.category = category;
241        expanded.to_fips_code()
242    }
243
244    /// Creates a copy of `self` with the ID number set to `id`.
245    pub fn set_id(&self, id: IdCode) -> Result<Self, FIPSError> {
246        let mut expanded = ExpandedFIPSCode::from_fips_code(*self);
247        expanded.id = id;
248        expanded.to_fips_code()
249    }
250
251    /// Creates a copy of `self` with the unused data region set to `data`.
252    pub fn set_data(&self, data: DataCode) -> Result<Self, FIPSError> {
253        let mut expanded = ExpandedFIPSCode::from_fips_code(*self);
254        expanded.data = data;
255        expanded.to_fips_code()
256    }
257
258    // endregion Setters
259
260    /// Sets the unused data region occupying the 10 LSB in place.
261    /// Returns `Ok(())` if `data` is in range, `Err(FIPSError)` otherwise.
262    #[inline(always)]
263    pub fn set_data_in_place(&mut self, data: DataCode) -> Result<(), FIPSError> {
264        if data <= NINE_BIT_MASK {
265            let inverse_mask = !(NINE_BIT_MASK as u64);
266            let code = (self.0.get() & inverse_mask) | ((data & NINE_BIT_MASK) as u64);
267            // The result is guaranteed to be nonzero if the original code was valid, so unwrap will succeed.
268            self.0 = NonZero::new(code).unwrap();
269            Ok(())
270        } else {
271            Err(FIPSError::from_data_code(data))
272        }
273    }
274
275    /// Compares the given values without respect to the data region (the Least Significant Bits). Use the usual
276    /// equality operators for comparing `FIPSCode`s including the data region.
277    #[inline(always)]
278    #[must_use]
279    pub fn compare_non_data(&self, other: Self) -> Ordering {
280        let inverse_mask = !(NINE_BIT_MASK as u64);
281        let this = self.0.get() & inverse_mask;
282        let other = other.0.get() & inverse_mask;
283
284        this.cmp(&other)
285    }
286
287    // region Encoding
288    // It is convenient to factor out the encode operations into their own functions.
289    // These functions take numeric values and return encoded `u64` values. To encode
290    // enum variants, call the `encode` function on the enum variant.
291
292    #[inline(always)]
293    fn encode_state(state: StateCode) -> Result<u64, FIPSError> {
294        // Validate: Two decimal digits
295        if state <= 99 && state != 0 {
296            Ok((state as u64) << STATE_OFFSET)
297        } else {
298            Err(FIPSError::from_state_code(state))
299        }
300    }
301
302    #[inline(always)]
303    fn encode_county(county: CountyCode) -> Result<u64, FIPSError> {
304        // Validate: Three decimal digits
305        if county <= 999 {
306            Ok((county as u64) << COUNTY_OFFSET)
307        } else {
308            Err(FIPSError::from_county_code(county))
309        }
310    }
311
312    #[inline(always)]
313    fn encode_tract(tract: TractCode) -> Result<u64, FIPSError> {
314        // Validate: Six decimal digits
315        if tract <= 999_999 {
316            Ok((tract as u64) << TRACT_OFFSET)
317        } else {
318            Err(FIPSError::from_tract_code(tract))
319        }
320    }
321
322    #[inline(always)]
323    fn encode_category(setting_category: SettingCategoryCode) -> Result<u64, FIPSError> {
324        // Validate
325        if setting_category <= FOUR_BIT_MASK {
326            Ok((setting_category as u64) << CATEGORY_OFFSET)
327        } else {
328            Err(FIPSError::from_setting_category_code(setting_category))
329        }
330    }
331
332    #[inline(always)]
333    fn encode_id(id: IdCode) -> Result<u64, FIPSError> {
334        // Validate
335        if id <= FOURTEEN_BIT_MASK {
336            Ok((id as u64) << ID_OFFSET)
337        } else {
338            Err(FIPSError::from_id_code(id))
339        }
340    }
341
342    #[inline(always)]
343    fn encode_data(data: DataCode) -> Result<u64, FIPSError> {
344        // Validate
345        if data <= NINE_BIT_MASK {
346            Ok(data as u64)
347        } else {
348            Err(FIPSError::from_data_code(data))
349        }
350    }
351    // endregion Encoding
352}
353
354impl Display for FIPSCode {
355    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
356        write!(f, "{}", ExpandedFIPSCode::from_fips_code(*self))
357    }
358}
359
360impl Debug for FIPSCode {
361    /// Format the code as a string of hex digits with fields separated by dashes. Note that this is different
362    /// from serializing to the original FIPS code encoding. Use `format_as_fips_code`/`format_as_fips_code`
363    /// for that purpose.
364    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
365        write!(f, "{:02}", self.state_code())?;
366        write!(f, "-{:03}", self.county_code())?;
367        write!(f, "-{:06}", self.census_tract_code())?;
368        write!(f, "-{:01}", self.category_code())?;
369        write!(f, "-{:05}", self.id())?;
370        write!(f, "-{:03x}", self.data())?;
371        Ok(())
372    }
373}
374
375/// A struct that holds an expanded version of a [`FIPSCode`] in which all fields are represented by
376/// their associated numeric types.
377///
378/// It is up to the client code to ensure the field values are within
379/// range. See the module level docs for range constraints.
380///
381/// This struct is useful for converting raw data to/from a [`FIPSCode`] for temporary direct field access.
382#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
383pub struct ExpandedFIPSCode {
384    pub state: StateCode,
385    pub county: CountyCode,
386    pub tract: TractCode,
387    pub category: SettingCategoryCode,
388    pub id: IdCode,
389    pub data: DataCode,
390}
391
392impl ExpandedFIPSCode {
393    #[must_use]
394    pub fn from_fips_code(fips_code: FIPSCode) -> Self {
395        Self {
396            state: fips_code.state_code(),
397            county: fips_code.county_code(),
398            tract: fips_code.census_tract_code(),
399            category: fips_code.category_code(),
400            id: fips_code.id(),
401            data: fips_code.data(),
402        }
403    }
404
405    pub fn to_fips_code(&self) -> Result<FIPSCode, FIPSError> {
406        FIPSCode::new(
407            self.state,
408            self.county,
409            self.tract,
410            self.category,
411            self.id,
412            self.data,
413        )
414    }
415}
416
417impl Display for ExpandedFIPSCode {
418    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
419        // Format the state if possible
420        if let Ok(state) = USState::decode(self.state) {
421            write!(f, "state: {}", state.as_ref())?;
422        } else {
423            write!(f, "state: {}", self.state)?;
424        }
425        // For the remaining fields, only print them if they are nonzero
426        if self.county != 0 {
427            write!(f, ", county: {}", self.county)?;
428        }
429        if self.tract != 0 {
430            write!(f, ", tract: {}", self.tract)?;
431        }
432        if self.category != 0 {
433            write!(f, ", setting: {}", self.category)?;
434        }
435        if self.id != 0 {
436            write!(f, ", id: {}", self.id)?;
437        }
438        if self.data != 0 {
439            write!(f, ", data field: {}", self.data)?;
440        }
441
442        Ok(())
443    }
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449
450    #[repr(u8)]
451    enum SettingCategory {
452        Unspecified = 0,
453        Home,
454        School,
455        Work,
456        CensusTract,
457    }
458
459    impl From<SettingCategory> for SettingCategoryCode {
460        fn from(value: SettingCategory) -> Self {
461            value as SettingCategoryCode
462        }
463    }
464
465    #[test]
466    fn test_data_ranges() {
467        // Encode functions
468        assert!(FIPSCode::encode_state(99).is_ok());
469        assert!(FIPSCode::encode_state(100).is_err());
470        assert!(FIPSCode::encode_county(999).is_ok());
471        assert!(FIPSCode::encode_county(1000).is_err());
472        assert!(FIPSCode::encode_tract(999_999).is_ok());
473        assert!(FIPSCode::encode_tract(1_000_000).is_err());
474        assert!(FIPSCode::encode_category(FOUR_BIT_MASK).is_ok());
475        assert!(FIPSCode::encode_category(FOUR_BIT_MASK + 1).is_err());
476        assert!(FIPSCode::encode_id(FOURTEEN_BIT_MASK).is_ok());
477        assert!(FIPSCode::encode_id(FOURTEEN_BIT_MASK + 1).is_err());
478        assert!(FIPSCode::encode_data(NINE_BIT_MASK).is_ok());
479        assert!(FIPSCode::encode_data(NINE_BIT_MASK + 1).is_err());
480        // Constructors
481        assert!(FIPSCode::with_state_code(1).is_ok());
482        assert!(FIPSCode::with_state_code(0).is_err());
483        assert!(FIPSCode::with_state_code(100).is_err());
484        assert!(FIPSCode::with_county(1, 0).is_ok());
485        assert!(FIPSCode::with_county(1, 1000).is_err());
486        assert!(FIPSCode::with_tract(1, 0, 0).is_ok());
487        assert!(FIPSCode::with_tract(1, 0, 1_000_000).is_err());
488        assert!(FIPSCode::with_category(1, 0, 0, 0).is_ok());
489        assert!(FIPSCode::with_category(1, 0, 0, FOUR_BIT_MASK + 1).is_err());
490    }
491
492    #[test]
493    fn fields_round_trip() {
494        let fips_code = FIPSCode::new(
495            USState::TX.into(),
496            123,
497            990101,
498            SettingCategory::Home.into(),
499            14938,
500            123,
501        )
502        .unwrap();
503        assert_eq!(fips_code.state().unwrap(), USState::TX);
504        assert_eq!(fips_code.county_code(), 123);
505        assert_eq!(fips_code.census_tract_code(), 990101);
506        assert_eq!(fips_code.category_code(), SettingCategory::Home.into());
507        assert_eq!(fips_code.id(), 14938);
508        assert_eq!(fips_code.data(), 123);
509    }
510
511    #[test]
512    fn expanded_round_trip() {
513        let fips_code = FIPSCode::new(
514            USState::TX.into(),
515            123,
516            990101,
517            SettingCategory::Home.into(),
518            14938,
519            0x01ff,
520        )
521        .unwrap();
522        let expanded = ExpandedFIPSCode::from_fips_code(fips_code);
523        let result = expanded.to_fips_code().unwrap();
524        assert_eq!(result, fips_code);
525    }
526
527    #[test]
528    fn test_compare_non_data() {
529        let fips_code_a = FIPSCode::new(
530            USState::TX.into(),
531            123,
532            990101,
533            SettingCategory::Home.into(),
534            14938,
535            0x01ff,
536        )
537        .unwrap();
538        let fips_code_b = FIPSCode::new(
539            USState::TX.into(),
540            123,
541            990101,
542            SettingCategory::Home.into(),
543            14938,
544            0x00ff,
545        )
546        .unwrap();
547
548        assert_eq!(fips_code_a.compare_non_data(fips_code_b), Ordering::Equal);
549        assert_eq!(fips_code_a.cmp(&fips_code_b), Ordering::Greater);
550    }
551
552    #[test]
553    fn test_set_id() {
554        // Exercises case that triggered a bug that causes a panic.
555        let fips_code = FIPSCode::with_category(
556            USState::AK.into(),
557            0,
558            0,
559            SettingCategory::CensusTract.into(),
560        )
561        .unwrap();
562
563        let other_fips_code = fips_code.set_id(0).unwrap();
564        assert_eq!(fips_code, other_fips_code);
565    }
566
567    #[test]
568    fn test_fips_error() {
569        let err = FIPSError::new("foo", 1, 2, 3);
570        assert_eq!(
571            format!("{}", err),
572            "value 1 provided for foo is outside valid range of 2..3"
573        );
574
575        let e = USState::decode(58).unwrap_err();
576        assert_eq!(
577            e.to_string(),
578            "value 58 provided for USState Code is outside valid range of 1..57"
579        );
580
581        let e = FIPSCode::encode_state(100).unwrap_err();
582        assert_eq!(
583            e.to_string(),
584            "value 100 provided for StateCode is outside valid range of 1..100"
585        );
586
587        let e = FIPSCode::encode_state(0).unwrap_err();
588        assert_eq!(
589            e.to_string(),
590            "value 0 provided for StateCode is outside valid range of 1..100"
591        );
592
593        let e = FIPSCode::encode_county(1000).unwrap_err();
594        assert_eq!(
595            e.to_string(),
596            "value 1000 provided for CountyCode is outside valid range of 0..1000"
597        );
598
599        let e = FIPSCode::encode_tract(1_000_000).unwrap_err();
600        assert_eq!(
601            e.to_string(),
602            "value 1000000 provided for TractCode is outside valid range of 0..1000000"
603        );
604
605        let e = FIPSCode::encode_category(16).unwrap_err();
606        assert_eq!(
607            e.to_string(),
608            "value 16 provided for SettingCategoryCode is outside valid range of 0..16"
609        );
610
611        let e = FIPSCode::encode_id(16_384).unwrap_err();
612        assert_eq!(
613            e.to_string(),
614            "value 16384 provided for IdCode is outside valid range of 0..16384"
615        );
616
617        let e = FIPSCode::encode_data(512).unwrap_err();
618        assert_eq!(
619            e.to_string(),
620            "value 512 provided for DataCode is outside valid range of 0..512"
621        );
622    }
623}