#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RuntimeExpressionSource {
Header,
Payload,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RuntimeExpression<'a> {
pub source: RuntimeExpressionSource,
pub pointer: &'a str,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RuntimeExpressionError {
MissingMessagePrefix,
UnknownSource,
MissingFragment,
InvalidPointer,
}
impl RuntimeExpressionError {
#[must_use]
pub fn message(self) -> &'static str {
match self {
Self::MissingMessagePrefix => "must start with `$message.`",
Self::UnknownSource => "source must be `header` or `payload`",
Self::MissingFragment => "must have a `#` fragment (use `#` alone for the whole value)",
Self::InvalidPointer => "fragment must be a JSON Pointer starting with `/`",
}
}
}
pub fn parse(expression: &str) -> Result<RuntimeExpression<'_>, RuntimeExpressionError> {
let rest = expression
.strip_prefix("$message.")
.ok_or(RuntimeExpressionError::MissingMessagePrefix)?;
let Some((source, pointer)) = rest.split_once('#') else {
return Err(match rest {
"header" | "payload" => RuntimeExpressionError::MissingFragment,
_ => RuntimeExpressionError::UnknownSource,
});
};
let source = match source {
"header" => RuntimeExpressionSource::Header,
"payload" => RuntimeExpressionSource::Payload,
_ => return Err(RuntimeExpressionError::UnknownSource),
};
if !pointer.is_empty() && !pointer.starts_with('/') {
return Err(RuntimeExpressionError::InvalidPointer);
}
Ok(RuntimeExpression { source, pointer })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_header_and_payload_with_pointer() {
let e = parse("$message.header#/correlationId").unwrap();
assert_eq!(e.source, RuntimeExpressionSource::Header);
assert_eq!(e.pointer, "/correlationId");
let e = parse("$message.payload#/user/id").unwrap();
assert_eq!(e.source, RuntimeExpressionSource::Payload);
assert_eq!(e.pointer, "/user/id");
}
#[test]
fn empty_fragment_selects_the_whole_value() {
let e = parse("$message.header#").unwrap();
assert_eq!(e.pointer, "");
}
#[test]
fn rejects_a_bare_source_without_a_fragment() {
for bad in ["$message.payload", "$message.header"] {
assert_eq!(
parse(bad).unwrap_err(),
RuntimeExpressionError::MissingFragment,
"should reject {bad}"
);
}
}
#[test]
fn rejects_missing_prefix() {
for bad in ["header#/x", "$request.header", "", "$message"] {
assert_eq!(
parse(bad).unwrap_err(),
RuntimeExpressionError::MissingMessagePrefix,
"should reject {bad}"
);
}
}
#[test]
fn rejects_unknown_source() {
for bad in ["$message.body#/x", "$message.", "$message.body"] {
assert_eq!(
parse(bad).unwrap_err(),
RuntimeExpressionError::UnknownSource,
"should reject {bad}"
);
}
}
#[test]
fn rejects_fragment_that_is_not_a_json_pointer() {
assert_eq!(
parse("$message.payload#user/id").unwrap_err(),
RuntimeExpressionError::InvalidPointer
);
}
#[test]
fn error_messages_are_human_readable() {
assert!(
RuntimeExpressionError::MissingMessagePrefix
.message()
.contains("$message.")
);
assert!(
RuntimeExpressionError::UnknownSource
.message()
.contains("header")
);
assert!(
RuntimeExpressionError::InvalidPointer
.message()
.contains("JSON Pointer")
);
assert!(
RuntimeExpressionError::MissingFragment
.message()
.contains('#')
);
}
}