rut_lib/lib.rs
1/*!
2A Rust library for parsing, format and validate a Chilean ID (RUT)
3
4# Usage
5This crate is [on crates.io](https://crates.io/crates/rut-lib) and can be used by adding `rut_lib` to your dependencies in your project's Cargo.toml.
6
7```toml
8[dependencies]
9rut-lib = "0.1.2"
10```
11
12If you're using Rust 2015, then you'll also need to add it to your crate root:
13
14```rust
15extern crate rut_lib;
16```
17
18# Examples
19
20## Parsing from String
21A easy way to validate and create a `Rut` using the `from` method, this returns a `Result<Rut, Error>`
22
23The input must be a valid `String` format, a few examples:
24- `17.951.585-7`
25- `17951585-7`
26- `179515857`
27
28```rust
29use rut_lib::Rut;
30
31let stringifier_rut = "17951585-7";
32
33match Rut::from(stringifier_rut) {
34 Ok(rut) => {
35 println!("Number: {:#}", rut.number());
36 println!("DV: {:#}", rut.dv());
37 println!("RUT: {:#}", rut)
38 },
39 Err(error) => println!("Error: {:#}", error)
40}
41```
42Output
43
44```bash
45Number: 17951585
46DV: 7
47RUT: 17951585-7
48```
49
50#### Error behaviour
51<details><summary>Error::InvalidFormat</summary>
52<p>
53
54```rust
55use rut_lib::Rut;
56
57let stringifier_rut = "17,951,585-7";
58
59match Rut::from(stringifier_rut) {
60 Ok(rut) => println!("RUT: {:#}", rut),
61 Err(error) => println!("Error: {:#}", error)
62}
63```
64
65Output
66
67```bash
68Error: The input format is invalid
69```
70</p>
71</details>
72
73<details><summary>Error::InvalidDV</summary>
74<p>
75
76```rust
77use rut_lib::Rut;
78
79let stringifier_rut = "17951585K";
80
81match Rut::from(stringifier_rut) {
82 Ok(rut) => println!("RUT: {:#}", rut),
83 Err(error) => println!("Error: {:#}", error)
84}
85```
86
87Output
88
89```bash
90Error: Invalid DV, must be 7, instead K.
91```
92</p>
93</details>
94
95## Parsing from Number
96Create a `Rut` using the `from_number` method (If you don't have a `DV`), this returns a `Result<Rut, Error>`
97
98The input must be a `number` (`u32`) and stay in a range from `1_000_000` to `99_999_999`
99
100```rust
101use rut_lib::Rut;
102
103let number = 24136773;
104
105match Rut::from_number(number) {
106 Ok(rut) => {
107 println!("Number: {:#}", rut.number());
108 println!("DV: {:#}", rut.dv());
109 println!("RUT: {:#}", rut)
110 },
111 Err(error) => println!("Error: {:#}", error)
112}
113```
114
115Output
116
117```bash
118Number: 24136773
119DV: 8
120RUT: 24136773-8
121```
122
123#### Error behaviour
124<details><summary>Error::OutOfRange</summary>
125<p>
126
127```rust
128use rut_lib::Rut;
129
130let number = 999_999;
131
132match Rut::from_number(number) {
133 Ok(rut) => {
134 println!("RUT: {:#}", rut)
135 },
136 Err(error) => println!("Error: {:#}", error)
137}
138```
139
140Output
141
142```bash
143Error: The input number must be between 1.000.000 to 99.999.999
144```
145</p>
146</details>
147
148## Randomize Rut
149Generate a randomize rut from scratch for testing use
150
151Example:
152```rust
153use rut_lib::Rut;
154
155let rut = Rut::randomize();
156
157println!("Number: {:#}", rut.number());
158println!("DV: {:#}", rut.dv());
159println!("RUT: {:#}", rut);
160```
161
162Output
163
164```bash
165Number: 56606059
166DV: 0
167RUT: 56606059-0
168```
169
170## Prettify Format
171The `to_format` method receive a `Format` (`enum`) as input to returns a Prettify `Rut`
172```rust
173use rut_lib::{Rut, Format};
174
175let input = "179515857";
176let rut = Rut::from(input).unwrap();
177
178println!("Dots: {}", rut.to_format(Format::DOTS));
179println!("Dash: {}", rut.to_format(Format::DASH));
180println!("None: {}", rut.to_format(Format::NONE));
181```
182
183Output
184
185```bash
186Dots: 17.951.585-7
187Dash: 17951585-7
188None: 179515857
189```
190*/
191
192mod error;
193mod range;
194mod utils;
195
196use core::fmt;
197use error::Error;
198use num_format::{Locale, ToFormattedString};
199use range::{random_number, Range};
200use regex::Regex;
201use utils::{mod_eleven, sum_product, PATTERN};
202
203#[derive(Debug)]
204pub struct Rut {
205 number: u32,
206 dv: char,
207}
208
209#[derive(Copy, Clone)]
210pub enum Format {
211 DOTS,
212 DASH,
213 NONE,
214}
215
216impl fmt::Display for Rut {
217 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
218 write!(f, "{}", self.to_format(Format::DASH))
219 }
220}
221
222impl Rut {
223 /// Create a `Rut` from a input String
224 /// This is useful when you want to parse to String
225 /// or check if is a valid input Rut.
226 ///
227 /// The Input must be a valid RUT format.
228 pub fn from(input: &str) -> Result<Rut, Error> {
229 match Rut::extract_from(input) {
230 Ok(unverified_rut) => Rut::check_dv(unverified_rut),
231 Err(error) => Err(error),
232 }
233 }
234
235 /// Create a `Rut` from a input Number
236 ///
237 /// The input must be between in a Range value.
238 pub fn from_number(number: u32) -> Result<Rut, Error> {
239 let min = Range::MIN.to_u32();
240 let max = Range::MAX.to_u32();
241 let range = min..max;
242 if range.contains(&number) {
243 let dv = Rut::generate_dv(number);
244 Ok(Rut { number, dv })
245 } else {
246 Err(Error::OutOfRange)
247 }
248 }
249
250 /// Generate a Rut from scratch with a random number.
251 pub fn randomize() -> Rut {
252 let number = random_number();
253 let dv = Rut::generate_dv(number);
254
255 Rut { number, dv }
256 }
257
258 /// Take a `Rut` and Prettify the output to String
259 /// This use the `Format` enum as input.
260 pub fn to_format(&self, format: Format) -> String {
261 match format {
262 Format::DOTS => format!(
263 "{}-{}",
264 self.number.to_formatted_string(&Locale::es_CL),
265 self.dv
266 ),
267 Format::DASH => format!("{}-{}", self.number, self.dv),
268 Format::NONE => format!("{}{}", self.number, self.dv),
269 }
270 }
271
272 /// Return the number output
273 pub fn number(&self) -> &u32 {
274 &self.number
275 }
276
277 /// Return the DV output
278 pub fn dv(&self) -> &char {
279 &self.dv
280 }
281
282 fn check_dv(unsigned_rut: Rut) -> Result<Rut, Error> {
283 let signed_rut = Rut::from_number(unsigned_rut.number).unwrap();
284 if unsigned_rut.dv != signed_rut.dv {
285 Err(Error::InvalidDV {
286 must_be: signed_rut.dv,
287 instead: unsigned_rut.dv,
288 })
289 } else {
290 Ok(signed_rut)
291 }
292 }
293
294 fn extract_from(input: &str) -> Result<Rut, Error> {
295 let regex = Regex::new(PATTERN).unwrap();
296 if regex.is_match(input) {
297 let captures = regex.captures(input).unwrap();
298 let number: u32 = captures["number"].replace(".", "").parse().unwrap();
299 let dv = captures["dv"].to_uppercase().chars().nth(0).unwrap();
300 Ok(Rut { number, dv })
301 } else {
302 Err(Error::InvalidFormat)
303 }
304 }
305
306 fn generate_dv(number: u32) -> char {
307 let total_number = sum_product(number);
308 let dv = mod_eleven(total_number);
309
310 match dv {
311 10 => 'K',
312 11 => '0',
313 _ => format!("{}", dv).chars().nth(0).unwrap(),
314 }
315 }
316}
317
318#[cfg(test)]
319mod rut_test {
320 use super::*;
321
322 #[test]
323 fn from() {
324 let valid_rut = ["17951585-7", "5.665.328-7", "241367738"];
325 for rut in valid_rut.iter() {
326 assert!(Rut::from(rut).is_ok())
327 }
328 }
329
330 #[test]
331 fn from_number() {
332 let valid_rut = [(17951585, '7'), (12621806, '0'), (24136773, '8')];
333
334 for (number, dv) in valid_rut.iter() {
335 let rut = Rut::from_number(*number).unwrap();
336 assert_eq!(&rut.number, number);
337 assert_eq!(rut.dv(), dv);
338 }
339 }
340
341 #[test]
342 fn to_format() {
343 let rut_tuple = [
344 (
345 "17951585-7",
346 Rut::from_number(17951585).unwrap(),
347 Format::DASH,
348 ),
349 (
350 "5.665.328-7",
351 Rut::from_number(5665328).unwrap(),
352 Format::DOTS,
353 ),
354 (
355 "241367738",
356 Rut::from_number(24136773).unwrap(),
357 Format::NONE,
358 ),
359 ];
360
361 for (input, rut, format) in rut_tuple.iter() {
362 assert_eq!(rut.to_format(*format), *input)
363 }
364 }
365
366 #[test]
367 fn randomize() {
368 let rut = Rut::randomize();
369 assert_eq!(rut.to_string(), rut.to_string())
370 }
371
372 #[test]
373 fn wrong_dv() {
374 assert_eq!(
375 Rut::from("17951585-K").unwrap_err().to_string(),
376 Error::InvalidDV {
377 must_be: '7',
378 instead: 'K'
379 }
380 .to_string()
381 )
382 }
383
384 #[test]
385 fn invalid_format() {
386 assert_eq!(
387 Rut::from("17.951,585-7").unwrap_err().to_string(),
388 Error::InvalidFormat.to_string()
389 )
390 }
391
392 #[test]
393 fn out_of_range() {
394 assert_eq!(
395 Rut::from_number(999999).unwrap_err().to_string(),
396 Error::OutOfRange.to_string()
397 );
398 assert_eq!(
399 Rut::from_number(100000000).unwrap_err().to_string(),
400 Error::OutOfRange.to_string()
401 );
402 }
403}