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