use osproxy_core::json::scalar_at_path;
use serde_json::Value;
use crate::error::RewriteError;
use crate::extract::extract_scalar;
pub fn construct_id(template: &str, partition: &str, doc: &Value) -> Result<String, RewriteError> {
construct_id_with(template, partition, |path| {
extract_scalar(doc, path.split('.'))
})
}
pub fn construct_id_bytes(
template: &str,
partition: &str,
body: &[u8],
) -> Result<String, RewriteError> {
construct_id_with(template, partition, |path| {
scalar_at_path(body, path.split('.')).map_err(RewriteError::from)
})
}
fn construct_id_with<F>(
template: &str,
partition: &str,
resolve_body: F,
) -> Result<String, RewriteError>
where
F: Fn(&str) -> Result<String, RewriteError>,
{
let mut out = String::with_capacity(template.len());
let mut rest = template;
while let Some(open) = rest.find('{') {
out.push_str(&rest[..open]);
let after = &rest[open + 1..];
let close = after
.find('}')
.ok_or_else(|| RewriteError::UnsupportedPlaceholder {
placeholder: after.to_owned(),
})?;
let placeholder = &after[..close];
if placeholder == "partition" {
out.push_str(partition);
} else if let Some(path) = placeholder.strip_prefix("body.") {
out.push_str(&resolve_body(path)?);
} else {
return Err(RewriteError::UnsupportedPlaceholder {
placeholder: placeholder.to_owned(),
});
}
rest = &after[close + 1..];
}
out.push_str(rest);
Ok(out)
}
pub fn map_logical_to_physical(
template: &str,
partition: &str,
logical_id: &str,
) -> Result<String, RewriteError> {
let (prefix, suffix) = id_frame(template, partition)?;
Ok(format!("{prefix}{logical_id}{suffix}"))
}
pub fn map_physical_to_logical(
template: &str,
partition: &str,
physical_id: &str,
) -> Result<Option<String>, RewriteError> {
let (prefix, suffix) = id_frame(template, partition)?;
Ok(physical_id
.strip_prefix(&prefix)
.and_then(|rest| rest.strip_suffix(&suffix))
.map(str::to_owned))
}
fn id_frame(template: &str, partition: &str) -> Result<(String, String), RewriteError> {
let mut prefix = String::new();
let mut suffix = String::new();
let mut seen_body = false;
let mut rest = template;
while let Some(open) = rest.find('{') {
let literal = &rest[..open];
let after = &rest[open + 1..];
let close = after
.find('}')
.ok_or_else(|| RewriteError::UnsupportedPlaceholder {
placeholder: after.to_owned(),
})?;
let placeholder = &after[..close];
let frame = if seen_body { &mut suffix } else { &mut prefix };
frame.push_str(literal);
if placeholder == "partition" {
frame.push_str(partition);
} else if placeholder.strip_prefix("body.").is_some() {
if seen_body {
return Err(RewriteError::IrreversibleIdTemplate);
}
seen_body = true;
} else {
return Err(RewriteError::UnsupportedPlaceholder {
placeholder: placeholder.to_owned(),
});
}
rest = &after[close + 1..];
}
if seen_body {
suffix.push_str(rest);
} else {
return Err(RewriteError::IrreversibleIdTemplate);
}
Ok((prefix, suffix))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn expands_partition_and_body_placeholders() {
let doc = json!({ "k": "natural", "nested": { "v": 9 } });
assert_eq!(
construct_id("{partition}:{body.k}", "p1", &doc).unwrap(),
"p1:natural"
);
assert_eq!(
construct_id("{body.nested.v}-{partition}", "p1", &doc).unwrap(),
"9-p1"
);
}
#[test]
fn literal_only_template_is_copied() {
let doc = json!({});
assert_eq!(construct_id("static-id", "p", &doc).unwrap(), "static-id");
}
#[test]
fn unknown_placeholder_is_rejected() {
let doc = json!({});
assert_eq!(
construct_id("{principal}", "p", &doc).unwrap_err(),
RewriteError::UnsupportedPlaceholder {
placeholder: "principal".to_owned()
}
);
}
#[test]
fn unterminated_placeholder_is_rejected() {
let doc = json!({});
assert!(construct_id("{partition", "p", &doc).is_err());
}
#[test]
fn missing_body_path_propagates_error() {
let doc = json!({ "a": 1 });
assert!(construct_id("{body.missing}", "p", &doc).is_err());
}
#[test]
fn logical_to_physical_substitutes_natural_key() {
assert_eq!(
map_logical_to_physical("{partition}:{body.id}", "acme", "7").unwrap(),
"acme:7"
);
assert_eq!(
map_logical_to_physical("doc-{body.k}@{partition}", "p1", "abc").unwrap(),
"doc-abc@p1"
);
}
#[test]
fn physical_to_logical_strips_the_frame() {
assert_eq!(
map_physical_to_logical("{partition}:{body.id}", "acme", "acme:7").unwrap(),
Some("7".to_owned())
);
assert_eq!(
map_physical_to_logical("{partition}:{body.id}", "acme", "other:7").unwrap(),
None
);
}
#[test]
fn mapping_round_trips_for_arbitrary_natural_keys() {
let template = "{partition}:{body.natural}";
for key in ["1001", "a-b", "", "x:y"] {
let physical = map_logical_to_physical(template, "acme", key).unwrap();
assert_eq!(
map_physical_to_logical(template, "acme", &physical).unwrap(),
Some(key.to_owned())
);
}
}
#[test]
fn templates_without_exactly_one_body_placeholder_are_irreversible() {
assert_eq!(
map_logical_to_physical("{partition}:static", "p", "x").unwrap_err(),
RewriteError::IrreversibleIdTemplate
);
assert_eq!(
map_logical_to_physical("{body.a}-{body.b}", "p", "x").unwrap_err(),
RewriteError::IrreversibleIdTemplate
);
}
}