1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
//! The crate provides function to encode data into [SLK581](http://registry.aristotlemetadata.com/share/349510/79)
//! format.
//!
//! The format allows encode given family name, given name, date of birth and sex into sequence
//! `XXXZZDDMMYYYYN`.
//! Where `XXX` encodes family name, `ZZ` encodes given name, `DDMMYYYY` encodes date of birth and
//! `N` encodes sex.

extern crate chrono;

use chrono::NaiveDate;
use chrono::format::ParseResult;
use std::error::Error;
use std::fmt;

use self::SLK581Error::{InvalidDateOfBirth, UnknownDateOfBirth, UnsupportedSex};

/// Placeholder for unknown family name `999`
pub const UNKNOWN_FAMILY_NAME: &'static str = "999";
/// Placeholder for unknown given name `99`
pub const UNKNOWN_GIVEN_NAME: &'static str = "99";
/// Placeholder for missing character in given or family name `2`
pub const UNKNOWN_CHARACTER_IN_NAME: char = '2';
/// Male code `1`
pub const MALE: &'static str = "1";
/// Female code `2`
pub const FEMALE: &'static str = "2";
/// Transgender code `3`
pub const TRANSGENDER: &'static str = "3";
/// Placeholder for unknown sex `3`
pub const UNKNOWN_SEX: &'static str = "3";
/// Supported input format of date of birth `YYYY-MM-DD`
pub const INPUT_DATE_FORMAT: &'static str = "%Y-%m-%d";
/// Output format of date of birth `DDMMYYYY`
pub const OUTPUT_DATE_FORMAT: &'static str = "%d%m%Y";

#[derive(PartialEq, Debug)]
pub enum SLK581Error<'a> {
    InvalidDateOfBirth,
    UnknownDateOfBirth,
    UnsupportedSex(&'a str),
}

impl<'a> fmt::Display for SLK581Error<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            UnsupportedSex(ref sex) => write!(f, "{}: '{}'", self.description(), sex),
            _ => write!(f, "{}", self.description()),
        }
    }
}

impl<'a> Error for SLK581Error<'a> {
    fn description(&self) -> &str {
        match *self {
            InvalidDateOfBirth => "Unsupported date of birth format.",
            UnknownDateOfBirth => "Unknown date of birth.",
            UnsupportedSex(..) => "Unsupported sex",
        }
    }
}

fn sanitize_name(name: &str) -> String {
    let mut buf: String = String::with_capacity(name.len());

    for c in name.chars() {
        match c {
            'A' ... 'Z' => buf.push(c),
            _ => ()
        }
    }

    return buf;
}

fn encode_name(name: Option<&str>, take: usize, vec_pos: Vec<usize>) -> String {
    let clean_name = sanitize_name(name.unwrap().to_uppercase().as_str());
    let mut chars_iter = clean_name.chars().into_iter().take(take);
    let buf_capacity: usize = vec_pos.len();

    return vec_pos.into_iter()
        .map(|position| {
            if let Some(c) = chars_iter.nth(position) {
                return c;
            } else {
                return UNKNOWN_CHARACTER_IN_NAME;
            }
        })
        .fold(String::with_capacity(buf_capacity), |mut buf, c| {
            buf.push(c);
            buf
        });
}

fn encode_family_name(family_name: Option<&str>) -> String {
    if family_name.is_none() {
        return String::from(UNKNOWN_FAMILY_NAME);
    }

    encode_name(family_name, 5, vec![1, 0, 1])
}

fn encode_given_name(given_name: Option<&str>) -> String {
    if given_name.is_none() {
        return String::from(UNKNOWN_GIVEN_NAME);
    }

    encode_name(given_name, 3, vec![1, 0])
}

fn encode_date_of_birth<'a>(date_of_birth: Option<&str>) -> Result<String, SLK581Error<'a>> {
    if date_of_birth.is_none() {
        return Err(UnknownDateOfBirth);
    }

    let _date_of_birth: ParseResult<NaiveDate> =
        NaiveDate::parse_from_str(date_of_birth.unwrap(), INPUT_DATE_FORMAT);

    if _date_of_birth.is_err() {
        return Err(InvalidDateOfBirth);
    }

    Ok(_date_of_birth.unwrap().format(OUTPUT_DATE_FORMAT).to_string())
}

fn encode_sex<'a>(sex: Option<&'a str>) -> Result<String, SLK581Error<'a>> {
    if sex.is_none() {
        return Ok(String::from(UNKNOWN_SEX));
    }

    let _sex = sex.unwrap();
    let lc_sex = _sex.to_lowercase();
    match lc_sex.as_str() {
        "m" | "male" => Ok(String::from(MALE)),
        "f" | "female" => Ok(String::from(FEMALE)),
        "t" | "trans" => Ok(String::from(TRANSGENDER)),
        _ => Err(UnsupportedSex(_sex))
    }
}

