use crate::traversal::{ExecutionContext, Traverser};
use crate::value::Value;
#[derive(Clone, Copy, Debug, Default)]
pub struct TextScoreStep;
impl TextScoreStep {
pub fn new() -> Self {
Self
}
}
#[inline]
fn project_score(t: &Traverser) -> Value {
match t.get_sack::<f32>() {
Some(score) => Value::Float(*score as f64),
None => Value::Null,
}
}
impl crate::traversal::step::Step for TextScoreStep {
type Iter<'a>
= impl Iterator<Item = Traverser> + 'a
where
Self: 'a;
fn apply<'a>(
&'a self,
_ctx: &'a ExecutionContext<'a>,
input: Box<dyn Iterator<Item = Traverser> + 'a>,
) -> Self::Iter<'a> {
input.map(|t| {
let v = project_score(&t);
t.with_value(v)
})
}
fn name(&self) -> &'static str {
"text_score"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Transform
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let v = project_score(&input);
Box::new(std::iter::once(input.with_value(v)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::Graph;
use crate::traversal::step::Step;
use crate::traversal::traverser::box_sack;
use crate::traversal::ExecutionContext;
use crate::traversal::SnapshotLike;
#[test]
fn projects_score_when_sack_has_f32() {
let g = Graph::new();
let snap = g.snapshot();
let ctx = ExecutionContext::new(snap.storage(), snap.interner());
let mut t = Traverser::new(Value::Int(1));
t.sack = Some(box_sack(0.875f32));
let step = TextScoreStep::new();
let out: Vec<_> = step.apply(&ctx, Box::new(std::iter::once(t))).collect();
assert_eq!(out.len(), 1);
match &out[0].value {
Value::Float(f) => assert!((f - 0.875).abs() < 1e-6),
other => panic!("expected Float, got {:?}", other),
}
}
#[test]
fn emits_null_when_sack_is_none() {
let g = Graph::new();
let snap = g.snapshot();
let ctx = ExecutionContext::new(snap.storage(), snap.interner());
let t = Traverser::new(Value::Int(1));
assert!(t.sack.is_none());
let step = TextScoreStep::new();
let out: Vec<_> = step.apply(&ctx, Box::new(std::iter::once(t))).collect();
assert_eq!(out.len(), 1);
assert!(matches!(out[0].value, Value::Null));
}
#[test]
fn emits_null_when_sack_has_wrong_type() {
let g = Graph::new();
let snap = g.snapshot();
let ctx = ExecutionContext::new(snap.storage(), snap.interner());
let mut t = Traverser::new(Value::Int(1));
t.sack = Some(box_sack(42i64));
let step = TextScoreStep::new();
let out: Vec<_> = step.apply(&ctx, Box::new(std::iter::once(t))).collect();
assert_eq!(out.len(), 1);
assert!(matches!(out[0].value, Value::Null));
}
#[test]
fn preserves_path_and_loops_and_bulk() {
let g = Graph::new();
let snap = g.snapshot();
let ctx = ExecutionContext::new(snap.storage(), snap.interner());
let mut t = Traverser::new(Value::Int(1));
t.sack = Some(box_sack(1.0f32));
t.extend_path_labeled("hit");
t.loops = 3;
t.bulk = 7;
let step = TextScoreStep::new();
let out: Vec<_> = step.apply(&ctx, Box::new(std::iter::once(t))).collect();
assert_eq!(out.len(), 1);
assert!(out[0].path.has_label("hit"));
assert_eq!(out[0].loops, 3);
assert_eq!(out[0].bulk, 7);
}
#[test]
fn streaming_path_matches_iterator_path() {
use crate::traversal::context::StreamingContext;
let g = Graph::new();
let snap = g.snapshot();
let mut t = Traverser::new(Value::Int(1));
t.sack = Some(box_sack(0.5f32));
let step = TextScoreStep::new();
let sctx = StreamingContext::new(snap.arc_streamable(), snap.arc_interner());
let out: Vec<_> = step.apply_streaming(sctx, t).collect();
assert_eq!(out.len(), 1);
match &out[0].value {
Value::Float(f) => assert!((f - 0.5).abs() < 1e-6),
other => panic!("expected Float, got {:?}", other),
}
}
#[test]
fn empty_input_yields_empty_output() {
let g = Graph::new();
let snap = g.snapshot();
let ctx = ExecutionContext::new(snap.storage(), snap.interner());
let step = TextScoreStep::new();
let input: Vec<Traverser> = vec![];
let out: Vec<_> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(out.is_empty());
}
#[test]
fn name_is_text_score() {
let step = TextScoreStep::new();
assert_eq!(<TextScoreStep as Step>::name(&step), "text_score");
}
}