quil_rs/validation/
identifier.rs1use std::str::FromStr;
3
4use once_cell::sync::Lazy;
5use regex::Regex;
6use thiserror;
7
8use crate::reserved::ReservedToken;
9
10#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
11pub enum IdentifierValidationError {
12 #[error("{0} is a reserved token")]
13 Reserved(ReservedToken),
14
15 #[error("{0} is not a valid identifier")]
16 Invalid(String),
17}
18
19const IDENTIFIER_REGEX_STRING: &str = r"^([A-Za-z_]|[A-Za-z_][A-Za-z0-9\-_]*[A-Za-z0-9_])$";
21
22static IDENTIFIER_REGEX: Lazy<Regex> =
23 Lazy::new(|| Regex::new(IDENTIFIER_REGEX_STRING).expect("regex should be valid"));
24
25pub fn validate_identifier(ident: &str) -> Result<(), IdentifierValidationError> {
27 match IDENTIFIER_REGEX.is_match(ident) {
28 true => Ok(()),
29 false => Err(IdentifierValidationError::Invalid(ident.to_string())),
30 }
31}
32
33pub fn validate_user_identifier(ident: &str) -> Result<(), IdentifierValidationError> {
35 ReservedToken::from_str(ident).map_or(validate_identifier(ident), |t| {
36 Err(IdentifierValidationError::Reserved(t))
37 })
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43 use rstest::rstest;
44
45 #[rstest]
46 #[case("Good_Ident1f1er-0", true)]
47 #[case("H", true)]
48 #[case("-Cant-start-with-dash", false)]
49 #[case("Cant-end-with-dash-", false)]
50 #[case("1-Cant-start-with-number", false)]
51 fn test_validate_identifier(#[case] input: &str, #[case] ok: bool) {
52 assert_eq!(validate_identifier(input).is_ok(), ok)
53 }
54
55 #[rstest]
56 #[case("Good_Ident1f1er-0", true)]
57 #[case("DEFGATE", false)]
58 #[case("AS", false)]
59 #[case("pi", false)]
60 #[case("PAULI-SUM", false)]
61 #[case("H", false)]
62 #[case("G", true)]
63 fn test_validate_user_identifier(#[case] input: &str, #[case] ok: bool) {
64 assert_eq!(validate_user_identifier(input).is_ok(), ok)
65 }
66}