ephemeris_forge/
variable_name.rs1use std::collections::BTreeSet;
2
3#[derive(Clone, Copy, Debug)]
5pub struct VariableNameRules {
6 pub reserved_names: &'static [&'static str],
8 pub reserved_prefixes: &'static [&'static str],
10}
11
12#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
14pub enum VariableNameError {
15 #[error("{name:?} is not a valid Actions variable name")]
17 Invalid {
18 name: String,
20 },
21 #[error("Actions variable {name:?} has another name that differs only in letter case")]
23 CaseClash {
24 name: String,
26 },
27}
28
29impl VariableNameRules {
30 #[must_use]
35 pub fn is_valid(&self, name: &str) -> bool {
36 let upper = name.to_ascii_uppercase();
37 let mut characters = name.chars();
38 characters
39 .next()
40 .is_some_and(|first| first.is_ascii_alphabetic() || first == '_')
41 && characters.all(|character| character.is_ascii_alphanumeric() || character == '_')
42 && !self.reserved_names.contains(&upper.as_str())
43 && !self
44 .reserved_prefixes
45 .iter()
46 .any(|prefix| upper.starts_with(prefix))
47 }
48
49 pub fn check<'a, I: IntoIterator<Item = &'a str>>(
56 &self,
57 names: I,
58 ) -> Result<(), VariableNameError> {
59 let mut seen = BTreeSet::new();
60 for name in names {
61 if !self.is_valid(name) {
62 return Err(VariableNameError::Invalid {
63 name: name.to_owned(),
64 });
65 }
66 if !seen.insert(name.to_ascii_uppercase()) {
67 return Err(VariableNameError::CaseClash {
68 name: name.to_owned(),
69 });
70 }
71 }
72 Ok(())
73 }
74}