use compact_str::CompactString;
use crate::graph::GraphStorage;
use crate::*;
pub(crate) const DEFAULT_FAIL_MESSAGE: &str = "fail() step triggered";
#[derive(Clone)]
struct StepFail {
message: Option<CompactString>,
}
impl fmt::Display for StepFail {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.message {
Some(message) => write!(f, "fail({})", crate::value::literal::rhai_string(message)),
None => write!(f, "fail()"),
}
}
}
fn fail_step<'g, S: GraphStorage + 'g>(message: Option<&str>) -> GraphStep<'g, S> {
GraphStep::SideEffect(
"fail",
Box::new(StepFail {
message: message.map(CompactString::new),
}),
)
}
impl<'g, S: GraphStorage + 'g> GraphTraversalSource<'g, S> {
pub fn fail(self, message: Option<&str>) -> Self {
self.push_step(fail_step(message))
}
}
impl __ {
pub fn fail<'g, S: GraphStorage + 'g>(message: Option<&str>) -> AnonymousTraversal<'g, S> {
AnonymousTraversal::new().fail(message)
}
}
impl<'g, S: GraphStorage + 'g> AnonymousTraversal<'g, S> {
pub fn fail(self, message: Option<&str>) -> Self {
self.push_step(fail_step(message))
}
}
fn render_value<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: &TraverserValue<S>,
) -> String {
match value {
TraverserValue::Vertex(_) | TraverserValue::Edge(_) => {
let prefix = if matches!(value, TraverserValue::Vertex(_)) {
"v"
} else {
"e"
};
match TraverserValue::fetch_id(context, value) {
Ok(Some(id)) => format!("{prefix}[{id}]"),
Ok(None) | Err(_) => format!("{prefix}[?]"),
}
}
other => {
let text = other
.into_node_property(context.storage)
.ok()
.and_then(|property| crate::value::physical::to_json_text(&property).ok())
.unwrap_or_else(|| other.type_name().to_string());
if text.chars().count() > 80 {
let cut: String = text.chars().take(80).collect();
format!("{cut}...")
} else {
text
}
}
}
}
impl<'g, S: GraphStorage + 'g> GraphStepSideEffect<'g, S> for StepFail {
fn process(
&self,
context: &mut TraverserContext<'_, S>,
source: &Traverser<S>,
) -> TraverserResult<()> {
Err(Box::new(TraverserError::Fail {
message: self
.message
.as_deref()
.unwrap_or(DEFAULT_FAIL_MESSAGE)
.to_string(),
value: render_value(context, &source.value),
}))
}
fn clone_box(&self) -> Box<dyn GraphStepSideEffect<'g, S> + 'g> {
Box::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fails_with_the_default_message() {
test_dual_err!(
graph: GraphSource::tinkerpop_modern(),
rust: |g| g.v(None).fail(None).to_list(),
rhai: [r#"g.V().fail()"#, r#"g.v().fail()"#],
assert: |err| assert!(err.to_string().contains("fail() step triggered"), "{err}"),
);
}
#[test]
fn fails_with_the_message() {
test_dual_err!(
graph: GraphSource::tinkerpop_modern(),
rust: |g| g.v(None).fail(Some("msg")).to_list(),
rhai: [r#"g.V().fail("msg")"#],
assert: |err| assert!(err.to_string().contains("msg"), "{err}"),
);
}
#[test]
fn only_when_reached() {
test_dual!(
graph: GraphSource::tinkerpop_modern(),
rust: |g| g.v(None).coalesce(vec![__::values("name"), __::fail(None)]).count().to_list(),
rhai: [r#"g.V().coalesce(__.values("name"), __.fail()).count()"#],
assert: |res| assert_eq!(res[0].as_i64(), Some(6)),
);
test_dual_err!(
graph: GraphSource::tinkerpop_modern(),
rust: |g| g.v(None).union(vec![__::out(None), __::fail(None)]).to_list(),
rhai: [r#"g.V().union(__.out(), __.fail())"#],
assert: |err| assert!(err.to_string().contains("fail()"), "{err}"),
);
}
#[test]
fn profile_names_the_step() {
let graph = GraphSource::tinkerpop_modern();
let lock = graph.read();
let result = lock.traversal().v("99").fail(Some("x")).profile().unwrap();
assert!(result.to_string().contains("fail(\"x\")"), "{result}");
}
}