wdl_analysis/stdlib/
constraints.rs

1//! Represents type constraints to standard library functions.
2
3use std::fmt;
4
5use crate::types::Coercible;
6use crate::types::CompoundType;
7use crate::types::PrimitiveType;
8use crate::types::Type;
9
10/// A trait implemented by type constraints.
11pub trait Constraint: fmt::Debug + Send + Sync {
12    /// Gets a description of the constraint.
13    fn description(&self) -> &'static str;
14
15    /// Determines if the given type satisfies the constraint.
16    ///
17    /// Returns `true` if the constraint is satisfied or false if not.
18    fn satisfied(&self, ty: &Type) -> bool;
19}
20
21/// Represents a constraint that ensure the type can be used in a file size
22/// calculation.
23///
24/// The constraint checks that the type is a compound type that recursively
25/// contains a `File` or `Directory` type.
26#[derive(Debug, Copy, Clone)]
27pub struct SizeableConstraint;
28
29impl Constraint for SizeableConstraint {
30    fn description(&self) -> &'static str {
31        "any compound type that recursively contains a `File` or `Directory`"
32    }
33
34    fn satisfied(&self, ty: &Type) -> bool {
35        /// Determines if the given primitive type is sizable.
36        fn primitive_type_is_sizable(ty: PrimitiveType) -> bool {
37            matches!(ty, PrimitiveType::File | PrimitiveType::Directory)
38        }
39
40        /// Determines if the given compound type is sizable.
41        fn compound_type_is_sizable(ty: &CompoundType) -> bool {
42            match ty {
43                CompoundType::Array(ty) => type_is_sizable(ty.element_type()),
44                CompoundType::Pair(ty) => {
45                    type_is_sizable(ty.left_type()) | type_is_sizable(ty.right_type())
46                }
47                CompoundType::Map(ty) => {
48                    type_is_sizable(ty.key_type()) | type_is_sizable(ty.value_type())
49                }
50                CompoundType::Struct(s) => s.members().values().any(type_is_sizable),
51            }
52        }
53
54        /// Determines if the given type is sizable.
55        fn type_is_sizable(ty: &Type) -> bool {
56            match ty {
57                Type::Primitive(ty, _) => primitive_type_is_sizable(*ty),
58                Type::Compound(ty, _) => compound_type_is_sizable(ty),
59                Type::Object | Type::OptionalObject => {
60                    // Note: checking the types of an object's members is a runtime constraint
61                    true
62                }
63                // Treat unions as sizable as they can only be checked at runtime
64                Type::Union | Type::None => true,
65                Type::Task | Type::Hints | Type::Input | Type::Output | Type::Call(_) => false,
66            }
67        }
68
69        type_is_sizable(ty)
70    }
71}
72
73/// Represents a constraint that ensures the type is any structure.
74#[derive(Debug, Copy, Clone)]
75pub struct StructConstraint;
76
77impl Constraint for StructConstraint {
78    fn description(&self) -> &'static str {
79        "any structure"
80    }
81
82    fn satisfied(&self, ty: &Type) -> bool {
83        matches!(ty, Type::Compound(CompoundType::Struct(_), _))
84    }
85}
86
87/// Represents a constraint that ensures the type is any structure that contains
88/// only primitive types.
89#[derive(Debug, Copy, Clone)]
90pub struct PrimitiveStructConstraint;
91
92impl Constraint for PrimitiveStructConstraint {
93    fn description(&self) -> &'static str {
94        "any structure containing only primitive types"
95    }
96
97    fn satisfied(&self, ty: &Type) -> bool {
98        if let Type::Compound(CompoundType::Struct(ty), _) = ty {
99            return ty
100                .members()
101                .values()
102                .all(|ty| matches!(ty, Type::Primitive(..)));
103        }
104
105        false
106    }
107}
108
109/// Represents a constraint that ensures the type is JSON serializable.
110#[derive(Debug, Copy, Clone)]
111pub struct JsonSerializableConstraint;
112
113impl Constraint for JsonSerializableConstraint {
114    fn description(&self) -> &'static str {
115        "any JSON-serializable type"
116    }
117
118    fn satisfied(&self, ty: &Type) -> bool {
119        /// Determines if the given compound type is JSON serializable.
120        fn compound_type_is_serializable(ty: &CompoundType) -> bool {
121            match ty {
122                CompoundType::Array(ty) => type_is_serializable(ty.element_type()),
123                CompoundType::Pair(_) => false,
124                CompoundType::Map(ty) => {
125                    ty.key_type().is_coercible_to(&PrimitiveType::String.into())
126                        && type_is_serializable(ty.value_type())
127                }
128                CompoundType::Struct(s) => s.members().values().all(type_is_serializable),
129            }
130        }
131
132        /// Determines if the given type is JSON serializable.
133        fn type_is_serializable(ty: &Type) -> bool {
134            match ty {
135                // Treat objects and unions as sizable as they can only be checked at runtime
136                Type::Primitive(..)
137                | Type::Object
138                | Type::OptionalObject
139                | Type::Union
140                | Type::None => true,
141                Type::Compound(ty, _) => compound_type_is_serializable(ty),
142                Type::Task | Type::Hints | Type::Input | Type::Output | Type::Call(_) => false,
143            }
144        }
145
146        type_is_serializable(ty)
147    }
148}
149
150/// Represents a constraint that ensures the type is a primitive type.
151#[derive(Debug, Copy, Clone)]
152pub struct PrimitiveTypeConstraint;
153
154impl Constraint for PrimitiveTypeConstraint {
155    fn description(&self) -> &'static str {
156        "any primitive type"
157    }
158
159    fn satisfied(&self, ty: &Type) -> bool {
160        match ty {
161            Type::Primitive(..) => true,
162            // Treat unions as primitive as they can only be checked at runtime
163            Type::Union | Type::None => true,
164            Type::Compound(..)
165            | Type::Object
166            | Type::OptionalObject
167            | Type::Task
168            | Type::Hints
169            | Type::Input
170            | Type::Output
171            | Type::Call(_) => false,
172        }
173    }
174}
175
176#[cfg(test)]
177mod test {
178    use super::*;
179    use crate::types::ArrayType;
180    use crate::types::MapType;
181    use crate::types::Optional;
182    use crate::types::PairType;
183    use crate::types::PrimitiveType;
184    use crate::types::StructType;
185
186    #[test]
187    fn test_sizable_constraint() {
188        let constraint = SizeableConstraint;
189        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
190        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
191        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
192        assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
193        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
194        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
195        assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
196        assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
197        assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
198        assert!(!constraint.satisfied(&PrimitiveType::String.into()));
199        assert!(constraint.satisfied(&PrimitiveType::File.into()));
200        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
201        assert!(constraint.satisfied(&Type::OptionalObject));
202        assert!(constraint.satisfied(&Type::Object));
203        assert!(constraint.satisfied(&Type::Union));
204        assert!(!constraint.satisfied(&ArrayType::new(PrimitiveType::String).into()));
205        assert!(constraint.satisfied(&ArrayType::new(PrimitiveType::File).into()));
206        assert!(
207            !constraint
208                .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String).into())
209        );
210        assert!(
211            constraint.satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::File).into())
212        );
213        assert!(
214            constraint.satisfied(
215                &Type::from(PairType::new(
216                    PrimitiveType::Directory,
217                    PrimitiveType::String
218                ))
219                .optional()
220            )
221        );
222        assert!(
223            !constraint.satisfied(
224                &Type::from(MapType::new(
225                    PrimitiveType::String,
226                    ArrayType::new(PrimitiveType::String)
227                ))
228                .optional()
229            )
230        );
231        assert!(
232            constraint.satisfied(
233                &MapType::new(
234                    PrimitiveType::String,
235                    Type::from(ArrayType::new(PrimitiveType::File)).optional()
236                )
237                .into()
238            )
239        );
240        assert!(
241            constraint.satisfied(
242                &Type::from(MapType::new(
243                    PrimitiveType::Directory,
244                    PrimitiveType::String
245                ))
246                .optional()
247            )
248        );
249        assert!(
250            !constraint.satisfied(&StructType::new("Foo", [("foo", PrimitiveType::String)]).into())
251        );
252        assert!(constraint.satisfied(
253            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::File)])).optional()
254        ));
255        assert!(
256            constraint
257                .satisfied(&StructType::new("Foo", [("foo", PrimitiveType::Directory,)]).into())
258        );
259    }
260
261    #[test]
262    fn test_struct_constraint() {
263        let constraint = StructConstraint;
264        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
265        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
266        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
267        assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
268        assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
269        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
270        assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
271        assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
272        assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
273        assert!(!constraint.satisfied(&PrimitiveType::String.into()));
274        assert!(!constraint.satisfied(&PrimitiveType::File.into()));
275        assert!(!constraint.satisfied(&PrimitiveType::Directory.into()));
276        assert!(!constraint.satisfied(&Type::OptionalObject));
277        assert!(!constraint.satisfied(&Type::Object));
278        assert!(!constraint.satisfied(&Type::Union));
279        assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
280        assert!(!constraint.satisfied(
281            &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
282        ));
283        assert!(
284            !constraint
285                .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
286        );
287        assert!(constraint.satisfied(
288            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
289        ));
290    }
291
292    #[test]
293    fn test_json_constraint() {
294        let constraint = JsonSerializableConstraint;
295        assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
296        assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
297        assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
298        assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
299        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
300        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
301        assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
302        assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
303        assert!(constraint.satisfied(&PrimitiveType::Float.into()));
304        assert!(constraint.satisfied(&PrimitiveType::String.into()));
305        assert!(constraint.satisfied(&PrimitiveType::File.into()));
306        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
307        assert!(constraint.satisfied(&Type::OptionalObject));
308        assert!(constraint.satisfied(&Type::Object));
309        assert!(constraint.satisfied(&Type::Union));
310        assert!(
311            constraint.satisfied(&Type::from(ArrayType::new(PrimitiveType::String)).optional())
312        );
313        assert!(
314            !constraint
315                .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String,).into())
316        );
317        assert!(constraint.satisfied(
318            &Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional()
319        ));
320        assert!(
321            !constraint
322                .satisfied(&MapType::new(PrimitiveType::Integer, PrimitiveType::String,).into())
323        );
324        assert!(constraint.satisfied(
325            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
326        ));
327    }
328
329    #[test]
330    fn test_primitive_constraint() {
331        let constraint = PrimitiveTypeConstraint;
332        assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
333        assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
334        assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
335        assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
336        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
337        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
338        assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
339        assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
340        assert!(constraint.satisfied(&PrimitiveType::Float.into()));
341        assert!(constraint.satisfied(&PrimitiveType::String.into()));
342        assert!(constraint.satisfied(&PrimitiveType::File.into()));
343        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
344        assert!(!constraint.satisfied(&Type::OptionalObject));
345        assert!(!constraint.satisfied(&Type::Object));
346        assert!(constraint.satisfied(&Type::Union));
347        assert!(constraint.satisfied(&Type::None));
348        assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
349        assert!(!constraint.satisfied(
350            &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
351        ));
352        assert!(
353            !constraint
354                .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
355        );
356        assert!(!constraint.satisfied(
357            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
358        ));
359    }
360}