Skip to content

Commit f05ed0e

Browse files
committed
add TurkishId::from_seq()
1 parent b96d65c commit f05ed0e

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
- `TurkishIdError` is renamed to `Error` to conform to Rust semantics. Perhaps,
88
I shouldn't have been so hesitant to make this package 1.0.0, huh :)
9+
10+
- Added `from_seq()` method that can generate a valid TurkishId from a given
11+
sequence number.
912

1013
## 4.0.0
1114

src/lib.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@
3131
use core::{
3232
convert::TryInto,
3333
fmt::{Display, Formatter},
34+
ops::Range,
3435
str::{self, FromStr},
3536
};
3637

3738
pub const LENGTH: usize = 11;
3839

3940
pub(crate) type Bytes = [u8; LENGTH];
4041

41-
/// Turkish citizenship ID number.
42+
/// Turkish citizenship ID number. The number is stored as ASCII digits
43+
/// "0".."9" in the structure.
4244
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
4345
pub struct TurkishId(Bytes);
4446

@@ -115,6 +117,8 @@ fn validate(str: &str) -> Result<(), Error> {
115117
return Err(Error::InvalidChecksum);
116118
}
117119

120+
// we use euclidian remainder due to the possibility that the final
121+
// checksum wmight be negative.
118122
let first_checksum_computed = ((odd_sum * 7) - even_sum).rem_euclid(10);
119123
if first_checksum_computed != first_checksum {
120124
return Err(Error::InvalidChecksum);
@@ -134,6 +138,51 @@ impl Display for TurkishId {
134138
}
135139
}
136140

141+
#[derive(Debug, Eq, PartialEq)]
142+
pub enum FromSeqError {
143+
OutOfRange,
144+
}
145+
146+
impl TurkishId {
147+
/// Generate a valid TurkishId from a sequence number by calculating
148+
/// checksums and building the buffer for it.
149+
///
150+
/// # Arguments
151+
/// - seq - A number between 100,000,000 and 999,999,999
152+
///
153+
/// # Returns
154+
/// A Result with `TurkishId` if the values are in range, otherwise
155+
/// `FromSeqError`
156+
pub fn from_seq(seq: u32) -> Result<TurkishId, FromSeqError> {
157+
fn to_ascii(digit: i32) -> u8 {
158+
digit as u8 + b'0'
159+
}
160+
const VALID_SEQ_RANGE: Range<u32> = 100_000_000..1_000_000_000;
161+
if !VALID_SEQ_RANGE.contains(&seq) {
162+
return Err(FromSeqError::OutOfRange);
163+
}
164+
let mut d: Bytes = [0; LENGTH];
165+
let mut odd_sum: i32 = 0;
166+
let mut even_sum: i32 = 0;
167+
let mut divisor = VALID_SEQ_RANGE.start;
168+
for (i, item) in d.iter_mut().enumerate().take(9) {
169+
let digit = (seq / divisor % 10) as i32;
170+
if i % 2 == 0 {
171+
odd_sum += digit;
172+
} else {
173+
even_sum += digit;
174+
}
175+
*item = to_ascii(digit);
176+
divisor /= 10;
177+
}
178+
let first_checksum = ((odd_sum * 7) - even_sum).rem_euclid(10);
179+
let second_checksum = (odd_sum + even_sum + first_checksum) % 10;
180+
d[9] = to_ascii(first_checksum);
181+
d[10] = to_ascii(second_checksum);
182+
Ok(TurkishId(d))
183+
}
184+
}
185+
137186
/// TurkishId can only be constructed from a string despite that it's stored
138187
/// as a fixed-length byte array internally.
139188
impl FromStr for TurkishId {

src/tests.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ const INVALID_NUMBERS: &[(&str, Error)] = &[
4949
("765582422781", Error::InvalidLength),
5050
];
5151

52+
const OUT_OF_RANGE_SEQUENCES: &[u32] = &[0, 99_999_999, 1_000_000_001, u32::MAX];
53+
5254
#[test]
5355
fn is_valid_validnumbers_returns_true() {
5456
for number in VALID_NUMBERS {
@@ -84,8 +86,25 @@ fn hashset_compatible() {
8486
#[test]
8587
fn display_returnsthesamerepresentation() {
8688
for number in VALID_NUMBERS {
87-
let id: TurkishId = number.parse().unwrap();
89+
let id = TurkishId::from_str(number).unwrap();
8890
let idstr = format!("{id}");
8991
assert_eq!(idstr, *number);
9092
}
9193
}
94+
95+
#[test]
96+
fn from_seq_produces_valid_numbers() {
97+
for number in VALID_NUMBERS {
98+
let seq: u32 = number[..9].parse().unwrap();
99+
let id = TurkishId::from_seq(seq).unwrap();
100+
assert_eq!(*number, id.to_string());
101+
}
102+
}
103+
104+
#[test]
105+
fn from_seq_out_of_range_values_return_error() {
106+
for seq in OUT_OF_RANGE_SEQUENCES {
107+
let result = TurkishId::from_seq(*seq);
108+
assert_eq!(result.err(), Some(FromSeqError::OutOfRange));
109+
}
110+
}

0 commit comments

Comments
 (0)