Skip to main content

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