wdl_analysis/stdlib/
constraints.rs1use std::fmt;
4
5use crate::types::Coercible;
6use crate::types::CompoundType;
7use crate::types::PrimitiveType;
8use crate::types::Type;
9
10pub trait Constraint: fmt::Debug + Send + Sync {
12 fn description(&self) -> &'static str;
14
15 fn satisfied(&self, ty: &Type) -> bool;
19}
20
21#[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 fn primitive_type_is_sizable(ty: PrimitiveType) -> bool {
37 matches!(ty, PrimitiveType::File | PrimitiveType::Directory)
38 }
39
40 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 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 true
62 }
63 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#[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#[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#[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 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 fn type_is_serializable(ty: &Type) -> bool {
134 match ty {
135 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#[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 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}