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