use super::*;
#[derive(Debug, Clone, Default)]
pub(super) struct ScriptGlobPathOptions {
max_depth: Option<i64>,
pub(super) prune: Option<ScriptGraphTraversalSource>,
}
const GLOB_PATH_OPTION_KEYS: &str = "max_depth, prune";
const GLOB_PATH_RESERVED_KEYS: [&str; 3] = ["edges", "path", "case_insensitive"];
pub(crate) fn type_name_of(arg: &Dynamic) -> String {
super::register::display_type_name(arg.type_name())
}
pub(crate) fn dynamic_text(arg: &Dynamic) -> String {
if arg.is_array() || arg.is_map() {
let mut out = String::new();
write_dynamic_literal(&mut out, arg, crate::value::depth::DEFAULT_MAX_VALUE_DEPTH);
out
} else if arg.type_name().contains("::") {
type_name_of(arg)
} else {
arg.to_string()
}
}
fn write_dynamic_literal(out: &mut String, arg: &Dynamic, levels: usize) {
if let Some(items) = arg.read_lock::<rhai::Array>() {
let Some(inner) = levels.checked_sub(1) else {
out.push_str("[…]");
return;
};
out.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
write_dynamic_literal(out, item, inner);
}
out.push(']');
} else if let Some(map) = arg.read_lock::<rhai::Map>() {
let Some(inner) = levels.checked_sub(1) else {
out.push_str("#{…}");
return;
};
out.push_str("#{");
for (i, (key, item)) in map.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push_str(&crate::value::literal::rhai_key(key));
out.push_str(": ");
write_dynamic_literal(out, item, inner);
}
out.push('}');
} else if arg.is_string() {
out.push_str(&crate::value::literal::rhai_string(&arg.to_string()));
} else {
out.push_str(&dynamic_text(arg));
}
}
pub(super) fn dynamic_to_string(arg: Dynamic) -> String {
if arg.is_string() {
arg.into_string().unwrap_or_else(|s| s.to_string())
} else if let Some(uuid) = arg.read_lock::<ScriptUuid>() {
uuid.canonical()
} else {
dynamic_text(&arg)
}
}
pub(super) fn dynamic_to_compact_string(arg: Dynamic) -> CompactString {
if arg.is_string() {
if let Some(imm) = arg.read_lock::<rhai::ImmutableString>() {
return CompactString::new(imm.as_str());
}
CompactString::new(arg.into_string().unwrap_or_else(|s| s.to_string()))
} else if let Some(uuid) = arg.read_lock::<ScriptUuid>() {
CompactString::new(uuid.canonical())
} else {
CompactString::new(dynamic_text(&arg))
}
}
pub(super) fn property_write_key(
step: &str,
arg: Dynamic,
) -> Result<PropKey, Box<rhai::EvalAltResult>> {
if let Some(path) = arg.read_lock::<crate::value::JsonPath>() {
return Ok(PropKey::Path(path.clone()));
}
match arg.into_immutable_string() {
Ok(name) => Ok(PropKey::Name(CompactString::new(name.as_str()))),
Err(type_name) if type_name.ends_with("ScriptGraphTraversalSource") => {
Err(Box::new(rhai::EvalAltResult::ErrorFunctionNotFound(
format!("{step} (GraphTraversalSource, ..)"),
rhai::Position::NONE,
)))
}
Err(type_name) => Err(script_error_from_script(
crate::error::ScriptError::ArgumentMismatch {
step_name: step.to_string(),
expected: "a property name string or a jpath(..)".to_string(),
actual: super::register::display_type_name(type_name),
},
)),
}
}
pub(super) fn nullable_string(
step: &str,
arg: Dynamic,
) -> Result<Option<CompactString>, Box<rhai::EvalAltResult>> {
if arg.is_unit() {
return Ok(None);
}
if arg.is_string() {
return Ok(Some(dynamic_to_compact_string(arg)));
}
Err(script_error_from_script(
crate::error::ScriptError::ArgumentMismatch {
step_name: step.to_string(),
expected: "a string or ()".to_string(),
actual: type_name_of(&arg),
},
))
}
pub(super) fn dynamic_to_prop_key(arg: Dynamic) -> PropKey {
if let Some(path) = arg.read_lock::<crate::value::JsonPath>() {
return PropKey::Path(path.clone());
}
PropKey::Name(dynamic_to_compact_string(arg))
}
pub(super) fn vec_dynamic_to_prop_key(arg: Vec<Dynamic>) -> Vec<PropKey> {
arg.into_iter().map(dynamic_to_prop_key).collect()
}
pub(super) fn vec_dynamic_to_string(arg: Vec<Dynamic>) -> Vec<String> {
arg.into_iter().map(dynamic_to_string).collect()
}
pub(super) fn clamp_i32(value: i64) -> i32 {
i32::try_from(value).unwrap_or(if value < 0 { i32::MIN } else { i32::MAX })
}
pub(super) fn ids_from_dynamic(
step_name: &str,
args: Vec<Dynamic>,
max_depth: usize,
) -> Result<Vec<CompactString>, Box<rhai::EvalAltResult>> {
fn collect(
step_name: &str,
arg: Dynamic,
out: &mut Vec<CompactString>,
levels: usize,
max_depth: usize,
) -> Result<(), Box<rhai::EvalAltResult>> {
if arg.is_array() {
let inner = levels
.checked_sub(1)
.ok_or_else(|| value_too_deep_error(max_depth))?;
for item in arg.cast::<rhai::Array>() {
collect(step_name, item, out, inner, max_depth)?;
}
} else if arg.is_map() {
let map = arg.cast::<rhai::Map>();
match map.get("id") {
Some(id) if !id.is_map() && !id.is_array() => {
out.push(dynamic_to_compact_string(id.clone()))
}
_ => {
return Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
Dynamic::from(crate::error::format_diagnostic(
&crate::error::ScriptError::ArgumentMismatch {
step_name: step_name.to_string(),
expected:
"ids (strings, numbers, UUIDs), elements or lists of them"
.to_string(),
actual: "a map without an `id` key".to_string(),
},
)),
rhai::Position::NONE,
)));
}
}
} else {
out.push(dynamic_to_compact_string(arg));
}
Ok(())
}
let mut out = Vec::with_capacity(args.len());
for (position, arg) in args.into_iter().enumerate() {
if position > 0 && arg.is_array() {
out.push(CompactString::default());
} else {
collect(step_name, arg, &mut out, max_depth, max_depth)?;
}
}
Ok(out)
}
pub(super) enum TokenArgs {
Literals(Vec<Dynamic>),
Predicate(ArgP),
}
pub(super) fn split_token_args(
step_name: &str,
args: Vec<Dynamic>,
) -> Result<TokenArgs, Box<rhai::EvalAltResult>> {
let predicates = args.iter().filter(|a| a.is::<ArgP>()).count();
if predicates == 0 {
return Ok(TokenArgs::Literals(args));
}
if args.len() != 1 {
return Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
Dynamic::from(crate::error::format_diagnostic(
&crate::error::ScriptError::ArgumentMismatch {
step_name: step_name.to_string(),
expected: "values, or a single predicate".to_string(),
actual: "a predicate mixed with other arguments".to_string(),
},
)),
rhai::Position::NONE,
)));
}
let predicate = try_dynamic_to_argp(&args[0])?;
Ok(match predicate {
ArgP::Eq(value) => TokenArgs::Literals(vec![value]),
ArgP::WithIn(values) => TokenArgs::Literals(values),
other => TokenArgs::Predicate(other),
})
}
pub(super) fn argp_to_p(arg: &ArgP) -> P<PValue> {
match arg {
ArgP::Eq(d) => P::Eq(dynamic_to_pvalue(d)),
ArgP::Neq(d) => P::Neq(dynamic_to_pvalue(d)),
ArgP::Lt(d) => P::Lt(dynamic_to_pvalue(d)),
ArgP::Gt(d) => P::Gt(dynamic_to_pvalue(d)),
ArgP::Lte(d) => P::Lte(dynamic_to_pvalue(d)),
ArgP::Gte(d) => P::Gte(dynamic_to_pvalue(d)),
ArgP::WithIn(v) => P::WithIn(vec_dynamic_to_pvalue(v)),
ArgP::WithOut(v) => P::WithOut(vec_dynamic_to_pvalue(v)),
ArgP::Inside(a, b) => P::Inside(dynamic_to_pvalue(a), dynamic_to_pvalue(b)),
ArgP::Outside(a, b) => P::Outside(dynamic_to_pvalue(a), dynamic_to_pvalue(b)),
ArgP::Between(a, b) => P::Between(dynamic_to_pvalue(a), dynamic_to_pvalue(b)),
ArgP::StartingWith(d) => P::StartingWith(dynamic_to_pvalue(d)),
ArgP::NotStartingWith(d) => P::NotStartingWith(dynamic_to_pvalue(d)),
ArgP::EndingWith(d) => P::EndingWith(dynamic_to_pvalue(d)),
ArgP::NotEndingWith(d) => P::NotEndingWith(dynamic_to_pvalue(d)),
ArgP::Containing(d) => P::Containing(dynamic_to_pvalue(d)),
ArgP::NotContaining(d) => P::NotContaining(dynamic_to_pvalue(d)),
ArgP::Regex(regex) => P::Regex(regex.clone()),
ArgP::NotRegex(regex) => P::NotRegex(regex.clone()),
ArgP::TypeOf(target) => P::type_of(target.clone()),
ArgP::And(a, b) => argp_to_p(a).and(argp_to_p(b)),
ArgP::Or(a, b) => argp_to_p(a).or(argp_to_p(b)),
ArgP::Not(inner) => P::not(argp_to_p(inner)),
}
}
pub(super) fn vec_dynamic_to_compact_string(arg: Vec<Dynamic>) -> Vec<CompactString> {
arg.into_iter().map(dynamic_to_compact_string).collect()
}
pub(super) fn list_item_to_dynamic(
item: crate::traversal::source::ListItem,
origin: &access::GraphOrigin,
) -> Dynamic {
match item {
crate::traversal::source::ListItem::Value(value) => element_property_to_dynamic(value),
crate::traversal::source::ListItem::Graph(graph) => Dynamic::from(origin.source(graph)),
}
}
pub(super) fn script_error(err: crate::error::BoxedTraverserError) -> Box<rhai::EvalAltResult> {
Box::new(rhai::EvalAltResult::ErrorRuntime(
Dynamic::from(crate::error::format_diagnostic(&err)),
rhai::Position::NONE,
))
}
#[inline(never)]
fn compressed_to_dynamic(text: &crate::value::CompressedText) -> Dynamic {
Dynamic::from(text.text().into_string())
}
pub(crate) fn element_property_to_dynamic(value: ElementProperty) -> Dynamic {
match value {
ElementProperty::Null => Dynamic::UNIT,
ElementProperty::String(value) => Dynamic::from(value.to_string()),
ElementProperty::Compressed(text) => compressed_to_dynamic(&text),
ElementProperty::Boolean(value) => Dynamic::from_bool(value),
ElementProperty::Uuid(value) => Dynamic::from(ScriptUuid(value)),
ElementProperty::Number(value) => {
if let Some(int) = value.as_i64() {
Dynamic::from_int(int)
} else if let Some(float) = value.as_f64() {
Dynamic::from_float(float)
} else {
Dynamic::UNIT
}
}
ElementProperty::Array(values) => Dynamic::from_array(
values
.into_iter()
.map(element_property_to_dynamic)
.collect::<Vec<Dynamic>>(),
),
ElementProperty::Object(element)
| ElementProperty::Vertex(element)
| ElementProperty::Edge(element) => Dynamic::from_map(element_to_map(*element)),
ElementProperty::Map(map) => Dynamic::from(ScriptMap(*map)),
}
}
fn element_to_map(element: crate::PropertyMap) -> rhai::Map {
element
.into_iter()
.map(|(key, value)| (key.as_str().into(), element_property_to_dynamic(value)))
.collect()
}
pub fn script_value_to_property(
value: Dynamic,
max_depth: usize,
) -> Result<ElementProperty, crate::TraverserError> {
let max_depth = if max_depth == 0 {
crate::value::depth::DEFAULT_MAX_VALUE_DEPTH
} else {
max_depth
};
convert_bounded(value, max_depth).map_err(|TooDeep| value_too_deep(max_depth))
}
pub(super) fn value_too_deep(max: usize) -> crate::TraverserError {
crate::TraverserError::ResourceLimitExceeded {
limit: limits::LIMIT_MAX_VALUE_DEPTH.to_string(),
max: Some(max as u64),
observed: None,
}
}
fn value_too_deep_error(max: usize) -> Box<rhai::EvalAltResult> {
Box::new(rhai::EvalAltResult::ErrorRuntime(
Dynamic::from(crate::error::format_diagnostic(&value_too_deep(max))),
rhai::Position::NONE,
))
}
pub(super) fn value_arg(
ctx: &NativeCallContext,
arg: Dynamic,
) -> Result<ElementProperty, Box<rhai::EvalAltResult>> {
dynamic_to_element_property(arg, access::value_depth_of(ctx))
}
pub(crate) fn is_schema_map(arg: &Dynamic) -> bool {
arg.read_lock::<rhai::Map>().is_some_and(|map| {
map.get("$schema")
.and_then(|url| {
url.read_lock::<rhai::ImmutableString>()
.map(|s| s.to_string())
})
.is_some_and(|url| url == crate::graph::schema::SCHEMA_FORMAT_URL)
})
}
pub(super) fn schema_arg(
ctx: &NativeCallContext,
arg: Dynamic,
) -> Result<GraphSchema, Box<rhai::EvalAltResult>> {
let parsed = if arg.is_string() {
GraphSchema::from_json(&dynamic_text(&arg))
} else if arg.is_map() {
GraphSchema::from_value(&value_arg(ctx, arg)?)
} else {
return Err(Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
"a schema (a map or JSON text)".to_string(),
type_name_of(&arg),
rhai::Position::NONE,
)));
};
parsed.map_err(|err| script_error(Box::new(crate::TraverserError::Schema(err))))
}
pub(super) fn schema_mode_arg(arg: Dynamic) -> Result<SchemaMode, Box<rhai::EvalAltResult>> {
if let Some(mode) = arg.clone().try_cast::<SchemaMode>() {
return Ok(mode);
}
if arg.is_string() {
let name = dynamic_text(&arg);
return SchemaMode::parse(&name)
.map_err(|err| script_error(Box::new(crate::TraverserError::Schema(err))));
}
Err(Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
"a schema mode (SchemaMode.none/open/closed or \"none\"/\"open\"/\"closed\")".to_string(),
type_name_of(&arg),
rhai::Position::NONE,
)))
}
pub(super) fn patch_arg(
ctx: &NativeCallContext,
arg: Dynamic,
) -> Result<crate::graph::schema::SchemaPatch, Box<rhai::EvalAltResult>> {
if arg.is_string() {
crate::graph::schema::SchemaPatch::from_json(&dynamic_text(&arg))
.map_err(|err| script_error(Box::new(crate::TraverserError::Schema(err))))
} else if arg.is_map() {
Ok(crate::graph::schema::SchemaPatch::from_value(value_arg(
ctx, arg,
)?))
} else {
Err(Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
"a schema patch (a map or JSON text)".to_string(),
type_name_of(&arg),
rhai::Position::NONE,
)))
}
}
pub(super) fn value_args(
ctx: &NativeCallContext,
args: Vec<Dynamic>,
) -> Result<Vec<ElementProperty>, Box<rhai::EvalAltResult>> {
let max = access::value_depth_of(ctx);
args.into_iter()
.map(|arg| dynamic_to_element_property(arg, max))
.collect()
}
pub(super) fn dynamic_to_element_property(
arg: Dynamic,
max_depth: usize,
) -> Result<ElementProperty, Box<rhai::EvalAltResult>> {
convert_bounded(arg, max_depth).map_err(|TooDeep| value_too_deep_error(max_depth))
}
pub(super) fn dynamic_exceeds_depth(value: &Dynamic, levels: usize) -> bool {
if value.is_array()
&& let Some(items) = value.read_lock::<rhai::Array>()
{
return match levels.checked_sub(1) {
None => true,
Some(inner) => items.iter().any(|item| dynamic_exceeds_depth(item, inner)),
};
}
if value.is_map()
&& let Some(map) = value.read_lock::<rhai::Map>()
{
return match levels.checked_sub(1) {
None => true,
Some(inner) => map.values().any(|item| dynamic_exceeds_depth(item, inner)),
};
}
if let Some(map) = value.read_lock::<ScriptMap>() {
return crate::value::depth::property_exceeds_depth(&map.0.clone().into_property(), levels);
}
false
}
struct TooDeep;
fn convert_bounded(arg: Dynamic, levels: usize) -> Result<ElementProperty, TooDeep> {
Ok(if arg.is_string() {
ElementProperty::String(dynamic_to_compact_string(arg))
} else if arg.is_float() {
if let Ok(val) = arg.as_float() {
ElementProperty::Number(Number::from_f64(val))
} else {
ElementProperty::String(CompactString::new(dynamic_text(&arg)))
}
} else if arg.is_int() {
if let Ok(val) = arg.as_int() {
ElementProperty::Number(Number::from_i64(val))
} else {
ElementProperty::String(CompactString::new(dynamic_text(&arg)))
}
} else if arg.is_bool() {
if let Ok(val) = arg.as_bool() {
ElementProperty::Boolean(val)
} else {
ElementProperty::Boolean(false)
}
} else if arg.is_array() {
let inner = levels.checked_sub(1).ok_or(TooDeep)?;
let arr = arg.cast::<rhai::Array>();
ElementProperty::Array(
arr.into_iter()
.map(|item| convert_bounded(item, inner))
.collect::<Result<_, _>>()?,
)
} else if arg.is_map() {
let inner = levels.checked_sub(1).ok_or(TooDeep)?;
let map = arg.cast::<rhai::Map>();
ElementProperty::Object(Box::new(
map.into_iter()
.map(|(key, value)| {
Ok((
CompactString::new(key.as_str()),
convert_bounded(value, inner)?,
))
})
.collect::<Result<_, _>>()?,
))
} else if arg.is_unit() {
ElementProperty::Null
} else if let Some(uuid) = arg.read_lock::<ScriptUuid>() {
ElementProperty::Uuid(uuid.0)
} else if let Some(map) = arg.read_lock::<ScriptMap>() {
let property = map.0.clone().into_property();
if crate::value::depth::property_exceeds_depth(&property, levels) {
return Err(TooDeep);
}
property
} else if let Some(token) = token_placeholder(&arg) {
ElementProperty::String(token)
} else {
ElementProperty::String(CompactString::new(dynamic_text(&arg)))
})
}
pub(super) fn token_placeholder(arg: &Dynamic) -> Option<CompactString> {
if let Some(merge) = arg.read_lock::<Merge>() {
Some(compact_str::format_compact!("Merge.{}", *merge))
} else if let Some(direction) = arg.read_lock::<Direction>() {
Some(compact_str::format_compact!("Direction.{}", *direction))
} else if let Some(cardinality) = arg.read_lock::<Cardinality>() {
Some(compact_str::format_compact!("Cardinality.{}", *cardinality))
} else if let Some(operator) = arg.read_lock::<Operator>() {
Some(compact_str::format_compact!("{}", *operator))
} else if let Some(barrier) = arg.read_lock::<Barrier>() {
Some(compact_str::format_compact!("{}", *barrier))
} else {
arg.read_lock::<CardinalityValue>().map(|wrapped| {
compact_str::format_compact!("Cardinality.{}({})", wrapped.cardinality, wrapped.value)
})
}
}
#[cfg(feature = "serde")]
pub(super) fn dynamic_to_json_value(
arg: Dynamic,
levels: usize,
max: usize,
) -> Result<serde_json::Value, Box<rhai::EvalAltResult>> {
let inner = || {
levels
.checked_sub(1)
.ok_or_else(|| value_too_deep_error(max))
};
Ok(if arg.is_unit() {
serde_json::Value::Null
} else if arg.is_bool() {
serde_json::Value::Bool(arg.as_bool().unwrap_or(false))
} else if arg.is_int() {
serde_json::Value::from(arg.as_int().unwrap_or_default())
} else if arg.is_float() {
let float = arg.as_float().unwrap_or_default();
serde_json::Number::from_f64(float)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null)
} else if arg.is_string() {
serde_json::Value::String(dynamic_to_compact_string(arg).to_string())
} else if arg.is_array() {
let inner = inner()?;
let arr = arg.cast::<rhai::Array>();
serde_json::Value::Array(
arr.into_iter()
.map(|item| dynamic_to_json_value(item, inner, max))
.collect::<Result<_, _>>()?,
)
} else if arg.is_map() {
let inner = inner()?;
let map = arg.cast::<rhai::Map>();
let object: serde_json::Map<String, serde_json::Value> = map
.into_iter()
.map(|(key, value)| Ok((key.to_string(), dynamic_to_json_value(value, inner, max)?)))
.collect::<Result<_, Box<rhai::EvalAltResult>>>()?;
serde_json::Value::Object(object)
} else if let Some(uuid) = arg.read_lock::<ScriptUuid>() {
serde_json::Value::String(uuid.canonical())
} else if let Some(map) = arg.read_lock::<ScriptMap>() {
let inner = inner()?;
let mut object = serde_json::Map::new();
for (key, value) in map.0.iter() {
match key.as_str() {
Some(name) => {
object.insert(
name.to_string(),
dynamic_to_json_value(
element_property_to_dynamic(value.clone()),
inner,
max,
)?,
);
}
None => {
return Err(script_error_from_script(
crate::error::ScriptError::ArgumentMismatch {
step_name: "property_json".to_string(),
expected: "JSON-compatible values (objects have string keys)"
.to_string(),
actual: format!("a map with the non-string key {}", map_key_text(key)),
},
));
}
}
}
serde_json::Value::Object(object)
} else {
serde_json::Value::String(dynamic_text(&arg))
})
}
fn dynamic_to_pvalue(arg: &Dynamic) -> PValue {
if arg.is_string() {
PValue::String(dynamic_to_string(arg.clone()).into())
} else if arg.is_float() {
if let Ok(val) = arg.as_float() {
PValue::Number(Number::from_f64(val))
} else {
PValue::String(dynamic_text(arg).into())
}
} else if arg.is_int() {
if let Ok(val) = arg.as_int() {
PValue::Number(Number::from_i64(val))
} else {
PValue::String(dynamic_text(arg).into())
}
} else if arg.is_bool() {
if let Ok(val) = arg.as_bool() {
PValue::Boolean(val)
} else {
PValue::Boolean(false)
}
} else if let Some(uuid) = arg.read_lock::<ScriptUuid>() {
PValue::Uuid(uuid.0)
} else if arg.is_unit() {
PValue::Null
} else {
PValue::String(dynamic_text(arg).into())
}
}
fn vec_dynamic_to_pvalue(arg: &[Dynamic]) -> Vec<PValue> {
arg.iter().map(dynamic_to_pvalue).collect()
}
pub(super) fn predicate_operand(
step_name: &str,
arg: Dynamic,
) -> Result<ArgP, Box<rhai::EvalAltResult>> {
match arg.clone().try_cast::<ArgP>() {
Some(p) => Ok(p),
None => Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
Dynamic::from(crate::error::format_diagnostic(
&crate::error::ScriptError::ArgumentMismatch {
step_name: step_name.to_string(),
expected: "a predicate (P.gt(1), TextP.containing(\"a\"), ...)".to_string(),
actual: type_name_of(&arg),
},
)),
rhai::Position::NONE,
))),
}
}
pub(super) fn try_dynamic_to_argp(arg: &Dynamic) -> Result<ArgP, Box<rhai::EvalAltResult>> {
if let Some(p) = arg.clone().read_lock::<ArgP>() {
Ok((*p).clone())
} else {
let type_name = arg.type_name();
match type_name {
"string" | "bool" | "i64" | "f64" => Ok(ArgP::Eq(arg.clone())),
_ if arg.is::<ScriptUuid>() => Ok(ArgP::Eq(arg.clone())),
_ => Err(Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
"a predicate (P.eq(1), ...) or a value".to_string(),
type_name_of(arg),
rhai::Position::NONE,
))),
}
}
}
pub(super) fn try_dynamic_to_by(arg: Dynamic) -> Result<ArgBy, Box<rhai::EvalAltResult>> {
if let Some(column) = arg.clone().try_cast::<Column>() {
return Ok(ArgBy::Column(column));
}
if let Some(path) = arg.read_lock::<crate::value::JsonPath>() {
return Ok(ArgBy::JsonPath(path.clone()));
}
let type_name = arg.type_name().to_string();
match type_name.as_str() {
"string" | "bool" | "i64" | "f64" => Ok(ArgBy::Property(dynamic_to_compact_string(arg))),
t if t == std::any::type_name::<ArgBy>() => {
if let Some(by) = arg.clone().read_lock::<ArgBy>() {
match &*by {
ArgBy::Property(prop) => Ok(ArgBy::Property(prop.clone())),
ArgBy::Label => Ok(ArgBy::Label),
ArgBy::Id => Ok(ArgBy::Id),
ArgBy::Value => Ok(ArgBy::Value),
ArgBy::Key => Ok(ArgBy::Key),
ArgBy::Count => Ok(ArgBy::Count),
ArgBy::Traversal(val) => Ok(ArgBy::Traversal(val.clone())),
ArgBy::JsonPath(path) => Ok(ArgBy::JsonPath(path.clone())),
ArgBy::Order(order) => Ok(ArgBy::Order(order.clone())),
ArgBy::Labels(labels) => Ok(ArgBy::Labels(labels.clone())),
ArgBy::Column(column) => Ok(ArgBy::Column(*column)),
}
} else {
Err(Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
"By".to_string(),
super::register::display_type_name(&type_name),
rhai::Position::NONE,
)))
}
}
t if t == std::any::type_name::<ScriptGraphTraversalSource>() => {
if let Some(val) = arg.clone().read_lock::<ScriptGraphTraversalSource>() {
Ok(ArgBy::Traversal(val.clone()))
} else {
Err(Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
"GraphTraversalSource".to_string(),
super::register::display_type_name(&type_name),
rhai::Position::NONE,
)))
}
}
t if t == std::any::type_name::<Order>() => {
if let Some(order) = arg.clone().read_lock::<Order>() {
Ok(ArgBy::Order(order.clone()))
} else {
Err(Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
"Order".to_string(),
super::register::display_type_name(&type_name),
rhai::Position::NONE,
)))
}
}
"array" => {
let items = arg.into_array().map_err(|actual| {
Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
"array".to_string(),
super::register::display_type_name(actual),
rhai::Position::NONE,
))
})?;
let mut labels = Vec::with_capacity(items.len());
for item in items {
if item.is_string() {
labels.push(dynamic_to_compact_string(item));
} else {
return Err(by_labels_mismatch(&type_name_of(&item)));
}
}
Ok(ArgBy::Labels(labels))
}
_ => Err(Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
"a property key string, a list of label strings, a By token, a Column, an Order or a traversal"
.to_string(),
super::register::display_type_name(&type_name),
rhai::Position::NONE,
))),
}
}
pub(super) fn value_map_keys(
step_name: &str,
args: Vec<Dynamic>,
) -> Result<(Vec<PropKey>, bool), Box<rhai::EvalAltResult>> {
let mut keys = Vec::with_capacity(args.len());
let mut tokens = false;
for (position, arg) in args.into_iter().enumerate() {
if let Some(key) = arg.read_lock::<rhai::ImmutableString>() {
keys.push(PropKey::Name(CompactString::new(key.as_str())));
continue;
}
if let Some(path) = arg.read_lock::<crate::value::JsonPath>() {
keys.push(PropKey::Path(path.clone()));
continue;
}
if arg.is_unit() {
continue;
}
if position == 0
&& let Ok(flag) = arg.as_bool()
{
tokens = flag;
continue;
}
let actual = if let Ok(flag) = arg.as_bool() {
format!("a boolean (`{flag}`) that is not the first argument")
} else if let Some(uuid) = arg.read_lock::<ScriptUuid>() {
format!("a UUID (`{}`)", uuid.canonical())
} else {
format!(
"a value of type {} (`{}`)",
type_name_of(&arg),
dynamic_text(&arg)
)
};
return Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
Dynamic::from(crate::error::format_diagnostic(
&crate::error::ScriptError::ArgumentMismatch {
step_name: step_name.to_string(),
expected: "property keys as strings, optionally preceded by a boolean"
.to_string(),
actual,
},
)),
rhai::Position::NONE,
)));
}
Ok((keys, tokens))
}
pub(super) fn glob_path_options(
options: rhai::Map,
) -> Result<ScriptGlobPathOptions, Box<rhai::EvalAltResult>> {
let mismatch = |expected: String, actual: String| {
Box::new(rhai::EvalAltResult::ErrorRuntime(
Dynamic::from(crate::error::format_diagnostic(
&crate::error::ScriptError::ArgumentMismatch {
step_name: "glob_path".to_string(),
expected,
actual,
},
)),
rhai::Position::NONE,
))
};
let describe = |value: &Dynamic| {
format!(
"a value of type {} (`{}`)",
type_name_of(value),
dynamic_text(value)
)
};
let mut parsed = ScriptGlobPathOptions::default();
for (key, value) in options {
match key.as_str() {
"max_depth" => match value.as_int() {
Ok(depth) => parsed.max_depth = Some(depth),
Err(_) => {
return Err(mismatch("max_depth as an integer, for example #{max_depth: 3}".to_string(), describe(&value)))
}
},
"prune" => match value.read_lock::<ScriptGraphTraversalSource>() {
Some(prune) => parsed.prune = Some(prune.clone()),
None => {
return Err(mismatch(
"prune as an anonymous traversal, for example #{prune: __.has(\"name\", \"target\")}".to_string(),
describe(&value),
))
}
},
reserved if GLOB_PATH_RESERVED_KEYS.contains(&reserved) => {
return Err(mismatch(
format!("an options map with the keys {GLOB_PATH_OPTION_KEYS}"),
format!("the reserved key `{reserved}`, which is not implemented yet"),
))
}
unknown => {
return Err(mismatch(
format!("an options map with the keys {GLOB_PATH_OPTION_KEYS}"),
format!("the unknown key `{unknown}`"),
))
}
}
}
Ok(parsed)
}
impl ScriptGlobPathOptions {
pub(super) fn convert_to_anonymous<'g, S: crate::graph::GraphStorage + 'g>(
&self,
) -> crate::GlobPathOptions<'g, S> {
let mut options = crate::GlobPathOptions::default();
if let Some(depth) = self.max_depth {
options = options.max_depth_raw(depth);
}
if let Some(prune) = &self.prune {
options = options.prune(prune.ast_convert_to_anonymous());
}
options
}
}
fn by_labels_mismatch(actual: &str) -> Box<rhai::EvalAltResult> {
Box::new(rhai::EvalAltResult::ErrorRuntime(
Dynamic::from(crate::error::format_diagnostic(
&crate::error::ScriptError::ArgumentMismatch {
step_name: "by".to_string(),
expected: "a list of vertex-label strings".to_string(),
actual: format!("a list with an element of type {actual}"),
},
)),
rhai::Position::NONE,
))
}
pub(super) fn has_t_mismatch(expected: &str, actual: String) -> Box<rhai::EvalAltResult> {
Box::new(rhai::EvalAltResult::ErrorRuntime(
Dynamic::from(crate::error::format_diagnostic(
&crate::error::ScriptError::ArgumentMismatch {
step_name: "has".to_string(),
expected: expected.to_string(),
actual,
},
)),
rhai::Position::NONE,
))
}
pub(super) fn property_entries_of(
map: rhai::Map,
max_depth: usize,
) -> Result<Vec<PropertyEntry>, Box<rhai::EvalAltResult>> {
map.into_iter()
.map(|(key, value)| {
let (value, cardinality) = match value.read_lock::<CardinalityValue>() {
Some(wrapped) => (wrapped.value.clone(), Some(wrapped.cardinality)),
None => (value.clone(), None),
};
Ok(PropertyEntry {
key: CompactString::new(key.as_str()),
value: dynamic_to_element_property(value, max_depth)?,
cardinality,
})
})
.collect()
}
pub(super) fn script_merge_arg(
arg: Dynamic,
max_depth: usize,
) -> Result<ScriptMergeArg, Box<rhai::EvalAltResult>> {
if arg.is_map() {
Ok(ScriptMergeArg::Entries(property_entries_of(
arg.cast::<rhai::Map>(),
max_depth,
)?))
} else if arg.is_unit() {
Ok(ScriptMergeArg::Entries(Vec::new()))
} else if arg.is::<ScriptGraphTraversalSource>() {
Ok(ScriptMergeArg::Traversal(try_dynamic_to_gts(arg)?))
} else {
Ok(ScriptMergeArg::Value(dynamic_to_element_property(
arg, max_depth,
)?))
}
}
pub(super) fn try_dynamic_to_gts(
arg: Dynamic,
) -> Result<ScriptGraphTraversalSource, Box<rhai::EvalAltResult>> {
let type_name = arg.type_name().to_string();
if type_name == std::any::type_name::<ScriptGraphTraversalSource>() {
if let Some(val) = arg.read_lock::<ScriptGraphTraversalSource>() {
return Ok(val.clone());
}
} else if is_schema_map(&arg) {
let mut gts = ScriptGraphSource::empty();
return Ok(gts.add_step(Step::InferSchema));
}
Err(Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
"a traversal (__.out(), ...)".to_string(),
super::register::display_type_name(&type_name),
rhai::Position::NONE,
)))
}
pub(super) fn select_pop_mismatch(actual: String) -> Box<rhai::EvalAltResult> {
script_error_from_script(crate::error::ScriptError::ArgumentMismatch {
step_name: "select".to_string(),
expected: "select(\"a\"), select(Pop.all, \"a\") or select(Column.keys): a Pop first, then step labels as strings"
.to_string(),
actual,
})
}
pub(super) fn one_step_source(step: Step) -> ScriptGraphTraversalSource {
ScriptGraphTraversalSource {
g: None,
steps: vec![step],
options: Default::default(),
access: Default::default(),
private: true,
anonymous: true,
}
}
fn choose_mismatch(step_name: &str, expected: &str, arg: &Dynamic) -> Box<rhai::EvalAltResult> {
script_error_from_script(crate::error::ScriptError::ArgumentMismatch {
step_name: step_name.to_string(),
expected: expected.to_string(),
actual: type_name_of(arg),
})
}
pub(super) fn selector_to_gts(
step_name: &str,
arg: Dynamic,
) -> Result<ScriptGraphTraversalSource, Box<rhai::EvalAltResult>> {
if arg.is::<ScriptGraphTraversalSource>() {
return try_dynamic_to_gts(arg);
}
match arg.clone().try_cast::<ArgBy>() {
Some(ArgBy::Label) => Ok(one_step_source(Step::Label)),
Some(ArgBy::Id) => Ok(one_step_source(Step::Id)),
_ => Err(choose_mismatch(
step_name,
"a traversal, T.label or T.id",
&arg,
)),
}
}
pub(super) fn predicate_to_gts(
step_name: &str,
arg: Dynamic,
) -> Result<ScriptGraphTraversalSource, Box<rhai::EvalAltResult>> {
if arg.is::<ScriptGraphTraversalSource>() {
return try_dynamic_to_gts(arg);
}
match arg.clone().try_cast::<ArgP>() {
Some(p) => Ok(one_step_source(Step::Is(p))),
None => Err(choose_mismatch(
step_name,
"a traversal or a predicate (P.gt(1), ...)",
&arg,
)),
}
}
pub(super) fn try_vec_dynamic_to_gts(
arg: Vec<Dynamic>,
) -> Result<Vec<ScriptGraphTraversalSource>, Box<rhai::EvalAltResult>> {
arg.into_iter().map(try_dynamic_to_gts).collect()
}