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::Hidden(_) | 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::Hidden(_) | 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::Hidden(_)
168            | Type::Call(_) => false,
169        }
170    }
171}
172
173#[cfg(test)]
174mod test {
175    use super::*;
176    use crate::types::ArrayType;
177    use crate::types::MapType;
178    use crate::types::Optional;
179    use crate::types::PairType;
180    use crate::types::PrimitiveType;
181    use crate::types::StructType;
182
183    #[test]
184    fn test_sizable_constraint() {
185        let constraint = SizeableConstraint;
186        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
187        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
188        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
189        assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
190        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
191        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
192        assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
193        assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
194        assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
195        assert!(!constraint.satisfied(&PrimitiveType::String.into()));
196        assert!(constraint.satisfied(&PrimitiveType::File.into()));
197        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
198        assert!(constraint.satisfied(&Type::OptionalObject));
199        assert!(constraint.satisfied(&Type::Object));
200        assert!(constraint.satisfied(&Type::Union));
201        assert!(!constraint.satisfied(&ArrayType::new(PrimitiveType::String).into()));
202        assert!(constraint.satisfied(&ArrayType::new(PrimitiveType::File).into()));
203        assert!(
204            !constraint
205                .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String).into())
206        );
207        assert!(
208            constraint.satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::File).into())
209        );
210        assert!(
211            constraint.satisfied(
212                &Type::from(PairType::new(
213                    PrimitiveType::Directory,
214                    PrimitiveType::String
215                ))
216                .optional()
217            )
218        );
219        assert!(
220            !constraint.satisfied(
221                &Type::from(MapType::new(
222                    PrimitiveType::String,
223                    ArrayType::new(PrimitiveType::String)
224                ))
225                .optional()
226            )
227        );
228        assert!(
229            constraint.satisfied(
230                &MapType::new(
231                    PrimitiveType::String,
232                    Type::from(ArrayType::new(PrimitiveType::File)).optional()
233                )
234                .into()
235            )
236        );
237        assert!(
238            constraint.satisfied(
239                &Type::from(MapType::new(
240                    PrimitiveType::Directory,
241                    PrimitiveType::String
242                ))
243                .optional()
244            )
245        );
246        assert!(
247            !constraint.satisfied(&StructType::new("Foo", [("foo", PrimitiveType::String)]).into())
248        );
249        assert!(constraint.satisfied(
250            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::File)])).optional()
251        ));
252        assert!(
253            constraint
254                .satisfied(&StructType::new("Foo", [("foo", PrimitiveType::Directory,)]).into())
255        );
256    }
257
258    #[test]
259    fn test_struct_constraint() {
260        let constraint = StructConstraint;
261        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
262        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
263        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
264        assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
265        assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
266        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
267        assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
268        assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
269        assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
270        assert!(!constraint.satisfied(&PrimitiveType::String.into()));
271        assert!(!constraint.satisfied(&PrimitiveType::File.into()));
272        assert!(!constraint.satisfied(&PrimitiveType::Directory.into()));
273        assert!(!constraint.satisfied(&Type::OptionalObject));
274        assert!(!constraint.satisfied(&Type::Object));
275        assert!(!constraint.satisfied(&Type::Union));
276        assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
277        assert!(!constraint.satisfied(
278            &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
279        ));
280        assert!(
281            !constraint
282                .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
283        );
284        assert!(constraint.satisfied(
285            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
286        ));
287    }
288
289    #[test]
290    fn test_json_constraint() {
291        let constraint = JsonSerializableConstraint;
292        assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
293        assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
294        assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
295        assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
296        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
297        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
298        assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
299        assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
300        assert!(constraint.satisfied(&PrimitiveType::Float.into()));
301        assert!(constraint.satisfied(&PrimitiveType::String.into()));
302        assert!(constraint.satisfied(&PrimitiveType::File.into()));
303        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
304        assert!(constraint.satisfied(&Type::OptionalObject));
305        assert!(constraint.satisfied(&Type::Object));
306        assert!(constraint.satisfied(&Type::Union));
307        assert!(
308            constraint.satisfied(&Type::from(ArrayType::new(PrimitiveType::String)).optional())
309        );
310        assert!(
311            !constraint
312                .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String,).into())
313        );
314        assert!(constraint.satisfied(
315            &Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional()
316        ));
317        assert!(
318            !constraint
319                .satisfied(&MapType::new(PrimitiveType::Integer, PrimitiveType::String,).into())
320        );
321        assert!(constraint.satisfied(
322            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
323        ));
324    }
325
326    #[test]
327    fn test_primitive_constraint() {
328        let constraint = PrimitiveTypeConstraint;
329        assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
330        assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
331        assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
332        assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
333        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
334        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
335        assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
336        assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
337        assert!(constraint.satisfied(&PrimitiveType::Float.into()));
338        assert!(constraint.satisfied(&PrimitiveType::String.into()));
339        assert!(constraint.satisfied(&PrimitiveType::File.into()));
340        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
341        assert!(!constraint.satisfied(&Type::OptionalObject));
342        assert!(!constraint.satisfied(&Type::Object));
343        assert!(constraint.satisfied(&Type::Union));
344        assert!(constraint.satisfied(&Type::None));
345        assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
346        assert!(!constraint.satisfied(
347            &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
348        ));
349        assert!(
350            !constraint
351                .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
352        );
353        assert!(!constraint.satisfied(
354            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
355        ));
356    }
357}