pub(crate) fn cedar_policy_version(manifest: &str) -> Option<String> {
let mut table = "";
for raw_line in manifest.lines() {
let line = without_comment(raw_line).trim();
if line.is_empty() {
continue;
}
if let Some(header) = line
.strip_prefix('[')
.and_then(|line| line.strip_suffix(']'))
{
table = header.trim();
continue;
}
let Some((key, value)) = line.split_once('=') else {
continue;
};
let key = key.trim().trim_matches(['"', '\'']);
if table == "dependencies" && key == "cedar-policy" {
if let Some(version) = quoted_value(value) {
return exact_version(version);
}
if let Some(version) = inline_table_version(value) {
return exact_version(version);
}
}
if matches!(
table,
"dependencies.cedar-policy" | "dependencies.\"cedar-policy\""
) && key == "version"
{
return quoted_value(value).and_then(exact_version);
}
}
None
}
fn inline_table_version(value: &str) -> Option<&str> {
let table = value.trim().strip_prefix('{')?.strip_suffix('}')?;
table.split(',').find_map(|entry| {
let (key, value) = entry.split_once('=')?;
(key.trim().trim_matches(['"', '\'']) == "version")
.then(|| quoted_value(value))
.flatten()
})
}
fn quoted_value(value: &str) -> Option<&str> {
let value = value.trim();
let quote = value.chars().next()?;
if !matches!(quote, '"' | '\'') {
return None;
}
let remainder = &value[quote.len_utf8()..];
let mut escaped = false;
for (index, character) in remainder.char_indices() {
if quote == '"' && character == '\\' && !escaped {
escaped = true;
continue;
}
if character == quote && !escaped {
return Some(&remainder[..index]);
}
escaped = false;
}
None
}
fn exact_version(version: &str) -> Option<String> {
let version = version.trim().strip_prefix('=')?.trim();
(!version.is_empty()).then(|| version.to_string())
}
fn without_comment(line: &str) -> &str {
let mut basic_string = false;
let mut literal_string = false;
let mut escaped = false;
for (index, character) in line.char_indices() {
match character {
'\\' if basic_string && !escaped => {
escaped = true;
continue;
}
'"' if !literal_string && !escaped => basic_string = !basic_string,
'\'' if !basic_string => literal_string = !literal_string,
'#' if !basic_string && !literal_string => return &line[..index],
_ => {}
}
escaped = false;
}
line
}
#[cfg(test)]
mod tests {
use super::cedar_policy_version;
#[test]
fn parses_source_string_dependency() {
let manifest = r#"
[dependencies]
cedar-policy = "=4.12.0"
"#;
assert_eq!(cedar_policy_version(manifest).as_deref(), Some("4.12.0"));
}
#[test]
fn parses_source_inline_dependency() {
let manifest = r#"
[dependencies]
cedar-policy = { version = "=4.12.0", features = ["partial-eval"] }
"#;
assert_eq!(cedar_policy_version(manifest).as_deref(), Some("4.12.0"));
}
#[test]
fn parses_cargo_normalized_dependency_table() {
let manifest = r#"
[package]
version = "0.0.18"
[dependencies.cedar-policy]
version = "=4.12.0"
"#;
assert_eq!(cedar_policy_version(manifest).as_deref(), Some("4.12.0"));
}
#[test]
fn rejects_a_compatible_version_requirement() {
let manifest = r#"
[dependencies]
cedar-policy = "4.12"
"#;
assert_eq!(cedar_policy_version(manifest), None);
}
#[test]
fn parses_current_source_manifest() {
assert_eq!(
cedar_policy_version(include_str!("Cargo.toml")).as_deref(),
Some("4.12.0")
);
}
}