// XXXXXDDMMYYYYN
// 1. XXX - 2, 3, 5 characters of family_name (indexing from 1)
//    if no character found at position then replace with 2
//    if no family_name return 999
// 2. XX - 2, 3 characters of given_name (indexing from 1)
//    if no character found at position then replace with 2
//    if no given_name return 999
// 3. DDMMYYYY - date_of_birth format
//    supported formats: [YYYY-MM-DD]
// 4. N - sex, 1 - male, 2 - female, 3 - unknown or transgender
//    supported values: m, male, f, female, t, trans - caseinsensetive
/// This function encodes given family name, given name, date of birth and sex in `XXXZZDDMMYYYYN`
/// sequence.
///
/// # Errors
///
/// Returns `UnknownDateOfBirth` when date of birth not provided:
///
/// ```
/// use slk581::encode;
/// use slk581::SLK581Error;
/// use slk581::SLK581Error::UnknownDateOfBirth;
///
/// let encoded_result: Result<String, SLK581Error> = encode(None, None, None, None);
/// assert_eq!(encoded_result.is_err(), true);
/// assert_eq!(encoded_result.unwrap_err(), UnknownDateOfBirth);
/// ```
///
/// Returns `InvalidDateOfBirth` when date of birth provided in invalid format:
///
/// ```
/// use slk581::encode;
/// use slk581::SLK581Error;
/// use slk581::SLK581Error::InvalidDateOfBirth;
///
/// let date_of_birth: Option<&str> = Some("20001219");
/// let encoded_result: Result<String, SLK581Error> = encode(None, None, date_of_birth, None);
/// assert_eq!(encoded_result.is_err(), true);
/// assert_eq!(encoded_result.unwrap_err(), InvalidDateOfBirth);
/// ```
///
/// Returns `UnsupportedSex` when unsupported sex value provided:
///
/// ```
/// use slk581::encode;
/// use slk581::SLK581Error;
/// use slk581::SLK581Error::UnsupportedSex;
///
/// let date_of_birth: Option<&str> = Some("2000-12-19");
/// let sex: Option<&str> = Some("test");
/// let encoded_result: Result<String, SLK581Error> = encode(None, None, date_of_birth, sex);
/// assert_eq!(encoded_result.is_err(), true);
/// assert_eq!(encoded_result.unwrap_err(), UnsupportedSex("test"));
/// ```
///
/// # Examples
/// ```
/// use slk581::encode;
/// use slk581::SLK581Error;
///
/// let date_of_birth: Option<&str> = Some("2000-12-19");
///
/// let encoded_result: Result<String, SLK581Error> =
///     encode(Some("Doe"), Some("John"), date_of_birth, Some("m"));
/// assert_eq!(encoded_result.is_ok(), true);
/// assert_eq!(encoded_result.unwrap(), "OE2OH191220001");
///
/// let encoded_result: Result<String, SLK581Error> =
///     encode(Some("Smith"), Some("Jane"), date_of_birth, Some("f"));
/// assert_eq!(encoded_result.is_ok(), true);
/// assert_eq!(encoded_result.unwrap(), "MIHAN191220002");
///
/// let encoded_result: Result<String, SLK581Error> =
///     encode(Some("O Bare"), Some("Foo"), date_of_birth, Some("t"));
/// assert_eq!(encoded_result.is_ok(), true);
/// assert_eq!(encoded_result.unwrap(), "BAEOO191220003");
/// ```
pub fn encode<'a>(family_name: Option<&str>,
                  given_name: Option<&str>,
                  date_of_birth: Option<&str>,
                  sex: Option<&'a str>) -> Result<String, SLK581Error<'a>> {

    let encoded_family_name: String = encode_family_name(family_name);
    let encoded_given_name: String = encode_given_name(given_name);
    let encoded_date_of_birth: String = try!(encode_date_of_birth(date_of_birth));
    let encoded_sex: String = try!(encode_sex(sex));

    let mut buf = String::with_capacity(14);
    buf.push_str(encoded_family_name.as_str());
    buf.push_str(encoded_given_name.as_str());
    buf.push_str(encoded_date_of_birth.as_str());
    buf.push_str(encoded_sex.as_str());

    Ok(buf)
}

#[cfg(test)]
mod tests {
    use super::encode;
    use super::SLK581Error;
    use super::SLK581Error::*;

    #[test]
    fn it_should_return_error_for_unknown_dob() {
        let encoded_result: Result<String, SLK581Error> = encode(None, None, None, None);
        assert_eq!(encoded_result.is_err(), true);
        assert_eq!(encoded_result.unwrap_err(), UnknownDateOfBirth);
    }

    #[test]
    fn it_should_return_error_for_invalid_dob() {
        let date_of_birth: Option<&str> = Some("20001219");
        let encoded_result: Result<String, SLK581Error> = encode(None, None, date_of_birth, None);
        assert_eq!(encoded_result.is_err(), true);
        assert_eq!(encoded_result.unwrap_err(), InvalidDateOfBirth);
    }

    #[test]
    fn it_should_encode_dob() {
        let date_of_birth: Option<&str> = Some("2000-12-19");
        let encoded_result: Result<String, SLK581Error> = encode(None, None, date_of_birth, None);
        assert_eq!(encoded_result.is_ok(), true);
        assert_eq!(encoded_result.unwrap(), "99999191220003");
    }

