use helm_schema_json_schema_walk::visit_subschemas_mut;
use serde_json::Value;
pub fn normalize_schema_pattern_dialects(schema: &mut Value) {
if let Some(object) = schema.as_object_mut() {
if let Some(Value::String(pattern)) = object.get_mut("pattern")
&& let Some(normalized) = ecma_case_folded_pattern(pattern)
{
*pattern = normalized;
}
if let Some(Value::Object(pattern_properties)) = object.get_mut("patternProperties") {
let renames: Vec<(String, String)> = pattern_properties
.keys()
.filter_map(|key| {
let normalized = ecma_case_folded_pattern(key)?;
(!pattern_properties.contains_key(&normalized))
.then(|| (key.clone(), normalized))
})
.collect();
for (key, normalized) in renames {
if let Some(subschema) = pattern_properties.remove(&key) {
pattern_properties.insert(normalized, subschema);
}
}
}
}
visit_subschemas_mut(schema, &mut normalize_schema_pattern_dialects);
}
fn ecma_case_folded_pattern(pattern: &str) -> Option<String> {
let (anchor, rest) = match pattern.strip_prefix('^') {
Some(rest) => ("^", rest),
None => ("", pattern),
};
let tail = rest.strip_prefix("(?i)")?;
let mut out = String::with_capacity(anchor.len() + tail.len() * 2);
out.push_str(anchor);
let mut chars = tail.chars().peekable();
let mut in_class = false;
while let Some(character) = chars.next() {
match character {
'\\' => {
let escaped = chars.next()?;
if matches!(
escaped,
'x' | 'u' | 'p' | 'P' | 'k' | 'Q' | 'E' | 'A' | 'z' | 'Z' | '1'..='9'
) {
return None;
}
out.push('\\');
out.push(escaped);
}
'[' if !in_class => {
in_class = true;
out.push('[');
if chars.peek() == Some(&'^') {
chars.next();
out.push('^');
}
if matches!(chars.peek(), Some(&'[' | &']')) {
return None;
}
}
']' if in_class => {
in_class = false;
out.push(']');
}
'-' if in_class => {
if chars.peek() != Some(&']') {
return None;
}
out.push('-');
}
'(' if !in_class => {
out.push('(');
if chars.peek() == Some(&'?') {
chars.next();
if chars.next() != Some(':') {
return None;
}
out.push_str("?:");
}
}
'a'..='z' | 'A'..='Z' => {
let lower = character.to_ascii_lowercase();
let upper = character.to_ascii_uppercase();
if !in_class {
out.push('[');
}
out.push(lower);
out.push(upper);
match lower {
'k' => out.push('\u{212A}'),
's' => out.push('\u{017F}'),
_ => {}
}
if !in_class {
out.push(']');
}
}
character if !character.is_ascii() => return None,
character => out.push(character),
}
}
if in_class {
return None;
}
Some(out)
}
#[cfg(test)]
#[path = "tests/pattern_dialect.rs"]
mod tests;