use crate::error::{Result, SammError};
pub fn validate_urns_batch(urns: &[&str]) -> Result<Vec<bool>> {
if urns.len() < 8 {
return Ok(urns.iter().map(|urn| is_valid_urn(urn)).collect());
}
use rayon::prelude::*;
Ok(urns.par_iter().map(|urn| is_valid_urn(urn)).collect())
}
fn is_valid_urn(urn: &str) -> bool {
if urn.len() < 20 {
return false;
}
if !urn.starts_with("urn:samm:") {
return false;
}
let colon_count = urn.chars().filter(|&c| c == ':').count();
if colon_count != 3 {
return false;
}
let hash_count = urn.chars().filter(|&c| c == '#').count();
if hash_count != 1 {
return false;
}
let parts: Vec<&str> = urn.split(':').collect();
if parts.len() != 4 {
return false;
}
let namespace = parts[2];
if namespace.is_empty() || !is_valid_namespace(namespace) {
return false;
}
let version_element = parts[3];
if !version_element.contains('#') {
return false;
}
let ve_parts: Vec<&str> = version_element.split('#').collect();
if ve_parts.len() != 2 {
return false;
}
let version = ve_parts[0];
let element = ve_parts[1];
if !is_valid_version(version) {
return false;
}
if element.is_empty() || !is_valid_identifier(element) {
return false;
}
true
}
fn is_valid_namespace(namespace: &str) -> bool {
if namespace.is_empty() {
return false;
}
if !namespace.contains('.') {
return false;
}
namespace
.split('.')
.all(|part| !part.is_empty() && part.chars().all(|c| c.is_alphanumeric() || c == '_'))
}
fn is_valid_version(version: &str) -> bool {
let parts: Vec<&str> = version.split('.').collect();
if parts.len() != 3 {
return false;
}
parts
.iter()
.all(|part| !part.is_empty() && part.chars().all(|c| c.is_ascii_digit()))
}
fn is_valid_identifier(id: &str) -> bool {
if id.is_empty() {
return false;
}
let mut chars = id.chars();
if let Some(first) = chars.next() {
if !first.is_alphabetic() {
return false;
}
}
chars.all(|c| c.is_alphanumeric() || c == '_')
}
pub fn count_char_simd(text: &str, target: char) -> usize {
if target.is_ascii() {
bytecount::count(text.as_bytes(), target as u8)
} else {
text.chars().filter(|&c| c == target).count()
}
}
pub fn extract_namespace_fast(urn: &str) -> Option<&str> {
if !urn.starts_with("urn:samm:") {
return None;
}
let after_prefix = &urn[9..];
after_prefix.find(':').map(|pos| &after_prefix[..pos])
}
pub fn extract_version_fast(urn: &str) -> Option<&str> {
if !urn.starts_with("urn:samm:") {
return None;
}
let colon_pos = urn.rfind(':')?;
let hash_pos = urn.find('#')?;
if colon_pos < hash_pos {
Some(&urn[colon_pos + 1..hash_pos])
} else {
None
}
}
pub fn extract_element_fast(urn: &str) -> Option<&str> {
urn.find('#').map(|pos| &urn[pos + 1..])
}
pub type UrnParts<'a> = (&'a str, &'a str, &'a str);
pub fn extract_urn_parts_batch<'a>(urns: &'a [&'a str]) -> Result<Vec<Option<UrnParts<'a>>>> {
use rayon::prelude::*;
Ok(urns
.par_iter()
.map(|&urn| {
let namespace = extract_namespace_fast(urn)?;
let version = extract_version_fast(urn)?;
let element = extract_element_fast(urn)?;
Some((namespace, version, element))
})
.collect())
}
pub fn find_urns_in_text(text: &str) -> Result<Vec<String>> {
let mut urns = Vec::new();
for word in text.split_whitespace() {
if word.starts_with("urn:samm:") {
let clean_urn = word.trim_end_matches(&[',', '.', ';', ')', ']', '}'][..]);
if is_valid_urn(clean_urn) {
urns.push(clean_urn.to_string());
}
}
}
Ok(urns)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_single_urn() {
assert!(is_valid_urn("urn:samm:org.example:1.0.0#MyAspect"));
assert!(is_valid_urn("urn:samm:com.company.product:2.1.3#Property"));
assert!(!is_valid_urn("invalid-urn"));
assert!(!is_valid_urn("urn:bamm:org.example:1.0.0#Old")); assert!(!is_valid_urn("urn:samm:org.example:1.0#Missing")); }
#[test]
fn test_validate_urns_batch() {
let urns = vec![
"urn:samm:org.example:1.0.0#MyAspect",
"invalid-urn",
"urn:samm:com.test:2.0.0#Property",
"urn:samm:io.sample:1.5.0#Characteristic",
];
let results = validate_urns_batch(&urns).expect("validation should succeed");
assert_eq!(results, vec![true, false, true, true]);
}
#[test]
fn test_count_char_simd() {
assert_eq!(
count_char_simd("urn:samm:org.example:1.0.0#MyAspect", ':'),
3
);
assert_eq!(count_char_simd("org.example.com", '.'), 2);
assert_eq!(count_char_simd("test#hash#symbols", '#'), 2);
assert_eq!(count_char_simd("no_target_here", 'x'), 0);
}
#[test]
fn test_extract_namespace_fast() {
assert_eq!(
extract_namespace_fast("urn:samm:org.example:1.0.0#MyAspect"),
Some("org.example")
);
assert_eq!(
extract_namespace_fast("urn:samm:com.company.product:2.0.0#Test"),
Some("com.company.product")
);
assert_eq!(extract_namespace_fast("invalid-urn"), None);
}
#[test]
fn test_extract_version_fast() {
assert_eq!(
extract_version_fast("urn:samm:org.example:1.0.0#MyAspect"),
Some("1.0.0")
);
assert_eq!(
extract_version_fast("urn:samm:com.test:2.1.3#Property"),
Some("2.1.3")
);
assert_eq!(extract_version_fast("invalid-urn"), None);
}
#[test]
fn test_extract_element_fast() {
assert_eq!(
extract_element_fast("urn:samm:org.example:1.0.0#MyAspect"),
Some("MyAspect")
);
assert_eq!(
extract_element_fast("urn:samm:com.test:2.0.0#Property"),
Some("Property")
);
assert_eq!(extract_element_fast("invalid-urn"), None);
}
#[test]
fn test_extract_urn_parts_batch() {
let urns = vec![
"urn:samm:org.example:1.0.0#MyAspect",
"urn:samm:com.test:2.0.0#Property",
];
let results = extract_urn_parts_batch(&urns).expect("result should be Ok");
assert_eq!(results[0], Some(("org.example", "1.0.0", "MyAspect")));
assert_eq!(results[1], Some(("com.test", "2.0.0", "Property")));
}
#[test]
fn test_find_urns_in_text() {
let text = r#"
The aspect urn:samm:org.example:1.0.0#MyAspect has properties.
Property urn:samm:org.example:1.0.0#temperature is required.
Invalid urn:bamm:old:1.0.0#Old should be ignored.
"#;
let urns = find_urns_in_text(text).expect("operation should succeed");
assert_eq!(urns.len(), 2);
assert!(urns.contains(&"urn:samm:org.example:1.0.0#MyAspect".to_string()));
assert!(urns.contains(&"urn:samm:org.example:1.0.0#temperature".to_string()));
}
#[test]
fn test_is_valid_namespace() {
assert!(is_valid_namespace("org.example"));
assert!(is_valid_namespace("com.company.product"));
assert!(is_valid_namespace("io.test_underscore"));
assert!(!is_valid_namespace("noperiod"));
assert!(!is_valid_namespace(""));
assert!(!is_valid_namespace(".invalid"));
}
#[test]
fn test_is_valid_version() {
assert!(is_valid_version("1.0.0"));
assert!(is_valid_version("2.1.3"));
assert!(is_valid_version("10.20.30"));
assert!(!is_valid_version("1.0"));
assert!(!is_valid_version("1.0.0.0"));
assert!(!is_valid_version("a.b.c"));
}
#[test]
fn test_is_valid_identifier() {
assert!(is_valid_identifier("MyAspect"));
assert!(is_valid_identifier("Property1"));
assert!(is_valid_identifier("valid_identifier"));
assert!(is_valid_identifier("Temperature"));
assert!(!is_valid_identifier("1Invalid")); assert!(!is_valid_identifier("")); assert!(!is_valid_identifier("invalid-hyphen")); }
}