phonenumber_fixed/
validator.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 either::*;
16
17use crate::metadata::{DATABASE, Database, Metadata};
18use crate::country;
19use crate::phone_number::{Type, PhoneNumber};
20use crate::consts;
21use crate::parser::helper::Number as ParseNumber;
22use crate::parser;
23
24/// Possible outcomes when testing if a `PhoneNumber` is possible.
25#[derive(Copy, Clone, Eq, PartialEq, Debug)]
26pub enum Validation {
27	/// The number length matches that of valid numbers for this region.
28	IsPossible,
29
30	/// The number length matches that of local numbers for this region only
31	/// (i.e. numbers that may be able to be dialled within an area, but do not
32	/// have all the information to be dialled from anywhere inside or outside
33	/// the country).
34	IsPossibleLocalOnly,
35
36	/// The number has an invalid country calling code.
37	InvalidCountryCode,
38
39	/// The number is shorter than all valid numbers for this region.
40	TooShort,
41
42	/// The number is longer than the shortest valid numbers for this region,
43	/// shorter than the longest valid numbers for this region, and does not
44	/// itself have a number length that matches valid numbers for this region.
45	InvalidLength,
46
47	/// The number is longer than all valid numbers for this region.
48	TooLong,
49}
50
51impl Validation {
52	/// Whether it's a possible number.
53	pub fn is_possible(&self) -> bool {
54		match *self {
55			Validation::IsPossible |
56			Validation::IsPossibleLocalOnly =>
57				true,
58
59			_ =>
60				false,
61		}
62	}
63
64	/// Whether it's an invalid number.
65	pub fn is_invalid(&self) -> bool {
66		match *self {
67			Validation::InvalidCountryCode |
68			Validation::TooShort |
69			Validation::InvalidLength |
70			Validation::TooLong =>
71				true,
72
73			_ =>
74				false,
75		}
76	}
77
78	/// Whether the length is invalid.
79	pub fn is_invalid_length(&self) -> bool {
80		match *self {
81			Validation::TooShort |
82			Validation::InvalidLength |
83			Validation::TooLong =>
84				true,
85
86			_ =>
87				false,
88		}
89	}
90}
91
92/// Check if the provided string is a viable phone number.
93pub fn is_viable<S: AsRef<str>>(string: S) -> bool {
94	let string = string.as_ref();
95
96	if string.len() < consts::MIN_LENGTH_FOR_NSN {
97		return false;
98	}
99
100	parser::valid::phone_number(string).is_ok()
101}
102
103/// Check if the phone number is valid.
104pub fn is_valid(number: &PhoneNumber) -> bool {
105	is_valid_with(&*DATABASE, number)
106}
107
108/// Check if the phone number is valid with the given `Database`.
109pub fn is_valid_with(database: &Database, number: &PhoneNumber) -> bool {
110	let code     = number.country().code();
111	let national = number.national.to_string();
112	let source   = try_opt!(false; source_for(database, code, &national));
113	let meta     = try_opt!(false; match source {
114		Left(region) =>
115			database.by_id(region.as_ref()),
116
117		Right(code) =>
118			database.by_code(&code).and_then(|m| m.into_iter().next()),
119	});
120
121	number_type(meta, &national) != Type::Unknown
122}
123
124pub fn length(meta: &Metadata, number: &ParseNumber, kind: Type) -> Validation {
125	let desc = if let Some(desc) = meta.descriptors().get(kind) { desc } else {
126		return Validation::InvalidLength;
127	};
128
129	let length   = number.national.len() as u16;
130	let local    = &desc.possible_local_length[..];
131	let possible = if desc.possible_length.is_empty() {
132		&desc.possible_length[..]
133	}
134	else {
135		&meta.descriptors.general.possible_length[..]
136	};
137
138	if possible.is_empty() {
139		return Validation::InvalidLength;
140	}
141
142	let minimum = possible[0];
143
144	if local.contains(&length) {
145		Validation::IsPossibleLocalOnly
146	}
147	else if length == minimum {
148		Validation::IsPossible
149	}
150	else if length < minimum {
151		Validation::TooShort
152	}
153	else if length > *possible.last().unwrap() {
154		Validation::TooLong
155	}
156	else if possible.contains(&length) {
157		Validation::IsPossible
158	}
159	else {
160		Validation::InvalidLength
161	}
162}
163
164/// Find the metadata source.
165pub fn source_for(database: &Database, code: u16, national: &str) -> Option<Either<country::Id, u16>> {
166	let regions = try_opt!(None; database.region(&code));
167
168	if regions.len() == 1 {
169		return if regions[0] == "001" {
170			Some(Right(code))
171		} else {
172			match regions[0].parse() {
173				Ok(value) => Some(Left(value)),
174				Err(_) => None,
175			}
176		}
177	}
178
179	for region in regions {
180		let meta = database.by_id(region).unwrap();
181
182		if let Some(pattern) = meta.leading_digits.as_ref() {
183			if let Some(index) = pattern.find(national) {
184				if index.start() == 0 {
185					return Some(Left(region.parse().unwrap()));
186				}
187			}
188		}
189		else if number_type(meta, national) != Type::Unknown {
190			return Some(Left(region.parse().unwrap()));
191		}
192	}
193
194	None
195}
196
197pub fn number_type(meta: &Metadata, value: &str) -> Type {
198	if !meta.descriptors.general.is_match(value) {
199		return Type::Unknown;
200	}
201
202	if meta.descriptors.premium_rate.as_ref().map(|d| d.is_match(value)).unwrap_or(false) {
203		return Type::PremiumRate;
204	}
205
206	if meta.descriptors.toll_free.as_ref().map(|d| d.is_match(value)).unwrap_or(false) {
207		return Type::TollFree;
208	}
209
210	if meta.descriptors.shared_cost.as_ref().map(|d| d.is_match(value)).unwrap_or(false) {
211		return Type::SharedCost;
212	}
213
214	if meta.descriptors.voip.as_ref().map(|d| d.is_match(value)).unwrap_or(false) {
215		return Type::Voip;
216	}
217
218	if meta.descriptors.personal_number.as_ref().map(|d| d.is_match(value)).unwrap_or(false) {
219		return Type::PersonalNumber;
220	}
221
222	if meta.descriptors.pager.as_ref().map(|d| d.is_match(value)).unwrap_or(false) {
223		return Type::Pager;
224	}
225
226	if meta.descriptors.uan.as_ref().map(|d| d.is_match(value)).unwrap_or(false) {
227		return Type::Uan;
228	}
229
230	if meta.descriptors.voicemail.as_ref().map(|d| d.is_match(value)).unwrap_or(false) {
231		return Type::Voicemail;
232	}
233
234	if meta.descriptors.fixed_line.as_ref().map(|d| d.is_match(value)).unwrap_or(false) {
235		if meta.descriptors.fixed_line.as_ref().map(|d| d.national_number.as_str()) ==
236		   meta.descriptors.mobile.as_ref().map(|d| d.national_number.as_str())
237		{
238			return Type::FixedLineOrMobile;
239		}
240
241		if meta.descriptors.mobile.as_ref().map(|d| d.is_match(value)).unwrap_or(false) {
242			return Type::FixedLineOrMobile;
243		}
244
245		return Type::FixedLine;
246	}
247
248	if meta.descriptors.mobile.as_ref().map(|d| d.is_match(value)).unwrap_or(false) {
249		return Type::Mobile;
250	}
251
252	Type::Unknown
253}
254
255#[cfg(test)]
256mod test {
257	use crate::validator;
258	use crate::parser;
259	use crate::country;
260
261	#[test]
262	fn validate() {
263		assert!(validator::is_valid(&parser::parse(
264			Some(country::US), "+1 6502530000").unwrap()));
265
266		assert!(validator::is_valid(&parser::parse(
267			Some(country::IT), "+39 0236618300").unwrap()));
268
269		assert!(validator::is_valid(&parser::parse(
270			Some(country::GB), "+44 7912345678").unwrap()));
271
272		assert!(validator::is_valid(&parser::parse(
273			None, "+800 12345678").unwrap()));
274
275		assert!(validator::is_valid(&parser::parse(
276			None, "+979 123456789").unwrap()));
277
278		assert!(validator::is_valid(&parser::parse(
279			None, "+64 21387835").unwrap()));
280
281		assert!(!validator::is_valid(&parser::parse(
282			None, "+1 2530000").unwrap()));
283
284		assert!(!validator::is_valid(&parser::parse(
285			None, "+39 023661830000").unwrap()));
286
287		assert!(!validator::is_valid(&parser::parse(
288			None, "+44 791234567").unwrap()));
289
290		assert!(!validator::is_valid(&parser::parse(
291			None, "+49 1234").unwrap()));
292
293		assert!(!validator::is_valid(&parser::parse(
294			None, "+64 3316005").unwrap()));
295
296		assert!(!validator::is_valid(&parser::parse(
297			None, "+3923 2366").unwrap()));
298
299		assert!(!validator::is_valid(&parser::parse(
300			None, "+800 123456789").unwrap()));
301	}
302}