acorn_lib/schema/
validate.rs1#![deny(missing_docs)]
2use crate::constants::*;
6use chrono::prelude::*;
7use fancy_regex::Regex;
8use uriparse::URI;
9use validator::ValidationError;
10
11pub fn format_phone_number(value: &str) -> Result<String, ValidationError> {
13 const MESSAGE: &str = "Unable to format telephone number";
14 match RE_PHONE.captures(value) {
15 | Ok(value) => match value {
16 | Some(captures) => {
17 let country_code = match captures.name("country") {
18 | Some(value) => Some(value.as_str().trim().to_string()),
19 | None => None,
20 };
21 let area_code = match captures.name("area") {
22 | Some(value) => Some(value.as_str().replace("(", "").replace(")", "")),
23 | None => None,
24 };
25 let prefix = match captures.name("prefix") {
26 | Some(value) => Some(value.as_str().to_string()),
27 | None => None,
28 };
29 let line = match captures.name("line") {
30 | Some(value) => Some(value.as_str().to_string()),
31 | None => None,
32 };
33 Ok([country_code, area_code, prefix, line]
34 .into_iter()
35 .flatten()
36 .collect::<Vec<String>>()
37 .join("."))
38 }
39 | None => Err(ValidationError::new("telephone").with_message(MESSAGE.into())),
40 },
41 | _ => Err(ValidationError::new("telephone").with_message(MESSAGE.into())),
42 }
43}
44pub fn has_image_extension(value: &str) -> Result<(), ValidationError> {
46 const MESSAGE: &str = "Please provide a path with a PNG, JPEG, GIF, WEBP, TIFF or SVG extension";
47 match RE_IMAGE_EXTENSION.is_match(value) {
48 | Ok(value) if value => Ok(()),
49 | _ => Err(ValidationError::new("image").with_message(MESSAGE.into())),
50 }
51}
52fn is_current_year(value: String) -> bool {
53 let now: DateTime<Utc> = Utc::now();
54 let year = now.year().to_string().parse::<i32>().unwrap_or_default();
55 value.parse::<i32>().unwrap_or_default() <= year
56}
57pub fn is_doi(value: &str) -> Result<(), ValidationError> {
61 const MESSAGE: &str = "Please provide a valid DOI, by itself and without domain or 'doi:' prefix.";
62 match RE_DOI.is_match(value) {
63 | Ok(x) if x && !value.contains("10.5555/") => Ok(()),
64 | _ => Err(ValidationError::new("doi").with_message(MESSAGE.into())),
65 }
66}
67pub fn is_ip6(value: &str) -> Result<(), ValidationError> {
69 const MESSAGE: &str = "Please provide a valid IP6 address";
70 match RE_IP6.is_match(value) {
71 | Ok(value) if value => Ok(()),
72 | _ => Err(ValidationError::new("IP6").with_message(MESSAGE.into())),
73 }
74}
75pub fn is_iso8601_date(value: &str) -> Result<(), ValidationError> {
79 const MESSAGE: &str = "Please provide a valid ISO 8601 date (e.g., YYYY-MM-DD)";
80 match RE_ISO_8601_DATE.is_match(value) {
82 | Ok(value) if value => Ok(()),
83 | _ => Err(ValidationError::new("ISO 8601 Date").with_message(MESSAGE.into())),
84 }
85}
86pub fn is_iso8601_year(value: &str) -> Result<(), ValidationError> {
90 const MESSAGE: &str = "Please provide a valid ISO 8601 year (e.g., YYYY)";
91 match RE_ISO_8601_YEAR.is_match(value) {
92 | Ok(x) if x && is_current_year(value.to_string()) => Ok(()),
93 | _ => Err(ValidationError::new("ISO 8601 Date").with_message(MESSAGE.into())),
94 }
95}
96pub fn is_kebabcase(value: &str) -> Result<(), ValidationError> {
98 const MESSAGE: &str = "Please provide an ID in kebab-case format";
99 let kebab = match Regex::new(r"[ *_./\!@#$%^&(){}]") {
100 | Ok(re) => re.replace_all(value, "").trim().to_string(),
101 | Err(err) => err.to_string(),
102 };
103 match kebab.to_lowercase().eq(&value) {
104 | true => Ok(()),
105 | _ => Err(ValidationError::new("kebabcase").with_message(MESSAGE.into())),
106 }
107}
108pub fn is_list_url(value: &[String]) -> Result<(), ValidationError> {
110 let is_valid = value.iter().all(|x| URI::try_from(x.as_str()).is_ok());
111 match is_valid {
112 | true => Ok(()),
113 | _ => Err(ValidationError::new("URLs").with_message("Every URL should be valid".to_string().into())),
114 }
115}
116pub fn is_phone_number(value: &str) -> Result<(), ValidationError> {
120 const MESSAGE: &str = "Please provide a valid phone number";
121 let is_fake = match RE_FAKE_PHONE.is_match(value) {
122 | Ok(value) if value => true,
123 | _ => false,
124 };
125 match RE_PHONE.is_match(value) {
126 | Ok(value) if value && !is_fake => Ok(()),
127 | _ => Err(ValidationError::new("phone").with_message(MESSAGE.into())),
128 }
129}
130pub fn is_raid(value: &str) -> Result<(), ValidationError> {
132 const MESSAGE: &str = "Please provide a valid RAiD";
133 match RE_RAID.is_match(value) {
134 | Ok(value) if value => Ok(()),
135 | _ => Err(ValidationError::new("raid").with_message(MESSAGE.into())),
136 }
137}
138pub fn is_ror(value: &str) -> Result<(), ValidationError> {
140 const MESSAGE: &str = "Please provide a valid ROR";
141 match RE_ROR.is_match(value) {
142 | Ok(value) if value => Ok(()),
143 | _ => Err(ValidationError::new("ror").with_message(MESSAGE.into())),
144 }
145}
146pub fn validate_attribute_approach(value: &[String]) -> Result<(), ValidationError> {
148 const MAX_LENGTH: usize = MAX_LENGTH_APPROACH;
149 let message: String = format!("Each approach statement should be less than {MAX_LENGTH} characters");
150 let is_valid = value.iter().all(|x| x.len() <= MAX_LENGTH);
151 match is_valid {
152 | true => Ok(()),
153 | _ => Err(ValidationError::new("approach").with_message(message.into())),
154 }
155}
156pub fn validate_attribute_areas(value: &[String]) -> Result<(), ValidationError> {
158 const MAX_LENGTH: usize = MAX_LENGTH_RESEARCH_AREA;
159 let is_valid = value.iter().all(|x| x.len() <= MAX_LENGTH);
160 match is_valid {
161 | true => Ok(()),
162 | _ => Err(ValidationError::new("area").with_message(format!("Each area should be less than {MAX_LENGTH} characters").into())),
163 }
164}
165pub fn validate_attribute_capabilities(value: &[String]) -> Result<(), ValidationError> {
169 const MAX_LENGTH: usize = MAX_LENGTH_CAPABILIY;
170 let is_valid = value.iter().all(|x| x.len() <= MAX_LENGTH);
171 match is_valid {
172 | true => Ok(()),
173 | _ => Err(ValidationError::new("capability").with_message(format!("Each capability should be less than {MAX_LENGTH} characters").into())),
174 }
175}
176pub fn validate_attribute_doi(value: &[String]) -> Result<(), ValidationError> {
180 let is_valid = value.iter().all(|x| is_doi(x).is_ok());
181 match is_valid {
182 | true => Ok(()),
183 | _ => Err(ValidationError::new("DOIs").with_message("Every DOI should be valid".to_string().into())),
184 }
185}
186pub fn validate_attribute_ror(value: &[String]) -> Result<(), ValidationError> {
190 let is_valid = value.iter().all(|x| is_ror(x).is_ok());
191 match is_valid {
192 | true => Ok(()),
193 | _ => Err(ValidationError::new("RORs").with_message("Every ROR should be valid".to_string().into())),
194 }
195}
196pub fn validate_attribute_impact(value: &[String]) -> Result<(), ValidationError> {
201 const MAX_LENGTH: usize = MAX_LENGTH_IMPACT;
202 match value.iter().all(|x| x.len() <= MAX_LENGTH) {
203 | true => {
204 let all_periods = value.iter().all(|x| x.trim().ends_with("."));
205 let no_periods = value.iter().all(|x| !x.trim().ends_with("."));
206 let is_valid = all_periods || no_periods;
207 match is_valid {
208 | true => Ok(()),
209 | _ => Err(ValidationError::new("impact")
210 .with_message("Impact statements should be all sentences with periods or all phrases without periods".into())),
211 }
212 }
213 | _ => Err(ValidationError::new("impact").with_message(format!("Each impact statement should be less than {MAX_LENGTH} characters").into())),
214 }
215}