nhs_number/
lib.rs

1//! # NHS Number
2//!
3//! A National Health Service (NHS) Number is a unique number allocated in a shared
4//! numbering scheme to registered users of the three public health services in
5//! England, Wales, and the Isle of Man.
6//!
7//! The NHS Number is the key to the identification of patients, especially in
8//! delivering safe care across provider organisations, and is required in all new
9//! software deployed within the National Health Service (NHS) organizations.
10//!
11//! References:
12//!
13//! * [National Health Service (NHS)](https://en.wikipedia.org/wiki/National_Health_Service)
14//!
15//! * [NHS Number](https://en.wikipedia.org/wiki/NHS_number)
16//!
17//! ## Syntax
18//!
19//! The current system uses a ten-digit number in '3 3 4' format with the final
20//! digit being an error-detecting checksum. Examples given include 987 654 4321.
21//!
22//! ## Ranges
23//!
24//! Currently issued numbers are in these ranges:
25//!
26//! * 300 000 000 to 399 999 999 (England)
27//!
28//! * 400 000 000 to 499 999 999 (England, Wales, Isle of Man)
29//!
30//! * 600 000 000 to 799 999 999 (England, Wales, Isle of Man)
31//!
32//! Unavailable number ranges include:
33//!
34//! * 320 000 001 to 399 999 999 (allocated to the Northern Irish system)
35//!
36//! * 010 100 0000 to 311 299 9999 (used for CHI numbers in Scotland)
37//!
38//! For test purposes, this range is valid but is guaranteed to never be issued:
39//!
40//! * 999 000 0000 to 999 999 9999
41//!
42//! ## Checksum
43//!
44//! The checksum is calculated by multiplying each of the first nine digits by 11
45//! minus its position. Using the number 943 476 5919 as an example:
46//!
47//! * The first digit is 9. This is multiplied by 10.
48//!
49//! * The second digit is 4. This is multiplied by 9.
50//!
51//! * And so on until the ninth digit (1) is multiplied by 2.
52//!
53//! * The result of this calculation is summed. In this example: (9×10) + (4×9) +
54//!   (3×8) + (4×7) + (7×6) + (6×5) + (5×4) + (9×3) + (1×2) = 299.
55//!
56//! * The remainder when dividing this number by 11 is calculated, yielding a number
57//!   in the range 0–10, which would be 2 in this case.
58//!
59//! * Finally, this number is subtracted from 11 to give the checksum in the range
60//!   1–11, in this case 9, which becomes the last digit of the NHS Number.
61//!
62//! * A checksum of 11 is represented by 0 in the final NHS Number. If the checksum
63//!   is 10 then the number is not valid.
64//!
65//! ## Examples
66//!
67//! ```rust
68//! use nhs_number::*;
69//! use std::str::FromStr;
70//! 
71//! // NHS Number that we can use for testing purposes
72//! let str = "999 123 4560";
73//! 
74//! // Create a new NHS Number by converting from a string.
75//! let nhs_number = NHSNumber::from_str(str).unwrap();
76//!
77//! // Validate a NHS Number using the check digit algorithm.
78//! let valid: bool = nhs_number.validate_check_digit();
79//! ```
80//!
81use serde::{Deserialize, Serialize};
82use std::fmt;
83
84pub mod from_str;
85pub mod parse_error;
86pub mod testable;
87pub use testable::*;
88
89/// NHS Number is a unique identifier for patients in the National Health
90/// Service of England, Wales, and the Isle of Man.
91///
92/// Reference:
93///
94/// * [National Health Service (NHS)](https://en.wikipedia.org/wiki/National_Health_Service)
95///
96/// * [NHS Number](https://en.wikipedia.org/wiki/NHS_number)
97///
98/// ```rust
99/// use nhs_number::NHSNumber;
100/// let digits = [9, 9, 9, 1, 2, 3, 4, 5, 6, 0];
101/// let nhs_number = NHSNumber { digits: digits };
102/// ```
103///
104#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Deserialize)]
105pub struct NHSNumber {
106    pub digits: [i8; 10],
107}
108
109impl NHSNumber {
110    /// Create a new NHS Number instance with the provided digits.
111    ///
112    /// Example:
113    ///
114    /// ```rust
115    /// use nhs_number::NHSNumber;
116    /// let digits = [9, 9, 9, 1, 2, 3, 4, 5, 6, 0];
117    /// let nhs_number = NHSNumber::new(digits);
118    /// ```
119    ///
120    #[allow(dead_code)]
121    pub fn new(digits: [i8; 10]) -> Self {
122        NHSNumber { digits }
123    }
124
125    /// Get the NHS Number check digit i.e. the last digit.
126    ///
127    /// Example:
128    ///
129    /// ```rust
130    /// use nhs_number::NHSNumber;
131    /// let digits = [9, 9, 9, 1, 2, 3, 4, 5, 6, 0];
132    /// let nhs_number = NHSNumber::new(digits);
133    /// let check_digit = nhs_number.check_digit();
134    /// assert_eq!(check_digit, 0);
135    /// ```
136    ///
137    /// This method calls the function [check_digit()].
138    ///
139    #[allow(dead_code)]
140    pub fn check_digit(&self) -> i8 {
141        crate::check_digit(self.digits)
142    }
143
144    /// Calculate the NHS Number check digit using a checksum algorithm.
145    ///
146    /// Example:
147    ///
148    /// ```rust
149    /// use nhs_number::NHSNumber;
150    /// let digits = [9, 9, 9, 1, 2 , 3, 4, 5, 6, 0];
151    /// let nhs_number = NHSNumber::new(digits);
152    /// let check_digit = nhs_number.calculate_check_digit();
153    /// assert_eq!(check_digit, 0);
154    /// ```
155    ///
156    /// This method calls the function [calculate_check_digit()].
157    ///
158    #[allow(dead_code)]
159    pub fn calculate_check_digit(&self) -> i8 {
160        crate::calculate_check_digit(self.digits)
161    }
162
163    /// Validate the NHS Number check digit equals the calculated check digit.
164    ///
165    /// Example:
166    ///     
167    /// ```rust
168    /// use nhs_number::NHSNumber;
169    /// let digits = [9, 9, 9, 1, 2, 3, 4, 5, 6, 0];
170    /// let nhs_number = NHSNumber::new(digits);
171    /// let is_valid = nhs_number.validate_check_digit();
172    /// assert_eq!(is_valid, true);
173    /// ```
174    ///
175    /// This method calls the function [validate_check_digit()].
176    ///
177    #[allow(dead_code)]
178    pub fn validate_check_digit(&self) -> bool {
179        crate::validate_check_digit(self.digits)
180    }
181
182    /// Generate a testable random sample NHS Number.
183    ///
184    /// Example:
185    ///
186    /// ```rust
187    /// use nhs_number::{NHSNumber, testable::{TESTABLE_MIN, TESTABLE_MAX}};
188    /// let sample = NHSNumber::testable_random_sample();
189    /// assert!(sample >= *TESTABLE_MIN);
190    /// assert!(sample <= *TESTABLE_MAX);
191    /// ```
192    ///
193    /// This method calls the function [testable_random_sample()].
194    ///
195    #[allow(dead_code)]
196    pub fn testable_random_sample() -> NHSNumber {
197        crate::testable_random_sample()
198    }
199}
200
201/// Format the NHS Number as a 10-digit number with spaces.
202///
203/// Example:
204///
205/// ```rust
206/// use nhs_number::NHSNumber;
207/// let digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
208/// let nhs_number = NHSNumber::new(digits);
209/// let nhs_number_string = nhs_number.to_string();
210/// assert_eq!(nhs_number_string, "012 345 6789");
211/// ```
212///
213/// The NHS Number is formatted as a 10-digit number with spaces:
214///
215/// * 3 digits
216/// * space
217/// * 3 digits
218/// * space
219/// * 4 digits
220///
221/// This method must be equivalent to the function [format()].
222///
223impl fmt::Display for NHSNumber {
224    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
225        write!(
226            f,
227            "{}{}{} {}{}{} {}{}{}{}",
228            self.digits[0],
229            self.digits[1],
230            self.digits[2],
231            self.digits[3],
232            self.digits[4],
233            self.digits[5],
234            self.digits[6],
235            self.digits[7],
236            self.digits[8],
237            self.digits[9],
238        )
239    }
240}
241
242/// Convert the NHSNumber into a String.
243///
244/// Example:
245/// ```rust
246/// use nhs_number::NHSNumber;
247/// let digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
248/// let nhs_number = NHSNumber::new(digits);
249/// let nhs_number_string: String = nhs_number.into();
250/// assert_eq!(nhs_number_string, "012 345 6789");
251/// ```
252///
253impl Into<String> for NHSNumber {
254    fn into(self) -> String {
255        self.to_string()
256    }
257}
258
259//// Functional utilities
260
261/// Format the NHS Number as a 10-digit number with spaces.
262///
263/// Example:
264///
265/// ```rust
266/// let digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
267/// let nhs_number_string = ::nhs_number::format(digits);
268/// assert_eq!(nhs_number_string, "012 345 6789");
269/// ```
270///
271/// The NHS Number is formatted as a 10-digit number with spaces:
272///
273/// * 3 digits
274/// * space
275/// * 3 digits
276/// * space
277/// * 4 digits
278///
279/// This function must be equivalent to the method
280/// [NHSNumber::Into](NHSNumber::into).
281///
282#[allow(dead_code)]
283pub fn format(digits: [i8; 10]) -> String {
284    format!(
285        "{}{}{} {}{}{} {}{}{}{}",
286        digits[0],
287        digits[1],
288        digits[2],
289        digits[3],
290        digits[4],
291        digits[5],
292        digits[6],
293        digits[7],
294        digits[8],
295        digits[9],
296    )
297}
298
299/// Get the NHS Number check digit i.e. the last digit.
300///
301/// Example:
302///
303/// ```rust
304/// let digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
305/// let check_digit = ::nhs_number::check_digit(digits);
306/// assert_eq!(check_digit, 9);
307/// ```
308///
309/// This function is called by the method [NHSNumber::check_digit](NHSNumber::check_digit).
310///
311#[allow(dead_code)]
312pub fn check_digit(digits: [i8; 10]) -> i8 {
313    digits[9]
314}
315
316/// Calculate the NHS Number check digit using a checksum algorithm.
317///
318/// Example:
319///
320/// ```rust
321/// let digits = [9, 9, 9, 1, 2, 3, 4, 5, 6, 0];
322/// let check_digit = ::nhs_number::calculate_check_digit(digits);
323/// assert_eq!(check_digit, 0);
324/// ```
325///
326/// This function is called by the method [NHSNumber::calculate_check_digit](NHSNumber::calculate_check_digit).
327///
328#[allow(dead_code)]
329pub fn calculate_check_digit(digits: [i8; 10]) -> i8 {
330    let sum: usize = digits
331        .iter()
332        .take(9)
333        .enumerate()
334        .map(|(i, &d)| d as usize * (10 - i as usize))
335        .sum();
336    ((11 - (sum % 11)) % 10) as i8
337}
338
339/// Validate the NHS Number check digit equals the calculated check digit.
340///
341/// Example:
342///     
343/// ```rust
344/// let digits = [9, 9, 9, 1, 2, 3, 4, 5, 6, 0];
345/// let is_valid = nhs_number::validate_check_digit(digits);
346/// assert_eq!(is_valid, true);
347/// ```
348///
349/// This function is called by the method [NHSNumber::validate_check_digit](NHSNumber::validate_check_digit).
350///
351#[allow(dead_code)]
352pub fn validate_check_digit(digits: [i8; 10]) -> bool {
353    crate::check_digit(digits) == crate::calculate_check_digit(digits)
354}
355
356#[cfg(test)]
357mod tests {
358
359    mod structure {
360        use super::super::*;
361
362        #[test]
363        fn test_new() {
364            let a: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
365            let actual = a.to_string();
366            let expect = "012 345 6789";
367            assert_eq!(actual, expect);
368        }
369
370        #[test]
371        fn test_display() {
372            let a: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
373            let actual = a.to_string();
374            let expect = "012 345 6789";
375            assert_eq!(actual, expect);
376        }
377
378        #[test]
379        fn test_into_string() {
380            let a: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
381            let actual: String = a.into();
382            let expect = "012 345 6789";
383            assert_eq!(actual, expect);
384        }
385
386        #[test]
387        fn test_partial_eq() {
388            {
389                let a: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
390                let b: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
391                assert_eq!(a, b);
392            }
393            {
394                let a: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
395                let b: NHSNumber = NHSNumber::new([9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
396                assert_ne!(a, b);
397            }
398        }
399
400        #[test]
401        fn test_check_digit() {
402            let a = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
403            let actual: i8 = a.check_digit();
404            let expect: i8 = 9;
405            assert_eq!(actual, expect);
406        }
407
408        #[test]
409        fn test_calculate_check_digit() {
410            let a: NHSNumber = NHSNumber::new([9, 9, 9, 1, 2, 3, 4, 5, 6, 0]);
411            let actual: i8 = a.calculate_check_digit();
412            let expect: i8 = 0;
413            assert_eq!(actual, expect);
414        }
415
416        #[test]
417        fn test_validate_check_digit() {
418            {
419                let a: NHSNumber = NHSNumber::new([9, 9, 9, 1, 2, 3, 4, 5, 6, 0]);
420                assert_eq!(a.validate_check_digit(), true);
421            }
422            {
423                let a: NHSNumber = NHSNumber::new([9, 9, 9, 1, 2, 3, 4, 5, 6, 1]);
424                assert_eq!(a.validate_check_digit(), false);
425            }
426        }
427
428        #[test]
429        fn test_testable_random_sample() {
430            let a: NHSNumber = NHSNumber::testable_random_sample();
431            assert!(a >= *crate::testable::TESTABLE_MIN);
432            assert!(a <= *crate::testable::TESTABLE_MAX);
433        }
434    }
435
436    mod utilities {
437
438        #[test]
439        fn test_format() {
440            let digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
441            let actual = crate::format(digits);
442            let expect = "012 345 6789";
443            assert_eq!(actual, expect);
444        }
445
446        #[test]
447        fn test_check_digit() {
448            let digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
449            let actual: i8 = crate::check_digit(digits);
450            let expect: i8 = 9;
451            assert_eq!(actual, expect);
452        }
453
454        #[test]
455        fn test_calculate_check_digit() {
456            let digits = [9, 9, 9, 1, 2, 3, 4, 5, 6, 0];
457            let actual: i8 = crate::calculate_check_digit(digits);
458            let expect: i8 = 0;
459            assert_eq!(actual, expect);
460        }
461    }
462}