phonenumber/parser/
mod.rs

1// Copyright (C) 2017 1aim GmbH
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::carrier::Carrier;
16use crate::consts;
17use crate::country;
18use crate::error;
19use crate::extension::Extension;
20use crate::metadata::{Database, DATABASE};
21use crate::national_number::NationalNumber;
22use crate::phone_number::{PhoneNumber, Type};
23use crate::validator::{self, Validation};
24use nom::{branch::alt, IResult};
25
26#[macro_use]
27pub mod helper;
28pub mod natural;
29pub mod rfc3966;
30pub mod valid;
31
32/// Parse a phone number.
33pub fn parse<S: AsRef<str>>(
34    country: Option<country::Id>,
35    string: S,
36) -> Result<PhoneNumber, error::Parse> {
37    parse_with(&DATABASE, country, string)
38}
39
40/// Parse a phone number using a specific `Database`.
41pub fn parse_with<S: AsRef<str>>(
42    database: &Database,
43    country: Option<country::Id>,
44    string: S,
45) -> Result<PhoneNumber, error::Parse> {
46    fn phone_number(i: &str) -> IResult<&str, helper::Number<'_>> {
47        parse! { i => alt((rfc3966::phone_number, natural::phone_number)) }
48    }
49
50    // Try to parse the number as RFC3966 or natural language.
51    let (_, mut number) = phone_number(string.as_ref()).or(Err(error::Parse::NoNumber))?;
52
53    // Normalize the number and extract country code.
54    number = helper::country_code(database, country, number)?;
55
56    // Extract carrier and strip national prefix if present.
57    if let Some(meta) = country.and_then(|c| database.by_id(c.as_ref())) {
58        let mut potential = helper::national_number(meta, number.clone());
59
60        // Strip national prefix if present.
61        if let Some(prefix) = meta.national_prefix.as_ref() {
62            if potential.national.starts_with(prefix) {
63                potential.national = helper::trim(potential.national, prefix.len());
64            }
65        }
66
67        if validator::length(meta, &potential, Type::Unknown) != Validation::TooShort {
68            number = potential;
69        }
70    }
71
72    if number.national.len() < consts::MIN_LENGTH_FOR_NSN {
73        return Err(error::Parse::TooShortNsn);
74    }
75
76    if number.national.len() > consts::MAX_LENGTH_FOR_NSN {
77        return Err(error::Parse::TooLong);
78    }
79
80    Ok(PhoneNumber {
81        code: country::Code {
82            value: number.prefix.map(|p| p.parse()).unwrap_or(Ok(0))?,
83            source: number.country,
84        },
85
86        national: NationalNumber::new(
87            number.national.parse()?,
88            number.national.chars().take_while(|&c| c == '0').count() as u8,
89        )?,
90
91        extension: number.extension.map(|s| Extension(s.into_owned())),
92        carrier: number.carrier.map(|s| Carrier(s.into_owned())),
93    })
94}
95
96#[cfg(test)]
97mod test {
98    use crate::country;
99    use crate::national_number::NationalNumber;
100    use crate::parser;
101    use crate::phone_number::PhoneNumber;
102
103    #[test]
104    fn parse() {
105        let mut number = PhoneNumber {
106            code: country::Code {
107                value: 64,
108                source: country::Source::Default,
109            },
110
111            national: NationalNumber::new(33316005, 0).unwrap(),
112
113            extension: None,
114            carrier: None,
115        };
116
117        number.code.source = country::Source::Default;
118        assert_eq!(
119            number,
120            parser::parse(Some(country::NZ), "033316005").unwrap()
121        );
122        assert_eq!(
123            number,
124            parser::parse(Some(country::NZ), "33316005").unwrap()
125        );
126        assert_eq!(
127            number,
128            parser::parse(Some(country::NZ), "03-331 6005").unwrap()
129        );
130        assert_eq!(
131            number,
132            parser::parse(Some(country::NZ), "03 331 6005").unwrap()
133        );
134
135        number.code.source = country::Source::Plus;
136        assert_eq!(
137            number,
138            parser::parse(Some(country::NZ), "tel:03-331-6005;phone-context=+64").unwrap()
139        );
140        // FIXME: What the fuck is this.
141        // assert_eq!(number, parser::parse(Some(country::NZ), "tel:331-6005;phone-context=+64-3").unwrap());
142        // assert_eq!(number, parser::parse(Some(country::NZ), "tel:331-6005;phone-context=+64-3").unwrap());
143        assert_eq!(
144            number,
145            parser::parse(Some(country::NZ), "tel:03-331-6005;phone-context=+64;a=%A1").unwrap()
146        );
147        assert_eq!(
148            number,
149            parser::parse(
150                Some(country::NZ),
151                "tel:03-331-6005;isub=12345;phone-context=+64"
152            )
153            .unwrap()
154        );
155        assert_eq!(
156            number,
157            parser::parse(Some(country::NZ), "tel:+64-3-331-6005;isub=12345").unwrap()
158        );
159        assert_eq!(
160            number,
161            parser::parse(Some(country::NZ), "03-331-6005;phone-context=+64").unwrap()
162        );
163
164        number.code.source = country::Source::Idd;
165        assert_eq!(
166            number,
167            parser::parse(Some(country::NZ), "0064 3 331 6005").unwrap()
168        );
169        assert_eq!(
170            number,
171            parser::parse(Some(country::US), "01164 3 331 6005").unwrap()
172        );
173
174        number.code.source = country::Source::Plus;
175        assert_eq!(
176            number,
177            parser::parse(Some(country::US), "+64 3 331 6005").unwrap()
178        );
179
180        assert_eq!(
181            number,
182            parser::parse(Some(country::US), "+01164 3 331 6005").unwrap()
183        );
184        assert_eq!(
185            number,
186            parser::parse(Some(country::NZ), "+0064 3 331 6005").unwrap()
187        );
188        assert_eq!(
189            number,
190            parser::parse(Some(country::NZ), "+ 00 64 3 331 6005").unwrap()
191        );
192
193        let number = PhoneNumber {
194            code: country::Code {
195                value: 64,
196                source: country::Source::Number,
197            },
198
199            national: NationalNumber::new(64123456, 0).unwrap(),
200
201            extension: None,
202            carrier: None,
203        };
204
205        assert_eq!(
206            number,
207            parser::parse(Some(country::NZ), "64(0)64123456").unwrap()
208        );
209
210        assert_eq!(
211            PhoneNumber {
212                code: country::Code {
213                    value: 49,
214                    source: country::Source::Default,
215                },
216
217                national: NationalNumber::new(30123456, 0).unwrap(),
218
219                extension: None,
220                carrier: None,
221            },
222            parser::parse(Some(country::DE), "301/23456").unwrap()
223        );
224
225        assert_eq!(
226            PhoneNumber {
227                code: country::Code {
228                    value: 81,
229                    source: country::Source::Plus,
230                },
231
232                national: NationalNumber::new(2345, 0,).unwrap(),
233
234                extension: None,
235                carrier: None,
236            },
237            parser::parse(Some(country::JP), "+81 *2345").unwrap()
238        );
239
240        assert_eq!(
241            PhoneNumber {
242                code: country::Code {
243                    value: 64,
244                    source: country::Source::Default,
245                },
246
247                national: NationalNumber::new(12, 0,).unwrap(),
248
249                extension: None,
250                carrier: None,
251            },
252            parser::parse(Some(country::NZ), "12").unwrap()
253        );
254
255        assert_eq!(
256            PhoneNumber {
257                code: country::Code {
258                    value: 55,
259                    source: country::Source::Default,
260                },
261
262                national: NationalNumber::new(3121286979, 0).unwrap(),
263
264                extension: None,
265                carrier: Some("12".into()),
266            },
267            parser::parse(Some(country::BR), "012 3121286979").unwrap()
268        );
269    }
270
271    #[test]
272    fn issue_43() {
273        let res = parser::parse(None, " 2 22#:");
274        assert!(res.is_err());
275    }
276
277    #[test]
278    fn advisory_1() {
279        let res = parser::parse(None, ".;phone-context=");
280        assert!(res.is_err(), "{res:?}");
281    }
282
283    #[test]
284    fn advisory_2() {
285        let res = parser::parse(None, "+dwPAA;phone-context=AA");
286        assert!(res.is_err(), "{res:?}");
287    }
288}