    #[test]
    fn it_should_return_error_for_unsupported_sex() {
        let date_of_birth: Option<&str> = Some("2000-12-19");
        let sex: Option<&str> = Some("test");
        let encoded_result: Result<String, SLK581Error> = encode(None, None, date_of_birth, sex);
        assert_eq!(encoded_result.is_err(), true);
        assert_eq!(encoded_result.unwrap_err(), UnsupportedSex("test"));
    }

    #[test]
    fn it_should_encode_male() {
        let date_of_birth: Option<&str> = Some("2000-12-19");
        let vec_sex: Vec<Option<&str>> = vec![Some("m"), Some("M"), Some("MaLe")];

        for sex in vec_sex {
            let encoded_result: Result<String, SLK581Error> =
                encode(None, None, date_of_birth, sex);
            assert_eq!(encoded_result.is_ok(), true);
            assert_eq!(encoded_result.unwrap(), "99999191220001");
        }
    }

    #[test]
    fn it_should_encode_female() {
        let date_of_birth: Option<&str> = Some("2000-12-19");
        let vec_sex: Vec<Option<&str>> = vec![Some("f"), Some("F"), Some("feMaLe")];

        for sex in vec_sex {
            let encoded_result: Result<String, SLK581Error> =
                encode(None, None, date_of_birth, sex);
            assert_eq!(encoded_result.is_ok(), true);
            assert_eq!(encoded_result.unwrap(), "99999191220002");
        }
    }

    #[test]
    fn it_should_encode_transgender() {
        let date_of_birth: Option<&str> = Some("2000-12-19");
        let vec_sex: Vec<Option<&str>> = vec![Some("t"), Some("T"), Some("trAnS")];

        for sex in vec_sex {
            let encoded_result: Result<String, SLK581Error> =
                encode(None, None, date_of_birth, sex);
            assert_eq!(encoded_result.is_ok(), true);
            assert_eq!(encoded_result.unwrap(), "99999191220003");
        }
    }

    #[test]
    fn it_should_encode_short_family_name() {
        let date_of_birth: Option<&str> = Some("2000-12-19");
        let sex: Option<&str> = Some("male");

        let encoded_result: Result<String, SLK581Error> =
            encode(Some("Y"), None, date_of_birth, sex);
        assert_eq!(encoded_result.is_ok(), true);
        assert_eq!(encoded_result.unwrap(), "22299191220001");

        let encoded_result: Result<String, SLK581Error> =
            encode(Some("Yo"), None, date_of_birth, sex);
        assert_eq!(encoded_result.is_ok(), true);
        assert_eq!(encoded_result.unwrap(), "O2299191220001");

        let encoded_result: Result<String, SLK581Error> =
            encode(Some("O-B"), None, date_of_birth, sex);
        assert_eq!(encoded_result.is_ok(), true);
        assert_eq!(encoded_result.unwrap(), "B2299191220001");

        let encoded_result: Result<String, SLK581Error> =
            encode(Some("O'Ber"), None, date_of_birth, sex);
        assert_eq!(encoded_result.is_ok(), true);
        assert_eq!(encoded_result.unwrap(), "BE299191220001");
    }

    #[test]
    fn it_should_encode_short_given_name() {
        let date_of_birth: Option<&str> = Some("2000-12-19");
        let sex: Option<&str> = Some("male");

        let encoded_result: Result<String, SLK581Error> =
            encode(None, Some("Y"), date_of_birth, sex);
        assert_eq!(encoded_result.is_ok(), true);
        assert_eq!(encoded_result.unwrap(), "99922191220001");

        let encoded_result: Result<String, SLK581Error> =
            encode(None, Some("Yo"), date_of_birth, sex);
        assert_eq!(encoded_result.is_ok(), true);
        assert_eq!(encoded_result.unwrap(), "999O2191220001");

        let encoded_result: Result<String, SLK581Error> =
            encode(None, Some("O-B"), date_of_birth, sex);
        assert_eq!(encoded_result.is_ok(), true);
        assert_eq!(encoded_result.unwrap(), "999B2191220001");
    }

    #[test]
    fn it_should_encode_happy_path() {
        let date_of_birth: Option<&str> = Some("2000-12-19");

        let encoded_result: Result<String, SLK581Error> =
            encode(Some("Doe"), Some("John"), date_of_birth, Some("m"));
        assert_eq!(encoded_result.is_ok(), true);
        assert_eq!(encoded_result.unwrap(), "OE2OH191220001");

        let encoded_result: Result<String, SLK581Error> =
            encode(Some("Smith"), Some("Jane"), date_of_birth, Some("f"));
        assert_eq!(encoded_result.is_ok(), true);
        assert_eq!(encoded_result.unwrap(), "MIHAN191220002");

        let encoded_result: Result<String, SLK581Error> =
            encode(Some("O Bare"), Some("Foo"), date_of_birth, Some("t"));
        assert_eq!(encoded_result.is_ok(), true);
        assert_eq!(encoded_result.unwrap(), "BAEOO191220003");
    }
}