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}