wdl_engine/
value.rs

1//! Implementation of the WDL runtime and values.
2
3use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::fmt;
6use std::hash::Hash;
7use std::hash::Hasher;
8use std::path::Path;
9use std::sync::Arc;
10
11use anyhow::Context;
12use anyhow::Result;
13use anyhow::anyhow;
14use anyhow::bail;
15use futures::FutureExt;
16use futures::StreamExt as _;
17use futures::TryStreamExt as _;
18use futures::future::BoxFuture;
19use indexmap::IndexMap;
20use itertools::Either;
21use ordered_float::OrderedFloat;
22use serde::ser::SerializeMap;
23use serde::ser::SerializeSeq;
24use wdl_analysis::stdlib::STDLIB as ANALYSIS_STDLIB;
25use wdl_analysis::types::ArrayType;
26use wdl_analysis::types::CallType;
27use wdl_analysis::types::Coercible as _;
28use wdl_analysis::types::CompoundType;
29use wdl_analysis::types::HiddenType;
30use wdl_analysis::types::MapType;
31use wdl_analysis::types::Optional;
32use wdl_analysis::types::PrimitiveType;
33use wdl_analysis::types::Type;
34use wdl_analysis::types::v1::task_member_type_post_evaluation;
35use wdl_ast::AstToken;
36use wdl_ast::SupportedVersion;
37use wdl_ast::TreeNode;
38use wdl_ast::v1;
39use wdl_ast::v1::TASK_FIELD_ATTEMPT;
40use wdl_ast::v1::TASK_FIELD_CONTAINER;
41use wdl_ast::v1::TASK_FIELD_CPU;
42use wdl_ast::v1::TASK_FIELD_DISKS;
43use wdl_ast::v1::TASK_FIELD_END_TIME;
44use wdl_ast::v1::TASK_FIELD_EXT;
45use wdl_ast::v1::TASK_FIELD_FPGA;
46use wdl_ast::v1::TASK_FIELD_GPU;
47use wdl_ast::v1::TASK_FIELD_ID;
48use wdl_ast::v1::TASK_FIELD_MAX_RETRIES;
49use wdl_ast::v1::TASK_FIELD_MEMORY;
50use wdl_ast::v1::TASK_FIELD_META;
51use wdl_ast::v1::TASK_FIELD_NAME;
52use wdl_ast::v1::TASK_FIELD_PARAMETER_META;
53use wdl_ast::v1::TASK_FIELD_PREVIOUS;
54use wdl_ast::v1::TASK_FIELD_RETURN_CODE;
55use wdl_ast::version::V1;
56
57use crate::EvaluationContext;
58use crate::GuestPath;
59use crate::HostPath;
60use crate::Outputs;
61use crate::backend::TaskExecutionConstraints;
62use crate::http::Transferer;
63use crate::path;
64
65/// Implemented on coercible values.
66pub trait Coercible: Sized {
67    /// Coerces the value into the given type.
68    ///
69    /// If the provided evaluation context is `None`, host to guest and guest to
70    /// host translation is not performed; `File` and `Directory` values will
71    /// coerce directly to string.
72    ///
73    /// Returns an error if the coercion is not supported.
74    fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self>;
75}
76
77/// Represents a WDL runtime value.
78///
79/// Values are cheap to clone.
80#[derive(Debug, Clone)]
81pub enum Value {
82    /// The value is a literal `None` value.
83    ///
84    /// The contained type is expected to be an optional type.
85    None(Type),
86    /// The value is a primitive value.
87    Primitive(PrimitiveValue),
88    /// The value is a compound value.
89    Compound(CompoundValue),
90    /// The value is a hidden value.
91    ///
92    /// A hidden value is one that has a hidden (i.e. not expressible in WDL
93    /// source) type.
94    Hidden(HiddenValue),
95    /// The value is the outputs of a call.
96    Call(CallValue),
97}
98
99impl Value {
100    /// Creates an object from an iterator of V1 AST metadata items.
101    ///
102    /// # Panics
103    ///
104    /// Panics if the metadata value contains an invalid numeric value.
105    pub fn from_v1_metadata<N: TreeNode>(value: &v1::MetadataValue<N>) -> Self {
106        match value {
107            v1::MetadataValue::Boolean(v) => v.value().into(),
108            v1::MetadataValue::Integer(v) => v.value().expect("number should be in range").into(),
109            v1::MetadataValue::Float(v) => v.value().expect("number should be in range").into(),
110            v1::MetadataValue::String(v) => PrimitiveValue::new_string(
111                v.text()
112                    .expect("metadata strings shouldn't have placeholders")
113                    .text(),
114            )
115            .into(),
116            v1::MetadataValue::Null(_) => Self::new_none(Type::None),
117            v1::MetadataValue::Object(o) => Object::from_v1_metadata(o.items()).into(),
118            v1::MetadataValue::Array(a) => Array::new_unchecked(
119                ANALYSIS_STDLIB.array_object_type().clone(),
120                a.elements().map(|v| Value::from_v1_metadata(&v)).collect(),
121            )
122            .into(),
123        }
124    }
125
126    /// Constructs a new `None` value with the given type.
127    ///
128    /// # Panics
129    ///
130    /// Panics if the provided type is not optional.
131    pub fn new_none(ty: Type) -> Self {
132        assert!(ty.is_optional(), "the provided `None` type is not optional");
133        Self::None(ty)
134    }
135
136    /// Gets the type of the value.
137    pub fn ty(&self) -> Type {
138        match self {
139            Self::None(ty) => ty.clone(),
140            Self::Primitive(v) => v.ty(),
141            Self::Compound(v) => v.ty(),
142            Self::Hidden(v) => v.ty(),
143            Self::Call(v) => Type::Call(v.ty.clone()),
144        }
145    }
146
147    /// Determines if the value is `None`.
148    pub fn is_none(&self) -> bool {
149        matches!(self, Self::None(_))
150    }
151
152    /// Gets the value as a primitive value.
153    ///
154    /// Returns `None` if the value is not a primitive value.
155    pub fn as_primitive(&self) -> Option<&PrimitiveValue> {
156        match self {
157            Self::Primitive(v) => Some(v),
158            _ => None,
159        }
160    }
161
162    /// Gets the value as a compound value.
163    ///
164    /// Returns `None` if the value is not a compound value.
165    pub fn as_compound(&self) -> Option<&CompoundValue> {
166        match self {
167            Self::Compound(v) => Some(v),
168            _ => None,
169        }
170    }
171
172    /// Gets the value as a `Boolean`.
173    ///
174    /// Returns `None` if the value is not a `Boolean`.
175    pub fn as_boolean(&self) -> Option<bool> {
176        match self {
177            Self::Primitive(PrimitiveValue::Boolean(v)) => Some(*v),
178            _ => None,
179        }
180    }
181
182    /// Unwraps the value into a `Boolean`.
183    ///
184    /// # Panics
185    ///
186    /// Panics if the value is not a `Boolean`.
187    pub fn unwrap_boolean(self) -> bool {
188        match self {
189            Self::Primitive(PrimitiveValue::Boolean(v)) => v,
190            _ => panic!("value is not a boolean"),
191        }
192    }
193
194    /// Gets the value as an `Int`.
195    ///
196    /// Returns `None` if the value is not an `Int`.
197    pub fn as_integer(&self) -> Option<i64> {
198        match self {
199            Self::Primitive(PrimitiveValue::Integer(v)) => Some(*v),
200            _ => None,
201        }
202    }
203
204    /// Unwraps the value into an integer.
205    ///
206    /// # Panics
207    ///
208    /// Panics if the value is not an integer.
209    pub fn unwrap_integer(self) -> i64 {
210        match self {
211            Self::Primitive(PrimitiveValue::Integer(v)) => v,
212            _ => panic!("value is not an integer"),
213        }
214    }
215
216    /// Gets the value as a `Float`.
217    ///
218    /// Returns `None` if the value is not a `Float`.
219    pub fn as_float(&self) -> Option<f64> {
220        match self {
221            Self::Primitive(PrimitiveValue::Float(v)) => Some((*v).into()),
222            _ => None,
223        }
224    }
225
226    /// Unwraps the value into a `Float`.
227    ///
228    /// # Panics
229    ///
230    /// Panics if the value is not a `Float`.
231    pub fn unwrap_float(self) -> f64 {
232        match self {
233            Self::Primitive(PrimitiveValue::Float(v)) => v.into(),
234            _ => panic!("value is not a float"),
235        }
236    }
237
238    /// Gets the value as a `String`.
239    ///
240    /// Returns `None` if the value is not a `String`.
241    pub fn as_string(&self) -> Option<&Arc<String>> {
242        match self {
243            Self::Primitive(PrimitiveValue::String(s)) => Some(s),
244            _ => None,
245        }
246    }
247
248    /// Unwraps the value into a `String`.
249    ///
250    /// # Panics
251    ///
252    /// Panics if the value is not a `String`.
253    pub fn unwrap_string(self) -> Arc<String> {
254        match self {
255            Self::Primitive(PrimitiveValue::String(s)) => s,
256            _ => panic!("value is not a string"),
257        }
258    }
259
260    /// Gets the value as a `File`.
261    ///
262    /// Returns `None` if the value is not a `File`.
263    pub fn as_file(&self) -> Option<&HostPath> {
264        match self {
265            Self::Primitive(PrimitiveValue::File(p)) => Some(p),
266            _ => None,
267        }
268    }
269
270    /// Unwraps the value into a `File`.
271    ///
272    /// # Panics
273    ///
274    /// Panics if the value is not a `File`.
275    pub fn unwrap_file(self) -> HostPath {
276        match self {
277            Self::Primitive(PrimitiveValue::File(p)) => p,
278            _ => panic!("value is not a file"),
279        }
280    }
281
282    /// Gets the value as a `Directory`.
283    ///
284    /// Returns `None` if the value is not a `Directory`.
285    pub fn as_directory(&self) -> Option<&HostPath> {
286        match self {
287            Self::Primitive(PrimitiveValue::Directory(p)) => Some(p),
288            _ => None,
289        }
290    }
291
292    /// Unwraps the value into a `Directory`.
293    ///
294    /// # Panics
295    ///
296    /// Panics if the value is not a `Directory`.
297    pub fn unwrap_directory(self) -> HostPath {
298        match self {
299            Self::Primitive(PrimitiveValue::Directory(p)) => p,
300            _ => panic!("value is not a directory"),
301        }
302    }
303
304    /// Gets the value as a `Pair`.
305    ///
306    /// Returns `None` if the value is not a `Pair`.
307    pub fn as_pair(&self) -> Option<&Pair> {
308        match self {
309            Self::Compound(CompoundValue::Pair(v)) => Some(v),
310            _ => None,
311        }
312    }
313
314    /// Unwraps the value into a `Pair`.
315    ///
316    /// # Panics
317    ///
318    /// Panics if the value is not a `Pair`.
319    pub fn unwrap_pair(self) -> Pair {
320        match self {
321            Self::Compound(CompoundValue::Pair(v)) => v,
322            _ => panic!("value is not a pair"),
323        }
324    }
325
326    /// Gets the value as an `Array`.
327    ///
328    /// Returns `None` if the value is not an `Array`.
329    pub fn as_array(&self) -> Option<&Array> {
330        match self {
331            Self::Compound(CompoundValue::Array(v)) => Some(v),
332            _ => None,
333        }
334    }
335
336    /// Unwraps the value into an `Array`.
337    ///
338    /// # Panics
339    ///
340    /// Panics if the value is not an `Array`.
341    pub fn unwrap_array(self) -> Array {
342        match self {
343            Self::Compound(CompoundValue::Array(v)) => v,
344            _ => panic!("value is not an array"),
345        }
346    }
347
348    /// Gets the value as a `Map`.
349    ///
350    /// Returns `None` if the value is not a `Map`.
351    pub fn as_map(&self) -> Option<&Map> {
352        match self {
353            Self::Compound(CompoundValue::Map(v)) => Some(v),
354            _ => None,
355        }
356    }
357
358    /// Unwraps the value into a `Map`.
359    ///
360    /// # Panics
361    ///
362    /// Panics if the value is not a `Map`.
363    pub fn unwrap_map(self) -> Map {
364        match self {
365            Self::Compound(CompoundValue::Map(v)) => v,
366            _ => panic!("value is not a map"),
367        }
368    }
369
370    /// Gets the value as an `Object`.
371    ///
372    /// Returns `None` if the value is not an `Object`.
373    pub fn as_object(&self) -> Option<&Object> {
374        match self {
375            Self::Compound(CompoundValue::Object(v)) => Some(v),
376            _ => None,
377        }
378    }
379
380    /// Unwraps the value into an `Object`.
381    ///
382    /// # Panics
383    ///
384    /// Panics if the value is not an `Object`.
385    pub fn unwrap_object(self) -> Object {
386        match self {
387            Self::Compound(CompoundValue::Object(v)) => v,
388            _ => panic!("value is not an object"),
389        }
390    }
391
392    /// Gets the value as a `Struct`.
393    ///
394    /// Returns `None` if the value is not a `Struct`.
395    pub fn as_struct(&self) -> Option<&Struct> {
396        match self {
397            Self::Compound(CompoundValue::Struct(v)) => Some(v),
398            _ => None,
399        }
400    }
401
402    /// Unwraps the value into a `Struct`.
403    ///
404    /// # Panics
405    ///
406    /// Panics if the value is not a `Map`.
407    pub fn unwrap_struct(self) -> Struct {
408        match self {
409            Self::Compound(CompoundValue::Struct(v)) => v,
410            _ => panic!("value is not a struct"),
411        }
412    }
413
414    /// Gets the value as a pre-evaluation task.
415    ///
416    /// Returns `None` if the value is not a pre-evaluation task.
417    pub fn as_task_pre_evaluation(&self) -> Option<&TaskPreEvaluationValue> {
418        match self {
419            Self::Hidden(HiddenValue::TaskPreEvaluation(v)) => Some(v),
420            _ => None,
421        }
422    }
423
424    /// Unwraps the value into a pre-evaluation task.
425    ///
426    /// # Panics
427    ///
428    /// Panics if the value is not a pre-evaluation task.
429    pub fn unwrap_task_pre_evaluation(self) -> TaskPreEvaluationValue {
430        match self {
431            Self::Hidden(HiddenValue::TaskPreEvaluation(v)) => v,
432            _ => panic!("value is not a pre-evaluation task"),
433        }
434    }
435
436    /// Gets the value as a post-evaluation task.
437    ///
438    /// Returns `None` if the value is not a post-evaluation task.
439    pub fn as_task_post_evaluation(&self) -> Option<&TaskPostEvaluationValue> {
440        match self {
441            Self::Hidden(HiddenValue::TaskPostEvaluation(v)) => Some(v),
442            _ => None,
443        }
444    }
445
446    /// Gets a mutable reference to the value as a post-evaluation task.
447    ///
448    /// Returns `None` if the value is not a post-evaluation task.
449    pub(crate) fn as_task_post_evaluation_mut(&mut self) -> Option<&mut TaskPostEvaluationValue> {
450        match self {
451            Self::Hidden(HiddenValue::TaskPostEvaluation(v)) => Some(v),
452            _ => None,
453        }
454    }
455
456    /// Unwraps the value into a post-evaluation task.
457    ///
458    /// # Panics
459    ///
460    /// Panics if the value is not a post-evaluation task.
461    pub fn unwrap_task_post_evaluation(self) -> TaskPostEvaluationValue {
462        match self {
463            Self::Hidden(HiddenValue::TaskPostEvaluation(v)) => v,
464            _ => panic!("value is not a post-evaluation task"),
465        }
466    }
467
468    /// Gets the value as a hints value.
469    ///
470    /// Returns `None` if the value is not a hints value.
471    pub fn as_hints(&self) -> Option<&HintsValue> {
472        match self {
473            Self::Hidden(HiddenValue::Hints(v)) => Some(v),
474            _ => None,
475        }
476    }
477
478    /// Unwraps the value into a hints value.
479    ///
480    /// # Panics
481    ///
482    /// Panics if the value is not a hints value.
483    pub fn unwrap_hints(self) -> HintsValue {
484        match self {
485            Self::Hidden(HiddenValue::Hints(v)) => v,
486            _ => panic!("value is not a hints value"),
487        }
488    }
489
490    /// Gets the value as a call value.
491    ///
492    /// Returns `None` if the value is not a call value.
493    pub fn as_call(&self) -> Option<&CallValue> {
494        match self {
495            Self::Call(v) => Some(v),
496            _ => None,
497        }
498    }
499
500    /// Unwraps the value into a call value.
501    ///
502    /// # Panics
503    ///
504    /// Panics if the value is not a call value.
505    pub fn unwrap_call(self) -> CallValue {
506        match self {
507            Self::Call(v) => v,
508            _ => panic!("value is not a call value"),
509        }
510    }
511
512    /// Visits any paths referenced by this value.
513    ///
514    /// The callback is invoked for each `File` and `Directory` value referenced
515    /// by this value.
516    pub(crate) fn visit_paths<F>(&self, cb: &mut F) -> Result<()>
517    where
518        F: FnMut(bool, &HostPath) -> Result<()> + Send + Sync,
519    {
520        match self {
521            Self::Primitive(PrimitiveValue::File(path)) => cb(true, path),
522            Self::Primitive(PrimitiveValue::Directory(path)) => cb(false, path),
523            Self::Compound(v) => v.visit_paths(cb),
524            _ => Ok(()),
525        }
526    }
527
528    /// Check that any paths referenced by a `File` or `Directory` value within
529    /// this value exist, and return a new value with any relevant host
530    /// paths transformed by the given `translate()` function.
531    ///
532    /// If a `File` or `Directory` value is optional and the path does not
533    /// exist, it is replaced with a WDL `None` value.
534    ///
535    /// If a `File` or `Directory` value is required and the path does not
536    /// exist, an error is returned.
537    ///
538    /// If a local base directory is provided, it will be joined with any
539    /// relative local paths prior to checking for existence.
540    ///
541    /// The provided transferer is used for checking remote URL existence.
542    ///
543    /// TODO ACF 2025-11-10: this function is an intermediate step on the way to
544    /// more thoroughly refactoring the code between `sprocket` and
545    /// `wdl_engine`. Expect this interface to change soon!
546    pub(crate) async fn resolve_paths<F>(
547        &self,
548        optional: bool,
549        base_dir: Option<&Path>,
550        transferer: Option<&dyn Transferer>,
551        translate: &F,
552    ) -> Result<Self>
553    where
554        F: Fn(&HostPath) -> Result<HostPath> + Send + Sync,
555    {
556        match self {
557            Self::Primitive(v @ PrimitiveValue::File(path))
558            | Self::Primitive(v @ PrimitiveValue::Directory(path)) => {
559                // We treat file and directory paths almost entirely the same, other than when
560                // reporting errors and choosing which variant to return in the result
561                let is_file = v.as_file().is_some();
562                let path = translate(path)?;
563
564                if path::is_file_url(path.as_str()) {
565                    // File URLs must be absolute paths, so we just check whether it exists without
566                    // performing any joining
567                    let exists = path::parse_supported_url(path.as_str())
568                        .and_then(|url| url.to_file_path().ok())
569                        .map(|p| p.exists())
570                        .unwrap_or(false);
571                    if exists {
572                        let v = PrimitiveValue::new_file_or_directory(is_file, path);
573                        return Ok(Self::Primitive(v));
574                    }
575
576                    if optional && !exists {
577                        return Ok(Value::new_none(self.ty().optional()));
578                    }
579
580                    bail!("path `{path}` does not exist");
581                } else if path::is_supported_url(path.as_str()) {
582                    match transferer {
583                        Some(transferer) => {
584                            let exists = transferer
585                                .exists(
586                                    &path
587                                        .as_str()
588                                        .parse()
589                                        .with_context(|| format!("invalid URL `{path}`"))?,
590                                )
591                                .await?;
592                            if exists {
593                                let v = PrimitiveValue::new_file_or_directory(is_file, path);
594                                return Ok(Self::Primitive(v));
595                            }
596
597                            if optional && !exists {
598                                return Ok(Value::new_none(self.ty().optional()));
599                            }
600
601                            bail!("URL `{path}` does not exist");
602                        }
603                        None => {
604                            // Assume the URL exists
605                            let v = PrimitiveValue::new_file_or_directory(is_file, path);
606                            return Ok(Self::Primitive(v));
607                        }
608                    }
609                }
610
611                // Check for existence
612                let exists_path: Cow<'_, Path> = base_dir
613                    .map(|d| d.join(path.as_str()).into())
614                    .unwrap_or_else(|| Path::new(path.as_str()).into());
615                if is_file && !exists_path.is_file() {
616                    if optional {
617                        return Ok(Value::new_none(self.ty().optional()));
618                    } else {
619                        bail!("file `{}` does not exist", exists_path.display());
620                    }
621                } else if !is_file && !exists_path.is_dir() {
622                    if optional {
623                        return Ok(Value::new_none(self.ty().optional()));
624                    } else {
625                        bail!("directory `{}` does not exist", exists_path.display())
626                    }
627                }
628
629                let v = PrimitiveValue::new_file_or_directory(is_file, path);
630                Ok(Self::Primitive(v))
631            }
632            Self::Compound(v) => Ok(Self::Compound(
633                v.resolve_paths(base_dir, transferer, translate)
634                    .boxed()
635                    .await?,
636            )),
637            v => Ok(v.clone()),
638        }
639    }
640
641    /// Determines if two values have equality according to the WDL
642    /// specification.
643    ///
644    /// Returns `None` if the two values cannot be compared for equality.
645    pub fn equals(left: &Self, right: &Self) -> Option<bool> {
646        match (left, right) {
647            (Value::None(_), Value::None(_)) => Some(true),
648            (Value::None(_), _) | (_, Value::None(_)) => Some(false),
649            (Value::Primitive(left), Value::Primitive(right)) => {
650                Some(PrimitiveValue::compare(left, right)? == Ordering::Equal)
651            }
652            (Value::Compound(left), Value::Compound(right)) => CompoundValue::equals(left, right),
653            _ => None,
654        }
655    }
656}
657
658impl fmt::Display for Value {
659    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
660        match self {
661            Self::None(_) => write!(f, "None"),
662            Self::Primitive(v) => v.fmt(f),
663            Self::Compound(v) => v.fmt(f),
664            Self::Hidden(v) => v.fmt(f),
665            Self::Call(c) => c.fmt(f),
666        }
667    }
668}
669
670impl Coercible for Value {
671    fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
672        if target.is_union() || target.is_none() || self.ty().eq(target) {
673            return Ok(self.clone());
674        }
675
676        match self {
677            Self::None(_) => {
678                if target.is_optional() {
679                    Ok(Self::new_none(target.clone()))
680                } else {
681                    bail!("cannot coerce `None` to non-optional type `{target}`");
682                }
683            }
684            Self::Primitive(v) => v.coerce(context, target).map(Self::Primitive),
685            Self::Compound(v) => v.coerce(context, target).map(Self::Compound),
686            Self::Hidden(v) => v.coerce(context, target).map(Self::Hidden),
687            Self::Call(_) => {
688                bail!("call values cannot be coerced to any other type");
689            }
690        }
691    }
692}
693
694impl From<bool> for Value {
695    fn from(value: bool) -> Self {
696        Self::Primitive(value.into())
697    }
698}
699
700impl From<i64> for Value {
701    fn from(value: i64) -> Self {
702        Self::Primitive(value.into())
703    }
704}
705
706impl TryFrom<u64> for Value {
707    type Error = std::num::TryFromIntError;
708
709    fn try_from(value: u64) -> std::result::Result<Self, Self::Error> {
710        let value: i64 = value.try_into()?;
711        Ok(value.into())
712    }
713}
714
715impl From<f64> for Value {
716    fn from(value: f64) -> Self {
717        Self::Primitive(value.into())
718    }
719}
720
721impl From<String> for Value {
722    fn from(value: String) -> Self {
723        Self::Primitive(value.into())
724    }
725}
726
727impl From<PrimitiveValue> for Value {
728    fn from(value: PrimitiveValue) -> Self {
729        Self::Primitive(value)
730    }
731}
732
733impl From<Option<PrimitiveValue>> for Value {
734    fn from(value: Option<PrimitiveValue>) -> Self {
735        match value {
736            Some(v) => v.into(),
737            None => Self::new_none(Type::None),
738        }
739    }
740}
741
742impl From<CompoundValue> for Value {
743    fn from(value: CompoundValue) -> Self {
744        Self::Compound(value)
745    }
746}
747
748impl From<HiddenValue> for Value {
749    fn from(value: HiddenValue) -> Self {
750        Self::Hidden(value)
751    }
752}
753
754impl From<Pair> for Value {
755    fn from(value: Pair) -> Self {
756        Self::Compound(value.into())
757    }
758}
759
760impl From<Array> for Value {
761    fn from(value: Array) -> Self {
762        Self::Compound(value.into())
763    }
764}
765
766impl From<Map> for Value {
767    fn from(value: Map) -> Self {
768        Self::Compound(value.into())
769    }
770}
771
772impl From<Object> for Value {
773    fn from(value: Object) -> Self {
774        Self::Compound(value.into())
775    }
776}
777
778impl From<Struct> for Value {
779    fn from(value: Struct) -> Self {
780        Self::Compound(value.into())
781    }
782}
783
784impl From<CallValue> for Value {
785    fn from(value: CallValue) -> Self {
786        Self::Call(value)
787    }
788}
789
790impl<'de> serde::Deserialize<'de> for Value {
791    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
792    where
793        D: serde::Deserializer<'de>,
794    {
795        use serde::Deserialize as _;
796
797        /// Visitor for deserialization.
798        struct Visitor;
799
800        impl<'de> serde::de::Visitor<'de> for Visitor {
801            type Value = Value;
802
803            fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
804            where
805                E: serde::de::Error,
806            {
807                Ok(Value::new_none(Type::None))
808            }
809
810            fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
811            where
812                E: serde::de::Error,
813            {
814                Ok(Value::new_none(Type::None))
815            }
816
817            fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
818            where
819                D: serde::Deserializer<'de>,
820            {
821                Value::deserialize(deserializer)
822            }
823
824            fn visit_bool<E>(self, v: bool) -> std::result::Result<Self::Value, E>
825            where
826                E: serde::de::Error,
827            {
828                Ok(Value::Primitive(PrimitiveValue::Boolean(v)))
829            }
830
831            fn visit_i64<E>(self, v: i64) -> std::result::Result<Self::Value, E>
832            where
833                E: serde::de::Error,
834            {
835                Ok(Value::Primitive(PrimitiveValue::Integer(v)))
836            }
837
838            fn visit_u64<E>(self, v: u64) -> std::result::Result<Self::Value, E>
839            where
840                E: serde::de::Error,
841            {
842                Ok(Value::Primitive(PrimitiveValue::Integer(
843                    v.try_into().map_err(|_| {
844                        E::custom("integer not in range for a 64-bit signed integer")
845                    })?,
846                )))
847            }
848
849            fn visit_f64<E>(self, v: f64) -> std::result::Result<Self::Value, E>
850            where
851                E: serde::de::Error,
852            {
853                Ok(Value::Primitive(PrimitiveValue::Float(v.into())))
854            }
855
856            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
857            where
858                E: serde::de::Error,
859            {
860                Ok(Value::Primitive(PrimitiveValue::new_string(v)))
861            }
862
863            fn visit_string<E>(self, v: String) -> std::result::Result<Self::Value, E>
864            where
865                E: serde::de::Error,
866            {
867                Ok(Value::Primitive(PrimitiveValue::new_string(v)))
868            }
869
870            fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
871            where
872                A: serde::de::SeqAccess<'de>,
873            {
874                use serde::de::Error as _;
875
876                let mut elements = vec![];
877                while let Some(element) = seq.next_element::<Value>()? {
878                    elements.push(element);
879                }
880
881                // Try to find a mutually-agreeable common type for the elements of the array.
882                let mut candidate_ty = None;
883                for element in elements.iter() {
884                    let new_candidate_ty = element.ty();
885                    let old_candidate_ty =
886                        candidate_ty.get_or_insert_with(|| new_candidate_ty.clone());
887                    let Some(new_common_ty) = old_candidate_ty.common_type(&new_candidate_ty)
888                    else {
889                        return Err(A::Error::custom(format!(
890                            "a common element type does not exist between `{old_candidate_ty}` \
891                             and `{new_candidate_ty}`"
892                        )));
893                    };
894                    candidate_ty = Some(new_common_ty);
895                }
896                // An empty array's elements have the `Union` type.
897                let array_ty: Type = ArrayType::new(candidate_ty.unwrap_or(Type::Union)).into();
898                Ok(Array::new(None, array_ty.clone(), elements)
899                    .map_err(|e| {
900                        A::Error::custom(format!("cannot coerce value to `{array_ty}`: {e:#}"))
901                    })?
902                    .into())
903            }
904
905            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
906            where
907                A: serde::de::MapAccess<'de>,
908            {
909                let mut members = IndexMap::new();
910                while let Some(key) = map.next_key::<String>()? {
911                    members.insert(key, map.next_value()?);
912                }
913
914                Ok(Value::Compound(CompoundValue::Object(Object::new(members))))
915            }
916
917            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
918                write!(f, "a WDL value")
919            }
920        }
921
922        deserializer.deserialize_any(Visitor)
923    }
924}
925
926/// Represents a primitive WDL value.
927///
928/// Primitive values are cheap to clone.
929#[derive(Debug, Clone)]
930pub enum PrimitiveValue {
931    /// The value is a `Boolean`.
932    Boolean(bool),
933    /// The value is an `Int`.
934    Integer(i64),
935    /// The value is a `Float`.
936    Float(OrderedFloat<f64>),
937    /// The value is a `String`.
938    String(Arc<String>),
939    /// The value is a `File`.
940    File(HostPath),
941    /// The value is a `Directory`.
942    Directory(HostPath),
943}
944
945impl PrimitiveValue {
946    /// Creates a new `String` value.
947    pub fn new_string(s: impl Into<String>) -> Self {
948        Self::String(Arc::new(s.into()))
949    }
950
951    /// Creates a new `File` value.
952    pub fn new_file(path: impl Into<HostPath>) -> Self {
953        Self::File(path.into())
954    }
955
956    /// Creates a new `Directory` value.
957    pub fn new_directory(path: impl Into<HostPath>) -> Self {
958        Self::Directory(path.into())
959    }
960
961    /// Create either a new `File` or `Directory` value, depending on whether
962    /// the `is_file` argument is `true`.
963    ///
964    /// This is a bit awkward, but can save a lot of repetition in code that
965    /// treats files and directories largely the same until having to
966    /// remember which enum variant the path needs to be stuffed back into.
967    fn new_file_or_directory(is_file: bool, path: impl Into<HostPath>) -> Self {
968        if is_file {
969            Self::File(path.into())
970        } else {
971            Self::Directory(path.into())
972        }
973    }
974
975    /// Gets the type of the value.
976    pub fn ty(&self) -> Type {
977        match self {
978            Self::Boolean(_) => PrimitiveType::Boolean.into(),
979            Self::Integer(_) => PrimitiveType::Integer.into(),
980            Self::Float(_) => PrimitiveType::Float.into(),
981            Self::String(_) => PrimitiveType::String.into(),
982            Self::File(_) => PrimitiveType::File.into(),
983            Self::Directory(_) => PrimitiveType::Directory.into(),
984        }
985    }
986
987    /// Gets the value as a `Boolean`.
988    ///
989    /// Returns `None` if the value is not a `Boolean`.
990    pub fn as_boolean(&self) -> Option<bool> {
991        match self {
992            Self::Boolean(v) => Some(*v),
993            _ => None,
994        }
995    }
996
997    /// Unwraps the value into a `Boolean`.
998    ///
999    /// # Panics
1000    ///
1001    /// Panics if the value is not a `Boolean`.
1002    pub fn unwrap_boolean(self) -> bool {
1003        match self {
1004            Self::Boolean(v) => v,
1005            _ => panic!("value is not a boolean"),
1006        }
1007    }
1008
1009    /// Gets the value as an `Int`.
1010    ///
1011    /// Returns `None` if the value is not an `Int`.
1012    pub fn as_integer(&self) -> Option<i64> {
1013        match self {
1014            Self::Integer(v) => Some(*v),
1015            _ => None,
1016        }
1017    }
1018
1019    /// Unwraps the value into an integer.
1020    ///
1021    /// # Panics
1022    ///
1023    /// Panics if the value is not an integer.
1024    pub fn unwrap_integer(self) -> i64 {
1025        match self {
1026            Self::Integer(v) => v,
1027            _ => panic!("value is not an integer"),
1028        }
1029    }
1030
1031    /// Gets the value as a `Float`.
1032    ///
1033    /// Returns `None` if the value is not a `Float`.
1034    pub fn as_float(&self) -> Option<f64> {
1035        match self {
1036            Self::Float(v) => Some((*v).into()),
1037            _ => None,
1038        }
1039    }
1040
1041    /// Unwraps the value into a `Float`.
1042    ///
1043    /// # Panics
1044    ///
1045    /// Panics if the value is not a `Float`.
1046    pub fn unwrap_float(self) -> f64 {
1047        match self {
1048            Self::Float(v) => v.into(),
1049            _ => panic!("value is not a float"),
1050        }
1051    }
1052
1053    /// Gets the value as a `String`.
1054    ///
1055    /// Returns `None` if the value is not a `String`.
1056    pub fn as_string(&self) -> Option<&Arc<String>> {
1057        match self {
1058            Self::String(s) => Some(s),
1059            _ => None,
1060        }
1061    }
1062
1063    /// Unwraps the value into a `String`.
1064    ///
1065    /// # Panics
1066    ///
1067    /// Panics if the value is not a `String`.
1068    pub fn unwrap_string(self) -> Arc<String> {
1069        match self {
1070            Self::String(s) => s,
1071            _ => panic!("value is not a string"),
1072        }
1073    }
1074
1075    /// Gets the value as a `File`.
1076    ///
1077    /// Returns `None` if the value is not a `File`.
1078    pub fn as_file(&self) -> Option<&HostPath> {
1079        match self {
1080            Self::File(p) => Some(p),
1081            _ => None,
1082        }
1083    }
1084
1085    /// Unwraps the value into a `File`.
1086    ///
1087    /// # Panics
1088    ///
1089    /// Panics if the value is not a `File`.
1090    pub fn unwrap_file(self) -> HostPath {
1091        match self {
1092            Self::File(p) => p,
1093            _ => panic!("value is not a file"),
1094        }
1095    }
1096
1097    /// Gets the value as a `Directory`.
1098    ///
1099    /// Returns `None` if the value is not a `Directory`.
1100    pub fn as_directory(&self) -> Option<&HostPath> {
1101        match self {
1102            Self::Directory(p) => Some(p),
1103            _ => None,
1104        }
1105    }
1106
1107    /// Unwraps the value into a `Directory`.
1108    ///
1109    /// # Panics
1110    ///
1111    /// Panics if the value is not a `Directory`.
1112    pub fn unwrap_directory(self) -> HostPath {
1113        match self {
1114            Self::Directory(p) => p,
1115            _ => panic!("value is not a directory"),
1116        }
1117    }
1118
1119    /// Compares two values for an ordering according to the WDL specification.
1120    ///
1121    /// Unlike a `PartialOrd` implementation, this takes into account automatic
1122    /// coercions.
1123    ///
1124    /// Returns `None` if the values cannot be compared based on their types.
1125    pub fn compare(left: &Self, right: &Self) -> Option<Ordering> {
1126        match (left, right) {
1127            (Self::Boolean(left), Self::Boolean(right)) => Some(left.cmp(right)),
1128            (Self::Integer(left), Self::Integer(right)) => Some(left.cmp(right)),
1129            (Self::Integer(left), Self::Float(right)) => {
1130                Some(OrderedFloat(*left as f64).cmp(right))
1131            }
1132            (Self::Float(left), Self::Integer(right)) => {
1133                Some(left.cmp(&OrderedFloat(*right as f64)))
1134            }
1135            (Self::Float(left), Self::Float(right)) => Some(left.cmp(right)),
1136            (Self::String(left), Self::String(right))
1137            | (Self::String(left), Self::File(HostPath(right)))
1138            | (Self::String(left), Self::Directory(HostPath(right)))
1139            | (Self::File(HostPath(left)), Self::File(HostPath(right)))
1140            | (Self::File(HostPath(left)), Self::String(right))
1141            | (Self::Directory(HostPath(left)), Self::Directory(HostPath(right)))
1142            | (Self::Directory(HostPath(left)), Self::String(right)) => Some(left.cmp(right)),
1143            _ => None,
1144        }
1145    }
1146
1147    /// Gets a raw display of the value.
1148    ///
1149    /// This differs from the [Display][fmt::Display] implementation in that
1150    /// strings, files, and directories are not quoted and not escaped.
1151    ///
1152    /// The provided coercion context is used to translate host paths to guest
1153    /// paths; if `None`, `File` and `Directory` values are displayed as-is.
1154    pub fn raw<'a>(
1155        &'a self,
1156        context: Option<&'a dyn EvaluationContext>,
1157    ) -> impl fmt::Display + use<'a> {
1158        /// Helper for displaying a raw value.
1159        struct Display<'a> {
1160            /// The value to display.
1161            value: &'a PrimitiveValue,
1162            /// The coercion context.
1163            context: Option<&'a dyn EvaluationContext>,
1164        }
1165
1166        impl fmt::Display for Display<'_> {
1167            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1168                match self.value {
1169                    PrimitiveValue::Boolean(v) => write!(f, "{v}"),
1170                    PrimitiveValue::Integer(v) => write!(f, "{v}"),
1171                    PrimitiveValue::Float(v) => write!(f, "{v:.6?}"),
1172                    PrimitiveValue::String(v) => write!(f, "{v}"),
1173                    PrimitiveValue::File(v) => {
1174                        write!(
1175                            f,
1176                            "{v}",
1177                            v = self
1178                                .context
1179                                .and_then(|c| c.guest_path(v).map(|p| Cow::Owned(p.0)))
1180                                .unwrap_or(Cow::Borrowed(&v.0))
1181                        )
1182                    }
1183                    PrimitiveValue::Directory(v) => {
1184                        write!(
1185                            f,
1186                            "{v}",
1187                            v = self
1188                                .context
1189                                .and_then(|c| c.guest_path(v).map(|p| Cow::Owned(p.0)))
1190                                .unwrap_or(Cow::Borrowed(&v.0))
1191                        )
1192                    }
1193                }
1194            }
1195        }
1196
1197        Display {
1198            value: self,
1199            context,
1200        }
1201    }
1202}
1203
1204impl fmt::Display for PrimitiveValue {
1205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1206        match self {
1207            Self::Boolean(v) => write!(f, "{v}"),
1208            Self::Integer(v) => write!(f, "{v}"),
1209            Self::Float(v) => write!(f, "{v:.6?}"),
1210            Self::String(s) | Self::File(HostPath(s)) | Self::Directory(HostPath(s)) => {
1211                // TODO: handle necessary escape sequences
1212                write!(f, "\"{s}\"")
1213            }
1214        }
1215    }
1216}
1217
1218impl PartialEq for PrimitiveValue {
1219    fn eq(&self, other: &Self) -> bool {
1220        Self::compare(self, other) == Some(Ordering::Equal)
1221    }
1222}
1223
1224impl Eq for PrimitiveValue {}
1225
1226impl Hash for PrimitiveValue {
1227    fn hash<H: Hasher>(&self, state: &mut H) {
1228        match self {
1229            Self::Boolean(v) => {
1230                0.hash(state);
1231                v.hash(state);
1232            }
1233            Self::Integer(v) => {
1234                1.hash(state);
1235                v.hash(state);
1236            }
1237            Self::Float(v) => {
1238                // Hash this with the same discriminant as integer; this allows coercion from
1239                // int to float.
1240                1.hash(state);
1241                v.hash(state);
1242            }
1243            Self::String(v) | Self::File(HostPath(v)) | Self::Directory(HostPath(v)) => {
1244                // Hash these with the same discriminant; this allows coercion from file and
1245                // directory to string
1246                2.hash(state);
1247                v.hash(state);
1248            }
1249        }
1250    }
1251}
1252
1253impl From<bool> for PrimitiveValue {
1254    fn from(value: bool) -> Self {
1255        Self::Boolean(value)
1256    }
1257}
1258
1259impl From<i64> for PrimitiveValue {
1260    fn from(value: i64) -> Self {
1261        Self::Integer(value)
1262    }
1263}
1264
1265impl From<f64> for PrimitiveValue {
1266    fn from(value: f64) -> Self {
1267        Self::Float(value.into())
1268    }
1269}
1270
1271impl From<String> for PrimitiveValue {
1272    fn from(value: String) -> Self {
1273        Self::String(value.into())
1274    }
1275}
1276
1277impl Coercible for PrimitiveValue {
1278    fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
1279        if target.is_union() || target.is_none() || self.ty().eq(target) {
1280            return Ok(self.clone());
1281        }
1282
1283        match self {
1284            Self::Boolean(v) => {
1285                target
1286                    .as_primitive()
1287                    .and_then(|ty| match ty {
1288                        // Boolean -> Boolean
1289                        PrimitiveType::Boolean => Some(Self::Boolean(*v)),
1290                        _ => None,
1291                    })
1292                    .with_context(|| format!("cannot coerce type `Boolean` to type `{target}`"))
1293            }
1294            Self::Integer(v) => {
1295                target
1296                    .as_primitive()
1297                    .and_then(|ty| match ty {
1298                        // Int -> Int
1299                        PrimitiveType::Integer => Some(Self::Integer(*v)),
1300                        // Int -> Float
1301                        PrimitiveType::Float => Some(Self::Float((*v as f64).into())),
1302                        _ => None,
1303                    })
1304                    .with_context(|| format!("cannot coerce type `Int` to type `{target}`"))
1305            }
1306            Self::Float(v) => {
1307                target
1308                    .as_primitive()
1309                    .and_then(|ty| match ty {
1310                        // Float -> Float
1311                        PrimitiveType::Float => Some(Self::Float(*v)),
1312                        _ => None,
1313                    })
1314                    .with_context(|| format!("cannot coerce type `Float` to type `{target}`"))
1315            }
1316            Self::String(s) => {
1317                target
1318                    .as_primitive()
1319                    .and_then(|ty| match ty {
1320                        // String -> String
1321                        PrimitiveType::String => Some(Self::String(s.clone())),
1322                        // String -> File
1323                        PrimitiveType::File => Some(Self::File(
1324                            context
1325                                .and_then(|c| c.host_path(&GuestPath(s.clone())))
1326                                .unwrap_or_else(|| s.clone().into()),
1327                        )),
1328                        // String -> Directory
1329                        PrimitiveType::Directory => Some(Self::Directory(
1330                            context
1331                                .and_then(|c| c.host_path(&GuestPath(s.clone())))
1332                                .unwrap_or_else(|| s.clone().into()),
1333                        )),
1334                        _ => None,
1335                    })
1336                    .with_context(|| format!("cannot coerce type `String` to type `{target}`"))
1337            }
1338            Self::File(p) => {
1339                target
1340                    .as_primitive()
1341                    .and_then(|ty| match ty {
1342                        // File -> File
1343                        PrimitiveType::File => Some(Self::File(p.clone())),
1344                        // File -> String
1345                        PrimitiveType::String => Some(Self::String(
1346                            context
1347                                .and_then(|c| c.guest_path(p).map(Into::into))
1348                                .unwrap_or_else(|| p.clone().into()),
1349                        )),
1350                        _ => None,
1351                    })
1352                    .with_context(|| format!("cannot coerce type `File` to type `{target}`"))
1353            }
1354            Self::Directory(p) => {
1355                target
1356                    .as_primitive()
1357                    .and_then(|ty| match ty {
1358                        // Directory -> Directory
1359                        PrimitiveType::Directory => Some(Self::Directory(p.clone())),
1360                        // Directory -> String
1361                        PrimitiveType::String => Some(Self::String(
1362                            context
1363                                .and_then(|c| c.guest_path(p).map(Into::into))
1364                                .unwrap_or_else(|| p.clone().into()),
1365                        )),
1366                        _ => None,
1367                    })
1368                    .with_context(|| format!("cannot coerce type `Directory` to type `{target}`"))
1369            }
1370        }
1371    }
1372}
1373
1374impl serde::Serialize for PrimitiveValue {
1375    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1376    where
1377        S: serde::Serializer,
1378    {
1379        match self {
1380            Self::Boolean(v) => v.serialize(serializer),
1381            Self::Integer(v) => v.serialize(serializer),
1382            Self::Float(v) => v.serialize(serializer),
1383            Self::String(s) | Self::File(HostPath(s)) | Self::Directory(HostPath(s)) => {
1384                s.serialize(serializer)
1385            }
1386        }
1387    }
1388}
1389
1390/// Represents a `Pair` value.
1391///
1392/// Pairs are cheap to clone.
1393#[derive(Debug, Clone)]
1394pub struct Pair {
1395    /// The type of the pair.
1396    ty: Type,
1397    /// The left and right values of the pair.
1398    values: Arc<(Value, Value)>,
1399}
1400
1401impl Pair {
1402    /// Creates a new `Pair` value.
1403    ///
1404    /// Returns an error if either the `left` value or the `right` value did not
1405    /// coerce to the pair's `left` type or `right` type, respectively.
1406    ///
1407    /// # Panics
1408    ///
1409    /// Panics if the given type is not a pair type.
1410    pub fn new(
1411        context: Option<&dyn EvaluationContext>,
1412        ty: impl Into<Type>,
1413        left: impl Into<Value>,
1414        right: impl Into<Value>,
1415    ) -> Result<Self> {
1416        let ty = ty.into();
1417        if let Type::Compound(CompoundType::Pair(ty), _) = ty {
1418            let left = left
1419                .into()
1420                .coerce(context, ty.left_type())
1421                .context("failed to coerce pair's left value")?;
1422            let right = right
1423                .into()
1424                .coerce(context, ty.right_type())
1425                .context("failed to coerce pair's right value")?;
1426            return Ok(Self::new_unchecked(
1427                Type::Compound(CompoundType::Pair(ty), false),
1428                left,
1429                right,
1430            ));
1431        }
1432
1433        panic!("type `{ty}` is not a pair type");
1434    }
1435
1436    /// Constructs a new pair without checking the given left and right conform
1437    /// to the given type.
1438    pub(crate) fn new_unchecked(ty: Type, left: Value, right: Value) -> Self {
1439        assert!(ty.as_pair().is_some());
1440        Self {
1441            ty: ty.require(),
1442            values: Arc::new((left, right)),
1443        }
1444    }
1445
1446    /// Gets the type of the `Pair`.
1447    pub fn ty(&self) -> Type {
1448        self.ty.clone()
1449    }
1450
1451    /// Gets the left value of the `Pair`.
1452    pub fn left(&self) -> &Value {
1453        &self.values.0
1454    }
1455
1456    /// Gets the right value of the `Pair`.
1457    pub fn right(&self) -> &Value {
1458        &self.values.1
1459    }
1460}
1461
1462impl fmt::Display for Pair {
1463    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1464        write!(
1465            f,
1466            "({left}, {right})",
1467            left = self.values.0,
1468            right = self.values.1
1469        )
1470    }
1471}
1472
1473/// Represents an `Array` value.
1474///
1475/// Arrays are cheap to clone.
1476#[derive(Debug, Clone)]
1477pub struct Array {
1478    /// The type of the array.
1479    ty: Type,
1480    /// The array's elements.
1481    ///
1482    /// A value of `None` indicates an empty array.
1483    elements: Option<Arc<Vec<Value>>>,
1484}
1485
1486impl Array {
1487    /// Creates a new `Array` value for the given array type.
1488    ///
1489    /// Returns an error if an element did not coerce to the array's element
1490    /// type.
1491    ///
1492    /// # Panics
1493    ///
1494    /// Panics if the given type is not an array type.
1495    pub fn new<V>(
1496        context: Option<&dyn EvaluationContext>,
1497        ty: impl Into<Type>,
1498        elements: impl IntoIterator<Item = V>,
1499    ) -> Result<Self>
1500    where
1501        V: Into<Value>,
1502    {
1503        let ty = ty.into();
1504        if let Type::Compound(CompoundType::Array(ty), _) = ty {
1505            let element_type = ty.element_type();
1506            let elements = elements
1507                .into_iter()
1508                .enumerate()
1509                .map(|(i, v)| {
1510                    let v = v.into();
1511                    v.coerce(context, element_type)
1512                        .with_context(|| format!("failed to coerce array element at index {i}"))
1513                })
1514                .collect::<Result<Vec<_>>>()?;
1515
1516            return Ok(Self::new_unchecked(
1517                Type::Compound(CompoundType::Array(ty.unqualified()), false),
1518                elements,
1519            ));
1520        }
1521
1522        panic!("type `{ty}` is not an array type");
1523    }
1524
1525    /// Constructs a new array without checking the given elements conform to
1526    /// the given type.
1527    pub(crate) fn new_unchecked(ty: Type, elements: Vec<Value>) -> Self {
1528        let ty = if let Type::Compound(CompoundType::Array(ty), _) = ty {
1529            Type::Compound(CompoundType::Array(ty.unqualified()), false)
1530        } else {
1531            panic!("type is not an array type");
1532        };
1533
1534        Self {
1535            ty,
1536            elements: if elements.is_empty() {
1537                None
1538            } else {
1539                Some(Arc::new(elements))
1540            },
1541        }
1542    }
1543
1544    /// Gets the type of the `Array` value.
1545    pub fn ty(&self) -> Type {
1546        self.ty.clone()
1547    }
1548
1549    /// Converts the array value to a slice of values.
1550    pub fn as_slice(&self) -> &[Value] {
1551        self.elements.as_ref().map(|v| v.as_slice()).unwrap_or(&[])
1552    }
1553
1554    /// Returns the number of elements in the array.
1555    pub fn len(&self) -> usize {
1556        self.elements.as_ref().map(|v| v.len()).unwrap_or(0)
1557    }
1558
1559    /// Returns `true` if the array has no elements.
1560    pub fn is_empty(&self) -> bool {
1561        self.len() == 0
1562    }
1563}
1564
1565impl fmt::Display for Array {
1566    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1567        write!(f, "[")?;
1568
1569        if let Some(elements) = &self.elements {
1570            for (i, element) in elements.iter().enumerate() {
1571                if i > 0 {
1572                    write!(f, ", ")?;
1573                }
1574
1575                write!(f, "{element}")?;
1576            }
1577        }
1578
1579        write!(f, "]")
1580    }
1581}
1582
1583/// Represents a `Map` value.
1584///
1585/// Maps are cheap to clone.
1586#[derive(Debug, Clone)]
1587pub struct Map {
1588    /// The type of the map value.
1589    ty: Type,
1590    /// The elements of the map value.
1591    ///
1592    /// A value of `None` indicates an empty map.
1593    elements: Option<Arc<IndexMap<Option<PrimitiveValue>, Value>>>,
1594}
1595
1596impl Map {
1597    /// Creates a new `Map` value.
1598    ///
1599    /// Returns an error if a key or value did not coerce to the map's key or
1600    /// value type, respectively.
1601    ///
1602    /// # Panics
1603    ///
1604    /// Panics if the given type is not a map type.
1605    pub fn new<K, V>(
1606        context: Option<&dyn EvaluationContext>,
1607        ty: impl Into<Type>,
1608        elements: impl IntoIterator<Item = (K, V)>,
1609    ) -> Result<Self>
1610    where
1611        K: Into<Value>,
1612        V: Into<Value>,
1613    {
1614        let ty = ty.into();
1615        if let Type::Compound(CompoundType::Map(ty), _) = ty {
1616            let key_type = ty.key_type();
1617            let value_type = ty.value_type();
1618
1619            let elements = elements
1620                .into_iter()
1621                .enumerate()
1622                .map(|(i, (k, v))| {
1623                    let k = k.into();
1624                    let v = v.into();
1625                    Ok((
1626                        if k.is_none() {
1627                            None
1628                        } else {
1629                            match k.coerce(context, key_type).with_context(|| {
1630                                format!("failed to coerce map key for element at index {i}")
1631                            })? {
1632                                Value::None(_) => None,
1633                                Value::Primitive(v) => Some(v),
1634                                _ => {
1635                                    bail!("not all key values are primitive")
1636                                }
1637                            }
1638                        },
1639                        v.coerce(context, value_type).with_context(|| {
1640                            format!("failed to coerce map value for element at index {i}")
1641                        })?,
1642                    ))
1643                })
1644                .collect::<Result<_>>()?;
1645
1646            return Ok(Self::new_unchecked(
1647                Type::Compound(CompoundType::Map(ty), false),
1648                elements,
1649            ));
1650        }
1651
1652        panic!("type `{ty}` is not a map type");
1653    }
1654
1655    /// Constructs a new map without checking the given elements conform to the
1656    /// given type.
1657    pub(crate) fn new_unchecked(
1658        ty: Type,
1659        elements: IndexMap<Option<PrimitiveValue>, Value>,
1660    ) -> Self {
1661        assert!(ty.as_map().is_some());
1662        Self {
1663            ty: ty.require(),
1664            elements: if elements.is_empty() {
1665                None
1666            } else {
1667                Some(Arc::new(elements))
1668            },
1669        }
1670    }
1671
1672    /// Gets the type of the `Map` value.
1673    pub fn ty(&self) -> Type {
1674        self.ty.clone()
1675    }
1676
1677    /// Iterates the elements of the map.
1678    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&Option<PrimitiveValue>, &Value)> {
1679        self.elements
1680            .as_ref()
1681            .map(|m| Either::Left(m.iter()))
1682            .unwrap_or(Either::Right(std::iter::empty()))
1683    }
1684
1685    /// Iterates the keys of the map.
1686    pub fn keys(&self) -> impl ExactSizeIterator<Item = &Option<PrimitiveValue>> {
1687        self.elements
1688            .as_ref()
1689            .map(|m| Either::Left(m.keys()))
1690            .unwrap_or(Either::Right(std::iter::empty()))
1691    }
1692
1693    /// Iterates the values of the map.
1694    pub fn values(&self) -> impl ExactSizeIterator<Item = &Value> {
1695        self.elements
1696            .as_ref()
1697            .map(|m| Either::Left(m.values()))
1698            .unwrap_or(Either::Right(std::iter::empty()))
1699    }
1700
1701    /// Determines if the map contains the given key.
1702    pub fn contains_key(&self, key: &Option<PrimitiveValue>) -> bool {
1703        self.elements
1704            .as_ref()
1705            .map(|m| m.contains_key(key))
1706            .unwrap_or(false)
1707    }
1708
1709    /// Gets a value from the map by key.
1710    pub fn get(&self, key: &Option<PrimitiveValue>) -> Option<&Value> {
1711        self.elements.as_ref().and_then(|m| m.get(key))
1712    }
1713
1714    /// Returns the number of elements in the map.
1715    pub fn len(&self) -> usize {
1716        self.elements.as_ref().map(|m| m.len()).unwrap_or(0)
1717    }
1718
1719    /// Returns `true` if the map has no elements.
1720    pub fn is_empty(&self) -> bool {
1721        self.len() == 0
1722    }
1723}
1724
1725impl fmt::Display for Map {
1726    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1727        write!(f, "{{")?;
1728
1729        for (i, (k, v)) in self.iter().enumerate() {
1730            if i > 0 {
1731                write!(f, ", ")?;
1732            }
1733
1734            match k {
1735                Some(k) => write!(f, "{k}: {v}")?,
1736                None => write!(f, "None: {v}")?,
1737            }
1738        }
1739
1740        write!(f, "}}")
1741    }
1742}
1743
1744/// Represents an `Object` value.
1745///
1746/// Objects are cheap to clone.
1747#[derive(Debug, Clone)]
1748pub struct Object {
1749    /// The members of the object.
1750    ///
1751    /// A value of `None` indicates an empty object.
1752    pub(crate) members: Option<Arc<IndexMap<String, Value>>>,
1753}
1754
1755impl Object {
1756    /// Creates a new `Object` value.
1757    ///
1758    /// Keys **must** be known WDL identifiers checked by the caller.
1759    pub(crate) fn new(members: IndexMap<String, Value>) -> Self {
1760        Self {
1761            members: if members.is_empty() {
1762                None
1763            } else {
1764                Some(Arc::new(members))
1765            },
1766        }
1767    }
1768
1769    /// Returns an empty object.
1770    pub fn empty() -> Self {
1771        Self::new(IndexMap::default())
1772    }
1773
1774    /// Creates an object from an iterator of V1 AST metadata items.
1775    pub fn from_v1_metadata<N: TreeNode>(
1776        items: impl Iterator<Item = v1::MetadataObjectItem<N>>,
1777    ) -> Self {
1778        Object::new(
1779            items
1780                .map(|i| {
1781                    (
1782                        i.name().text().to_string(),
1783                        Value::from_v1_metadata(&i.value()),
1784                    )
1785                })
1786                .collect::<IndexMap<_, _>>(),
1787        )
1788    }
1789
1790    /// Gets the type of the `Object` value.
1791    pub fn ty(&self) -> Type {
1792        Type::Object
1793    }
1794
1795    /// Iterates the members of the object.
1796    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&str, &Value)> {
1797        self.members
1798            .as_ref()
1799            .map(|m| Either::Left(m.iter().map(|(k, v)| (k.as_str(), v))))
1800            .unwrap_or(Either::Right(std::iter::empty()))
1801    }
1802
1803    /// Iterates the keys of the object.
1804    pub fn keys(&self) -> impl ExactSizeIterator<Item = &str> {
1805        self.members
1806            .as_ref()
1807            .map(|m| Either::Left(m.keys().map(|k| k.as_str())))
1808            .unwrap_or(Either::Right(std::iter::empty()))
1809    }
1810
1811    /// Iterates the values of the object.
1812    pub fn values(&self) -> impl ExactSizeIterator<Item = &Value> {
1813        self.members
1814            .as_ref()
1815            .map(|m| Either::Left(m.values()))
1816            .unwrap_or(Either::Right(std::iter::empty()))
1817    }
1818
1819    /// Determines if the object contains the given key.
1820    pub fn contains_key(&self, key: &str) -> bool {
1821        self.members
1822            .as_ref()
1823            .map(|m| m.contains_key(key))
1824            .unwrap_or(false)
1825    }
1826
1827    /// Gets a value from the object by key.
1828    pub fn get(&self, key: &str) -> Option<&Value> {
1829        self.members.as_ref().and_then(|m| m.get(key))
1830    }
1831
1832    /// Returns the number of members in the object.
1833    pub fn len(&self) -> usize {
1834        self.members.as_ref().map(|m| m.len()).unwrap_or(0)
1835    }
1836
1837    /// Returns `true` if the object has no members.
1838    pub fn is_empty(&self) -> bool {
1839        self.len() == 0
1840    }
1841}
1842
1843impl fmt::Display for Object {
1844    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1845        write!(f, "object {{")?;
1846
1847        for (i, (k, v)) in self.iter().enumerate() {
1848            if i > 0 {
1849                write!(f, ", ")?;
1850            }
1851
1852            write!(f, "{k}: {v}")?;
1853        }
1854
1855        write!(f, "}}")
1856    }
1857}
1858
1859/// Represents a `Struct` value.
1860///
1861/// Structs are cheap to clone.
1862#[derive(Debug, Clone)]
1863pub struct Struct {
1864    /// The type of the struct value.
1865    ty: Type,
1866    /// The name of the struct.
1867    name: Arc<String>,
1868    /// The members of the struct value.
1869    pub(crate) members: Arc<IndexMap<String, Value>>,
1870}
1871
1872impl Struct {
1873    /// Creates a new struct value.
1874    ///
1875    /// Returns an error if the struct type does not contain a member of a given
1876    /// name or if a value does not coerce to the corresponding member's type.
1877    ///
1878    /// # Panics
1879    ///
1880    /// Panics if the given type is not a struct type.
1881    pub fn new<S, V>(
1882        context: Option<&dyn EvaluationContext>,
1883        ty: impl Into<Type>,
1884        members: impl IntoIterator<Item = (S, V)>,
1885    ) -> Result<Self>
1886    where
1887        S: Into<String>,
1888        V: Into<Value>,
1889    {
1890        let ty = ty.into();
1891        if let Type::Compound(CompoundType::Struct(ty), optional) = ty {
1892            let mut members = members
1893                .into_iter()
1894                .map(|(n, v)| {
1895                    let n = n.into();
1896                    let v = v.into();
1897                    let v = v
1898                        .coerce(
1899                            context,
1900                            ty.members().get(&n).ok_or_else(|| {
1901                                anyhow!("struct does not contain a member named `{n}`")
1902                            })?,
1903                        )
1904                        .with_context(|| format!("failed to coerce struct member `{n}`"))?;
1905                    Ok((n, v))
1906                })
1907                .collect::<Result<IndexMap<_, _>>>()?;
1908
1909            for (name, ty) in ty.members().iter() {
1910                // Check for optional members that should be set to `None`
1911                if ty.is_optional() {
1912                    if !members.contains_key(name) {
1913                        members.insert(name.clone(), Value::new_none(ty.clone()));
1914                    }
1915                } else {
1916                    // Check for a missing required member
1917                    if !members.contains_key(name) {
1918                        bail!("missing a value for struct member `{name}`");
1919                    }
1920                }
1921            }
1922
1923            let name = ty.name().to_string();
1924            return Ok(Self {
1925                ty: Type::Compound(CompoundType::Struct(ty), optional),
1926                name: Arc::new(name),
1927                members: Arc::new(members),
1928            });
1929        }
1930
1931        panic!("type `{ty}` is not a struct type");
1932    }
1933
1934    /// Constructs a new struct without checking the given members conform to
1935    /// the given type.
1936    pub(crate) fn new_unchecked(
1937        ty: Type,
1938        name: Arc<String>,
1939        members: Arc<IndexMap<String, Value>>,
1940    ) -> Self {
1941        assert!(ty.as_struct().is_some());
1942        Self {
1943            ty: ty.require(),
1944            name,
1945            members,
1946        }
1947    }
1948
1949    /// Gets the type of the `Struct` value.
1950    pub fn ty(&self) -> Type {
1951        self.ty.clone()
1952    }
1953
1954    /// Gets the name of the struct.
1955    pub fn name(&self) -> &Arc<String> {
1956        &self.name
1957    }
1958
1959    /// Iterates the members of the struct.
1960    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&str, &Value)> {
1961        self.members.iter().map(|(k, v)| (k.as_str(), v))
1962    }
1963
1964    /// Iterates the keys of the struct.
1965    pub fn keys(&self) -> impl ExactSizeIterator<Item = &str> {
1966        self.members.keys().map(|k| k.as_str())
1967    }
1968
1969    /// Iterates the values of the struct.
1970    pub fn values(&self) -> impl ExactSizeIterator<Item = &Value> {
1971        self.members.values()
1972    }
1973
1974    /// Determines if the struct contains the given member name.
1975    pub fn contains_key(&self, key: &str) -> bool {
1976        self.members.contains_key(key)
1977    }
1978
1979    /// Gets a value from the struct by member name.
1980    pub fn get(&self, key: &str) -> Option<&Value> {
1981        self.members.get(key)
1982    }
1983}
1984
1985impl fmt::Display for Struct {
1986    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1987        write!(f, "{name} {{", name = self.name)?;
1988
1989        for (i, (k, v)) in self.members.iter().enumerate() {
1990            if i > 0 {
1991                write!(f, ", ")?;
1992            }
1993
1994            write!(f, "{k}: {v}")?;
1995        }
1996
1997        write!(f, "}}")
1998    }
1999}
2000
2001/// Represents a compound value.
2002///
2003/// Compound values are cheap to clone.
2004#[derive(Debug, Clone)]
2005pub enum CompoundValue {
2006    /// The value is a `Pair` of values.
2007    Pair(Pair),
2008    /// The value is an `Array` of values.
2009    Array(Array),
2010    /// The value is a `Map` of values.
2011    Map(Map),
2012    /// The value is an `Object`.
2013    Object(Object),
2014    /// The value is a struct.
2015    Struct(Struct),
2016}
2017
2018impl CompoundValue {
2019    /// Gets the type of the compound value.
2020    pub fn ty(&self) -> Type {
2021        match self {
2022            CompoundValue::Pair(v) => v.ty(),
2023            CompoundValue::Array(v) => v.ty(),
2024            CompoundValue::Map(v) => v.ty(),
2025            CompoundValue::Object(v) => v.ty(),
2026            CompoundValue::Struct(v) => v.ty(),
2027        }
2028    }
2029
2030    /// Gets the value as a `Pair`.
2031    ///
2032    /// Returns `None` if the value is not a `Pair`.
2033    pub fn as_pair(&self) -> Option<&Pair> {
2034        match self {
2035            Self::Pair(v) => Some(v),
2036            _ => None,
2037        }
2038    }
2039
2040    /// Unwraps the value into a `Pair`.
2041    ///
2042    /// # Panics
2043    ///
2044    /// Panics if the value is not a `Pair`.
2045    pub fn unwrap_pair(self) -> Pair {
2046        match self {
2047            Self::Pair(v) => v,
2048            _ => panic!("value is not a pair"),
2049        }
2050    }
2051
2052    /// Gets the value as an `Array`.
2053    ///
2054    /// Returns `None` if the value is not an `Array`.
2055    pub fn as_array(&self) -> Option<&Array> {
2056        match self {
2057            Self::Array(v) => Some(v),
2058            _ => None,
2059        }
2060    }
2061
2062    /// Unwraps the value into an `Array`.
2063    ///
2064    /// # Panics
2065    ///
2066    /// Panics if the value is not an `Array`.
2067    pub fn unwrap_array(self) -> Array {
2068        match self {
2069            Self::Array(v) => v,
2070            _ => panic!("value is not an array"),
2071        }
2072    }
2073
2074    /// Gets the value as a `Map`.
2075    ///
2076    /// Returns `None` if the value is not a `Map`.
2077    pub fn as_map(&self) -> Option<&Map> {
2078        match self {
2079            Self::Map(v) => Some(v),
2080            _ => None,
2081        }
2082    }
2083
2084    /// Unwraps the value into a `Map`.
2085    ///
2086    /// # Panics
2087    ///
2088    /// Panics if the value is not a `Map`.
2089    pub fn unwrap_map(self) -> Map {
2090        match self {
2091            Self::Map(v) => v,
2092            _ => panic!("value is not a map"),
2093        }
2094    }
2095
2096    /// Gets the value as an `Object`.
2097    ///
2098    /// Returns `None` if the value is not an `Object`.
2099    pub fn as_object(&self) -> Option<&Object> {
2100        match self {
2101            Self::Object(v) => Some(v),
2102            _ => None,
2103        }
2104    }
2105
2106    /// Unwraps the value into an `Object`.
2107    ///
2108    /// # Panics
2109    ///
2110    /// Panics if the value is not an `Object`.
2111    pub fn unwrap_object(self) -> Object {
2112        match self {
2113            Self::Object(v) => v,
2114            _ => panic!("value is not an object"),
2115        }
2116    }
2117
2118    /// Gets the value as a `Struct`.
2119    ///
2120    /// Returns `None` if the value is not a `Struct`.
2121    pub fn as_struct(&self) -> Option<&Struct> {
2122        match self {
2123            Self::Struct(v) => Some(v),
2124            _ => None,
2125        }
2126    }
2127
2128    /// Unwraps the value into a `Struct`.
2129    ///
2130    /// # Panics
2131    ///
2132    /// Panics if the value is not a `Map`.
2133    pub fn unwrap_struct(self) -> Struct {
2134        match self {
2135            Self::Struct(v) => v,
2136            _ => panic!("value is not a struct"),
2137        }
2138    }
2139
2140    /// Compares two compound values for equality based on the WDL
2141    /// specification.
2142    ///
2143    /// Returns `None` if the two compound values cannot be compared for
2144    /// equality.
2145    pub fn equals(left: &Self, right: &Self) -> Option<bool> {
2146        // The values must have type equivalence to compare for compound values
2147        // Coercion doesn't take place for this check
2148        if left.ty() != right.ty() {
2149            return None;
2150        }
2151
2152        match (left, right) {
2153            (Self::Pair(left), Self::Pair(right)) => Some(
2154                Value::equals(left.left(), right.left())?
2155                    && Value::equals(left.right(), right.right())?,
2156            ),
2157            (CompoundValue::Array(left), CompoundValue::Array(right)) => Some(
2158                left.len() == right.len()
2159                    && left
2160                        .as_slice()
2161                        .iter()
2162                        .zip(right.as_slice())
2163                        .all(|(l, r)| Value::equals(l, r).unwrap_or(false)),
2164            ),
2165            (CompoundValue::Map(left), CompoundValue::Map(right)) => Some(
2166                left.len() == right.len()
2167                    // Maps are ordered, so compare via iteration
2168                    && left.iter().zip(right.iter()).all(|((lk, lv), (rk, rv))| {
2169                        match (lk, rk) {
2170                            (None, None) => {},
2171                            (Some(lk), Some(rk)) if lk == rk => {},
2172                            _ => return false
2173                        }
2174
2175                        Value::equals(lv, rv).unwrap_or(false)
2176                    }),
2177            ),
2178            (CompoundValue::Object(left), CompoundValue::Object(right)) => Some(
2179                left.len() == right.len()
2180                    && left.iter().all(|(k, left)| match right.get(k) {
2181                        Some(right) => Value::equals(left, right).unwrap_or(false),
2182                        None => false,
2183                    }),
2184            ),
2185            (
2186                CompoundValue::Struct(Struct { members: left, .. }),
2187                CompoundValue::Struct(Struct { members: right, .. }),
2188            ) => Some(
2189                left.len() == right.len()
2190                    && left.iter().all(|(k, left)| match right.get(k) {
2191                        Some(right) => Value::equals(left, right).unwrap_or(false),
2192                        None => false,
2193                    }),
2194            ),
2195            _ => None,
2196        }
2197    }
2198
2199    /// Visits any paths referenced by this value.
2200    ///
2201    /// The callback is invoked for each `File` and `Directory` value referenced
2202    /// by this value.
2203    fn visit_paths<F>(&self, cb: &mut F) -> Result<()>
2204    where
2205        F: FnMut(bool, &HostPath) -> Result<()> + Send + Sync,
2206    {
2207        match self {
2208            Self::Pair(pair) => {
2209                pair.left().visit_paths(cb)?;
2210                pair.right().visit_paths(cb)?;
2211            }
2212            Self::Array(array) => {
2213                for v in array.as_slice() {
2214                    v.visit_paths(cb)?;
2215                }
2216            }
2217            Self::Map(map) => {
2218                for (k, v) in map.iter() {
2219                    match k {
2220                        Some(PrimitiveValue::File(path)) => cb(true, path)?,
2221                        Some(PrimitiveValue::Directory(path)) => cb(false, path)?,
2222                        _ => {}
2223                    }
2224
2225                    v.visit_paths(cb)?;
2226                }
2227            }
2228            Self::Object(object) => {
2229                for v in object.values() {
2230                    v.visit_paths(cb)?;
2231                }
2232            }
2233            Self::Struct(s) => {
2234                for v in s.values() {
2235                    v.visit_paths(cb)?;
2236                }
2237            }
2238        }
2239
2240        Ok(())
2241    }
2242
2243    /// Like [`Value::resolve_paths()`], but for recurring into
2244    /// [`CompoundValue`]s.
2245    fn resolve_paths<'a, F>(
2246        &'a self,
2247        base_dir: Option<&'a Path>,
2248        transferer: Option<&'a dyn Transferer>,
2249        translate: &'a F,
2250    ) -> BoxFuture<'a, Result<Self>>
2251    where
2252        F: Fn(&HostPath) -> Result<HostPath> + Send + Sync,
2253    {
2254        async move {
2255            match self {
2256                Self::Pair(pair) => {
2257                    let ty = pair.ty.as_pair().expect("should be a pair type");
2258                    let (left_optional, right_optional) =
2259                        (ty.left_type().is_optional(), ty.right_type().is_optional());
2260                    let (fst, snd) = pair.values.as_ref();
2261                    let fst = fst
2262                        .resolve_paths(left_optional, base_dir, transferer, translate)
2263                        .await?;
2264                    let snd = snd
2265                        .resolve_paths(right_optional, base_dir, transferer, translate)
2266                        .await?;
2267                    Ok(Self::Pair(Pair::new_unchecked(ty.clone().into(), fst, snd)))
2268                }
2269                Self::Array(array) => {
2270                    let ty = array.ty.as_array().expect("should be an array type");
2271                    let optional = ty.element_type().is_optional();
2272                    if let Some(elements) = &array.elements {
2273                        let resolved_elements = futures::stream::iter(elements.iter())
2274                            .then(|v| v.resolve_paths(optional, base_dir, transferer, translate))
2275                            .try_collect()
2276                            .await?;
2277                        Ok(Self::Array(Array::new_unchecked(
2278                            ty.clone().into(),
2279                            resolved_elements,
2280                        )))
2281                    } else {
2282                        Ok(self.clone())
2283                    }
2284                }
2285                Self::Map(map) => {
2286                    let ty = map.ty.as_map().expect("should be a map type").clone();
2287                    let (key_optional, value_optional) =
2288                        (ty.key_type().is_optional(), ty.value_type().is_optional());
2289                    if let Some(elements) = &map.elements {
2290                        let resolved_elements = futures::stream::iter(elements.iter())
2291                            .then(async |(k, v)| {
2292                                let resolved_key = if let Some(k) = k {
2293                                    Value::from(k.clone())
2294                                        .resolve_paths(
2295                                            key_optional,
2296                                            base_dir,
2297                                            transferer,
2298                                            translate,
2299                                        )
2300                                        .await?
2301                                        .as_primitive()
2302                                        .cloned()
2303                                } else {
2304                                    None
2305                                };
2306                                let resolved_value = v
2307                                    .resolve_paths(value_optional, base_dir, transferer, translate)
2308                                    .await?;
2309                                Ok::<_, anyhow::Error>((resolved_key, resolved_value))
2310                            })
2311                            .try_collect()
2312                            .await?;
2313                        Ok(Self::Map(Map::new_unchecked(ty.into(), resolved_elements)))
2314                    } else {
2315                        Ok(Self::Map(Map::new_unchecked(ty.into(), IndexMap::new())))
2316                    }
2317                }
2318                Self::Object(object) => {
2319                    if let Some(members) = &object.members {
2320                        let resolved_members = futures::stream::iter(members.iter())
2321                            .then(async |(n, v)| {
2322                                let resolved = v
2323                                    .resolve_paths(false, base_dir, transferer, translate)
2324                                    .await?;
2325                                Ok::<_, anyhow::Error>((n.to_string(), resolved))
2326                            })
2327                            .try_collect()
2328                            .await?;
2329                        Ok(Self::Object(Object::new(resolved_members)))
2330                    } else {
2331                        Ok(self.clone())
2332                    }
2333                }
2334                Self::Struct(s) => {
2335                    let ty = s.ty.as_struct().expect("should be a struct type");
2336                    let name = s.name();
2337                    let resolved_members = futures::stream::iter(s.iter())
2338                        .then(async |(n, v)| {
2339                            let resolved = v
2340                                .resolve_paths(
2341                                    ty.members()[n].is_optional(),
2342                                    base_dir,
2343                                    transferer,
2344                                    translate,
2345                                )
2346                                .await?;
2347                            Ok::<_, anyhow::Error>((n.to_string(), resolved))
2348                        })
2349                        .try_collect()
2350                        .await?;
2351                    Ok(Self::Struct(Struct::new_unchecked(
2352                        ty.clone().into(),
2353                        name.clone(),
2354                        Arc::new(resolved_members),
2355                    )))
2356                }
2357            }
2358        }
2359        .boxed()
2360    }
2361}
2362
2363impl fmt::Display for CompoundValue {
2364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2365        match self {
2366            Self::Pair(v) => v.fmt(f),
2367            Self::Array(v) => v.fmt(f),
2368            Self::Map(v) => v.fmt(f),
2369            Self::Object(v) => v.fmt(f),
2370            Self::Struct(v) => v.fmt(f),
2371        }
2372    }
2373}
2374
2375impl Coercible for CompoundValue {
2376    fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
2377        if target.is_union() || target.is_none() || self.ty().eq(target) {
2378            return Ok(self.clone());
2379        }
2380
2381        if let Type::Compound(target_ty, _) = target {
2382            match (self, target_ty) {
2383                // Array[X] -> Array[Y](+) where X -> Y
2384                (Self::Array(v), CompoundType::Array(target_ty)) => {
2385                    // Don't allow coercion when the source is empty but the target has the
2386                    // non-empty qualifier
2387                    if v.is_empty() && target_ty.is_non_empty() {
2388                        bail!("cannot coerce empty array value to non-empty array type `{target}`",);
2389                    }
2390
2391                    return Ok(Self::Array(Array::new(
2392                        context,
2393                        target.clone(),
2394                        v.as_slice().iter().cloned(),
2395                    )?));
2396                }
2397                // Map[W, Y] -> Map[X, Z] where W -> X and Y -> Z
2398                (Self::Map(v), CompoundType::Map(map_ty)) => {
2399                    return Ok(Self::Map(Map::new(
2400                        context,
2401                        target.clone(),
2402                        v.iter().map(|(k, v)| {
2403                            (
2404                                k.clone()
2405                                    .map(Into::into)
2406                                    .unwrap_or(Value::new_none(map_ty.key_type().optional())),
2407                                v.clone(),
2408                            )
2409                        }),
2410                    )?));
2411                }
2412                // Pair[W, Y] -> Pair[X, Z] where W -> X and Y -> Z
2413                (Self::Pair(v), CompoundType::Pair(_)) => {
2414                    return Ok(Self::Pair(Pair::new(
2415                        context,
2416                        target.clone(),
2417                        v.values.0.clone(),
2418                        v.values.1.clone(),
2419                    )?));
2420                }
2421                // Map[X, Y] -> Struct where: X -> String
2422                (Self::Map(v), CompoundType::Struct(target_ty)) => {
2423                    let len = v.len();
2424                    let expected_len = target_ty.members().len();
2425
2426                    if len != expected_len {
2427                        bail!(
2428                            "cannot coerce a map of {len} element{s1} to struct type `{target}` \
2429                             as the struct has {expected_len} member{s2}",
2430                            s1 = if len == 1 { "" } else { "s" },
2431                            s2 = if expected_len == 1 { "" } else { "s" }
2432                        );
2433                    }
2434
2435                    return Ok(Self::Struct(Struct {
2436                        ty: target.clone(),
2437                        name: target_ty.name().clone(),
2438                        members: Arc::new(
2439                            v.iter()
2440                                .map(|(k, v)| {
2441                                    let k = k
2442                                        .as_ref()
2443                                        .and_then(|k| {
2444                                            k.coerce(context, &PrimitiveType::String.into()).ok()
2445                                        })
2446                                        .with_context(|| {
2447                                            format!(
2448                                                "cannot coerce a map of type `{map_type}` to \
2449                                                 struct type `{target}` as the key type cannot \
2450                                                 coerce to `String`",
2451                                                map_type = v.ty()
2452                                            )
2453                                        })?
2454                                        .unwrap_string();
2455                                    let ty =
2456                                        target_ty.members().get(k.as_ref()).with_context(|| {
2457                                            format!(
2458                                                "cannot coerce a map with key `{k}` to struct \
2459                                                 type `{target}` as the struct does not contain a \
2460                                                 member with that name"
2461                                            )
2462                                        })?;
2463                                    let v = v.coerce(context, ty).with_context(|| {
2464                                        format!("failed to coerce value of map key `{k}")
2465                                    })?;
2466                                    Ok((k.to_string(), v))
2467                                })
2468                                .collect::<Result<_>>()?,
2469                        ),
2470                    }));
2471                }
2472                // Struct -> Map[X, Y] where: String -> X
2473                (Self::Struct(Struct { members, .. }), CompoundType::Map(map_ty)) => {
2474                    let key_ty = map_ty.key_type();
2475                    if !Type::from(PrimitiveType::String).is_coercible_to(key_ty) {
2476                        bail!(
2477                            "cannot coerce a struct to type `{target}` as key type `{key_ty}` \
2478                             cannot be coerced from `String`"
2479                        );
2480                    }
2481
2482                    let value_ty = map_ty.value_type();
2483                    return Ok(Self::Map(Map::new_unchecked(
2484                        target.clone(),
2485                        members
2486                            .iter()
2487                            .map(|(n, v)| {
2488                                let v = v
2489                                    .coerce(context, value_ty)
2490                                    .with_context(|| format!("failed to coerce member `{n}`"))?;
2491                                Ok((
2492                                    PrimitiveValue::new_string(n)
2493                                        .coerce(context, key_ty)
2494                                        .expect("should coerce")
2495                                        .into(),
2496                                    v,
2497                                ))
2498                            })
2499                            .collect::<Result<_>>()?,
2500                    )));
2501                }
2502                // Object -> Map[X, Y] where: String -> X
2503                (Self::Object(object), CompoundType::Map(map_ty)) => {
2504                    let key_ty = map_ty.key_type();
2505                    if !Type::from(PrimitiveType::String).is_coercible_to(key_ty) {
2506                        bail!(
2507                            "cannot coerce an object to type `{target}` as key type `{key_ty}` \
2508                             cannot be coerced from `String`"
2509                        );
2510                    }
2511
2512                    let value_ty = map_ty.value_type();
2513                    return Ok(Self::Map(Map::new_unchecked(
2514                        target.clone(),
2515                        object
2516                            .iter()
2517                            .map(|(n, v)| {
2518                                let v = v
2519                                    .coerce(context, value_ty)
2520                                    .with_context(|| format!("failed to coerce member `{n}`"))?;
2521                                Ok((
2522                                    PrimitiveValue::new_string(n)
2523                                        .coerce(context, key_ty)
2524                                        .expect("should coerce")
2525                                        .into(),
2526                                    v,
2527                                ))
2528                            })
2529                            .collect::<Result<_>>()?,
2530                    )));
2531                }
2532                // Object -> Struct
2533                (Self::Object(v), CompoundType::Struct(_)) => {
2534                    return Ok(Self::Struct(Struct::new(
2535                        context,
2536                        target.clone(),
2537                        v.iter().map(|(k, v)| (k, v.clone())),
2538                    )?));
2539                }
2540                // Struct -> Struct
2541                (Self::Struct(v), CompoundType::Struct(struct_ty)) => {
2542                    let len = v.members.len();
2543                    let expected_len = struct_ty.members().len();
2544
2545                    if len != expected_len {
2546                        bail!(
2547                            "cannot coerce a struct of {len} members{s1} to struct type \
2548                             `{target}` as the target struct has {expected_len} member{s2}",
2549                            s1 = if len == 1 { "" } else { "s" },
2550                            s2 = if expected_len == 1 { "" } else { "s" }
2551                        );
2552                    }
2553
2554                    return Ok(Self::Struct(Struct {
2555                        ty: target.clone(),
2556                        name: struct_ty.name().clone(),
2557                        members: Arc::new(
2558                            v.members
2559                                .iter()
2560                                .map(|(k, v)| {
2561                                    let ty = struct_ty.members().get(k).ok_or_else(|| {
2562                                        anyhow!(
2563                                            "cannot coerce a struct with member `{k}` to struct \
2564                                             type `{target}` as the target struct does not \
2565                                             contain a member with that name",
2566                                        )
2567                                    })?;
2568                                    let v = v.coerce(context, ty).with_context(|| {
2569                                        format!("failed to coerce member `{k}`")
2570                                    })?;
2571                                    Ok((k.clone(), v))
2572                                })
2573                                .collect::<Result<_>>()?,
2574                        ),
2575                    }));
2576                }
2577                _ => {}
2578            }
2579        }
2580
2581        if let Type::Object = target {
2582            match self {
2583                // Map[X, Y] -> Object where: X -> String
2584                Self::Map(v) => {
2585                    return Ok(Self::Object(Object::new(
2586                        v.iter()
2587                            .map(|(k, v)| {
2588                                let k = k
2589                                    .as_ref()
2590                                    .and_then(|k| {
2591                                        k.coerce(context, &PrimitiveType::String.into()).ok()
2592                                    })
2593                                    .with_context(|| {
2594                                        format!(
2595                                            "cannot coerce a map of type `{map_type}` to `Object` \
2596                                             as the key type cannot coerce to `String`",
2597                                            map_type = v.ty()
2598                                        )
2599                                    })?
2600                                    .unwrap_string();
2601                                Ok((k.to_string(), v.clone()))
2602                            })
2603                            .collect::<Result<IndexMap<_, _>>>()?,
2604                    )));
2605                }
2606                // Struct -> Object
2607                Self::Struct(v) => {
2608                    return Ok(Self::Object(Object {
2609                        members: Some(v.members.clone()),
2610                    }));
2611                }
2612                _ => {}
2613            };
2614        }
2615
2616        bail!(
2617            "cannot coerce a value of type `{ty}` to type `{target}`",
2618            ty = self.ty()
2619        );
2620    }
2621}
2622
2623impl From<Pair> for CompoundValue {
2624    fn from(value: Pair) -> Self {
2625        Self::Pair(value)
2626    }
2627}
2628
2629impl From<Array> for CompoundValue {
2630    fn from(value: Array) -> Self {
2631        Self::Array(value)
2632    }
2633}
2634
2635impl From<Map> for CompoundValue {
2636    fn from(value: Map) -> Self {
2637        Self::Map(value)
2638    }
2639}
2640
2641impl From<Object> for CompoundValue {
2642    fn from(value: Object) -> Self {
2643        Self::Object(value)
2644    }
2645}
2646
2647impl From<Struct> for CompoundValue {
2648    fn from(value: Struct) -> Self {
2649        Self::Struct(value)
2650    }
2651}
2652
2653/// Represents a hidden value.
2654///
2655/// Hidden values are cheap to clone.
2656#[derive(Debug, Clone)]
2657pub enum HiddenValue {
2658    /// The value is a hints value.
2659    ///
2660    /// Hints values only appear in a task hints section in WDL 1.2.
2661    Hints(HintsValue),
2662    /// The value is an input value.
2663    ///
2664    /// Input values only appear in a task hints section in WDL 1.2.
2665    Input(InputValue),
2666    /// The value is an output value.
2667    ///
2668    /// Output values only appear in a task hints section in WDL 1.2.
2669    Output(OutputValue),
2670    /// The value is a task variable before evaluation.
2671    ///
2672    /// This value occurs during requirements, hints, and runtime section
2673    /// evaluation in WDL 1.3+ tasks.
2674    TaskPreEvaluation(TaskPreEvaluationValue),
2675    /// The value is a task variable after evaluation.
2676    ///
2677    /// This value occurs during command and output section evaluation in
2678    /// WDL 1.2+ tasks.
2679    TaskPostEvaluation(TaskPostEvaluationValue),
2680    /// The value is a previous requirements value.
2681    ///
2682    /// This value contains the previous attempt's requirements and is available
2683    /// in WDL 1.3+ via `task.previous`.
2684    PreviousTaskData(PreviousTaskDataValue),
2685}
2686
2687impl HiddenValue {
2688    /// Gets the type of the value.
2689    pub fn ty(&self) -> Type {
2690        match self {
2691            Self::Hints(_) => Type::Hidden(HiddenType::Hints),
2692            Self::Input(_) => Type::Hidden(HiddenType::Input),
2693            Self::Output(_) => Type::Hidden(HiddenType::Output),
2694            Self::TaskPreEvaluation(_) => Type::Hidden(HiddenType::TaskPreEvaluation),
2695            Self::TaskPostEvaluation(_) => Type::Hidden(HiddenType::TaskPostEvaluation),
2696            Self::PreviousTaskData(_) => Type::Hidden(HiddenType::PreviousTaskData),
2697        }
2698    }
2699}
2700
2701impl fmt::Display for HiddenValue {
2702    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2703        match self {
2704            Self::Hints(v) => v.fmt(f),
2705            Self::Input(v) => v.fmt(f),
2706            Self::Output(v) => v.fmt(f),
2707            Self::TaskPreEvaluation(_) | Self::TaskPostEvaluation(_) => write!(f, "task"),
2708            Self::PreviousTaskData(_) => write!(f, "task.previous"),
2709        }
2710    }
2711}
2712
2713impl Coercible for HiddenValue {
2714    fn coerce(&self, _: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
2715        match self {
2716            Self::Hints(_) => {
2717                if matches!(target, Type::Hidden(HiddenType::Hints)) {
2718                    return Ok(self.clone());
2719                }
2720
2721                bail!("hints values cannot be coerced to any other type");
2722            }
2723            Self::Input(_) => {
2724                if matches!(target, Type::Hidden(HiddenType::Input)) {
2725                    return Ok(self.clone());
2726                }
2727
2728                bail!("input values cannot be coerced to any other type");
2729            }
2730            Self::Output(_) => {
2731                if matches!(target, Type::Hidden(HiddenType::Output)) {
2732                    return Ok(self.clone());
2733                }
2734
2735                bail!("output values cannot be coerced to any other type");
2736            }
2737            Self::TaskPreEvaluation(_) | Self::TaskPostEvaluation(_) => {
2738                if matches!(
2739                    target,
2740                    Type::Hidden(HiddenType::TaskPreEvaluation)
2741                        | Type::Hidden(HiddenType::TaskPostEvaluation)
2742                ) {
2743                    return Ok(self.clone());
2744                }
2745
2746                bail!("task variables cannot be coerced to any other type");
2747            }
2748            Self::PreviousTaskData(_) => {
2749                if matches!(target, Type::Hidden(HiddenType::PreviousTaskData)) {
2750                    return Ok(self.clone());
2751                }
2752
2753                bail!("previous task data values cannot be coerced to any other type");
2754            }
2755        }
2756    }
2757}
2758
2759/// Immutable data for task values after requirements evaluation (WDL 1.2+).
2760///
2761/// Contains all evaluated requirement fields.
2762#[derive(Debug, Clone)]
2763pub struct TaskPostEvaluationData {
2764    /// The container of the task.
2765    container: Option<Arc<String>>,
2766    /// The allocated number of cpus for the task.
2767    cpu: f64,
2768    /// The allocated memory (in bytes) for the task.
2769    memory: i64,
2770    /// The GPU allocations for the task.
2771    ///
2772    /// An array with one specification per allocated GPU; the specification is
2773    /// execution engine-specific.
2774    gpu: Array,
2775    /// The FPGA allocations for the task.
2776    ///
2777    /// An array with one specification per allocated FPGA; the specification is
2778    /// execution engine-specific.
2779    fpga: Array,
2780    /// The disk allocations for the task.
2781    ///
2782    /// A map with one entry for each disk mount point.
2783    ///
2784    /// The key is the mount point and the value is the initial amount of disk
2785    /// space allocated, in bytes.
2786    disks: Map,
2787    /// The maximum number of retries for the task.
2788    max_retries: i64,
2789}
2790
2791/// Represents a `task.previous` value containing data from a previous attempt.
2792///
2793/// The data is stored in an `Arc<TaskPostEvaluationData>` for cheap cloning.
2794#[derive(Debug, Clone)]
2795pub struct PreviousTaskDataValue(Option<Arc<TaskPostEvaluationData>>);
2796
2797impl PreviousTaskDataValue {
2798    /// Creates a new previous task data from task post-evaluation data.
2799    pub fn new(data: Arc<TaskPostEvaluationData>) -> Self {
2800        Self(Some(data))
2801    }
2802
2803    /// Creates an empty previous task data (for first attempt).
2804    pub fn empty() -> Self {
2805        Self(None)
2806    }
2807
2808    /// Gets the value of a field in the previous task data.
2809    ///
2810    /// Returns `None` if the field name is not valid for previous task data.
2811    /// Returns `Some(Value::None)` for valid fields when there is no previous
2812    /// data (first attempt).
2813    pub fn field(&self, name: &str) -> Option<Value> {
2814        match name {
2815            TASK_FIELD_MEMORY => Some(
2816                self.0
2817                    .as_ref()
2818                    .map(|data| Value::from(data.memory))
2819                    .unwrap_or_else(|| {
2820                        Value::new_none(Type::from(PrimitiveType::Integer).optional())
2821                    }),
2822            ),
2823            TASK_FIELD_CPU => Some(
2824                self.0
2825                    .as_ref()
2826                    .map(|data| Value::from(data.cpu))
2827                    .unwrap_or_else(|| {
2828                        Value::new_none(Type::from(PrimitiveType::Float).optional())
2829                    }),
2830            ),
2831            TASK_FIELD_CONTAINER => Some(
2832                self.0
2833                    .as_ref()
2834                    .and_then(|data| {
2835                        data.container
2836                            .as_ref()
2837                            .map(|c| PrimitiveValue::String(c.clone()).into())
2838                    })
2839                    .unwrap_or_else(|| {
2840                        Value::new_none(Type::from(PrimitiveType::String).optional())
2841                    }),
2842            ),
2843            TASK_FIELD_GPU => Some(
2844                self.0
2845                    .as_ref()
2846                    .map(|data| Value::from(data.gpu.clone()))
2847                    .unwrap_or_else(|| {
2848                        Value::new_none(Type::Compound(
2849                            CompoundType::Array(ArrayType::new(PrimitiveType::String)),
2850                            true,
2851                        ))
2852                    }),
2853            ),
2854            TASK_FIELD_FPGA => Some(
2855                self.0
2856                    .as_ref()
2857                    .map(|data| Value::from(data.fpga.clone()))
2858                    .unwrap_or_else(|| {
2859                        Value::new_none(Type::Compound(
2860                            CompoundType::Array(ArrayType::new(PrimitiveType::String)),
2861                            true,
2862                        ))
2863                    }),
2864            ),
2865            TASK_FIELD_DISKS => Some(
2866                self.0
2867                    .as_ref()
2868                    .map(|data| Value::from(data.disks.clone()))
2869                    .unwrap_or_else(|| {
2870                        Value::new_none(Type::Compound(
2871                            CompoundType::Map(Arc::new(MapType::new(
2872                                PrimitiveType::String,
2873                                PrimitiveType::Integer,
2874                            ))),
2875                            true,
2876                        ))
2877                    }),
2878            ),
2879            TASK_FIELD_MAX_RETRIES => Some(
2880                self.0
2881                    .as_ref()
2882                    .map(|data| Value::from(data.max_retries))
2883                    .unwrap_or_else(|| {
2884                        Value::new_none(Type::from(PrimitiveType::Integer).optional())
2885                    }),
2886            ),
2887            _ => None,
2888        }
2889    }
2890}
2891
2892/// Represents a `task` variable value before requirements evaluation (WDL
2893/// 1.3+).
2894///
2895/// Only exposes `name`, `id`, `attempt`, `previous`, and metadata fields.
2896///
2897/// Task values are cheap to clone.
2898#[derive(Debug, Clone)]
2899pub struct TaskPreEvaluationValue {
2900    /// The task name.
2901    name: Arc<String>,
2902    /// The task id.
2903    id: Arc<String>,
2904    /// The current task attempt count.
2905    ///
2906    /// The value must be 0 the first time the task is executed and incremented
2907    /// by 1 each time the task is retried (if any).
2908    attempt: i64,
2909    /// The task's `meta` section as an object.
2910    meta: Object,
2911    /// The tasks's `parameter_meta` section as an object.
2912    parameter_meta: Object,
2913    /// The task's extension metadata.
2914    ext: Object,
2915    /// The previous attempt's task data (WDL 1.3+).
2916    ///
2917    /// Contains the evaluated task data from the previous attempt.
2918    ///
2919    /// On the first attempt, this is empty.
2920    previous: PreviousTaskDataValue,
2921}
2922
2923impl TaskPreEvaluationValue {
2924    /// Constructs a new pre-evaluation task value with the given name and
2925    /// identifier.
2926    pub(crate) fn new(
2927        name: impl Into<String>,
2928        id: impl Into<String>,
2929        attempt: i64,
2930        meta: Object,
2931        parameter_meta: Object,
2932        ext: Object,
2933    ) -> Self {
2934        Self {
2935            name: Arc::new(name.into()),
2936            id: Arc::new(id.into()),
2937            meta,
2938            parameter_meta,
2939            ext,
2940            attempt,
2941            previous: PreviousTaskDataValue::empty(),
2942        }
2943    }
2944
2945    /// Sets the previous task data for retry attempts.
2946    pub(crate) fn set_previous(&mut self, data: Arc<TaskPostEvaluationData>) {
2947        self.previous = PreviousTaskDataValue::new(data);
2948    }
2949
2950    /// Gets the task name.
2951    pub fn name(&self) -> &Arc<String> {
2952        &self.name
2953    }
2954
2955    /// Gets the unique ID of the task.
2956    pub fn id(&self) -> &Arc<String> {
2957        &self.id
2958    }
2959
2960    /// Gets current task attempt count.
2961    pub fn attempt(&self) -> i64 {
2962        self.attempt
2963    }
2964
2965    /// Accesses a field of the task value by name.
2966    ///
2967    /// Returns `None` if the name is not a known field name.
2968    pub fn field(&self, name: &str) -> Option<Value> {
2969        match name {
2970            TASK_FIELD_NAME => Some(PrimitiveValue::String(self.name.clone()).into()),
2971            TASK_FIELD_ID => Some(PrimitiveValue::String(self.id.clone()).into()),
2972            TASK_FIELD_ATTEMPT => Some(self.attempt.into()),
2973            TASK_FIELD_META => Some(self.meta.clone().into()),
2974            TASK_FIELD_PARAMETER_META => Some(self.parameter_meta.clone().into()),
2975            TASK_FIELD_EXT => Some(self.ext.clone().into()),
2976            TASK_FIELD_PREVIOUS => {
2977                Some(HiddenValue::PreviousTaskData(self.previous.clone()).into())
2978            }
2979            _ => None,
2980        }
2981    }
2982}
2983
2984/// Represents a `task` variable value after requirements evaluation (WDL 1.2+).
2985///
2986/// Exposes all task fields including evaluated constraints.
2987///
2988/// Task values are cheap to clone.
2989#[derive(Debug, Clone)]
2990pub struct TaskPostEvaluationValue {
2991    /// The immutable data for task values including evaluated requirements.
2992    data: Arc<TaskPostEvaluationData>,
2993    /// The task name.
2994    name: Arc<String>,
2995    /// The task id.
2996    id: Arc<String>,
2997    /// The current task attempt count.
2998    ///
2999    /// The value must be 0 the first time the task is executed and incremented
3000    /// by 1 each time the task is retried (if any).
3001    attempt: i64,
3002    /// The task's `meta` section as an object.
3003    meta: Object,
3004    /// The tasks's `parameter_meta` section as an object.
3005    parameter_meta: Object,
3006    /// The task's extension metadata.
3007    ext: Object,
3008    /// The task's return code.
3009    ///
3010    /// Initially set to [`None`], but set after task execution completes.
3011    return_code: Option<i64>,
3012    /// The time by which the task must be completed, as a Unix time stamp.
3013    ///
3014    /// A value of `None` indicates there is no deadline.
3015    end_time: Option<i64>,
3016    /// The previous attempt's task data (WDL 1.3+).
3017    ///
3018    /// Contains the evaluated task data from the previous attempt.
3019    ///
3020    /// On the first attempt, this is empty.
3021    previous: PreviousTaskDataValue,
3022}
3023
3024impl TaskPostEvaluationValue {
3025    /// Constructs a new post-evaluation task value with the given name,
3026    /// identifier, and constraints.
3027    #[allow(clippy::too_many_arguments)]
3028    pub(crate) fn new(
3029        name: impl Into<String>,
3030        id: impl Into<String>,
3031        constraints: TaskExecutionConstraints,
3032        max_retries: i64,
3033        attempt: i64,
3034        meta: Object,
3035        parameter_meta: Object,
3036        ext: Object,
3037    ) -> Self {
3038        Self {
3039            name: Arc::new(name.into()),
3040            id: Arc::new(id.into()),
3041            data: Arc::new(TaskPostEvaluationData {
3042                container: constraints.container.map(Into::into),
3043                cpu: constraints.cpu,
3044                memory: constraints.memory,
3045                gpu: Array::new_unchecked(
3046                    ANALYSIS_STDLIB.array_string_type().clone(),
3047                    constraints
3048                        .gpu
3049                        .into_iter()
3050                        .map(|v| PrimitiveValue::new_string(v).into())
3051                        .collect(),
3052                ),
3053                fpga: Array::new_unchecked(
3054                    ANALYSIS_STDLIB.array_string_type().clone(),
3055                    constraints
3056                        .fpga
3057                        .into_iter()
3058                        .map(|v| PrimitiveValue::new_string(v).into())
3059                        .collect(),
3060                ),
3061                disks: Map::new_unchecked(
3062                    ANALYSIS_STDLIB.map_string_int_type().clone(),
3063                    constraints
3064                        .disks
3065                        .into_iter()
3066                        .map(|(k, v)| (Some(PrimitiveValue::new_string(k)), v.into()))
3067                        .collect(),
3068                ),
3069                max_retries,
3070            }),
3071            attempt,
3072            meta,
3073            parameter_meta,
3074            ext,
3075            return_code: None,
3076            end_time: None,
3077            previous: PreviousTaskDataValue::empty(),
3078        }
3079    }
3080
3081    /// Gets the task name.
3082    pub fn name(&self) -> &Arc<String> {
3083        &self.name
3084    }
3085
3086    /// Gets the unique ID of the task.
3087    pub fn id(&self) -> &Arc<String> {
3088        &self.id
3089    }
3090
3091    /// Gets the container in which the task is executing.
3092    pub fn container(&self) -> Option<&Arc<String>> {
3093        self.data.container.as_ref()
3094    }
3095
3096    /// Gets the allocated number of cpus for the task.
3097    pub fn cpu(&self) -> f64 {
3098        self.data.cpu
3099    }
3100
3101    /// Gets the allocated memory (in bytes) for the task.
3102    pub fn memory(&self) -> i64 {
3103        self.data.memory
3104    }
3105
3106    /// Gets the GPU allocations for the task.
3107    ///
3108    /// An array with one specification per allocated GPU; the specification is
3109    /// execution engine-specific.
3110    pub fn gpu(&self) -> &Array {
3111        &self.data.gpu
3112    }
3113
3114    /// Gets the FPGA allocations for the task.
3115    ///
3116    /// An array with one specification per allocated FPGA; the specification is
3117    /// execution engine-specific.
3118    pub fn fpga(&self) -> &Array {
3119        &self.data.fpga
3120    }
3121
3122    /// Gets the disk allocations for the task.
3123    ///
3124    /// A map with one entry for each disk mount point.
3125    ///
3126    /// The key is the mount point and the value is the initial amount of disk
3127    /// space allocated, in bytes.
3128    pub fn disks(&self) -> &Map {
3129        &self.data.disks
3130    }
3131
3132    /// Gets current task attempt count.
3133    ///
3134    /// The value must be 0 the first time the task is executed and incremented
3135    /// by 1 each time the task is retried (if any).
3136    pub fn attempt(&self) -> i64 {
3137        self.attempt
3138    }
3139
3140    /// Gets the time by which the task must be completed, as a Unix time stamp.
3141    ///
3142    /// A value of `None` indicates there is no deadline.
3143    pub fn end_time(&self) -> Option<i64> {
3144        self.end_time
3145    }
3146
3147    /// Gets the task's return code.
3148    ///
3149    /// Initially set to `None`, but set after task execution completes.
3150    pub fn return_code(&self) -> Option<i64> {
3151        self.return_code
3152    }
3153
3154    /// Gets the task's `meta` section as an object.
3155    pub fn meta(&self) -> &Object {
3156        &self.meta
3157    }
3158
3159    /// Gets the tasks's `parameter_meta` section as an object.
3160    pub fn parameter_meta(&self) -> &Object {
3161        &self.parameter_meta
3162    }
3163
3164    /// Gets the task's extension metadata.
3165    pub fn ext(&self) -> &Object {
3166        &self.ext
3167    }
3168
3169    /// Sets the return code after the task execution has completed.
3170    pub(crate) fn set_return_code(&mut self, code: i32) {
3171        self.return_code = Some(code as i64);
3172    }
3173
3174    /// Sets the attempt number for the task.
3175    pub(crate) fn set_attempt(&mut self, attempt: i64) {
3176        self.attempt = attempt;
3177    }
3178
3179    /// Sets the previous task data for retry attempts.
3180    pub(crate) fn set_previous(&mut self, data: Arc<TaskPostEvaluationData>) {
3181        self.previous = PreviousTaskDataValue::new(data);
3182    }
3183
3184    /// Gets the task post-evaluation data.
3185    pub(crate) fn data(&self) -> &Arc<TaskPostEvaluationData> {
3186        &self.data
3187    }
3188
3189    /// Accesses a field of the task value by name.
3190    ///
3191    /// Returns `None` if the name is not a known field name.
3192    pub fn field(&self, version: SupportedVersion, name: &str) -> Option<Value> {
3193        match name {
3194            TASK_FIELD_NAME => Some(PrimitiveValue::String(self.name.clone()).into()),
3195            TASK_FIELD_ID => Some(PrimitiveValue::String(self.id.clone()).into()),
3196            TASK_FIELD_ATTEMPT => Some(self.attempt.into()),
3197            TASK_FIELD_CONTAINER => Some(
3198                self.data
3199                    .container
3200                    .clone()
3201                    .map(|c| PrimitiveValue::String(c).into())
3202                    .unwrap_or_else(|| {
3203                        Value::new_none(
3204                            task_member_type_post_evaluation(version, TASK_FIELD_CONTAINER)
3205                                .expect("failed to get task field type"),
3206                        )
3207                    }),
3208            ),
3209            TASK_FIELD_CPU => Some(self.data.cpu.into()),
3210            TASK_FIELD_MEMORY => Some(self.data.memory.into()),
3211            TASK_FIELD_GPU => Some(self.data.gpu.clone().into()),
3212            TASK_FIELD_FPGA => Some(self.data.fpga.clone().into()),
3213            TASK_FIELD_DISKS => Some(self.data.disks.clone().into()),
3214            TASK_FIELD_END_TIME => Some(self.end_time.map(Into::into).unwrap_or_else(|| {
3215                Value::new_none(
3216                    task_member_type_post_evaluation(version, TASK_FIELD_END_TIME)
3217                        .expect("failed to get task field type"),
3218                )
3219            })),
3220            TASK_FIELD_RETURN_CODE => Some(self.return_code.map(Into::into).unwrap_or_else(|| {
3221                Value::new_none(
3222                    task_member_type_post_evaluation(version, TASK_FIELD_RETURN_CODE)
3223                        .expect("failed to get task field type"),
3224                )
3225            })),
3226            TASK_FIELD_META => Some(self.meta.clone().into()),
3227            TASK_FIELD_PARAMETER_META => Some(self.parameter_meta.clone().into()),
3228            TASK_FIELD_EXT => Some(self.ext.clone().into()),
3229            TASK_FIELD_MAX_RETRIES if version >= SupportedVersion::V1(V1::Three) => {
3230                Some(self.data.max_retries.into())
3231            }
3232            TASK_FIELD_PREVIOUS if version >= SupportedVersion::V1(V1::Three) => {
3233                Some(HiddenValue::PreviousTaskData(self.previous.clone()).into())
3234            }
3235            _ => None,
3236        }
3237    }
3238}
3239
3240/// Represents a hints value from a WDL 1.2 hints section.
3241///
3242/// Hints values are cheap to clone.
3243#[derive(Debug, Clone)]
3244pub struct HintsValue(Object);
3245
3246impl HintsValue {
3247    /// Converts the hints value to an object.
3248    pub fn as_object(&self) -> &Object {
3249        &self.0
3250    }
3251}
3252
3253impl fmt::Display for HintsValue {
3254    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3255        write!(f, "hints {{")?;
3256
3257        for (i, (k, v)) in self.0.iter().enumerate() {
3258            if i > 0 {
3259                write!(f, ", ")?;
3260            }
3261
3262            write!(f, "{k}: {v}")?;
3263        }
3264
3265        write!(f, "}}")
3266    }
3267}
3268
3269impl From<Object> for HintsValue {
3270    fn from(value: Object) -> Self {
3271        Self(value)
3272    }
3273}
3274
3275/// Represents an input value from a WDL 1.2 hints section.
3276///
3277/// Input values are cheap to clone.
3278#[derive(Debug, Clone)]
3279pub struct InputValue(Object);
3280
3281impl InputValue {
3282    /// Converts the input value to an object.
3283    pub fn as_object(&self) -> &Object {
3284        &self.0
3285    }
3286}
3287
3288impl fmt::Display for InputValue {
3289    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3290        write!(f, "input {{")?;
3291
3292        for (i, (k, v)) in self.0.iter().enumerate() {
3293            if i > 0 {
3294                write!(f, ", ")?;
3295            }
3296
3297            write!(f, "{k}: {v}")?;
3298        }
3299
3300        write!(f, "}}")
3301    }
3302}
3303
3304impl From<Object> for InputValue {
3305    fn from(value: Object) -> Self {
3306        Self(value)
3307    }
3308}
3309
3310/// Represents an output value from a WDL 1.2 hints section.
3311///
3312/// Output values are cheap to clone.
3313#[derive(Debug, Clone)]
3314pub struct OutputValue(Object);
3315
3316impl OutputValue {
3317    /// Converts the output value to an object.
3318    pub fn as_object(&self) -> &Object {
3319        &self.0
3320    }
3321}
3322
3323impl fmt::Display for OutputValue {
3324    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3325        write!(f, "output {{")?;
3326
3327        for (i, (k, v)) in self.0.iter().enumerate() {
3328            if i > 0 {
3329                write!(f, ", ")?;
3330            }
3331
3332            write!(f, "{k}: {v}")?;
3333        }
3334
3335        write!(f, "}}")
3336    }
3337}
3338
3339impl From<Object> for OutputValue {
3340    fn from(value: Object) -> Self {
3341        Self(value)
3342    }
3343}
3344
3345/// Represents the outputs of a call.
3346///
3347/// Call values are cheap to clone.
3348#[derive(Debug, Clone)]
3349pub struct CallValue {
3350    /// The type of the call.
3351    ty: CallType,
3352    /// The outputs of the call.
3353    outputs: Arc<Outputs>,
3354}
3355
3356impl CallValue {
3357    /// Constructs a new call value without checking the outputs conform to the
3358    /// call type.
3359    pub(crate) fn new_unchecked(ty: CallType, outputs: Arc<Outputs>) -> Self {
3360        Self { ty, outputs }
3361    }
3362
3363    /// Gets the type of the call.
3364    pub fn ty(&self) -> &CallType {
3365        &self.ty
3366    }
3367
3368    /// Gets the outputs of the call.
3369    pub fn outputs(&self) -> &Outputs {
3370        self.outputs.as_ref()
3371    }
3372}
3373
3374impl fmt::Display for CallValue {
3375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3376        write!(f, "call output {{")?;
3377
3378        for (i, (k, v)) in self.outputs.iter().enumerate() {
3379            if i > 0 {
3380                write!(f, ", ")?;
3381            }
3382
3383            write!(f, "{k}: {v}")?;
3384        }
3385
3386        write!(f, "}}")
3387    }
3388}
3389
3390/// Serializes a value with optional serialization of pairs.
3391pub struct ValueSerializer<'a> {
3392    /// The value to serialize.
3393    value: &'a Value,
3394    /// Whether pairs should be serialized as a map with `left` and `right`
3395    /// keys.
3396    allow_pairs: bool,
3397}
3398
3399impl<'a> ValueSerializer<'a> {
3400    /// Constructs a new `ValueSerializer`.
3401    pub fn new(value: &'a Value, allow_pairs: bool) -> Self {
3402        Self { value, allow_pairs }
3403    }
3404}
3405
3406impl serde::Serialize for ValueSerializer<'_> {
3407    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
3408    where
3409        S: serde::Serializer,
3410    {
3411        use serde::ser::Error;
3412
3413        match &self.value {
3414            Value::None(_) => serializer.serialize_none(),
3415            Value::Primitive(v) => v.serialize(serializer),
3416            Value::Compound(v) => {
3417                CompoundValueSerializer::new(v, self.allow_pairs).serialize(serializer)
3418            }
3419            Value::Hidden(_) | Value::Call(_) => {
3420                Err(S::Error::custom("value cannot be serialized"))
3421            }
3422        }
3423    }
3424}
3425
3426/// Serializes a `CompoundValue` with optional serialization of pairs.
3427pub struct CompoundValueSerializer<'a> {
3428    /// The compound value to serialize.
3429    value: &'a CompoundValue,
3430    /// Whether pairs should be serialized as a map with `left` and `right`
3431    /// keys.
3432    allow_pairs: bool,
3433}
3434
3435impl<'a> CompoundValueSerializer<'a> {
3436    /// Constructs a new `CompoundValueSerializer`.
3437    pub fn new(value: &'a CompoundValue, allow_pairs: bool) -> Self {
3438        Self { value, allow_pairs }
3439    }
3440}
3441
3442impl serde::Serialize for CompoundValueSerializer<'_> {
3443    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
3444    where
3445        S: serde::Serializer,
3446    {
3447        use serde::ser::Error;
3448
3449        match &self.value {
3450            CompoundValue::Pair(pair) if self.allow_pairs => {
3451                let mut state = serializer.serialize_map(Some(2))?;
3452                let left = ValueSerializer::new(pair.left(), self.allow_pairs);
3453                let right = ValueSerializer::new(pair.right(), self.allow_pairs);
3454                state.serialize_entry("left", &left)?;
3455                state.serialize_entry("right", &right)?;
3456                state.end()
3457            }
3458            CompoundValue::Pair(_) => Err(S::Error::custom("a pair cannot be serialized")),
3459            CompoundValue::Array(v) => {
3460                let mut s = serializer.serialize_seq(Some(v.len()))?;
3461                for v in v.as_slice() {
3462                    s.serialize_element(&ValueSerializer::new(v, self.allow_pairs))?;
3463                }
3464
3465                s.end()
3466            }
3467            CompoundValue::Map(v) => {
3468                let ty = v.ty();
3469                let map_type = ty.as_map().expect("type should be a map");
3470                if !map_type
3471                    .key_type()
3472                    .is_coercible_to(&PrimitiveType::String.into())
3473                {
3474                    return Err(S::Error::custom(format!(
3475                        "cannot serialize a map of type `{ty}` as the key type cannot be coerced \
3476                         to `String`",
3477                    )));
3478                }
3479
3480                let mut s = serializer.serialize_map(Some(v.len()))?;
3481                for (k, v) in v.iter() {
3482                    s.serialize_entry(k, &ValueSerializer::new(v, self.allow_pairs))?;
3483                }
3484
3485                s.end()
3486            }
3487            CompoundValue::Object(object) => {
3488                let mut s = serializer.serialize_map(Some(object.len()))?;
3489                for (k, v) in object.iter() {
3490                    s.serialize_entry(k, &ValueSerializer::new(v, self.allow_pairs))?;
3491                }
3492
3493                s.end()
3494            }
3495            CompoundValue::Struct(Struct { members, .. }) => {
3496                let mut s = serializer.serialize_map(Some(members.len()))?;
3497                for (k, v) in members.iter() {
3498                    s.serialize_entry(k, &ValueSerializer::new(v, self.allow_pairs))?;
3499                }
3500
3501                s.end()
3502            }
3503        }
3504    }
3505}
3506
3507#[cfg(test)]
3508mod test {
3509    use approx::assert_relative_eq;
3510    use pretty_assertions::assert_eq;
3511    use wdl_analysis::types::ArrayType;
3512    use wdl_analysis::types::MapType;
3513    use wdl_analysis::types::PairType;
3514    use wdl_analysis::types::StructType;
3515    use wdl_ast::Diagnostic;
3516    use wdl_ast::Span;
3517    use wdl_ast::SupportedVersion;
3518
3519    use super::*;
3520    use crate::http::Transferer;
3521    use crate::path::EvaluationPath;
3522
3523    #[test]
3524    fn boolean_coercion() {
3525        // Boolean -> Boolean
3526        assert_eq!(
3527            Value::from(false)
3528                .coerce(None, &PrimitiveType::Boolean.into())
3529                .expect("should coerce")
3530                .unwrap_boolean(),
3531            Value::from(false).unwrap_boolean()
3532        );
3533        // Boolean -> String (invalid)
3534        assert_eq!(
3535            format!(
3536                "{e:?}",
3537                e = Value::from(true)
3538                    .coerce(None, &PrimitiveType::String.into())
3539                    .unwrap_err()
3540            ),
3541            "cannot coerce type `Boolean` to type `String`"
3542        );
3543    }
3544
3545    #[test]
3546    fn boolean_display() {
3547        assert_eq!(Value::from(false).to_string(), "false");
3548        assert_eq!(Value::from(true).to_string(), "true");
3549    }
3550
3551    #[test]
3552    fn integer_coercion() {
3553        // Int -> Int
3554        assert_eq!(
3555            Value::from(12345)
3556                .coerce(None, &PrimitiveType::Integer.into())
3557                .expect("should coerce")
3558                .unwrap_integer(),
3559            Value::from(12345).unwrap_integer()
3560        );
3561        // Int -> Float
3562        assert_relative_eq!(
3563            Value::from(12345)
3564                .coerce(None, &PrimitiveType::Float.into())
3565                .expect("should coerce")
3566                .unwrap_float(),
3567            Value::from(12345.0).unwrap_float()
3568        );
3569        // Int -> Boolean (invalid)
3570        assert_eq!(
3571            format!(
3572                "{e:?}",
3573                e = Value::from(12345)
3574                    .coerce(None, &PrimitiveType::Boolean.into())
3575                    .unwrap_err()
3576            ),
3577            "cannot coerce type `Int` to type `Boolean`"
3578        );
3579    }
3580
3581    #[test]
3582    fn integer_display() {
3583        assert_eq!(Value::from(12345).to_string(), "12345");
3584        assert_eq!(Value::from(-12345).to_string(), "-12345");
3585    }
3586
3587    #[test]
3588    fn float_coercion() {
3589        // Float -> Float
3590        assert_relative_eq!(
3591            Value::from(12345.0)
3592                .coerce(None, &PrimitiveType::Float.into())
3593                .expect("should coerce")
3594                .unwrap_float(),
3595            Value::from(12345.0).unwrap_float()
3596        );
3597        // Float -> Int (invalid)
3598        assert_eq!(
3599            format!(
3600                "{e:?}",
3601                e = Value::from(12345.0)
3602                    .coerce(None, &PrimitiveType::Integer.into())
3603                    .unwrap_err()
3604            ),
3605            "cannot coerce type `Float` to type `Int`"
3606        );
3607    }
3608
3609    #[test]
3610    fn float_display() {
3611        assert_eq!(Value::from(12345.12345).to_string(), "12345.123450");
3612        assert_eq!(Value::from(-12345.12345).to_string(), "-12345.123450");
3613    }
3614
3615    #[test]
3616    fn string_coercion() {
3617        let value = PrimitiveValue::new_string("foo");
3618        // String -> String
3619        assert_eq!(
3620            value
3621                .coerce(None, &PrimitiveType::String.into())
3622                .expect("should coerce"),
3623            value
3624        );
3625        // String -> File
3626        assert_eq!(
3627            value
3628                .coerce(None, &PrimitiveType::File.into())
3629                .expect("should coerce"),
3630            PrimitiveValue::File(value.as_string().expect("should be string").clone().into())
3631        );
3632        // String -> Directory
3633        assert_eq!(
3634            value
3635                .coerce(None, &PrimitiveType::Directory.into())
3636                .expect("should coerce"),
3637            PrimitiveValue::Directory(value.as_string().expect("should be string").clone().into())
3638        );
3639        // String -> Boolean (invalid)
3640        assert_eq!(
3641            format!(
3642                "{e:?}",
3643                e = value
3644                    .coerce(None, &PrimitiveType::Boolean.into())
3645                    .unwrap_err()
3646            ),
3647            "cannot coerce type `String` to type `Boolean`"
3648        );
3649
3650        struct Context;
3651
3652        impl EvaluationContext for Context {
3653            fn version(&self) -> SupportedVersion {
3654                unimplemented!()
3655            }
3656
3657            fn resolve_name(&self, _: &str, _: Span) -> Result<Value, Diagnostic> {
3658                unimplemented!()
3659            }
3660
3661            fn resolve_type_name(&self, _: &str, _: Span) -> Result<Type, Diagnostic> {
3662                unimplemented!()
3663            }
3664
3665            fn base_dir(&self) -> &EvaluationPath {
3666                unimplemented!()
3667            }
3668
3669            fn temp_dir(&self) -> &Path {
3670                unimplemented!()
3671            }
3672
3673            fn transferer(&self) -> &dyn Transferer {
3674                unimplemented!()
3675            }
3676
3677            fn host_path(&self, path: &GuestPath) -> Option<HostPath> {
3678                if path.as_str() == "/mnt/task/input/0/path" {
3679                    Some(HostPath::new("/some/host/path"))
3680                } else {
3681                    None
3682                }
3683            }
3684        }
3685
3686        // String (guest path) -> File
3687        assert_eq!(
3688            PrimitiveValue::new_string("/mnt/task/input/0/path")
3689                .coerce(Some(&Context), &PrimitiveType::File.into())
3690                .expect("should coerce")
3691                .unwrap_file()
3692                .as_str(),
3693            "/some/host/path"
3694        );
3695
3696        // String (not a guest path) -> File
3697        assert_eq!(
3698            value
3699                .coerce(Some(&Context), &PrimitiveType::File.into())
3700                .expect("should coerce")
3701                .unwrap_file()
3702                .as_str(),
3703            "foo"
3704        );
3705
3706        // String (guest path) -> Directory
3707        assert_eq!(
3708            PrimitiveValue::new_string("/mnt/task/input/0/path")
3709                .coerce(Some(&Context), &PrimitiveType::Directory.into())
3710                .expect("should coerce")
3711                .unwrap_directory()
3712                .as_str(),
3713            "/some/host/path"
3714        );
3715
3716        // String (not a guest path) -> Directory
3717        assert_eq!(
3718            value
3719                .coerce(Some(&Context), &PrimitiveType::Directory.into())
3720                .expect("should coerce")
3721                .unwrap_directory()
3722                .as_str(),
3723            "foo"
3724        );
3725    }
3726
3727    #[test]
3728    fn string_display() {
3729        let value = PrimitiveValue::new_string("hello world!");
3730        assert_eq!(value.to_string(), "\"hello world!\"");
3731    }
3732
3733    #[test]
3734    fn file_coercion() {
3735        let value = PrimitiveValue::new_file("foo");
3736
3737        // File -> File
3738        assert_eq!(
3739            value
3740                .coerce(None, &PrimitiveType::File.into())
3741                .expect("should coerce"),
3742            value
3743        );
3744        // File -> String
3745        assert_eq!(
3746            value
3747                .coerce(None, &PrimitiveType::String.into())
3748                .expect("should coerce"),
3749            PrimitiveValue::String(value.as_file().expect("should be file").0.clone())
3750        );
3751        // File -> Directory (invalid)
3752        assert_eq!(
3753            format!(
3754                "{e:?}",
3755                e = value
3756                    .coerce(None, &PrimitiveType::Directory.into())
3757                    .unwrap_err()
3758            ),
3759            "cannot coerce type `File` to type `Directory`"
3760        );
3761
3762        struct Context;
3763
3764        impl EvaluationContext for Context {
3765            fn version(&self) -> SupportedVersion {
3766                unimplemented!()
3767            }
3768
3769            fn resolve_name(&self, _: &str, _: Span) -> Result<Value, Diagnostic> {
3770                unimplemented!()
3771            }
3772
3773            fn resolve_type_name(&self, _: &str, _: Span) -> Result<Type, Diagnostic> {
3774                unimplemented!()
3775            }
3776
3777            fn base_dir(&self) -> &EvaluationPath {
3778                unimplemented!()
3779            }
3780
3781            fn temp_dir(&self) -> &Path {
3782                unimplemented!()
3783            }
3784
3785            fn transferer(&self) -> &dyn Transferer {
3786                unimplemented!()
3787            }
3788
3789            fn guest_path(&self, path: &HostPath) -> Option<GuestPath> {
3790                if path.as_str() == "/some/host/path" {
3791                    Some(GuestPath::new("/mnt/task/input/0/path"))
3792                } else {
3793                    None
3794                }
3795            }
3796        }
3797
3798        // File (mapped) -> String
3799        assert_eq!(
3800            PrimitiveValue::new_file("/some/host/path")
3801                .coerce(Some(&Context), &PrimitiveType::String.into())
3802                .expect("should coerce")
3803                .unwrap_string()
3804                .as_str(),
3805            "/mnt/task/input/0/path"
3806        );
3807
3808        // File (not mapped) -> String
3809        assert_eq!(
3810            value
3811                .coerce(Some(&Context), &PrimitiveType::String.into())
3812                .expect("should coerce")
3813                .unwrap_string()
3814                .as_str(),
3815            "foo"
3816        );
3817    }
3818
3819    #[test]
3820    fn file_display() {
3821        let value = PrimitiveValue::new_file("/foo/bar/baz.txt");
3822        assert_eq!(value.to_string(), "\"/foo/bar/baz.txt\"");
3823    }
3824
3825    #[test]
3826    fn directory_coercion() {
3827        let value = PrimitiveValue::new_directory("foo");
3828
3829        // Directory -> Directory
3830        assert_eq!(
3831            value
3832                .coerce(None, &PrimitiveType::Directory.into())
3833                .expect("should coerce"),
3834            value
3835        );
3836        // Directory -> String
3837        assert_eq!(
3838            value
3839                .coerce(None, &PrimitiveType::String.into())
3840                .expect("should coerce"),
3841            PrimitiveValue::String(value.as_directory().expect("should be directory").0.clone())
3842        );
3843        // Directory -> File (invalid)
3844        assert_eq!(
3845            format!(
3846                "{e:?}",
3847                e = value.coerce(None, &PrimitiveType::File.into()).unwrap_err()
3848            ),
3849            "cannot coerce type `Directory` to type `File`"
3850        );
3851
3852        struct Context;
3853
3854        impl EvaluationContext for Context {
3855            fn version(&self) -> SupportedVersion {
3856                unimplemented!()
3857            }
3858
3859            fn resolve_name(&self, _: &str, _: Span) -> Result<Value, Diagnostic> {
3860                unimplemented!()
3861            }
3862
3863            fn resolve_type_name(&self, _: &str, _: Span) -> Result<Type, Diagnostic> {
3864                unimplemented!()
3865            }
3866
3867            fn base_dir(&self) -> &EvaluationPath {
3868                unimplemented!()
3869            }
3870
3871            fn temp_dir(&self) -> &Path {
3872                unimplemented!()
3873            }
3874
3875            fn transferer(&self) -> &dyn Transferer {
3876                unimplemented!()
3877            }
3878
3879            fn guest_path(&self, path: &HostPath) -> Option<GuestPath> {
3880                if path.as_str() == "/some/host/path" {
3881                    Some(GuestPath::new("/mnt/task/input/0/path"))
3882                } else {
3883                    None
3884                }
3885            }
3886        }
3887
3888        // Directory (mapped) -> String
3889        assert_eq!(
3890            PrimitiveValue::new_directory("/some/host/path")
3891                .coerce(Some(&Context), &PrimitiveType::String.into())
3892                .expect("should coerce")
3893                .unwrap_string()
3894                .as_str(),
3895            "/mnt/task/input/0/path"
3896        );
3897
3898        // Directory (not mapped) -> String
3899        assert_eq!(
3900            value
3901                .coerce(Some(&Context), &PrimitiveType::String.into())
3902                .expect("should coerce")
3903                .unwrap_string()
3904                .as_str(),
3905            "foo"
3906        );
3907    }
3908
3909    #[test]
3910    fn directory_display() {
3911        let value = PrimitiveValue::new_directory("/foo/bar/baz");
3912        assert_eq!(value.to_string(), "\"/foo/bar/baz\"");
3913    }
3914
3915    #[test]
3916    fn none_coercion() {
3917        // None -> String?
3918        assert!(
3919            Value::new_none(Type::None)
3920                .coerce(None, &Type::from(PrimitiveType::String).optional())
3921                .expect("should coerce")
3922                .is_none(),
3923        );
3924
3925        // None -> String (invalid)
3926        assert_eq!(
3927            format!(
3928                "{e:?}",
3929                e = Value::new_none(Type::None)
3930                    .coerce(None, &PrimitiveType::String.into())
3931                    .unwrap_err()
3932            ),
3933            "cannot coerce `None` to non-optional type `String`"
3934        );
3935    }
3936
3937    #[test]
3938    fn none_display() {
3939        assert_eq!(Value::new_none(Type::None).to_string(), "None");
3940    }
3941
3942    #[test]
3943    fn array_coercion() {
3944        let src_ty: Type = ArrayType::new(PrimitiveType::Integer).into();
3945        let target_ty: Type = ArrayType::new(PrimitiveType::Float).into();
3946
3947        // Array[Int] -> Array[Float]
3948        let src: CompoundValue = Array::new(None, src_ty, [1, 2, 3])
3949            .expect("should create array value")
3950            .into();
3951        let target = src.coerce(None, &target_ty).expect("should coerce");
3952        assert_eq!(
3953            target.unwrap_array().to_string(),
3954            "[1.000000, 2.000000, 3.000000]"
3955        );
3956
3957        // Array[Int] -> Array[String] (invalid)
3958        let target_ty: Type = ArrayType::new(PrimitiveType::String).into();
3959        assert_eq!(
3960            format!("{e:?}", e = src.coerce(None, &target_ty).unwrap_err()),
3961            r#"failed to coerce array element at index 0
3962
3963Caused by:
3964    cannot coerce type `Int` to type `String`"#
3965        );
3966    }
3967
3968    #[test]
3969    fn non_empty_array_coercion() {
3970        let ty: Type = ArrayType::new(PrimitiveType::String).into();
3971        let target_ty: Type = ArrayType::non_empty(PrimitiveType::String).into();
3972
3973        // Array[String] (non-empty) -> Array[String]+
3974        let string = PrimitiveValue::new_string("foo");
3975        let value: Value = Array::new(None, ty.clone(), [string])
3976            .expect("should create array")
3977            .into();
3978        assert!(value.coerce(None, &target_ty).is_ok(), "should coerce");
3979
3980        // Array[String] (empty) -> Array[String]+ (invalid)
3981        let value: Value = Array::new::<Value>(None, ty, [])
3982            .expect("should create array")
3983            .into();
3984        assert_eq!(
3985            format!("{e:?}", e = value.coerce(None, &target_ty).unwrap_err()),
3986            "cannot coerce empty array value to non-empty array type `Array[String]+`"
3987        );
3988    }
3989
3990    #[test]
3991    fn array_display() {
3992        let ty: Type = ArrayType::new(PrimitiveType::Integer).into();
3993        let value: Value = Array::new(None, ty, [1, 2, 3])
3994            .expect("should create array")
3995            .into();
3996
3997        assert_eq!(value.to_string(), "[1, 2, 3]");
3998    }
3999
4000    #[test]
4001    fn map_coerce() {
4002        let key1 = PrimitiveValue::new_file("foo");
4003        let value1 = PrimitiveValue::new_string("bar");
4004        let key2 = PrimitiveValue::new_file("baz");
4005        let value2 = PrimitiveValue::new_string("qux");
4006
4007        let ty = MapType::new(PrimitiveType::File, PrimitiveType::String);
4008        let file_to_string: Value = Map::new(None, ty, [(key1, value1), (key2, value2)])
4009            .expect("should create map value")
4010            .into();
4011
4012        // Map[File, String] -> Map[String, File]
4013        let ty = MapType::new(PrimitiveType::String, PrimitiveType::File).into();
4014        let string_to_file = file_to_string
4015            .coerce(None, &ty)
4016            .expect("value should coerce");
4017        assert_eq!(
4018            string_to_file.to_string(),
4019            r#"{"foo": "bar", "baz": "qux"}"#
4020        );
4021
4022        // Map[String, File] -> Map[Int, File] (invalid)
4023        let ty = MapType::new(PrimitiveType::Integer, PrimitiveType::File).into();
4024        assert_eq!(
4025            format!("{e:?}", e = string_to_file.coerce(None, &ty).unwrap_err()),
4026            r#"failed to coerce map key for element at index 0
4027
4028Caused by:
4029    cannot coerce type `String` to type `Int`"#
4030        );
4031
4032        // Map[String, File] -> Map[String, Int] (invalid)
4033        let ty = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
4034        assert_eq!(
4035            format!("{e:?}", e = string_to_file.coerce(None, &ty).unwrap_err()),
4036            r#"failed to coerce map value for element at index 0
4037
4038Caused by:
4039    cannot coerce type `File` to type `Int`"#
4040        );
4041
4042        // Map[String, File] -> Struct
4043        let ty = StructType::new(
4044            "Foo",
4045            [("foo", PrimitiveType::File), ("baz", PrimitiveType::File)],
4046        )
4047        .into();
4048        let struct_value = string_to_file
4049            .coerce(None, &ty)
4050            .expect("value should coerce");
4051        assert_eq!(struct_value.to_string(), r#"Foo {foo: "bar", baz: "qux"}"#);
4052
4053        // Map[File, String] -> Struct
4054        let ty = StructType::new(
4055            "Foo",
4056            [
4057                ("foo", PrimitiveType::String),
4058                ("baz", PrimitiveType::String),
4059            ],
4060        )
4061        .into();
4062        let struct_value = file_to_string
4063            .coerce(None, &ty)
4064            .expect("value should coerce");
4065        assert_eq!(struct_value.to_string(), r#"Foo {foo: "bar", baz: "qux"}"#);
4066
4067        // Map[String, File] -> Struct (invalid)
4068        let ty = StructType::new(
4069            "Foo",
4070            [
4071                ("foo", PrimitiveType::File),
4072                ("baz", PrimitiveType::File),
4073                ("qux", PrimitiveType::File),
4074            ],
4075        )
4076        .into();
4077        assert_eq!(
4078            format!("{e:?}", e = string_to_file.coerce(None, &ty).unwrap_err()),
4079            "cannot coerce a map of 2 elements to struct type `Foo` as the struct has 3 members"
4080        );
4081
4082        // Map[String, File] -> Object
4083        let object_value = string_to_file
4084            .coerce(None, &Type::Object)
4085            .expect("value should coerce");
4086        assert_eq!(
4087            object_value.to_string(),
4088            r#"object {foo: "bar", baz: "qux"}"#
4089        );
4090
4091        // Map[File, String] -> Object
4092        let object_value = file_to_string
4093            .coerce(None, &Type::Object)
4094            .expect("value should coerce");
4095        assert_eq!(
4096            object_value.to_string(),
4097            r#"object {foo: "bar", baz: "qux"}"#
4098        );
4099    }
4100
4101    #[test]
4102    fn map_display() {
4103        let ty = MapType::new(PrimitiveType::Integer, PrimitiveType::Boolean);
4104        let value: Value = Map::new(None, ty, [(1, true), (2, false)])
4105            .expect("should create map value")
4106            .into();
4107        assert_eq!(value.to_string(), "{1: true, 2: false}");
4108    }
4109
4110    #[test]
4111    fn pair_coercion() {
4112        let left = PrimitiveValue::new_file("foo");
4113        let right = PrimitiveValue::new_string("bar");
4114
4115        let ty = PairType::new(PrimitiveType::File, PrimitiveType::String);
4116        let value: Value = Pair::new(None, ty, left, right)
4117            .expect("should create pair value")
4118            .into();
4119
4120        // Pair[File, String] -> Pair[String, File]
4121        let ty = PairType::new(PrimitiveType::String, PrimitiveType::File).into();
4122        let value = value.coerce(None, &ty).expect("value should coerce");
4123        assert_eq!(value.to_string(), r#"("foo", "bar")"#);
4124
4125        // Pair[String, File] -> Pair[Int, Int]
4126        let ty = PairType::new(PrimitiveType::Integer, PrimitiveType::Integer).into();
4127        assert_eq!(
4128            format!("{e:?}", e = value.coerce(None, &ty).unwrap_err()),
4129            r#"failed to coerce pair's left value
4130
4131Caused by:
4132    cannot coerce type `String` to type `Int`"#
4133        );
4134    }
4135
4136    #[test]
4137    fn pair_display() {
4138        let ty = PairType::new(PrimitiveType::Integer, PrimitiveType::Boolean);
4139        let value: Value = Pair::new(None, ty, 12345, false)
4140            .expect("should create pair value")
4141            .into();
4142        assert_eq!(value.to_string(), "(12345, false)");
4143    }
4144
4145    #[test]
4146    fn struct_coercion() {
4147        let ty = StructType::new(
4148            "Foo",
4149            [
4150                ("foo", PrimitiveType::Float),
4151                ("bar", PrimitiveType::Float),
4152                ("baz", PrimitiveType::Float),
4153            ],
4154        );
4155        let value: Value = Struct::new(None, ty, [("foo", 1.0), ("bar", 2.0), ("baz", 3.0)])
4156            .expect("should create map value")
4157            .into();
4158
4159        // Struct -> Map[String, Float]
4160        let ty = MapType::new(PrimitiveType::String, PrimitiveType::Float).into();
4161        let map_value = value.coerce(None, &ty).expect("value should coerce");
4162        assert_eq!(
4163            map_value.to_string(),
4164            r#"{"foo": 1.000000, "bar": 2.000000, "baz": 3.000000}"#
4165        );
4166
4167        // Struct -> Map[File, Float]
4168        let ty = MapType::new(PrimitiveType::File, PrimitiveType::Float).into();
4169        let map_value = value.coerce(None, &ty).expect("value should coerce");
4170        assert_eq!(
4171            map_value.to_string(),
4172            r#"{"foo": 1.000000, "bar": 2.000000, "baz": 3.000000}"#
4173        );
4174
4175        // Struct -> Struct
4176        let ty = StructType::new(
4177            "Bar",
4178            [
4179                ("foo", PrimitiveType::Float),
4180                ("bar", PrimitiveType::Float),
4181                ("baz", PrimitiveType::Float),
4182            ],
4183        )
4184        .into();
4185        let struct_value = value.coerce(None, &ty).expect("value should coerce");
4186        assert_eq!(
4187            struct_value.to_string(),
4188            r#"Bar {foo: 1.000000, bar: 2.000000, baz: 3.000000}"#
4189        );
4190
4191        // Struct -> Object
4192        let object_value = value
4193            .coerce(None, &Type::Object)
4194            .expect("value should coerce");
4195        assert_eq!(
4196            object_value.to_string(),
4197            r#"object {foo: 1.000000, bar: 2.000000, baz: 3.000000}"#
4198        );
4199    }
4200
4201    #[test]
4202    fn struct_display() {
4203        let ty = StructType::new(
4204            "Foo",
4205            [
4206                ("foo", PrimitiveType::Float),
4207                ("bar", PrimitiveType::String),
4208                ("baz", PrimitiveType::Integer),
4209            ],
4210        );
4211        let value: Value = Struct::new(
4212            None,
4213            ty,
4214            [
4215                ("foo", Value::from(1.101)),
4216                ("bar", PrimitiveValue::new_string("foo").into()),
4217                ("baz", 1234.into()),
4218            ],
4219        )
4220        .expect("should create map value")
4221        .into();
4222        assert_eq!(
4223            value.to_string(),
4224            r#"Foo {foo: 1.101000, bar: "foo", baz: 1234}"#
4225        );
4226    }
4227
4228    #[test]
4229    fn pair_serialization() {
4230        let pair_ty = PairType::new(PrimitiveType::File, PrimitiveType::String);
4231        let pair: Value = Pair::new(
4232            None,
4233            pair_ty,
4234            PrimitiveValue::new_file("foo"),
4235            PrimitiveValue::new_string("bar"),
4236        )
4237        .expect("should create pair value")
4238        .into();
4239        // Serialize pair with `left` and `right` keys
4240        let value_serializer = ValueSerializer::new(&pair, true);
4241        let serialized = serde_json::to_string(&value_serializer).expect("should serialize");
4242        assert_eq!(serialized, r#"{"left":"foo","right":"bar"}"#);
4243
4244        // Serialize pair without `left` and `right` keys (should fail)
4245        let value_serializer = ValueSerializer::new(&pair, false);
4246        assert!(serde_json::to_string(&value_serializer).is_err());
4247
4248        let array_ty = ArrayType::new(PairType::new(PrimitiveType::File, PrimitiveType::String));
4249        let array: Value = Array::new(None, array_ty, [pair])
4250            .expect("should create array value")
4251            .into();
4252
4253        // Serialize array of pairs with `left` and `right` keys
4254        let value_serializer = ValueSerializer::new(&array, true);
4255        let serialized = serde_json::to_string(&value_serializer).expect("should serialize");
4256        assert_eq!(serialized, r#"[{"left":"foo","right":"bar"}]"#);
4257    }
4258}