use std::borrow::Cow;
use std::cmp::Ordering;
use std::fmt;
use std::hash::Hash;
use std::hash::Hasher;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::LazyLock;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use anyhow::bail;
use futures::FutureExt;
use futures::StreamExt as _;
use futures::TryStreamExt as _;
use futures::future::BoxFuture;
use indexmap::IndexMap;
use ordered_float::OrderedFloat;
use serde::ser::SerializeMap;
use serde::ser::SerializeSeq;
use url::Url;
use wdl_analysis::stdlib::STDLIB as ANALYSIS_STDLIB;
use wdl_analysis::types::ArrayType;
use wdl_analysis::types::CallType;
use wdl_analysis::types::Coercible as _;
use wdl_analysis::types::CompoundType;
use wdl_analysis::types::CustomType;
use wdl_analysis::types::EnumType;
use wdl_analysis::types::HiddenType;
use wdl_analysis::types::MapType;
use wdl_analysis::types::Optional;
use wdl_analysis::types::PairType;
use wdl_analysis::types::PrimitiveType;
use wdl_analysis::types::StructType;
use wdl_analysis::types::Type;
use wdl_analysis::types::v1::task_member_type_post_evaluation;
use wdl_ast::AstToken;
use wdl_ast::SupportedVersion;
use wdl_ast::TreeNode;
use wdl_ast::v1;
use wdl_ast::v1::TASK_FIELD_ATTEMPT;
use wdl_ast::v1::TASK_FIELD_CONTAINER;
use wdl_ast::v1::TASK_FIELD_CPU;
use wdl_ast::v1::TASK_FIELD_DISKS;
use wdl_ast::v1::TASK_FIELD_END_TIME;
use wdl_ast::v1::TASK_FIELD_EXT;
use wdl_ast::v1::TASK_FIELD_FPGA;
use wdl_ast::v1::TASK_FIELD_GPU;
use wdl_ast::v1::TASK_FIELD_ID;
use wdl_ast::v1::TASK_FIELD_MAX_RETRIES;
use wdl_ast::v1::TASK_FIELD_MEMORY;
use wdl_ast::v1::TASK_FIELD_META;
use wdl_ast::v1::TASK_FIELD_NAME;
use wdl_ast::v1::TASK_FIELD_PARAMETER_META;
use wdl_ast::v1::TASK_FIELD_PREVIOUS;
use wdl_ast::v1::TASK_FIELD_RETURN_CODE;
use wdl_ast::version::V1;
use crate::EvaluationContext;
use crate::EvaluationPath;
use crate::Outputs;
use crate::backend::TaskExecutionConstraints;
use crate::http::Transferer;
use crate::path;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct HostPath(pub Arc<String>);
impl HostPath {
pub fn new(path: impl Into<String>) -> Self {
Self(Arc::new(path.into()))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn expand(&self, base_dir: &EvaluationPath) -> Result<Self> {
let shell_expanded = shellexpand::full(self.as_str()).with_context(|| {
format!("failed to shell-expand path `{path}`", path = self.as_str())
})?;
if path::is_supported_url(&shell_expanded) {
Ok(Self::new(shell_expanded))
} else {
Ok(Self::new(base_dir.join(&shell_expanded)?.to_string()))
}
}
pub fn is_relative(&self) -> bool {
!path::is_supported_url(&self.0) && Path::new(self.0.as_str()).is_relative()
}
}
impl fmt::Display for HostPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
fn write_escaped_wdl_string(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result {
let mut chars = s.char_indices().peekable();
while let Some((_, c)) = chars.next() {
let next_is_brace = chars.peek().map(|(_, n)| *n == '{').unwrap_or(false);
match c {
'\\' => f.write_str(r"\\")?,
'\n' => f.write_str(r"\n")?,
'\r' => f.write_str(r"\r")?,
'\t' => f.write_str(r"\t")?,
'"' => f.write_str("\\\"")?,
'$' if next_is_brace => f.write_str(r"\$")?,
'~' if next_is_brace => f.write_str(r"\~")?,
c if c.is_control() => write!(f, "\\x{code:02X}", code = c as u32)?,
c => write!(f, "{c}")?,
}
}
Ok(())
}
impl From<Arc<String>> for HostPath {
fn from(path: Arc<String>) -> Self {
Self(path)
}
}
impl From<HostPath> for Arc<String> {
fn from(path: HostPath) -> Self {
path.0
}
}
impl From<String> for HostPath {
fn from(s: String) -> Self {
Arc::new(s).into()
}
}
impl<'a> From<&'a str> for HostPath {
fn from(s: &'a str) -> Self {
s.to_string().into()
}
}
impl From<url::Url> for HostPath {
fn from(url: url::Url) -> Self {
url.as_str().into()
}
}
impl From<HostPath> for PathBuf {
fn from(path: HostPath) -> Self {
PathBuf::from(path.0.as_str())
}
}
impl From<&HostPath> for PathBuf {
fn from(path: &HostPath) -> Self {
PathBuf::from(path.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct GuestPath(pub Arc<String>);
impl GuestPath {
pub fn new(path: impl Into<String>) -> Self {
Self(Arc::new(path.into()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for GuestPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<Arc<String>> for GuestPath {
fn from(path: Arc<String>) -> Self {
Self(path)
}
}
impl From<GuestPath> for Arc<String> {
fn from(path: GuestPath) -> Self {
path.0
}
}
pub(crate) trait Coercible: Sized {
fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self>;
}
#[derive(Debug, Clone)]
pub struct NoneValue(Arc<Type>);
impl NoneValue {
pub fn new(ty: Type) -> Self {
Self(Arc::new(ty))
}
pub fn untyped() -> Self {
static INSTANCE: LazyLock<NoneValue> = LazyLock::new(|| NoneValue::new(Type::None));
INSTANCE.clone()
}
pub fn ty(&self) -> &Type {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct TypeNameRefValue(Arc<Type>);
impl TypeNameRefValue {
pub fn new(ty: Type) -> Self {
Self(Arc::new(ty))
}
pub fn ty(&self) -> &Type {
&self.0
}
}
#[derive(Debug, Clone)]
pub enum Value {
None(NoneValue),
Primitive(PrimitiveValue),
Compound(CompoundValue),
Hidden(HiddenValue),
Call(CallValue),
TypeNameRef(TypeNameRefValue),
}
const _: () = {
assert!(std::mem::size_of::<Value>() <= 24);
};
impl Value {
pub fn from_v1_metadata<N: TreeNode>(value: &v1::MetadataValue<N>) -> Self {
match value {
v1::MetadataValue::Boolean(v) => v.value().into(),
v1::MetadataValue::Integer(v) => v.value().expect("number should be in range").into(),
v1::MetadataValue::Float(v) => v.value().expect("number should be in range").into(),
v1::MetadataValue::String(v) => PrimitiveValue::new_string(
v.text()
.expect("metadata strings shouldn't have placeholders")
.text(),
)
.into(),
v1::MetadataValue::Null(_) => Self::new_none(Type::None),
v1::MetadataValue::Object(o) => Object::from_v1_metadata(o.items()).into(),
v1::MetadataValue::Array(a) => Array::new_unchecked(
ANALYSIS_STDLIB.array_object_type().clone(),
a.elements().map(|v| Value::from_v1_metadata(&v)).collect(),
)
.into(),
}
}
pub fn new_none(ty: Type) -> Self {
assert!(ty.is_optional(), "the provided `None` type is not optional");
Self::None(NoneValue::new(ty))
}
pub fn ty(&self) -> Type {
match self {
Self::None(v) => v.ty().clone(),
Self::Primitive(v) => v.ty(),
Self::Compound(v) => v.ty(),
Self::Hidden(v) => v.ty(),
Self::Call(v) => Type::Call(v.ty().clone()),
Self::TypeNameRef(v) => v.ty().clone(),
}
}
pub fn is_none(&self) -> bool {
matches!(self, Self::None(_))
}
pub fn as_primitive(&self) -> Option<&PrimitiveValue> {
match self {
Self::Primitive(v) => Some(v),
_ => None,
}
}
pub fn as_compound(&self) -> Option<&CompoundValue> {
match self {
Self::Compound(v) => Some(v),
_ => None,
}
}
pub fn as_boolean(&self) -> Option<bool> {
match self {
Self::Primitive(PrimitiveValue::Boolean(v)) => Some(*v),
_ => None,
}
}
pub fn unwrap_boolean(self) -> bool {
match self {
Self::Primitive(PrimitiveValue::Boolean(v)) => v,
_ => panic!("value is not a boolean"),
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
Self::Primitive(PrimitiveValue::Integer(v)) => Some(*v),
_ => None,
}
}
pub fn unwrap_integer(self) -> i64 {
match self {
Self::Primitive(PrimitiveValue::Integer(v)) => v,
_ => panic!("value is not an integer"),
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
Self::Primitive(PrimitiveValue::Float(v)) => Some((*v).into()),
_ => None,
}
}
pub fn unwrap_float(self) -> f64 {
match self {
Self::Primitive(PrimitiveValue::Float(v)) => v.into(),
_ => panic!("value is not a float"),
}
}
pub fn as_string(&self) -> Option<&Arc<String>> {
match self {
Self::Primitive(PrimitiveValue::String(s)) => Some(s),
_ => None,
}
}
pub fn unwrap_string(self) -> Arc<String> {
match self {
Self::Primitive(PrimitiveValue::String(s)) => s,
_ => panic!("value is not a string"),
}
}
pub fn as_file(&self) -> Option<&HostPath> {
match self {
Self::Primitive(PrimitiveValue::File(p)) => Some(p),
_ => None,
}
}
pub fn unwrap_file(self) -> HostPath {
match self {
Self::Primitive(PrimitiveValue::File(p)) => p,
_ => panic!("value is not a file"),
}
}
pub fn as_directory(&self) -> Option<&HostPath> {
match self {
Self::Primitive(PrimitiveValue::Directory(p)) => Some(p),
_ => None,
}
}
pub fn unwrap_directory(self) -> HostPath {
match self {
Self::Primitive(PrimitiveValue::Directory(p)) => p,
_ => panic!("value is not a directory"),
}
}
pub fn as_pair(&self) -> Option<&Pair> {
match self {
Self::Compound(CompoundValue::Pair(v)) => Some(v),
_ => None,
}
}
pub fn unwrap_pair(self) -> Pair {
match self {
Self::Compound(CompoundValue::Pair(v)) => v,
_ => panic!("value is not a pair"),
}
}
pub fn as_array(&self) -> Option<&Array> {
match self {
Self::Compound(CompoundValue::Array(v)) => Some(v),
_ => None,
}
}
pub fn unwrap_array(self) -> Array {
match self {
Self::Compound(CompoundValue::Array(v)) => v,
_ => panic!("value is not an array"),
}
}
pub fn as_map(&self) -> Option<&Map> {
match self {
Self::Compound(CompoundValue::Map(v)) => Some(v),
_ => None,
}
}
pub fn unwrap_map(self) -> Map {
match self {
Self::Compound(CompoundValue::Map(v)) => v,
_ => panic!("value is not a map"),
}
}
pub fn as_object(&self) -> Option<&Object> {
match self {
Self::Compound(CompoundValue::Object(v)) => Some(v),
_ => None,
}
}
pub fn unwrap_object(self) -> Object {
match self {
Self::Compound(CompoundValue::Object(v)) => v,
_ => panic!("value is not an object"),
}
}
pub fn as_struct(&self) -> Option<&Struct> {
match self {
Self::Compound(CompoundValue::Struct(v)) => Some(v),
_ => None,
}
}
pub fn unwrap_struct(self) -> Struct {
match self {
Self::Compound(CompoundValue::Struct(v)) => v,
_ => panic!("value is not a struct"),
}
}
pub fn as_task_pre_evaluation(&self) -> Option<&TaskPreEvaluationValue> {
match self {
Self::Hidden(HiddenValue::TaskPreEvaluation(v)) => Some(v),
_ => None,
}
}
pub fn unwrap_task_pre_evaluation(self) -> TaskPreEvaluationValue {
match self {
Self::Hidden(HiddenValue::TaskPreEvaluation(v)) => v,
_ => panic!("value is not a pre-evaluation task"),
}
}
pub fn as_task_post_evaluation(&self) -> Option<&TaskPostEvaluationValue> {
match self {
Self::Hidden(HiddenValue::TaskPostEvaluation(v)) => Some(v),
_ => None,
}
}
pub(crate) fn as_task_post_evaluation_mut(&mut self) -> Option<&mut TaskPostEvaluationValue> {
match self {
Self::Hidden(HiddenValue::TaskPostEvaluation(v)) => Some(v),
_ => None,
}
}
pub fn unwrap_task_post_evaluation(self) -> TaskPostEvaluationValue {
match self {
Self::Hidden(HiddenValue::TaskPostEvaluation(v)) => v,
_ => panic!("value is not a post-evaluation task"),
}
}
pub fn as_hints(&self) -> Option<&HintsValue> {
match self {
Self::Hidden(HiddenValue::Hints(v)) => Some(v),
_ => None,
}
}
pub fn unwrap_hints(self) -> HintsValue {
match self {
Self::Hidden(HiddenValue::Hints(v)) => v,
_ => panic!("value is not a hints value"),
}
}
pub fn as_call(&self) -> Option<&CallValue> {
match self {
Self::Call(v) => Some(v),
_ => None,
}
}
pub fn unwrap_call(self) -> CallValue {
match self {
Self::Call(v) => v,
_ => panic!("value is not a call value"),
}
}
pub(crate) fn visit_paths<F>(&self, cb: &mut F) -> Result<()>
where
F: FnMut(bool, &HostPath) -> Result<()> + Send + Sync,
{
match self {
Self::Primitive(PrimitiveValue::File(path)) => cb(true, path),
Self::Primitive(PrimitiveValue::Directory(path)) => cb(false, path),
Self::Compound(v) => v.visit_paths(cb),
_ => Ok(()),
}
}
pub(crate) async fn resolve_paths<F>(
&self,
optional: bool,
base_dir: Option<&Path>,
transferer: Option<&dyn Transferer>,
translate: &F,
) -> Result<Self>
where
F: Fn(&HostPath) -> Result<HostPath> + Send + Sync,
{
fn new_file_or_directory(is_file: bool, path: impl Into<HostPath>) -> PrimitiveValue {
if is_file {
PrimitiveValue::File(path.into())
} else {
PrimitiveValue::Directory(path.into())
}
}
match self {
Self::Primitive(v @ PrimitiveValue::File(path))
| Self::Primitive(v @ PrimitiveValue::Directory(path)) => {
let is_file = v.as_file().is_some();
let path = translate(path)?;
if path::is_file_url(path.as_str()) {
let exists = path
.as_str()
.parse::<Url>()
.ok()
.and_then(|url| url.to_file_path().ok())
.map(|p| p.exists())
.unwrap_or(false);
if exists {
let v = new_file_or_directory(is_file, path);
return Ok(Self::Primitive(v));
}
if optional && !exists {
return Ok(Value::new_none(self.ty().optional()));
}
bail!("path `{path}` does not exist");
} else if path::is_supported_url(path.as_str()) {
match transferer {
Some(transferer) => {
let exists = transferer
.exists(
&path
.as_str()
.parse()
.with_context(|| format!("invalid URL `{path}`"))?,
)
.await?;
if exists {
let v = new_file_or_directory(is_file, path);
return Ok(Self::Primitive(v));
}
if optional && !exists {
return Ok(Value::new_none(self.ty().optional()));
}
bail!("URL `{path}` does not exist");
}
None => {
let v = new_file_or_directory(is_file, path);
return Ok(Self::Primitive(v));
}
}
}
let exists_path: Cow<'_, Path> = base_dir
.map(|d| d.join(path.as_str()).into())
.unwrap_or_else(|| Path::new(path.as_str()).into());
if is_file && !exists_path.is_file() {
if optional {
return Ok(Value::new_none(self.ty().optional()));
} else {
bail!("file `{}` does not exist", exists_path.display());
}
} else if !is_file && !exists_path.is_dir() {
if optional {
return Ok(Value::new_none(self.ty().optional()));
} else {
bail!("directory `{}` does not exist", exists_path.display())
}
}
let v = new_file_or_directory(is_file, path);
Ok(Self::Primitive(v))
}
Self::Compound(v) => Ok(Self::Compound(
v.resolve_paths(base_dir, transferer, translate)
.boxed()
.await?,
)),
v => Ok(v.clone()),
}
}
pub fn equals(left: &Self, right: &Self) -> Option<bool> {
match (left, right) {
(Value::None(_), Value::None(_)) => Some(true),
(Value::None(_), _) | (_, Value::None(_)) => Some(false),
(Value::Primitive(left), Value::Primitive(right)) => {
Some(PrimitiveValue::compare(left, right)? == Ordering::Equal)
}
(Value::Compound(left), Value::Compound(right)) => CompoundValue::equals(left, right),
_ => None,
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None(_) => write!(f, "None"),
Self::Primitive(v) => v.fmt(f),
Self::Compound(v) => v.fmt(f),
Self::Hidden(v) => v.fmt(f),
Self::Call(c) => c.fmt(f),
Self::TypeNameRef(v) => v.ty().fmt(f),
}
}
}
impl Coercible for Value {
fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
if target.is_union() || target.is_none() || self.ty().eq(target) {
return Ok(self.clone());
}
match self {
Self::None(_) => {
if target.is_optional() {
Ok(Self::new_none(target.clone()))
} else {
bail!("cannot coerce `None` to non-optional {target:#}");
}
}
Self::Primitive(PrimitiveValue::String(s)) if target.as_enum().is_some() => {
let enum_ty = target.as_enum().unwrap();
if enum_ty
.variants()
.iter()
.any(|variant_name| variant_name == s.as_str())
{
if let Some(context) = context {
if let Ok(value) = context.enum_variant_value(enum_ty.name(), s) {
return Ok(Value::Compound(CompoundValue::EnumVariant(
EnumVariant::new(enum_ty.clone(), s.as_str(), value),
)));
} else {
bail!(
"enum variant value lookup failed for variant `{s}` in enum `{}`",
enum_ty.name()
);
}
} else {
bail!(
"context does not exist when creating enum variant value `{s}` in \
enum `{}`",
enum_ty.name()
);
}
}
let variants = if enum_ty.variants().is_empty() {
None
} else {
let mut variant_names = enum_ty.variants().to_vec();
variant_names.sort();
Some(format!(" (variants: `{}`)", variant_names.join("`, `")))
}
.unwrap_or_default();
bail!(
"cannot coerce type `String` to {target:#}: variant `{s}` not found in enum \
`{}`{variants}",
enum_ty.name()
);
}
Self::Compound(CompoundValue::EnumVariant(e))
if target
.as_primitive()
.map(|t| matches!(t, PrimitiveType::String))
.unwrap_or(false) =>
{
Ok(Value::Primitive(PrimitiveValue::new_string(e.name())))
}
Self::Primitive(v) => v.coerce(context, target).map(Self::Primitive),
Self::Compound(v) => v.coerce(context, target).map(Self::Compound),
Self::Hidden(v) => v.coerce(context, target).map(Self::Hidden),
Self::Call(_) => {
bail!("call values cannot be coerced to any other type");
}
Self::TypeNameRef(_) => {
bail!("type name references cannot be coerced to any other type");
}
}
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Self::Primitive(value.into())
}
}
impl From<i64> for Value {
fn from(value: i64) -> Self {
Self::Primitive(value.into())
}
}
impl TryFrom<u64> for Value {
type Error = std::num::TryFromIntError;
fn try_from(value: u64) -> std::result::Result<Self, Self::Error> {
let value: i64 = value.try_into()?;
Ok(value.into())
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Self::Primitive(value.into())
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Self::Primitive(value.into())
}
}
impl From<PrimitiveValue> for Value {
fn from(value: PrimitiveValue) -> Self {
Self::Primitive(value)
}
}
impl From<Option<PrimitiveValue>> for Value {
fn from(value: Option<PrimitiveValue>) -> Self {
match value {
Some(v) => v.into(),
None => Self::new_none(Type::None),
}
}
}
impl From<CompoundValue> for Value {
fn from(value: CompoundValue) -> Self {
Self::Compound(value)
}
}
impl From<HiddenValue> for Value {
fn from(value: HiddenValue) -> Self {
Self::Hidden(value)
}
}
impl From<Pair> for Value {
fn from(value: Pair) -> Self {
Self::Compound(value.into())
}
}
impl From<Array> for Value {
fn from(value: Array) -> Self {
Self::Compound(value.into())
}
}
impl From<Map> for Value {
fn from(value: Map) -> Self {
Self::Compound(value.into())
}
}
impl From<Object> for Value {
fn from(value: Object) -> Self {
Self::Compound(value.into())
}
}
impl From<Struct> for Value {
fn from(value: Struct) -> Self {
Self::Compound(value.into())
}
}
impl From<CallValue> for Value {
fn from(value: CallValue) -> Self {
Self::Call(value)
}
}
impl<'de> serde::Deserialize<'de> for Value {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize as _;
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = Value;
fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::new_none(Type::None))
}
fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::new_none(Type::None))
}
fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
Value::deserialize(deserializer)
}
fn visit_bool<E>(self, v: bool) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::Primitive(PrimitiveValue::Boolean(v)))
}
fn visit_i64<E>(self, v: i64) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::Primitive(PrimitiveValue::Integer(v)))
}
fn visit_u64<E>(self, v: u64) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::Primitive(PrimitiveValue::Integer(
v.try_into().map_err(|_| {
E::custom("integer not in range for a 64-bit signed integer")
})?,
)))
}
fn visit_f64<E>(self, v: f64) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::Primitive(PrimitiveValue::Float(v.into())))
}
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::Primitive(PrimitiveValue::new_string(v)))
}
fn visit_string<E>(self, v: String) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::Primitive(PrimitiveValue::new_string(v)))
}
fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
use serde::de::Error as _;
let mut elements = vec![];
while let Some(element) = seq.next_element::<Value>()? {
elements.push(element);
}
let mut candidate_ty = None;
for element in elements.iter() {
let new_candidate_ty = element.ty();
let old_candidate_ty =
candidate_ty.get_or_insert_with(|| new_candidate_ty.clone());
let Some(new_common_ty) = old_candidate_ty.common_type(&new_candidate_ty)
else {
return Err(A::Error::custom(format!(
"a common element type does not exist between {old_candidate_ty:#} \
and {new_candidate_ty:#}"
)));
};
candidate_ty = Some(new_common_ty);
}
let array_ty = ArrayType::new(candidate_ty.unwrap_or(Type::Union));
Ok(Array::new(array_ty.clone(), elements)
.map_err(|e| {
A::Error::custom(format!("cannot coerce value to {array_ty:#}: {e:#}"))
})?
.into())
}
fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut members = IndexMap::new();
while let Some(key) = map.next_key::<String>()? {
members.insert(key, map.next_value()?);
}
Ok(Value::Compound(CompoundValue::Object(Object::new(members))))
}
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "a WDL value")
}
}
deserializer.deserialize_any(Visitor)
}
}
#[derive(Debug, Clone)]
pub enum PrimitiveValue {
Boolean(bool),
Integer(i64),
Float(OrderedFloat<f64>),
String(Arc<String>),
File(HostPath),
Directory(HostPath),
}
impl PrimitiveValue {
pub fn new_string(s: impl Into<String>) -> Self {
Self::String(Arc::new(s.into()))
}
pub fn new_file(path: impl Into<HostPath>) -> Self {
Self::File(path.into())
}
pub fn new_directory(path: impl Into<HostPath>) -> Self {
Self::Directory(path.into())
}
pub fn ty(&self) -> Type {
match self {
Self::Boolean(_) => PrimitiveType::Boolean.into(),
Self::Integer(_) => PrimitiveType::Integer.into(),
Self::Float(_) => PrimitiveType::Float.into(),
Self::String(_) => PrimitiveType::String.into(),
Self::File(_) => PrimitiveType::File.into(),
Self::Directory(_) => PrimitiveType::Directory.into(),
}
}
pub fn as_boolean(&self) -> Option<bool> {
match self {
Self::Boolean(v) => Some(*v),
_ => None,
}
}
pub fn unwrap_boolean(self) -> bool {
match self {
Self::Boolean(v) => v,
_ => panic!("value is not a boolean"),
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
Self::Integer(v) => Some(*v),
_ => None,
}
}
pub fn unwrap_integer(self) -> i64 {
match self {
Self::Integer(v) => v,
_ => panic!("value is not an integer"),
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
Self::Float(v) => Some((*v).into()),
_ => None,
}
}
pub fn unwrap_float(self) -> f64 {
match self {
Self::Float(v) => v.into(),
_ => panic!("value is not a float"),
}
}
pub fn as_string(&self) -> Option<&Arc<String>> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
pub fn unwrap_string(self) -> Arc<String> {
match self {
Self::String(s) => s,
_ => panic!("value is not a string"),
}
}
pub fn as_file(&self) -> Option<&HostPath> {
match self {
Self::File(p) => Some(p),
_ => None,
}
}
pub fn unwrap_file(self) -> HostPath {
match self {
Self::File(p) => p,
_ => panic!("value is not a file"),
}
}
pub fn as_directory(&self) -> Option<&HostPath> {
match self {
Self::Directory(p) => Some(p),
_ => None,
}
}
pub fn unwrap_directory(self) -> HostPath {
match self {
Self::Directory(p) => p,
_ => panic!("value is not a directory"),
}
}
pub fn compare(left: &Self, right: &Self) -> Option<Ordering> {
match (left, right) {
(Self::Boolean(left), Self::Boolean(right)) => Some(left.cmp(right)),
(Self::Integer(left), Self::Integer(right)) => Some(left.cmp(right)),
(Self::Integer(left), Self::Float(right)) => {
Some(OrderedFloat(*left as f64).cmp(right))
}
(Self::Float(left), Self::Integer(right)) => {
Some(left.cmp(&OrderedFloat(*right as f64)))
}
(Self::Float(left), Self::Float(right)) => Some(left.cmp(right)),
(Self::String(left), Self::String(right))
| (Self::String(left), Self::File(HostPath(right)))
| (Self::String(left), Self::Directory(HostPath(right)))
| (Self::File(HostPath(left)), Self::File(HostPath(right)))
| (Self::File(HostPath(left)), Self::String(right))
| (Self::Directory(HostPath(left)), Self::Directory(HostPath(right)))
| (Self::Directory(HostPath(left)), Self::String(right)) => Some(left.cmp(right)),
_ => None,
}
}
pub(crate) fn raw<'a>(
&'a self,
context: Option<&'a dyn EvaluationContext>,
) -> impl fmt::Display + use<'a> {
struct Display<'a> {
value: &'a PrimitiveValue,
context: Option<&'a dyn EvaluationContext>,
}
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.value {
PrimitiveValue::Boolean(v) => write!(f, "{v}"),
PrimitiveValue::Integer(v) => write!(f, "{v}"),
PrimitiveValue::Float(v) => write!(f, "{v:.6?}"),
PrimitiveValue::String(v) => write!(f, "{v}"),
PrimitiveValue::File(v) => {
write!(
f,
"{v}",
v = self
.context
.and_then(|c| c.guest_path(v).map(|p| Cow::Owned(p.0)))
.unwrap_or(Cow::Borrowed(&v.0))
)
}
PrimitiveValue::Directory(v) => {
write!(
f,
"{v}",
v = self
.context
.and_then(|c| c.guest_path(v).map(|p| Cow::Owned(p.0)))
.unwrap_or(Cow::Borrowed(&v.0))
)
}
}
}
}
Display {
value: self,
context,
}
}
}
impl fmt::Display for PrimitiveValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Boolean(v) => write!(f, "{v}"),
Self::Integer(v) => write!(f, "{v}"),
Self::Float(v) => write!(f, "{v:.6?}"),
Self::String(s) | Self::File(HostPath(s)) | Self::Directory(HostPath(s)) => {
f.write_str("\"")?;
write_escaped_wdl_string(f, s.as_str())?;
f.write_str("\"")
}
}
}
}
impl PartialEq for PrimitiveValue {
fn eq(&self, other: &Self) -> bool {
Self::compare(self, other) == Some(Ordering::Equal)
}
}
impl Eq for PrimitiveValue {}
impl Hash for PrimitiveValue {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Boolean(v) => {
0.hash(state);
v.hash(state);
}
Self::Integer(v) => {
1.hash(state);
v.hash(state);
}
Self::Float(v) => {
1.hash(state);
v.hash(state);
}
Self::String(v) | Self::File(HostPath(v)) | Self::Directory(HostPath(v)) => {
2.hash(state);
v.hash(state);
}
}
}
}
impl From<bool> for PrimitiveValue {
fn from(value: bool) -> Self {
Self::Boolean(value)
}
}
impl From<i64> for PrimitiveValue {
fn from(value: i64) -> Self {
Self::Integer(value)
}
}
impl From<f64> for PrimitiveValue {
fn from(value: f64) -> Self {
Self::Float(value.into())
}
}
impl From<String> for PrimitiveValue {
fn from(value: String) -> Self {
Self::String(value.into())
}
}
impl Coercible for PrimitiveValue {
fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
if target.is_union() || target.is_none() || self.ty().eq(target) {
return Ok(self.clone());
}
match self {
Self::Boolean(v) => {
target
.as_primitive()
.and_then(|ty| match ty {
PrimitiveType::Boolean => Some(Self::Boolean(*v)),
_ => None,
})
.with_context(|| format!("cannot coerce type `Boolean` to {target:#}"))
}
Self::Integer(v) => {
target
.as_primitive()
.and_then(|ty| match ty {
PrimitiveType::Integer => Some(Self::Integer(*v)),
PrimitiveType::Float => Some(Self::Float((*v as f64).into())),
_ => None,
})
.with_context(|| format!("cannot coerce type `Int` to {target:#}"))
}
Self::Float(v) => {
target
.as_primitive()
.and_then(|ty| match ty {
PrimitiveType::Float => Some(Self::Float(*v)),
_ => None,
})
.with_context(|| format!("cannot coerce type `Float` to {target:#}"))
}
Self::String(s) => {
target
.as_primitive()
.and_then(|ty| match ty {
PrimitiveType::String => Some(Self::String(s.clone())),
PrimitiveType::File => Some(Self::File(
context
.and_then(|c| c.host_path(&GuestPath(s.clone())))
.unwrap_or_else(|| s.clone().into()),
)),
PrimitiveType::Directory => Some(Self::Directory(
context
.and_then(|c| c.host_path(&GuestPath(s.clone())))
.unwrap_or_else(|| s.clone().into()),
)),
_ => None,
})
.with_context(|| format!("cannot coerce type `String` to {target:#}"))
}
Self::File(p) => {
target
.as_primitive()
.and_then(|ty| match ty {
PrimitiveType::File => Some(Self::File(p.clone())),
PrimitiveType::String => Some(Self::String(
context
.and_then(|c| c.guest_path(p).map(Into::into))
.unwrap_or_else(|| p.clone().into()),
)),
_ => None,
})
.with_context(|| format!("cannot coerce type `File` to {target:#}"))
}
Self::Directory(p) => {
target
.as_primitive()
.and_then(|ty| match ty {
PrimitiveType::Directory => Some(Self::Directory(p.clone())),
PrimitiveType::String => Some(Self::String(
context
.and_then(|c| c.guest_path(p).map(Into::into))
.unwrap_or_else(|| p.clone().into()),
)),
_ => None,
})
.with_context(|| format!("cannot coerce type `Directory` to {target:#}"))
}
}
}
}
#[derive(Debug)]
struct PairInner {
ty: Type,
left: Value,
right: Value,
}
#[derive(Debug, Clone)]
pub struct Pair(Arc<PairInner>);
impl Pair {
pub fn new(ty: PairType, left: impl Into<Value>, right: impl Into<Value>) -> Result<Self> {
Self::new_with_context(None, ty, left, right)
}
pub(crate) fn new_with_context(
context: Option<&dyn EvaluationContext>,
ty: PairType,
left: impl Into<Value>,
right: impl Into<Value>,
) -> Result<Self> {
let left = left
.into()
.coerce(context, ty.left_type())
.context("failed to coerce pair's left value")?;
let right = right
.into()
.coerce(context, ty.right_type())
.context("failed to coerce pair's right value")?;
Ok(Self::new_unchecked(ty, left, right))
}
pub(crate) fn new_unchecked(ty: impl Into<Type>, left: Value, right: Value) -> Self {
let ty = ty.into();
assert!(ty.as_pair().is_some());
Self(Arc::new(PairInner {
ty: ty.require(),
left,
right,
}))
}
pub fn ty(&self) -> Type {
self.0.ty.clone()
}
pub fn left(&self) -> &Value {
&self.0.left
}
pub fn right(&self) -> &Value {
&self.0.right
}
}
impl fmt::Display for Pair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"({left}, {right})",
left = self.0.left,
right = self.0.right
)
}
}
#[derive(Debug)]
struct ArrayInner {
ty: Type,
elements: Vec<Value>,
}
#[derive(Debug, Clone)]
pub struct Array(Arc<ArrayInner>);
impl Array {
pub fn new<V>(ty: ArrayType, elements: impl IntoIterator<Item = V>) -> Result<Self>
where
V: Into<Value>,
{
Self::new_with_context(None, ty, elements)
}
pub(crate) fn new_with_context<V>(
context: Option<&dyn EvaluationContext>,
ty: ArrayType,
elements: impl IntoIterator<Item = V>,
) -> Result<Self>
where
V: Into<Value>,
{
let element_type = ty.element_type();
let elements = elements
.into_iter()
.enumerate()
.map(|(i, v)| {
let v = v.into();
v.coerce(context, element_type)
.with_context(|| format!("failed to coerce array element at index {i}"))
})
.collect::<Result<Vec<_>>>()?;
Ok(Self::new_unchecked(ty, elements))
}
pub(crate) fn new_unchecked(ty: impl Into<Type>, elements: Vec<Value>) -> Self {
let ty = if let Type::Compound(CompoundType::Array(ty), _) = ty.into() {
Type::Compound(CompoundType::Array(ty.unqualified()), false)
} else {
panic!("type is not an array type");
};
Self(Arc::new(ArrayInner { ty, elements }))
}
pub fn ty(&self) -> Type {
self.0.ty.clone()
}
pub fn as_slice(&self) -> &[Value] {
&self.0.elements
}
pub fn len(&self) -> usize {
self.0.elements.len()
}
pub fn is_empty(&self) -> bool {
self.0.elements.is_empty()
}
}
impl fmt::Display for Array {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[")?;
for (i, element) in self.0.elements.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{element}")?;
}
write!(f, "]")
}
}
#[derive(Debug)]
struct MapInner {
ty: Type,
elements: IndexMap<PrimitiveValue, Value>,
}
#[derive(Debug, Clone)]
pub struct Map(Arc<MapInner>);
impl Map {
pub fn new<K, V>(ty: MapType, elements: impl IntoIterator<Item = (K, V)>) -> Result<Self>
where
K: Into<PrimitiveValue>,
V: Into<Value>,
{
Self::new_with_context(None, ty, elements)
}
pub(crate) fn new_with_context<K, V>(
context: Option<&dyn EvaluationContext>,
ty: MapType,
elements: impl IntoIterator<Item = (K, V)>,
) -> Result<Self>
where
K: Into<PrimitiveValue>,
V: Into<Value>,
{
let key_type = ty.key_type();
let value_type = ty.value_type();
let elements = elements
.into_iter()
.enumerate()
.map(|(i, (k, v))| {
let k = k.into();
let v = v.into();
Ok((
k.coerce(context, key_type).with_context(|| {
format!("failed to coerce map key for element at index {i}")
})?,
v.coerce(context, value_type).with_context(|| {
format!("failed to coerce map value for element at index {i}")
})?,
))
})
.collect::<Result<_>>()?;
Ok(Self::new_unchecked(ty, elements))
}
pub(crate) fn new_unchecked(
ty: impl Into<Type>,
elements: IndexMap<PrimitiveValue, Value>,
) -> Self {
let ty = ty.into();
assert!(ty.as_map().is_some());
Self(Arc::new(MapInner {
ty: ty.require(),
elements,
}))
}
pub fn ty(&self) -> Type {
self.0.ty.clone()
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&PrimitiveValue, &Value)> {
self.0.elements.iter()
}
pub fn keys(&self) -> impl ExactSizeIterator<Item = &PrimitiveValue> {
self.0.elements.keys()
}
pub fn values(&self) -> impl ExactSizeIterator<Item = &Value> {
self.0.elements.values()
}
pub fn contains_key(&self, key: &PrimitiveValue) -> bool {
self.0.elements.contains_key(key)
}
pub fn get(&self, key: &PrimitiveValue) -> Option<&Value> {
self.0.elements.get(key)
}
pub fn len(&self) -> usize {
self.0.elements.len()
}
pub fn is_empty(&self) -> bool {
self.0.elements.is_empty()
}
}
impl fmt::Display for Map {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{")?;
for (i, (k, v)) in self.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}: {v}")?;
}
write!(f, "}}")
}
}
#[derive(Debug, Clone)]
pub struct Object {
pub(crate) members: Arc<IndexMap<String, Value>>,
}
impl Object {
pub(crate) fn new(members: IndexMap<String, Value>) -> Self {
Self {
members: Arc::new(members),
}
}
pub fn empty() -> Self {
Self::new(IndexMap::default())
}
pub fn from_v1_metadata<N: TreeNode>(
items: impl Iterator<Item = v1::MetadataObjectItem<N>>,
) -> Self {
Object::new(
items
.map(|i| {
(
i.name().text().to_string(),
Value::from_v1_metadata(&i.value()),
)
})
.collect::<IndexMap<_, _>>(),
)
}
pub fn ty(&self) -> Type {
Type::Object
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&str, &Value)> {
self.members.iter().map(|(k, v)| (k.as_str(), v))
}
pub fn keys(&self) -> impl ExactSizeIterator<Item = &str> {
self.members.keys().map(|k| k.as_str())
}
pub fn values(&self) -> impl ExactSizeIterator<Item = &Value> {
self.members.values()
}
pub fn contains_key(&self, key: &str) -> bool {
self.members.contains_key(key)
}
pub fn get(&self, key: &str) -> Option<&Value> {
self.members.get(key)
}
pub fn len(&self) -> usize {
self.members.len()
}
pub fn is_empty(&self) -> bool {
self.members.is_empty()
}
}
impl fmt::Display for Object {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "object {{")?;
for (i, (k, v)) in self.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}: {v}")?;
}
write!(f, "}}")
}
}
#[derive(Debug)]
struct StructInner {
ty: Type,
name: Arc<String>,
members: IndexMap<String, Value>,
}
#[derive(Debug, Clone)]
pub struct Struct(Arc<StructInner>);
impl Struct {
pub fn new<S, V>(ty: StructType, members: impl IntoIterator<Item = (S, V)>) -> Result<Self>
where
S: Into<String>,
V: Into<Value>,
{
Self::new_with_context(None, ty, members)
}
pub(crate) fn new_with_context<S, V>(
context: Option<&dyn EvaluationContext>,
ty: StructType,
members: impl IntoIterator<Item = (S, V)>,
) -> Result<Self>
where
S: Into<String>,
V: Into<Value>,
{
let mut members = members
.into_iter()
.map(|(n, v)| {
let n = n.into();
let v = v.into();
let v = v
.coerce(
context,
ty.members().get(&n).ok_or_else(|| {
anyhow!("struct does not contain a member named `{n}`")
})?,
)
.with_context(|| format!("failed to coerce struct member `{n}`"))?;
Ok((n, v))
})
.collect::<Result<IndexMap<_, _>>>()?;
for (name, ty) in ty.members().iter() {
if ty.is_optional() {
if !members.contains_key(name) {
members.insert(name.clone(), Value::new_none(ty.clone()));
}
} else {
if !members.contains_key(name) {
bail!("missing a value for struct member `{name}`");
}
}
}
let name = Arc::new(ty.name().to_string());
Ok(Self::new_unchecked(ty, name, members))
}
pub(crate) fn new_unchecked(
ty: impl Into<Type>,
name: Arc<String>,
members: impl Into<IndexMap<String, Value>>,
) -> Self {
let ty = ty.into();
assert!(ty.as_struct().is_some());
Self(Arc::new(StructInner {
ty: ty.require(),
name,
members: members.into(),
}))
}
pub fn ty(&self) -> Type {
self.0.ty.clone()
}
pub fn name(&self) -> &Arc<String> {
&self.0.name
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&str, &Value)> {
self.0.members.iter().map(|(k, v)| (k.as_str(), v))
}
pub fn keys(&self) -> impl ExactSizeIterator<Item = &str> {
self.0.members.keys().map(|k| k.as_str())
}
pub fn values(&self) -> impl ExactSizeIterator<Item = &Value> {
self.0.members.values()
}
pub fn contains_key(&self, key: &str) -> bool {
self.0.members.contains_key(key)
}
pub fn get(&self, key: &str) -> Option<&Value> {
self.0.members.get(key)
}
}
impl fmt::Display for Struct {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{name} {{", name = self.0.name)?;
for (i, (k, v)) in self.0.members.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}: {v}")?;
}
write!(f, "}}")
}
}
#[derive(Debug)]
struct EnumVariantInner {
enum_ty: EnumType,
variant_index: usize,
value: Value,
}
#[derive(Debug, Clone)]
pub struct EnumVariant(Arc<EnumVariantInner>);
impl PartialEq for EnumVariant {
fn eq(&self, other: &Self) -> bool {
self.0.enum_ty == other.0.enum_ty && self.0.variant_index == other.0.variant_index
}
}
impl EnumVariant {
pub fn new(enum_ty: impl Into<EnumType>, name: &str, value: impl Into<Value>) -> Self {
let enum_ty = enum_ty.into();
let variant_index = enum_ty
.variants()
.iter()
.position(|v| v == name)
.expect("variant name must exist in enum type");
Self(Arc::new(EnumVariantInner {
enum_ty,
variant_index,
value: value.into(),
}))
}
pub fn enum_ty(&self) -> EnumType {
self.0.enum_ty.clone()
}
pub fn name(&self) -> &str {
&self.0.enum_ty.variants()[self.0.variant_index]
}
pub fn value(&self) -> &Value {
&self.0.value
}
}
impl fmt::Display for EnumVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone)]
pub enum CompoundValue {
Pair(Pair),
Array(Array),
Map(Map),
Object(Object),
Struct(Struct),
EnumVariant(EnumVariant),
}
impl CompoundValue {
pub fn ty(&self) -> Type {
match self {
CompoundValue::Pair(v) => v.ty(),
CompoundValue::Array(v) => v.ty(),
CompoundValue::Map(v) => v.ty(),
CompoundValue::Object(v) => v.ty(),
CompoundValue::Struct(v) => v.ty(),
CompoundValue::EnumVariant(v) => v.enum_ty().into(),
}
}
pub fn as_pair(&self) -> Option<&Pair> {
match self {
Self::Pair(v) => Some(v),
_ => None,
}
}
pub fn unwrap_pair(self) -> Pair {
match self {
Self::Pair(v) => v,
_ => panic!("value is not a pair"),
}
}
pub fn as_array(&self) -> Option<&Array> {
match self {
Self::Array(v) => Some(v),
_ => None,
}
}
pub fn unwrap_array(self) -> Array {
match self {
Self::Array(v) => v,
_ => panic!("value is not an array"),
}
}
pub fn as_map(&self) -> Option<&Map> {
match self {
Self::Map(v) => Some(v),
_ => None,
}
}
pub fn unwrap_map(self) -> Map {
match self {
Self::Map(v) => v,
_ => panic!("value is not a map"),
}
}
pub fn as_object(&self) -> Option<&Object> {
match self {
Self::Object(v) => Some(v),
_ => None,
}
}
pub fn unwrap_object(self) -> Object {
match self {
Self::Object(v) => v,
_ => panic!("value is not an object"),
}
}
pub fn as_struct(&self) -> Option<&Struct> {
match self {
Self::Struct(v) => Some(v),
_ => None,
}
}
pub fn unwrap_struct(self) -> Struct {
match self {
Self::Struct(v) => v,
_ => panic!("value is not a struct"),
}
}
pub fn as_enum_variant(&self) -> Option<&EnumVariant> {
match self {
Self::EnumVariant(v) => Some(v),
_ => None,
}
}
pub fn unwrap_enum_variant(self) -> EnumVariant {
match self {
Self::EnumVariant(v) => v,
_ => panic!("value is not an enum"),
}
}
pub fn equals(left: &Self, right: &Self) -> Option<bool> {
if left.ty() != right.ty() {
return None;
}
match (left, right) {
(Self::Pair(left), Self::Pair(right)) => Some(
Value::equals(left.left(), right.left())?
&& Value::equals(left.right(), right.right())?,
),
(CompoundValue::Array(left), CompoundValue::Array(right)) => Some(
left.len() == right.len()
&& left
.as_slice()
.iter()
.zip(right.as_slice())
.all(|(l, r)| Value::equals(l, r).unwrap_or(false)),
),
(CompoundValue::Map(left), CompoundValue::Map(right)) => Some(
left.len() == right.len()
&& left.iter().zip(right.iter()).all(|((lk, lv), (rk, rv))| {
lk == rk && Value::equals(lv, rv).unwrap_or(false)
}),
),
(CompoundValue::Object(left), CompoundValue::Object(right)) => Some(
left.len() == right.len()
&& left.iter().all(|(k, left)| match right.get(k) {
Some(right) => Value::equals(left, right).unwrap_or(false),
None => false,
}),
),
(CompoundValue::Struct(left), CompoundValue::Struct(right)) => Some(
left.0.members.len() == right.0.members.len()
&& left
.0
.members
.iter()
.all(|(k, lv)| match right.0.members.get(k) {
Some(rv) => Value::equals(lv, rv).unwrap_or(false),
None => false,
}),
),
(CompoundValue::EnumVariant(left), CompoundValue::EnumVariant(right)) => {
Some(left.enum_ty() == right.enum_ty() && left.name() == right.name())
}
_ => None,
}
}
fn visit_paths<F>(&self, cb: &mut F) -> Result<()>
where
F: FnMut(bool, &HostPath) -> Result<()> + Send + Sync,
{
match self {
Self::Pair(pair) => {
pair.left().visit_paths(cb)?;
pair.right().visit_paths(cb)?;
}
Self::Array(array) => {
for v in array.as_slice() {
v.visit_paths(cb)?;
}
}
Self::Map(map) => {
for (k, v) in map.iter() {
match k {
PrimitiveValue::File(path) => cb(true, path)?,
PrimitiveValue::Directory(path) => cb(false, path)?,
_ => {}
}
v.visit_paths(cb)?;
}
}
Self::Object(object) => {
for v in object.values() {
v.visit_paths(cb)?;
}
}
Self::Struct(s) => {
for v in s.values() {
v.visit_paths(cb)?;
}
}
Self::EnumVariant(e) => {
e.value().visit_paths(cb)?;
}
}
Ok(())
}
fn resolve_paths<'a, F>(
&'a self,
base_dir: Option<&'a Path>,
transferer: Option<&'a dyn Transferer>,
translate: &'a F,
) -> BoxFuture<'a, Result<Self>>
where
F: Fn(&HostPath) -> Result<HostPath> + Send + Sync,
{
async move {
match self {
Self::Pair(pair) => {
let ty = pair.0.ty.as_pair().expect("should be a pair type");
let (left_optional, right_optional) =
(ty.left_type().is_optional(), ty.right_type().is_optional());
let fst = pair
.0
.left
.resolve_paths(left_optional, base_dir, transferer, translate)
.await?;
let snd = pair
.0
.right
.resolve_paths(right_optional, base_dir, transferer, translate)
.await?;
Ok(Self::Pair(Pair::new_unchecked(ty.clone(), fst, snd)))
}
Self::Array(array) => {
let ty = array.0.ty.as_array().expect("should be an array type");
let optional = ty.element_type().is_optional();
if !array.0.elements.is_empty() {
let resolved_elements = futures::stream::iter(array.0.elements.iter())
.then(|v| v.resolve_paths(optional, base_dir, transferer, translate))
.try_collect::<Vec<Value>>()
.await?;
Ok(Self::Array(Array::new_unchecked(
ty.clone(),
resolved_elements,
)))
} else {
Ok(self.clone())
}
}
Self::Map(map) => {
let ty = map.0.ty.as_map().expect("should be a map type").clone();
let (key_optional, value_optional) =
(ty.key_type().is_optional(), ty.value_type().is_optional());
if !map.0.elements.is_empty() {
let resolved_elements = futures::stream::iter(map.0.elements.iter())
.then(async |(k, v)| {
let resolved_key = Value::from(k.clone())
.resolve_paths(key_optional, base_dir, transferer, translate)
.await?
.as_primitive()
.cloned()
.expect("key should be primitive");
let resolved_value = v
.resolve_paths(value_optional, base_dir, transferer, translate)
.await?;
Ok::<_, anyhow::Error>((resolved_key, resolved_value))
})
.try_collect()
.await?;
Ok(Self::Map(Map::new_unchecked(ty, resolved_elements)))
} else {
Ok(Self::Map(Map::new_unchecked(ty, IndexMap::new())))
}
}
Self::Object(object) => {
if object.is_empty() {
Ok(self.clone())
} else {
let resolved_members = futures::stream::iter(object.iter())
.then(async |(n, v)| {
let resolved = v
.resolve_paths(false, base_dir, transferer, translate)
.await?;
Ok::<_, anyhow::Error>((n.to_string(), resolved))
})
.try_collect()
.await?;
Ok(Self::Object(Object::new(resolved_members)))
}
}
Self::Struct(s) => {
let ty = s.0.ty.as_struct().expect("should be a struct type");
let name = s.name().clone();
let resolved_members: IndexMap<String, Value> = futures::stream::iter(s.iter())
.then(async |(n, v)| {
let resolved = v
.resolve_paths(
ty.members()[n].is_optional(),
base_dir,
transferer,
translate,
)
.await?;
Ok::<_, anyhow::Error>((n.to_string(), resolved))
})
.try_collect()
.await?;
Ok(Self::Struct(Struct::new_unchecked(
ty.clone(),
name,
resolved_members,
)))
}
Self::EnumVariant(e) => {
let optional = e.enum_ty().inner_value_type().is_optional();
let value =
e.0.value
.resolve_paths(optional, base_dir, transferer, translate)
.await?;
Ok(Self::EnumVariant(EnumVariant::new(
e.0.enum_ty.clone(),
e.name(),
value,
)))
}
}
}
.boxed()
}
}
impl fmt::Display for CompoundValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pair(v) => v.fmt(f),
Self::Array(v) => v.fmt(f),
Self::Map(v) => v.fmt(f),
Self::Object(v) => v.fmt(f),
Self::Struct(v) => v.fmt(f),
Self::EnumVariant(v) => v.fmt(f),
}
}
}
impl Coercible for CompoundValue {
fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
if target.is_union() || target.is_none() || self.ty().eq(target) {
return Ok(self.clone());
}
if let Type::Compound(target_ty, _) = target {
match (self, target_ty) {
(Self::Array(v), CompoundType::Array(target_ty)) => {
if v.is_empty() && target_ty.is_non_empty() {
bail!("cannot coerce empty array value to non-empty array {target:#}");
}
return Ok(Self::Array(Array::new_with_context(
context,
target_ty.clone(),
v.as_slice().iter().cloned(),
)?));
}
(Self::Map(v), CompoundType::Map(target_ty)) => {
return Ok(Self::Map(Map::new_with_context(
context,
target_ty.clone(),
v.iter().map(|(k, v)| (k.clone(), v.clone())),
)?));
}
(Self::Pair(v), CompoundType::Pair(target_ty)) => {
return Ok(Self::Pair(Pair::new_with_context(
context,
target_ty.clone(),
v.0.left.clone(),
v.0.right.clone(),
)?));
}
(Self::Map(v), CompoundType::Custom(CustomType::Struct(target_ty))) => {
let len = v.len();
let expected_len = target_ty.members().len();
if len != expected_len {
bail!(
"cannot coerce a map of {len} element{s1} to {target:#} as the struct \
has {expected_len} member{s2}",
s1 = if len == 1 { "" } else { "s" },
s2 = if expected_len == 1 { "" } else { "s" }
);
}
return Ok(Self::Struct(Struct::new_unchecked(
target.clone(),
target_ty.name().clone(),
v.iter()
.map(|(k, v)| {
let k = k
.coerce(context, &PrimitiveType::String.into())
.with_context(|| {
format!(
"cannot coerce a map of {map_type:#} to {target:#} as \
the key type cannot coerce to type `String`",
map_type = v.ty()
)
})?
.unwrap_string();
let ty =
target_ty.members().get(k.as_ref()).with_context(|| {
format!(
"cannot coerce a map with key `{k}` to {target:#} as \
the struct does not contain a member with that name"
)
})?;
let v = v.coerce(context, ty).with_context(|| {
format!("failed to coerce value of map key `{k}")
})?;
Ok((k.to_string(), v))
})
.collect::<Result<IndexMap<_, _>>>()?,
)));
}
(Self::Struct(s), CompoundType::Map(map_ty)) => {
let key_ty = map_ty.key_type();
if !Type::from(PrimitiveType::String).is_coercible_to(key_ty) {
bail!(
"cannot coerce a struct to {target:#} as key {key_ty:#} cannot be \
coerced from type `String`"
);
}
let value_ty = map_ty.value_type();
return Ok(Self::Map(Map::new_unchecked(
target.clone(),
s.0.members
.iter()
.map(|(n, v)| {
let v = v
.coerce(context, value_ty)
.with_context(|| format!("failed to coerce member `{n}`"))?;
Ok((
PrimitiveValue::new_string(n)
.coerce(context, key_ty)
.expect("should coerce"),
v,
))
})
.collect::<Result<_>>()?,
)));
}
(Self::Object(object), CompoundType::Map(map_ty)) => {
let key_ty = map_ty.key_type();
if !Type::from(PrimitiveType::String).is_coercible_to(key_ty) {
bail!(
"cannot coerce an object to {target:#} as key {key_ty:#} cannot be \
coerced from type `String`"
);
}
let value_ty = map_ty.value_type();
return Ok(Self::Map(Map::new_unchecked(
target.clone(),
object
.iter()
.map(|(n, v)| {
let v = v
.coerce(context, value_ty)
.with_context(|| format!("failed to coerce member `{n}`"))?;
Ok((
PrimitiveValue::new_string(n)
.coerce(context, key_ty)
.expect("should coerce"),
v,
))
})
.collect::<Result<_>>()?,
)));
}
(Self::Object(v), CompoundType::Custom(CustomType::Struct(struct_ty))) => {
return Ok(Self::Struct(Struct::new_with_context(
context,
struct_ty.clone(),
v.iter().map(|(k, v)| (k, v.clone())),
)?));
}
(Self::Struct(v), CompoundType::Custom(CustomType::Struct(struct_ty))) => {
let len = v.0.members.len();
let expected_len = struct_ty.members().len();
if len != expected_len {
bail!(
"cannot coerce a struct of {len} members{s1} to struct type \
`{target:#}` as the target struct has {expected_len} member{s2}",
s1 = if len == 1 { "" } else { "s" },
s2 = if expected_len == 1 { "" } else { "s" }
);
}
return Ok(Self::Struct(Struct::new_unchecked(
target.clone(),
struct_ty.name().clone(),
v.0.members
.iter()
.map(|(k, v)| {
let ty = struct_ty.members().get(k).ok_or_else(|| {
anyhow!(
"cannot coerce a struct with member `{k}` to struct type \
`{target:#}` as the target struct does not contain a \
member with that name",
)
})?;
let v = v
.coerce(context, ty)
.with_context(|| format!("failed to coerce member `{k}`"))?;
Ok((k.clone(), v))
})
.collect::<Result<IndexMap<_, _>>>()?,
)));
}
_ => {}
}
}
if let Type::Object = target {
match self {
Self::Map(v) => {
return Ok(Self::Object(Object::new(
v.iter()
.map(|(k, v)| {
let k = k
.coerce(context, &PrimitiveType::String.into())
.with_context(|| {
format!(
"cannot coerce a map of {map_type:#} to type `Object` \
as the key type cannot coerce to type `String`",
map_type = v.ty()
)
})?
.unwrap_string();
Ok((k.to_string(), v.clone()))
})
.collect::<Result<IndexMap<_, _>>>()?,
)));
}
Self::Struct(v) => {
return Ok(Self::Object(Object {
members: Arc::new(v.0.members.clone()),
}));
}
_ => {}
};
}
bail!(
"cannot coerce a value of {ty:#} to {target:#}",
ty = self.ty()
);
}
}
impl From<Pair> for CompoundValue {
fn from(value: Pair) -> Self {
Self::Pair(value)
}
}
impl From<Array> for CompoundValue {
fn from(value: Array) -> Self {
Self::Array(value)
}
}
impl From<Map> for CompoundValue {
fn from(value: Map) -> Self {
Self::Map(value)
}
}
impl From<Object> for CompoundValue {
fn from(value: Object) -> Self {
Self::Object(value)
}
}
impl From<Struct> for CompoundValue {
fn from(value: Struct) -> Self {
Self::Struct(value)
}
}
#[derive(Debug, Clone)]
pub enum HiddenValue {
Hints(HintsValue),
Input(InputValue),
Output(OutputValue),
TaskPreEvaluation(TaskPreEvaluationValue),
TaskPostEvaluation(TaskPostEvaluationValue),
PreviousTaskData(PreviousTaskDataValue),
}
impl HiddenValue {
pub fn ty(&self) -> Type {
match self {
Self::Hints(_) => Type::Hidden(HiddenType::Hints),
Self::Input(_) => Type::Hidden(HiddenType::Input),
Self::Output(_) => Type::Hidden(HiddenType::Output),
Self::TaskPreEvaluation(_) => Type::Hidden(HiddenType::TaskPreEvaluation),
Self::TaskPostEvaluation(_) => Type::Hidden(HiddenType::TaskPostEvaluation),
Self::PreviousTaskData(_) => Type::Hidden(HiddenType::PreviousTaskData),
}
}
}
impl fmt::Display for HiddenValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Hints(v) => v.fmt(f),
Self::Input(v) => v.fmt(f),
Self::Output(v) => v.fmt(f),
Self::TaskPreEvaluation(_) | Self::TaskPostEvaluation(_) => write!(f, "task"),
Self::PreviousTaskData(_) => write!(f, "task.previous"),
}
}
}
impl Coercible for HiddenValue {
fn coerce(&self, _: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
match self {
Self::Hints(_) => {
if matches!(target, Type::Hidden(HiddenType::Hints)) {
return Ok(self.clone());
}
bail!("hints values cannot be coerced to any other type");
}
Self::Input(_) => {
if matches!(target, Type::Hidden(HiddenType::Input)) {
return Ok(self.clone());
}
bail!("input values cannot be coerced to any other type");
}
Self::Output(_) => {
if matches!(target, Type::Hidden(HiddenType::Output)) {
return Ok(self.clone());
}
bail!("output values cannot be coerced to any other type");
}
Self::TaskPreEvaluation(_) | Self::TaskPostEvaluation(_) => {
if matches!(
target,
Type::Hidden(HiddenType::TaskPreEvaluation)
| Type::Hidden(HiddenType::TaskPostEvaluation)
) {
return Ok(self.clone());
}
bail!("task variables cannot be coerced to any other type");
}
Self::PreviousTaskData(_) => {
if matches!(target, Type::Hidden(HiddenType::PreviousTaskData)) {
return Ok(self.clone());
}
bail!("previous task data values cannot be coerced to any other type");
}
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct TaskPostEvaluationData {
container: Option<Arc<String>>,
cpu: f64,
memory: i64,
gpu: Array,
fpga: Array,
disks: Map,
max_retries: i64,
}
#[derive(Debug, Clone)]
pub struct PreviousTaskDataValue(Option<Arc<TaskPostEvaluationData>>);
impl PreviousTaskDataValue {
pub(crate) fn new(data: Arc<TaskPostEvaluationData>) -> Self {
Self(Some(data))
}
pub(crate) fn empty() -> Self {
Self(None)
}
pub fn field(&self, name: &str) -> Option<Value> {
match name {
TASK_FIELD_MEMORY => Some(
self.0
.as_ref()
.map(|data| Value::from(data.memory))
.unwrap_or_else(|| {
Value::new_none(Type::from(PrimitiveType::Integer).optional())
}),
),
TASK_FIELD_CPU => Some(
self.0
.as_ref()
.map(|data| Value::from(data.cpu))
.unwrap_or_else(|| {
Value::new_none(Type::from(PrimitiveType::Float).optional())
}),
),
TASK_FIELD_CONTAINER => Some(
self.0
.as_ref()
.and_then(|data| {
data.container
.as_ref()
.map(|c| PrimitiveValue::String(c.clone()).into())
})
.unwrap_or_else(|| {
Value::new_none(Type::from(PrimitiveType::String).optional())
}),
),
TASK_FIELD_GPU => Some(
self.0
.as_ref()
.map(|data| Value::from(data.gpu.clone()))
.unwrap_or_else(|| {
Value::new_none(Type::Compound(
CompoundType::Array(ArrayType::new(PrimitiveType::String)),
true,
))
}),
),
TASK_FIELD_FPGA => Some(
self.0
.as_ref()
.map(|data| Value::from(data.fpga.clone()))
.unwrap_or_else(|| {
Value::new_none(Type::Compound(
CompoundType::Array(ArrayType::new(PrimitiveType::String)),
true,
))
}),
),
TASK_FIELD_DISKS => Some(
self.0
.as_ref()
.map(|data| Value::from(data.disks.clone()))
.unwrap_or_else(|| {
Value::new_none(Type::Compound(
MapType::new(PrimitiveType::String, PrimitiveType::Integer).into(),
true,
))
}),
),
TASK_FIELD_MAX_RETRIES => Some(
self.0
.as_ref()
.map(|data| Value::from(data.max_retries))
.unwrap_or_else(|| {
Value::new_none(Type::from(PrimitiveType::Integer).optional())
}),
),
_ => None,
}
}
}
#[derive(Debug, Clone)]
struct TaskPreEvaluationInner {
name: Arc<String>,
id: Arc<String>,
attempt: i64,
meta: Object,
parameter_meta: Object,
ext: Object,
previous: PreviousTaskDataValue,
}
#[derive(Debug, Clone)]
pub struct TaskPreEvaluationValue(Arc<TaskPreEvaluationInner>);
impl TaskPreEvaluationValue {
pub(crate) fn new(
name: impl Into<String>,
id: impl Into<String>,
attempt: i64,
meta: Object,
parameter_meta: Object,
ext: Object,
) -> Self {
Self(Arc::new(TaskPreEvaluationInner {
name: Arc::new(name.into()),
id: Arc::new(id.into()),
meta,
parameter_meta,
ext,
attempt,
previous: PreviousTaskDataValue::empty(),
}))
}
pub(crate) fn set_previous(&mut self, data: Arc<TaskPostEvaluationData>) {
Arc::get_mut(&mut self.0)
.expect("task value must be uniquely owned to mutate")
.previous = PreviousTaskDataValue::new(data);
}
pub fn name(&self) -> &Arc<String> {
&self.0.name
}
pub fn id(&self) -> &Arc<String> {
&self.0.id
}
pub fn attempt(&self) -> i64 {
self.0.attempt
}
pub fn field(&self, name: &str) -> Option<Value> {
match name {
TASK_FIELD_NAME => Some(PrimitiveValue::String(self.0.name.clone()).into()),
TASK_FIELD_ID => Some(PrimitiveValue::String(self.0.id.clone()).into()),
TASK_FIELD_ATTEMPT => Some(self.0.attempt.into()),
TASK_FIELD_META => Some(self.0.meta.clone().into()),
TASK_FIELD_PARAMETER_META => Some(self.0.parameter_meta.clone().into()),
TASK_FIELD_EXT => Some(self.0.ext.clone().into()),
TASK_FIELD_PREVIOUS => {
Some(HiddenValue::PreviousTaskData(self.0.previous.clone()).into())
}
_ => None,
}
}
}
#[derive(Debug, Clone)]
struct TaskPostEvaluationInner {
data: Arc<TaskPostEvaluationData>,
name: Arc<String>,
id: Arc<String>,
attempt: i64,
meta: Object,
parameter_meta: Object,
ext: Object,
return_code: Option<i64>,
end_time: Option<i64>,
previous: PreviousTaskDataValue,
}
#[derive(Debug, Clone)]
pub struct TaskPostEvaluationValue(Arc<TaskPostEvaluationInner>);
impl TaskPostEvaluationValue {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
name: impl Into<String>,
id: impl Into<String>,
constraints: &TaskExecutionConstraints,
max_retries: i64,
attempt: i64,
meta: Object,
parameter_meta: Object,
ext: Object,
) -> Self {
Self(Arc::new(TaskPostEvaluationInner {
name: Arc::new(name.into()),
id: Arc::new(id.into()),
data: Arc::new(TaskPostEvaluationData {
container: None,
cpu: constraints.cpu,
memory: constraints
.memory
.try_into()
.expect("memory exceeds a valid WDL value"),
gpu: Array::new_unchecked(
ANALYSIS_STDLIB.array_string_type().clone(),
constraints
.gpu
.iter()
.map(|v| PrimitiveValue::new_string(v).into())
.collect(),
),
fpga: Array::new_unchecked(
ANALYSIS_STDLIB.array_string_type().clone(),
constraints
.fpga
.iter()
.map(|v| PrimitiveValue::new_string(v).into())
.collect(),
),
disks: Map::new_unchecked(
ANALYSIS_STDLIB.map_string_int_type().clone(),
constraints
.disks
.iter()
.map(|(k, v)| (PrimitiveValue::new_string(k), (*v).into()))
.collect(),
),
max_retries,
}),
attempt,
meta,
parameter_meta,
ext,
return_code: None,
end_time: None,
previous: PreviousTaskDataValue::empty(),
}))
}
pub fn name(&self) -> &Arc<String> {
&self.0.name
}
pub fn id(&self) -> &Arc<String> {
&self.0.id
}
pub fn container(&self) -> Option<&Arc<String>> {
self.0.data.container.as_ref()
}
pub fn cpu(&self) -> f64 {
self.0.data.cpu
}
pub fn memory(&self) -> i64 {
self.0.data.memory
}
pub fn gpu(&self) -> &Array {
&self.0.data.gpu
}
pub fn fpga(&self) -> &Array {
&self.0.data.fpga
}
pub fn disks(&self) -> &Map {
&self.0.data.disks
}
pub fn attempt(&self) -> i64 {
self.0.attempt
}
pub fn end_time(&self) -> Option<i64> {
self.0.end_time
}
pub fn return_code(&self) -> Option<i64> {
self.0.return_code
}
pub fn meta(&self) -> &Object {
&self.0.meta
}
pub fn parameter_meta(&self) -> &Object {
&self.0.parameter_meta
}
pub fn ext(&self) -> &Object {
&self.0.ext
}
pub(crate) fn set_container(&mut self, container: String) {
let inner = Arc::get_mut(&mut self.0).expect("task value must be uniquely owned to mutate");
Arc::make_mut(&mut inner.data).container = Some(Arc::new(container));
}
pub(crate) fn set_return_code(&mut self, code: i32) {
Arc::get_mut(&mut self.0)
.expect("task value must be uniquely owned to mutate")
.return_code = Some(code as i64);
}
pub(crate) fn set_attempt(&mut self, attempt: i64) {
Arc::get_mut(&mut self.0)
.expect("task value must be uniquely owned to mutate")
.attempt = attempt;
}
pub(crate) fn set_previous(&mut self, data: Arc<TaskPostEvaluationData>) {
Arc::get_mut(&mut self.0)
.expect("task value must be uniquely owned to mutate")
.previous = PreviousTaskDataValue::new(data);
}
pub(crate) fn data(&self) -> &Arc<TaskPostEvaluationData> {
&self.0.data
}
pub fn field(&self, version: SupportedVersion, name: &str) -> Option<Value> {
match name {
TASK_FIELD_NAME => Some(PrimitiveValue::String(self.0.name.clone()).into()),
TASK_FIELD_ID => Some(PrimitiveValue::String(self.0.id.clone()).into()),
TASK_FIELD_ATTEMPT => Some(self.0.attempt.into()),
TASK_FIELD_CONTAINER => Some(
self.0
.data
.container
.clone()
.map(|c| PrimitiveValue::String(c).into())
.unwrap_or_else(|| {
Value::new_none(
task_member_type_post_evaluation(version, TASK_FIELD_CONTAINER)
.expect("failed to get task field type"),
)
}),
),
TASK_FIELD_CPU => Some(self.0.data.cpu.into()),
TASK_FIELD_MEMORY => Some(self.0.data.memory.into()),
TASK_FIELD_GPU => Some(self.0.data.gpu.clone().into()),
TASK_FIELD_FPGA => Some(self.0.data.fpga.clone().into()),
TASK_FIELD_DISKS => Some(self.0.data.disks.clone().into()),
TASK_FIELD_END_TIME => Some(self.0.end_time.map(Into::into).unwrap_or_else(|| {
Value::new_none(
task_member_type_post_evaluation(version, TASK_FIELD_END_TIME)
.expect("failed to get task field type"),
)
})),
TASK_FIELD_RETURN_CODE => {
Some(self.0.return_code.map(Into::into).unwrap_or_else(|| {
Value::new_none(
task_member_type_post_evaluation(version, TASK_FIELD_RETURN_CODE)
.expect("failed to get task field type"),
)
}))
}
TASK_FIELD_META => Some(self.0.meta.clone().into()),
TASK_FIELD_PARAMETER_META => Some(self.0.parameter_meta.clone().into()),
TASK_FIELD_EXT => Some(self.0.ext.clone().into()),
TASK_FIELD_MAX_RETRIES if version >= SupportedVersion::V1(V1::Three) => {
Some(self.0.data.max_retries.into())
}
TASK_FIELD_PREVIOUS if version >= SupportedVersion::V1(V1::Three) => {
Some(HiddenValue::PreviousTaskData(self.0.previous.clone()).into())
}
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct HintsValue(Object);
impl HintsValue {
pub fn new(members: IndexMap<String, Value>) -> Self {
Self(Object::new(members))
}
pub fn as_object(&self) -> &Object {
&self.0
}
}
impl From<HintsValue> for Value {
fn from(value: HintsValue) -> Self {
Self::Hidden(HiddenValue::Hints(value))
}
}
impl fmt::Display for HintsValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "hints {{")?;
for (i, (k, v)) in self.0.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}: {v}")?;
}
write!(f, "}}")
}
}
impl From<Object> for HintsValue {
fn from(value: Object) -> Self {
Self(value)
}
}
#[derive(Debug, Clone)]
pub struct InputValue(Object);
impl InputValue {
pub fn new(members: IndexMap<String, Value>) -> Self {
Self(Object::new(members))
}
pub fn as_object(&self) -> &Object {
&self.0
}
}
impl From<InputValue> for Value {
fn from(value: InputValue) -> Self {
Self::Hidden(HiddenValue::Input(value))
}
}
impl fmt::Display for InputValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "input {{")?;
for (i, (k, v)) in self.0.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}: {v}")?;
}
write!(f, "}}")
}
}
impl From<Object> for InputValue {
fn from(value: Object) -> Self {
Self(value)
}
}
#[derive(Debug, Clone)]
pub struct OutputValue(Object);
impl OutputValue {
pub fn new(members: IndexMap<String, Value>) -> Self {
Self(Object::new(members))
}
pub fn as_object(&self) -> &Object {
&self.0
}
}
impl From<OutputValue> for Value {
fn from(value: OutputValue) -> Self {
Self::Hidden(HiddenValue::Output(value))
}
}
impl fmt::Display for OutputValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "output {{")?;
for (i, (k, v)) in self.0.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}: {v}")?;
}
write!(f, "}}")
}
}
impl From<Object> for OutputValue {
fn from(value: Object) -> Self {
Self(value)
}
}
#[derive(Debug, Clone)]
pub struct CallValue {
ty: Arc<CallType>,
outputs: Arc<Outputs>,
}
impl CallValue {
pub(crate) fn new_unchecked(ty: CallType, outputs: Arc<Outputs>) -> Self {
Self {
ty: Arc::new(ty),
outputs,
}
}
pub fn ty(&self) -> &CallType {
&self.ty
}
pub fn outputs(&self) -> &Outputs {
self.outputs.as_ref()
}
}
impl fmt::Display for CallValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "call output {{")?;
for (i, (k, v)) in self.outputs.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}: {v}")?;
}
write!(f, "}}")
}
}
pub(crate) struct ValueSerializer<'a> {
context: Option<&'a dyn EvaluationContext>,
value: &'a Value,
allow_pairs: bool,
}
impl<'a> ValueSerializer<'a> {
pub fn new(
context: Option<&'a dyn EvaluationContext>,
value: &'a Value,
allow_pairs: bool,
) -> Self {
Self {
context,
value,
allow_pairs,
}
}
}
impl serde::Serialize for ValueSerializer<'_> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::Error;
match &self.value {
Value::None(_) => serializer.serialize_none(),
Value::Primitive(v) => {
PrimitiveValueSerializer::new(self.context, v).serialize(serializer)
}
Value::Compound(v) => CompoundValueSerializer::new(self.context, v, self.allow_pairs)
.serialize(serializer),
Value::Call(_) | Value::Hidden(_) | Value::TypeNameRef(_) => {
Err(S::Error::custom("value cannot be serialized"))
}
}
}
}
pub(crate) struct PrimitiveValueSerializer<'a> {
context: Option<&'a dyn EvaluationContext>,
value: &'a PrimitiveValue,
}
impl<'a> PrimitiveValueSerializer<'a> {
pub fn new(context: Option<&'a dyn EvaluationContext>, value: &'a PrimitiveValue) -> Self {
Self { context, value }
}
}
impl serde::Serialize for PrimitiveValueSerializer<'_> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self.value {
PrimitiveValue::Boolean(v) => v.serialize(serializer),
PrimitiveValue::Integer(v) => v.serialize(serializer),
PrimitiveValue::Float(v) => v.serialize(serializer),
PrimitiveValue::String(s) => s.serialize(serializer),
PrimitiveValue::File(p) | PrimitiveValue::Directory(p) => {
let path = self
.context
.and_then(|c| c.guest_path(p).map(|p| Cow::Owned(p.0)))
.unwrap_or(Cow::Borrowed(&p.0));
path.serialize(serializer)
}
}
}
}
pub(crate) struct CompoundValueSerializer<'a> {
context: Option<&'a dyn EvaluationContext>,
value: &'a CompoundValue,
allow_pairs: bool,
}
impl<'a> CompoundValueSerializer<'a> {
pub fn new(
context: Option<&'a dyn EvaluationContext>,
value: &'a CompoundValue,
allow_pairs: bool,
) -> Self {
Self {
context,
value,
allow_pairs,
}
}
}
impl serde::Serialize for CompoundValueSerializer<'_> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::Error;
match &self.value {
CompoundValue::Pair(pair) if self.allow_pairs => {
let mut map = serializer.serialize_map(Some(2))?;
let left = ValueSerializer::new(self.context, pair.left(), self.allow_pairs);
let right = ValueSerializer::new(self.context, pair.right(), self.allow_pairs);
map.serialize_entry("left", &left)?;
map.serialize_entry("right", &right)?;
map.end()
}
CompoundValue::Pair(_) => Err(S::Error::custom("a pair cannot be serialized")),
CompoundValue::Array(v) => {
let mut seq = serializer.serialize_seq(Some(v.len()))?;
for v in v.as_slice() {
seq.serialize_element(&ValueSerializer::new(
self.context,
v,
self.allow_pairs,
))?;
}
seq.end()
}
CompoundValue::Map(v) => {
let mut map = serializer.serialize_map(Some(v.len()))?;
for (k, v) in v.iter() {
match k {
PrimitiveValue::String(s) => {
map.serialize_entry(
s.as_str(),
&ValueSerializer::new(self.context, v, self.allow_pairs),
)?;
}
PrimitiveValue::File(p) | PrimitiveValue::Directory(p) => {
map.serialize_entry(
p.as_str(),
&ValueSerializer::new(self.context, v, self.allow_pairs),
)?;
}
_ => {
map.serialize_entry(
&k.raw(None).to_string(),
&ValueSerializer::new(self.context, v, self.allow_pairs),
)?;
}
}
}
map.end()
}
CompoundValue::Object(object) => {
let mut map = serializer.serialize_map(Some(object.len()))?;
for (k, v) in object.iter() {
map.serialize_entry(
k,
&ValueSerializer::new(self.context, v, self.allow_pairs),
)?;
}
map.end()
}
CompoundValue::Struct(s) => {
let mut map = serializer.serialize_map(Some(s.0.members.len()))?;
for (k, v) in s.0.members.iter() {
map.serialize_entry(
k,
&ValueSerializer::new(self.context, v, self.allow_pairs),
)?;
}
map.end()
}
CompoundValue::EnumVariant(e) => serializer.serialize_str(e.name()),
}
}
}
#[cfg(test)]
mod test {
use std::iter::empty;
use approx::assert_relative_eq;
use pretty_assertions::assert_eq;
use wdl_analysis::types::ArrayType;
use wdl_analysis::types::MapType;
use wdl_analysis::types::PairType;
use wdl_analysis::types::StructType;
use wdl_ast::Diagnostic;
use wdl_ast::Span;
use wdl_ast::SupportedVersion;
use super::*;
use crate::EvaluationPath;
use crate::http::Transferer;
#[test]
fn boolean_coercion() {
assert_eq!(
Value::from(false)
.coerce(None, &PrimitiveType::Boolean.into())
.expect("should coerce")
.unwrap_boolean(),
Value::from(false).unwrap_boolean()
);
assert_eq!(
format!(
"{e:#}",
e = Value::from(true)
.coerce(None, &PrimitiveType::String.into())
.unwrap_err()
),
"cannot coerce type `Boolean` to type `String`"
);
}
#[test]
fn boolean_display() {
assert_eq!(Value::from(false).to_string(), "false");
assert_eq!(Value::from(true).to_string(), "true");
}
#[test]
fn integer_coercion() {
assert_eq!(
Value::from(12345)
.coerce(None, &PrimitiveType::Integer.into())
.expect("should coerce")
.unwrap_integer(),
Value::from(12345).unwrap_integer()
);
assert_relative_eq!(
Value::from(12345)
.coerce(None, &PrimitiveType::Float.into())
.expect("should coerce")
.unwrap_float(),
Value::from(12345.0).unwrap_float()
);
assert_eq!(
format!(
"{e:#}",
e = Value::from(12345)
.coerce(None, &PrimitiveType::Boolean.into())
.unwrap_err()
),
"cannot coerce type `Int` to type `Boolean`"
);
}
#[test]
fn integer_display() {
assert_eq!(Value::from(12345).to_string(), "12345");
assert_eq!(Value::from(-12345).to_string(), "-12345");
}
#[test]
fn float_coercion() {
assert_relative_eq!(
Value::from(12345.0)
.coerce(None, &PrimitiveType::Float.into())
.expect("should coerce")
.unwrap_float(),
Value::from(12345.0).unwrap_float()
);
assert_eq!(
format!(
"{e:#}",
e = Value::from(12345.0)
.coerce(None, &PrimitiveType::Integer.into())
.unwrap_err()
),
"cannot coerce type `Float` to type `Int`"
);
}
#[test]
fn float_display() {
assert_eq!(Value::from(12345.12345).to_string(), "12345.123450");
assert_eq!(Value::from(-12345.12345).to_string(), "-12345.123450");
}
#[test]
fn string_coercion() {
let value = PrimitiveValue::new_string("foo");
assert_eq!(
value
.coerce(None, &PrimitiveType::String.into())
.expect("should coerce"),
value
);
assert_eq!(
value
.coerce(None, &PrimitiveType::File.into())
.expect("should coerce"),
PrimitiveValue::File(value.as_string().expect("should be string").clone().into())
);
assert_eq!(
value
.coerce(None, &PrimitiveType::Directory.into())
.expect("should coerce"),
PrimitiveValue::Directory(value.as_string().expect("should be string").clone().into())
);
assert_eq!(
format!(
"{e:#}",
e = value
.coerce(None, &PrimitiveType::Boolean.into())
.unwrap_err()
),
"cannot coerce type `String` to type `Boolean`"
);
struct Context;
impl EvaluationContext for Context {
fn version(&self) -> SupportedVersion {
unimplemented!()
}
fn resolve_name(&self, _: &str, _: Span) -> Result<Value, Diagnostic> {
unimplemented!()
}
fn resolve_type_name(&self, _: &str, _: Span) -> Result<Type, Diagnostic> {
unimplemented!()
}
fn enum_variant_value(&self, _: &str, _: &str) -> Result<Value, Diagnostic> {
unimplemented!()
}
fn base_dir(&self) -> &EvaluationPath {
unimplemented!()
}
fn temp_dir(&self) -> &Path {
unimplemented!()
}
fn transferer(&self) -> &dyn Transferer {
unimplemented!()
}
fn host_path(&self, path: &GuestPath) -> Option<HostPath> {
if path.as_str() == "/mnt/task/input/0/path" {
Some(HostPath::new("/some/host/path"))
} else {
None
}
}
}
assert_eq!(
PrimitiveValue::new_string("/mnt/task/input/0/path")
.coerce(Some(&Context), &PrimitiveType::File.into())
.expect("should coerce")
.unwrap_file()
.as_str(),
"/some/host/path"
);
assert_eq!(
value
.coerce(Some(&Context), &PrimitiveType::File.into())
.expect("should coerce")
.unwrap_file()
.as_str(),
"foo"
);
assert_eq!(
PrimitiveValue::new_string("/mnt/task/input/0/path")
.coerce(Some(&Context), &PrimitiveType::Directory.into())
.expect("should coerce")
.unwrap_directory()
.as_str(),
"/some/host/path"
);
assert_eq!(
value
.coerce(Some(&Context), &PrimitiveType::Directory.into())
.expect("should coerce")
.unwrap_directory()
.as_str(),
"foo"
);
}
#[test]
fn string_display() {
let value = PrimitiveValue::new_string("hello world!");
assert_eq!(value.to_string(), "\"hello world!\"");
}
#[test]
fn string_display_escapes_special_characters() {
let value = PrimitiveValue::new_string(
"\u{1b}[31m${name} ~{color} \"quoted\" \\\\ tab\tline\ncarriage\r$HOME ~user",
);
assert_eq!(
value.to_string(),
r#""\x1B[31m\${name} \~{color} \"quoted\" \\\\ tab\tline\ncarriage\r$HOME ~user""#
);
}
#[test]
fn file_coercion() {
let value = PrimitiveValue::new_file("foo");
assert_eq!(
value
.coerce(None, &PrimitiveType::File.into())
.expect("should coerce"),
value
);
assert_eq!(
value
.coerce(None, &PrimitiveType::String.into())
.expect("should coerce"),
PrimitiveValue::String(value.as_file().expect("should be file").0.clone())
);
assert_eq!(
format!(
"{e:#}",
e = value
.coerce(None, &PrimitiveType::Directory.into())
.unwrap_err()
),
"cannot coerce type `File` to type `Directory`"
);
struct Context;
impl EvaluationContext for Context {
fn version(&self) -> SupportedVersion {
unimplemented!()
}
fn resolve_name(&self, _: &str, _: Span) -> Result<Value, Diagnostic> {
unimplemented!()
}
fn resolve_type_name(&self, _: &str, _: Span) -> Result<Type, Diagnostic> {
unimplemented!()
}
fn enum_variant_value(&self, _: &str, _: &str) -> Result<Value, Diagnostic> {
unimplemented!()
}
fn base_dir(&self) -> &EvaluationPath {
unimplemented!()
}
fn temp_dir(&self) -> &Path {
unimplemented!()
}
fn transferer(&self) -> &dyn Transferer {
unimplemented!()
}
fn guest_path(&self, path: &HostPath) -> Option<GuestPath> {
if path.as_str() == "/some/host/path" {
Some(GuestPath::new("/mnt/task/input/0/path"))
} else {
None
}
}
}
assert_eq!(
PrimitiveValue::new_file("/some/host/path")
.coerce(Some(&Context), &PrimitiveType::String.into())
.expect("should coerce")
.unwrap_string()
.as_str(),
"/mnt/task/input/0/path"
);
assert_eq!(
value
.coerce(Some(&Context), &PrimitiveType::String.into())
.expect("should coerce")
.unwrap_string()
.as_str(),
"foo"
);
}
#[test]
fn file_display() {
let value = PrimitiveValue::new_file("/foo/bar/baz.txt");
assert_eq!(value.to_string(), "\"/foo/bar/baz.txt\"");
}
#[test]
fn directory_coercion() {
let value = PrimitiveValue::new_directory("foo");
assert_eq!(
value
.coerce(None, &PrimitiveType::Directory.into())
.expect("should coerce"),
value
);
assert_eq!(
value
.coerce(None, &PrimitiveType::String.into())
.expect("should coerce"),
PrimitiveValue::String(value.as_directory().expect("should be directory").0.clone())
);
assert_eq!(
format!(
"{e:#}",
e = value.coerce(None, &PrimitiveType::File.into()).unwrap_err()
),
"cannot coerce type `Directory` to type `File`"
);
struct Context;
impl EvaluationContext for Context {
fn version(&self) -> SupportedVersion {
unimplemented!()
}
fn resolve_name(&self, _: &str, _: Span) -> Result<Value, Diagnostic> {
unimplemented!()
}
fn resolve_type_name(&self, _: &str, _: Span) -> Result<Type, Diagnostic> {
unimplemented!()
}
fn enum_variant_value(&self, _: &str, _: &str) -> Result<Value, Diagnostic> {
unimplemented!()
}
fn base_dir(&self) -> &EvaluationPath {
unimplemented!()
}
fn temp_dir(&self) -> &Path {
unimplemented!()
}
fn transferer(&self) -> &dyn Transferer {
unimplemented!()
}
fn guest_path(&self, path: &HostPath) -> Option<GuestPath> {
if path.as_str() == "/some/host/path" {
Some(GuestPath::new("/mnt/task/input/0/path"))
} else {
None
}
}
}
assert_eq!(
PrimitiveValue::new_directory("/some/host/path")
.coerce(Some(&Context), &PrimitiveType::String.into())
.expect("should coerce")
.unwrap_string()
.as_str(),
"/mnt/task/input/0/path"
);
assert_eq!(
value
.coerce(Some(&Context), &PrimitiveType::String.into())
.expect("should coerce")
.unwrap_string()
.as_str(),
"foo"
);
}
#[test]
fn directory_display() {
let value = PrimitiveValue::new_directory("/foo/bar/baz");
assert_eq!(value.to_string(), "\"/foo/bar/baz\"");
}
#[test]
fn none_coercion() {
assert!(
Value::new_none(Type::None)
.coerce(None, &Type::from(PrimitiveType::String).optional())
.expect("should coerce")
.is_none(),
);
assert_eq!(
format!(
"{e:#}",
e = Value::new_none(Type::None)
.coerce(None, &PrimitiveType::String.into())
.unwrap_err()
),
"cannot coerce `None` to non-optional type `String`"
);
}
#[test]
fn none_display() {
assert_eq!(Value::new_none(Type::None).to_string(), "None");
}
#[test]
fn array_coercion() {
let src_ty = ArrayType::new(PrimitiveType::Integer);
let target_ty: Type = ArrayType::new(PrimitiveType::Float).into();
let src: CompoundValue = Array::new(src_ty, [1, 2, 3])
.expect("should create array value")
.into();
let target = src.coerce(None, &target_ty).expect("should coerce");
assert_eq!(
target.unwrap_array().to_string(),
"[1.000000, 2.000000, 3.000000]"
);
let target_ty: Type = ArrayType::new(PrimitiveType::String).into();
assert_eq!(
format!("{e:#}", e = src.coerce(None, &target_ty).unwrap_err()),
"failed to coerce array element at index 0: cannot coerce type `Int` to type `String`"
);
}
#[test]
fn non_empty_array_coercion() {
let ty = ArrayType::new(PrimitiveType::String);
let target_ty: Type = ArrayType::non_empty(PrimitiveType::String).into();
let string = PrimitiveValue::new_string("foo");
let value: Value = Array::new(ty.clone(), [string])
.expect("should create array")
.into();
assert!(value.coerce(None, &target_ty).is_ok(), "should coerce");
let value: Value = Array::new::<Value>(ty, [])
.expect("should create array")
.into();
assert_eq!(
format!("{e:#}", e = value.coerce(None, &target_ty).unwrap_err()),
"cannot coerce empty array value to non-empty array type `Array[String]+`"
);
}
#[test]
fn array_display() {
let ty = ArrayType::new(PrimitiveType::Integer);
let value: Value = Array::new(ty, [1, 2, 3])
.expect("should create array")
.into();
assert_eq!(value.to_string(), "[1, 2, 3]");
}
#[test]
fn map_coerce() {
let key1 = PrimitiveValue::new_file("foo");
let value1 = PrimitiveValue::new_string("bar");
let key2 = PrimitiveValue::new_file("baz");
let value2 = PrimitiveValue::new_string("qux");
let ty = MapType::new(PrimitiveType::File, PrimitiveType::String);
let file_to_string: Value = Map::new(ty, [(key1, value1), (key2, value2)])
.expect("should create map value")
.into();
let ty = MapType::new(PrimitiveType::String, PrimitiveType::File).into();
let string_to_file = file_to_string
.coerce(None, &ty)
.expect("value should coerce");
assert_eq!(
string_to_file.to_string(),
r#"{"foo": "bar", "baz": "qux"}"#
);
let ty = MapType::new(PrimitiveType::Integer, PrimitiveType::File).into();
assert_eq!(
format!("{e:#}", e = string_to_file.coerce(None, &ty).unwrap_err()),
"failed to coerce map key for element at index 0: cannot coerce type `String` to type \
`Int`"
);
let ty = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
assert_eq!(
format!("{e:#}", e = string_to_file.coerce(None, &ty).unwrap_err()),
"failed to coerce map value for element at index 0: cannot coerce type `File` to type \
`Int`"
);
let ty = StructType::new(
"Foo",
[("foo", PrimitiveType::File), ("baz", PrimitiveType::File)],
)
.into();
let struct_value = string_to_file
.coerce(None, &ty)
.expect("value should coerce");
assert_eq!(struct_value.to_string(), r#"Foo {foo: "bar", baz: "qux"}"#);
let ty = StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("baz", PrimitiveType::String),
],
)
.into();
let struct_value = file_to_string
.coerce(None, &ty)
.expect("value should coerce");
assert_eq!(struct_value.to_string(), r#"Foo {foo: "bar", baz: "qux"}"#);
let ty = StructType::new(
"Foo",
[
("foo", PrimitiveType::File),
("baz", PrimitiveType::File),
("qux", PrimitiveType::File),
],
)
.into();
assert_eq!(
format!("{e:#}", e = string_to_file.coerce(None, &ty).unwrap_err()),
"cannot coerce a map of 2 elements to an instance of struct `Foo` as the struct has 3 \
members"
);
let object_value = string_to_file
.coerce(None, &Type::Object)
.expect("value should coerce");
assert_eq!(
object_value.to_string(),
r#"object {foo: "bar", baz: "qux"}"#
);
let object_value = file_to_string
.coerce(None, &Type::Object)
.expect("value should coerce");
assert_eq!(
object_value.to_string(),
r#"object {foo: "bar", baz: "qux"}"#
);
}
#[test]
fn map_display() {
let ty = MapType::new(PrimitiveType::Integer, PrimitiveType::Boolean);
let value: Value = Map::new(ty, [(1, true), (2, false)])
.expect("should create map value")
.into();
assert_eq!(value.to_string(), "{1: true, 2: false}");
}
#[test]
fn pair_coercion() {
let left = PrimitiveValue::new_file("foo");
let right = PrimitiveValue::new_string("bar");
let ty = PairType::new(PrimitiveType::File, PrimitiveType::String);
let value: Value = Pair::new(ty, left, right)
.expect("should create pair value")
.into();
let ty = PairType::new(PrimitiveType::String, PrimitiveType::File).into();
let value = value.coerce(None, &ty).expect("value should coerce");
assert_eq!(value.to_string(), r#"("foo", "bar")"#);
let ty = PairType::new(PrimitiveType::Integer, PrimitiveType::Integer).into();
assert_eq!(
format!("{e:#}", e = value.coerce(None, &ty).unwrap_err()),
"failed to coerce pair's left value: cannot coerce type `String` to type `Int`"
);
}
#[test]
fn pair_display() {
let ty = PairType::new(PrimitiveType::Integer, PrimitiveType::Boolean);
let value: Value = Pair::new(ty, 12345, false)
.expect("should create pair value")
.into();
assert_eq!(value.to_string(), "(12345, false)");
}
#[test]
fn struct_coercion() {
let ty = StructType::new(
"Foo",
[
("foo", PrimitiveType::Float),
("bar", PrimitiveType::Float),
("baz", PrimitiveType::Float),
],
);
let value: Value = Struct::new(ty, [("foo", 1.0), ("bar", 2.0), ("baz", 3.0)])
.expect("should create map value")
.into();
let ty = MapType::new(PrimitiveType::String, PrimitiveType::Float).into();
let map_value = value.coerce(None, &ty).expect("value should coerce");
assert_eq!(
map_value.to_string(),
r#"{"foo": 1.000000, "bar": 2.000000, "baz": 3.000000}"#
);
let ty = MapType::new(PrimitiveType::File, PrimitiveType::Float).into();
let map_value = value.coerce(None, &ty).expect("value should coerce");
assert_eq!(
map_value.to_string(),
r#"{"foo": 1.000000, "bar": 2.000000, "baz": 3.000000}"#
);
let ty = StructType::new(
"Bar",
[
("foo", PrimitiveType::Float),
("bar", PrimitiveType::Float),
("baz", PrimitiveType::Float),
],
)
.into();
let struct_value = value.coerce(None, &ty).expect("value should coerce");
assert_eq!(
struct_value.to_string(),
r#"Bar {foo: 1.000000, bar: 2.000000, baz: 3.000000}"#
);
let object_value = value
.coerce(None, &Type::Object)
.expect("value should coerce");
assert_eq!(
object_value.to_string(),
r#"object {foo: 1.000000, bar: 2.000000, baz: 3.000000}"#
);
}
#[test]
fn struct_display() {
let ty = StructType::new(
"Foo",
[
("foo", PrimitiveType::Float),
("bar", PrimitiveType::String),
("baz", PrimitiveType::Integer),
],
);
let value: Value = Struct::new(
ty,
[
("foo", Value::from(1.101)),
("bar", PrimitiveValue::new_string("foo").into()),
("baz", 1234.into()),
],
)
.expect("should create map value")
.into();
assert_eq!(
value.to_string(),
r#"Foo {foo: 1.101000, bar: "foo", baz: 1234}"#
);
}
#[test]
fn pair_serialization() {
let pair_ty = PairType::new(PrimitiveType::File, PrimitiveType::String);
let pair: Value = Pair::new(
pair_ty,
PrimitiveValue::new_file("foo"),
PrimitiveValue::new_string("bar"),
)
.expect("should create pair value")
.into();
let value_serializer = ValueSerializer::new(None, &pair, true);
let serialized = serde_json::to_string(&value_serializer).expect("should serialize");
assert_eq!(serialized, r#"{"left":"foo","right":"bar"}"#);
let value_serializer = ValueSerializer::new(None, &pair, false);
assert!(serde_json::to_string(&value_serializer).is_err());
let array_ty = ArrayType::new(PairType::new(PrimitiveType::File, PrimitiveType::String));
let array: Value = Array::new(array_ty, [pair])
.expect("should create array value")
.into();
let value_serializer = ValueSerializer::new(None, &array, true);
let serialized = serde_json::to_string(&value_serializer).expect("should serialize");
assert_eq!(serialized, r#"[{"left":"foo","right":"bar"}]"#);
}
#[test]
fn type_name_ref_equality() {
use wdl_analysis::types::EnumType;
let enum_type = Type::Compound(
CompoundType::Custom(CustomType::Enum(
EnumType::new(
"MyEnum",
Span::new(0, 0),
Type::Primitive(PrimitiveType::Integer, false),
Vec::<(String, Type)>::new(),
&[],
)
.expect("should create enum type"),
)),
false,
);
let value1 = Value::TypeNameRef(TypeNameRefValue::new(enum_type.clone()));
let value2 = Value::TypeNameRef(TypeNameRefValue::new(enum_type.clone()));
assert_eq!(value1.ty(), value2.ty());
}
#[test]
fn type_name_ref_ty() {
let struct_type = Type::Compound(
CompoundType::Custom(CustomType::Struct(StructType::new(
"MyStruct",
empty::<(&str, Type)>(),
))),
false,
);
let value = Value::TypeNameRef(TypeNameRefValue::new(struct_type.clone()));
assert_eq!(value.ty(), struct_type);
}
#[test]
fn type_name_ref_display() {
use wdl_analysis::types::EnumType;
let enum_type = Type::Compound(
CompoundType::Custom(CustomType::Enum(
EnumType::new(
"Color",
Span::new(0, 0),
Type::Primitive(PrimitiveType::Integer, false),
Vec::<(String, Type)>::new(),
&[],
)
.expect("should create enum type"),
)),
false,
);
let value = Value::TypeNameRef(TypeNameRefValue::new(enum_type));
assert_eq!(value.to_string(), "Color");
}
}