use crate::graph::GraphStorage;
use crate::traversal::step::map_steps::temporal::{describe, held_value, replaced};
use crate::value::temporal::{
Date, DateTime, LocalDateTime, Temporal, TemporalKind, TemporalResult, Time,
};
use crate::*;
#[derive(Clone, Debug)]
enum Target {
Tinkerpop,
Kind(TemporalKind),
Rejected(GType),
}
#[derive(Clone)]
struct StepAsDate {
target: Target,
}
impl fmt::Display for StepAsDate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.target {
Target::Tinkerpop => f.write_str("as_date()"),
Target::Kind(kind) => write!(f, "as_date({})", gtype_of(*kind).token()),
Target::Rejected(token) => write!(f, "as_date({})", token.token()),
}
}
}
fn gtype_of(kind: TemporalKind) -> GType {
match kind {
TemporalKind::Date => GType::DATE,
TemporalKind::LocalDateTime => GType::LOCALDATETIME,
TemporalKind::DateTime => GType::DATETIME,
TemporalKind::Time => GType::TIME,
TemporalKind::Duration => GType::DURATION,
}
}
fn target_of(target: GType) -> Target {
let kind = target
.as_type()
.and_then(crate::graph::schema::ValueType::temporal_kind);
match kind {
Some(TemporalKind::Duration) | None => Target::Rejected(target),
Some(kind) => Target::Kind(kind),
}
}
fn step<'g, S: GraphStorage + 'g>(target: Target) -> GraphStep<'g, S> {
GraphStep::Map("as_date", Box::new(StepAsDate { target }))
}
impl<'g, S: GraphStorage + 'g> GraphTraversalSource<'g, S> {
pub fn as_date(self) -> Self {
self.push_step(step(Target::Tinkerpop))
}
pub fn as_date_to(self, target: GType) -> Self {
self.push_step(step(target_of(target)))
}
}
impl __ {
pub fn as_date<'g, S: GraphStorage + 'g>() -> AnonymousTraversal<'g, S> {
AnonymousTraversal::new().as_date()
}
pub fn as_date_to<'g, S: GraphStorage + 'g>(target: GType) -> AnonymousTraversal<'g, S> {
AnonymousTraversal::new().as_date_to(target)
}
}
impl<'g, S: GraphStorage + 'g> AnonymousTraversal<'g, S> {
pub fn as_date(self) -> Self {
self.push_step(step(Target::Tinkerpop))
}
pub fn as_date_to(self, target: GType) -> Self {
self.push_step(step(target_of(target)))
}
}
type Refusal = String;
fn from_temporal(value: Temporal, kind: TemporalKind) -> Result<Temporal, Refusal> {
let midnight = Time::from_micros_of_day(0).map_err(|err| err.to_string())?;
let converted: TemporalResult<Temporal> = match (kind, value) {
(TemporalKind::DateTime, Temporal::DateTime(_))
| (TemporalKind::Date, Temporal::Date(_))
| (TemporalKind::LocalDateTime, Temporal::LocalDateTime(_))
| (TemporalKind::Time, Temporal::Time(_)) => Ok(value),
(TemporalKind::DateTime, Temporal::Date(v)) => {
DateTime::from_local(LocalDateTime::new(v, midnight), 0).map(Temporal::DateTime)
}
(TemporalKind::DateTime, Temporal::LocalDateTime(v)) => {
DateTime::from_local(v, 0).map(Temporal::DateTime)
}
(TemporalKind::Date, Temporal::DateTime(v)) => Ok(Temporal::Date(v.local().date())),
(TemporalKind::Date, Temporal::LocalDateTime(v)) => Ok(Temporal::Date(v.date())),
(TemporalKind::LocalDateTime, Temporal::DateTime(v)) => {
Ok(Temporal::LocalDateTime(v.local()))
}
(TemporalKind::LocalDateTime, Temporal::Date(v)) => {
Ok(Temporal::LocalDateTime(LocalDateTime::new(v, midnight)))
}
(TemporalKind::Time, Temporal::DateTime(v)) => Ok(Temporal::Time(v.local().time())),
(TemporalKind::Time, Temporal::LocalDateTime(v)) => Ok(Temporal::Time(v.time())),
_ => {
return Err(format!(
"a {} does not convert to a {}",
value.kind(),
kind.name()
));
}
};
converted.map_err(|err| err.to_string())
}
fn from_text(text: &str, kind: TemporalKind) -> Result<Temporal, Refusal> {
let parsed = match kind {
TemporalKind::DateTime => text.parse::<DateTime>().map(Temporal::DateTime),
TemporalKind::Date => text.parse::<Date>().map(Temporal::Date),
TemporalKind::LocalDateTime => text.parse::<LocalDateTime>().map(Temporal::LocalDateTime),
TemporalKind::Time => text.parse::<Time>().map(Temporal::Time),
TemporalKind::Duration => Temporal::parse_as(kind, text),
};
parsed.map_err(|err| match err {
crate::value::temporal::TemporalError::Parse { reason, .. } => reason.to_string(),
other => other.to_string(),
})
}
pub(super) fn convert(value: &ElementProperty, kind: TemporalKind) -> Result<Temporal, Refusal> {
match value {
ElementProperty::Temporal(temporal) => from_temporal(*temporal, kind),
ElementProperty::String(text) => from_text(text, kind),
ElementProperty::Compressed(text) => text.with_text(|text| from_text(text, kind)),
ElementProperty::Number(number) if kind == TemporalKind::DateTime => {
match number.as_i64() {
Some(millis) => DateTime::from_epoch_millis(millis)
.map(Temporal::DateTime)
.map_err(|err| err.to_string()),
None => Err(
"a float is not a number of epoch milliseconds (only an integer is; \
as_number(GType.LONG) truncates it)"
.to_string(),
),
}
}
ElementProperty::Number(_) => Err(format!(
"a number converts to a datetime only, not to a {}",
kind.name()
)),
ElementProperty::Null => Err("null is not a date".to_string()),
ElementProperty::Boolean(_)
| ElementProperty::Uuid(_)
| ElementProperty::Vector(_)
| ElementProperty::Array(_)
| ElementProperty::Object(_)
| ElementProperty::Map(_)
| ElementProperty::Vertex(_)
| ElementProperty::Edge(_) => Err(format!(
"a {} is not a date or time",
crate::graph::schema::value_type_name(value)
)),
}
}
pub(super) fn parse_error(
step: &'static str,
target: &'static str,
value: String,
reason: Refusal,
) -> Box<TraverserError> {
Box::new(TraverserError::Value(ValueError::TemporalParse {
step,
target,
value,
reason,
}))
}
impl<'g, S: GraphStorage + 'g> GraphStepMap<'g, S> for StepAsDate {
fn process(
&self,
context: &mut TraverserContext<'_, S>,
source: Traverser<S>,
) -> TraverserResult<Traverser<S>> {
let kind = match &self.target {
Target::Tinkerpop => TemporalKind::DateTime,
Target::Kind(kind) => *kind,
Target::Rejected(_) => {
<Self as GraphStepMap<'g, S>>::plan_check(self)?;
TemporalKind::DateTime
}
};
let converted = match held_value(context, &source.value)? {
Ok(held) => convert(held.as_ref(), kind)
.map_err(|reason| parse_error("as_date", kind.name(), describe(&held), reason))?,
Err(stream_kind) => {
return Err(parse_error(
"as_date",
kind.name(),
format!("a {stream_kind}"),
format!("a {stream_kind} is not a date or time"),
));
}
};
Ok(replaced(
source,
crate::traversal::step::map_steps::temporal::computed(converted),
))
}
fn process_filter(
&self,
context: &mut TraverserContext<'_, S>,
source: Traverser<S>,
) -> TraverserResult<Option<Traverser<S>>> {
self.process(context, source).map(Some)
}
fn may_filter(&self) -> bool {
true
}
fn plan_check(&self) -> TraverserResult<()> {
match &self.target {
Target::Rejected(token) => Err(Box::new(TraverserError::Value(
ValueError::UnsupportedCastTarget {
target: token.token(),
reason: "as_date() converts to GType.DATETIME, GType.DATE, \
GType.LOCALDATETIME or GType.TIME; as_duration() reads durations",
},
))),
Target::Tinkerpop | Target::Kind(_) => Ok(()),
}
}
fn clone_box(&self) -> Box<dyn GraphStepMap<'g, S> + 'g> {
Box::new(self.clone())
}
}