use std::sync::Arc;
use wdl_analysis::stdlib::STDLIB as ANALYSIS_STDLIB;
use wdl_analysis::types::PrimitiveType;
use wdl_analysis::types::Type;
use wdl_ast::Diagnostic;
use super::CallContext;
use super::Callback;
use super::Function;
use super::Signature;
use crate::CompoundValue;
use crate::PrimitiveValue;
use crate::Value;
fn contains_key_map(context: CallContext<'_>) -> Result<Value, Diagnostic> {
debug_assert_eq!(context.arguments.len(), 2);
debug_assert!(context.return_type_eq(PrimitiveType::Boolean));
let map = context.arguments[0]
.value
.as_map()
.expect("first argument should be a map");
let key = match &context.arguments[1].value {
Value::Primitive(v) => v.clone(),
_ => unreachable!("expected a primitive value for second argument"),
};
Ok(map.contains_key(&key).into())
}
fn contains_key_object(context: CallContext<'_>) -> Result<Value, Diagnostic> {
debug_assert_eq!(context.arguments.len(), 2);
debug_assert!(context.return_type_eq(PrimitiveType::Boolean));
if context.arguments[0].value.as_map().is_some() {
return contains_key_map(context);
}
let object = context.coerce_argument(0, Type::Object).unwrap_object();
let key = context.coerce_argument(1, PrimitiveType::String);
Ok(object.contains_key(key.unwrap_string().as_str()).into())
}
fn contains_key_recursive(context: CallContext<'_>) -> Result<Value, Diagnostic> {
debug_assert_eq!(context.arguments.len(), 2);
debug_assert!(context.return_type_eq(PrimitiveType::Boolean));
fn get(value: &Value, key: &Arc<String>) -> Option<Value> {
match value {
Value::Compound(CompoundValue::Map(map)) => {
map.get(&PrimitiveValue::String(key.clone())).cloned()
}
Value::Compound(CompoundValue::Object(object)) => object.get(key.as_str()).cloned(),
Value::Compound(CompoundValue::Struct(s)) => s.get(key.as_str()).cloned(),
_ => None,
}
}
let mut value = context.arguments[0].value.clone();
let keys = context
.coerce_argument(1, ANALYSIS_STDLIB.array_string_type().clone())
.unwrap_array();
for key in keys
.as_slice()
.iter()
.map(|v| v.as_string().expect("element should be a string"))
{
match get(&value, key) {
Some(v) => value = v,
None => return Ok(false.into()),
}
}
Ok(true.into())
}
pub const fn descriptor() -> Function {
Function::new(
const {
&[
Signature::new(
"(map: Map[K, V], key: K) -> Boolean where `K`: any non-optional primitive \
type",
Callback::Sync(contains_key_map),
),
Signature::new(
"(object: Object, key: String) -> Boolean",
Callback::Sync(contains_key_object),
),
Signature::new(
"(map: Map[String, V], keys: Array[String]) -> Boolean",
Callback::Sync(contains_key_recursive),
),
Signature::new(
"(struct: S, keys: Array[String]) -> Boolean where `S`: any structure",
Callback::Sync(contains_key_recursive),
),
Signature::new(
"(object: Object, keys: Array[String]) -> Boolean",
Callback::Sync(contains_key_recursive),
),
]
},
)
}
#[cfg(test)]
mod test {
use wdl_analysis::types::PrimitiveType;
use wdl_analysis::types::StructType;
use wdl_analysis::types::Type;
use wdl_ast::version::V1;
use crate::v1::test::TestEnv;
use crate::v1::test::eval_v1_expr;
#[tokio::test]
async fn contains_key() {
let mut env = TestEnv::default();
let bar_ty: Type = StructType::new("Bar", [("baz", PrimitiveType::String)]).into();
env.insert_struct("Bar", bar_ty.clone());
let foo_ty = StructType::new("Foo", [("bar", bar_ty)]);
env.insert_struct("Foo", foo_ty);
let value = eval_v1_expr(&env, V1::Two, "contains_key({}, 1)")
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(&env, V1::Two, "contains_key({ 1: 2, 2: 3}, 3)")
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(&env, V1::Two, "contains_key({ 1: 2 }, 1)")
.await
.unwrap();
assert!(value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key({ 'foo': 1, 'bar': 2, 'baz': 3 }, 'qux')",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key({ 'foo': 1, 'bar': 2, 'baz': 3 }, 'baz')",
)
.await
.unwrap();
assert!(value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(object { foo: 1, bar: 2, baz: 3 }, 'qux')",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(object { foo: 1, bar: 2, baz: 3 }, 'baz')",
)
.await
.unwrap();
assert!(value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key({ 'foo': 1, 'bar': 2, 'baz': 3 }, ['qux'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key({ 'foo': 1, 'bar': 2, 'baz': 3 }, ['baz'])",
)
.await
.unwrap();
assert!(value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(object { foo: 1, bar: 2, baz: 3 }, ['qux'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(object { foo: 1, bar: 2, baz: 3 }, ['baz'])",
)
.await
.unwrap();
assert!(value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(Foo { bar: Bar { baz: 'qux' } }, ['qux'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(Foo { bar: Bar { baz: 'qux' } }, ['bar'])",
)
.await
.unwrap();
assert!(value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key({ 'foo': 1, 'bar': 2, 'baz': 3 }, ['qux', 'nope'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key({ 'foo': 1, 'bar': 2, 'baz': 3 }, ['baz', 'nope'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(object { foo: 1, bar: 2, baz: 3 }, ['qux', 'nope'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(object { foo: 1, bar: 2, baz: 3 }, ['baz', 'nope'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(Foo { bar: Bar { baz: 'qux' } }, ['qux', 'nope'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(Foo { bar: Bar { baz: 'qux' } }, ['bar', 'nope'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key({ 'foo': { 'qux': 1 }, 'bar': { 'qux': 2 }, 'baz': { 'qux': 3 } }, \
['baz', 'qux'])",
)
.await
.unwrap();
assert!(value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key({ 'foo': { 'qux': 1 }, 'bar': { 'qux': 2 }, 'baz': { 'qux': 3 } }, \
['baz', 'qux', 'nope'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(object { foo: 1, bar: 2, baz: object { qux: 3 } }, ['baz', 'qux'])",
)
.await
.unwrap();
assert!(value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(object { foo: 1, bar: 2, baz: object { qux: 3 } }, ['baz', 'qux', \
'nope'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(Foo { bar: Bar { baz: 'qux' } }, ['bar', 'baz'])",
)
.await
.unwrap();
assert!(value.unwrap_boolean());
let value = eval_v1_expr(
&env,
V1::Two,
"contains_key(Foo { bar: Bar { baz: 'qux' } }, ['bar', 'baz', 'nope'])",
)
.await
.unwrap();
assert!(!value.unwrap_boolean());
}
}