wdl_analysis/stdlib/
constraints.rs1use std::fmt;
4
5use crate::types::Coercible;
6use crate::types::CompoundType;
7use crate::types::CustomType;
8use crate::types::PrimitiveType;
9use crate::types::Type;
10
11pub trait Constraint: fmt::Debug + Send + Sync {
13 fn description(&self) -> &'static str;
15
16 fn satisfied(&self, ty: &Type) -> bool;
20}
21
22#[derive(Debug, Copy, Clone)]
28pub struct SizeableConstraint;
29
30impl Constraint for SizeableConstraint {
31 fn description(&self) -> &'static str {
32 "any compound type that recursively contains a `File` or `Directory`"
33 }
34
35 fn satisfied(&self, ty: &Type) -> bool {
36 fn primitive_type_is_sizable(ty: PrimitiveType) -> bool {
38 matches!(ty, PrimitiveType::File | PrimitiveType::Directory)
39 }
40
41 fn compound_type_is_sizable(ty: &CompoundType) -> bool {
43 match ty {
44 CompoundType::Array(ty) => type_is_sizable(ty.element_type()),
45 CompoundType::Pair(ty) => {
46 type_is_sizable(ty.left_type()) | type_is_sizable(ty.right_type())
47 }
48 CompoundType::Map(ty) => {
49 type_is_sizable(ty.key_type()) | type_is_sizable(ty.value_type())
50 }
51 CompoundType::Custom(CustomType::Struct(s)) => {
52 s.members().values().any(type_is_sizable)
53 }
54 CompoundType::Custom(CustomType::Enum(_)) => false,
55 }
56 }
57
58 fn type_is_sizable(ty: &Type) -> bool {
60 match ty {
61 Type::Primitive(ty, _) => primitive_type_is_sizable(*ty),
62 Type::Compound(ty, _) => compound_type_is_sizable(ty),
63 Type::Object | Type::OptionalObject => {
64 true
66 }
67 Type::Union | Type::None => true,
69 Type::Hidden(_) | Type::Call(_) | Type::TypeNameRef(_) => false,
70 }
71 }
72
73 type_is_sizable(ty)
74 }
75}
76
77#[derive(Debug, Copy, Clone)]
79pub struct StructConstraint;
80
81impl Constraint for StructConstraint {
82 fn description(&self) -> &'static str {
83 "any structure"
84 }
85
86 fn satisfied(&self, ty: &Type) -> bool {
87 ty.as_struct().is_some()
88 }
89}
90
91#[derive(Debug, Copy, Clone)]
94pub struct PrimitiveStructConstraint;
95
96impl Constraint for PrimitiveStructConstraint {
97 fn description(&self) -> &'static str {
98 "any structure containing only primitive types"
99 }
100
101 fn satisfied(&self, ty: &Type) -> bool {
102 if let Some(ty) = ty.as_struct() {
103 return ty
104 .members()
105 .values()
106 .all(|ty| matches!(ty, Type::Primitive(..)));
107 }
108
109 false
110 }
111}
112
113#[derive(Debug, Copy, Clone)]
115pub struct JsonSerializableConstraint;
116
117impl Constraint for JsonSerializableConstraint {
118 fn description(&self) -> &'static str {
119 "any JSON-serializable type"
120 }
121
122 fn satisfied(&self, ty: &Type) -> bool {
123 fn compound_type_is_serializable(ty: &CompoundType) -> bool {
125 match ty {
126 CompoundType::Array(ty) => type_is_serializable(ty.element_type()),
127 CompoundType::Pair(_) => false,
128 CompoundType::Map(ty) => {
129 ty.key_type().is_coercible_to(&PrimitiveType::String.into())
130 && type_is_serializable(ty.value_type())
131 }
132 CompoundType::Custom(CustomType::Struct(s)) => {
133 s.members().values().all(type_is_serializable)
134 }
135 CompoundType::Custom(CustomType::Enum(_)) => {
136 true
139 }
140 }
141 }
142
143 fn type_is_serializable(ty: &Type) -> bool {
145 match ty {
146 Type::Primitive(..)
148 | Type::Object
149 | Type::OptionalObject
150 | Type::Union
151 | Type::None => true,
152 Type::Compound(ty, _) => compound_type_is_serializable(ty),
153 Type::Hidden(_) | Type::Call(_) | Type::TypeNameRef(_) => false,
154 }
155 }
156
157 type_is_serializable(ty)
158 }
159}
160
161#[derive(Debug, Copy, Clone)]
163pub struct PrimitiveTypeConstraint;
164
165impl Constraint for PrimitiveTypeConstraint {
166 fn description(&self) -> &'static str {
167 "any primitive type"
168 }
169
170 fn satisfied(&self, ty: &Type) -> bool {
171 match ty {
172 Type::Primitive(..) => true,
173 Type::Union | Type::None => true,
175 Type::Compound(..)
176 | Type::Object
177 | Type::OptionalObject
178 | Type::Hidden(_)
179 | Type::Call(_)
180 | Type::TypeNameRef(_) => false,
181 }
182 }
183}
184
185#[derive(Debug, Copy, Clone)]
187pub struct MapKeyConstraint;
188
189impl Constraint for MapKeyConstraint {
190 fn description(&self) -> &'static str {
191 "any non-optional primitive type"
192 }
193
194 fn satisfied(&self, ty: &Type) -> bool {
195 match ty {
196 Type::Union | Type::Primitive(_, false) => true,
197 Type::Primitive(_, true)
198 | Type::Compound(..)
199 | Type::None
200 | Type::Object
201 | Type::OptionalObject
202 | Type::Hidden(_)
203 | Type::Call(_)
204 | Type::TypeNameRef(_) => false,
205 }
206 }
207}
208
209#[derive(Debug, Copy, Clone)]
211pub struct EnumVariantConstraint;
212
213impl Constraint for EnumVariantConstraint {
214 fn description(&self) -> &'static str {
215 "any enum variant"
216 }
217
218 fn satisfied(&self, ty: &Type) -> bool {
219 ty.as_enum().is_some()
220 }
221}
222
223#[cfg(test)]
224mod test {
225 use super::*;
226 use crate::types::ArrayType;
227 use crate::types::MapType;
228 use crate::types::Optional;
229 use crate::types::PairType;
230 use crate::types::PrimitiveType;
231 use crate::types::StructType;
232
233 #[test]
234 fn test_sizable_constraint() {
235 let constraint = SizeableConstraint;
236 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
237 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
238 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
239 assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
240 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
241 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
242 assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
243 assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
244 assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
245 assert!(!constraint.satisfied(&PrimitiveType::String.into()));
246 assert!(constraint.satisfied(&PrimitiveType::File.into()));
247 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
248 assert!(constraint.satisfied(&Type::OptionalObject));
249 assert!(constraint.satisfied(&Type::Object));
250 assert!(constraint.satisfied(&Type::Union));
251 assert!(!constraint.satisfied(&ArrayType::new(PrimitiveType::String).into()));
252 assert!(constraint.satisfied(&ArrayType::new(PrimitiveType::File).into()));
253 assert!(
254 !constraint
255 .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String).into())
256 );
257 assert!(
258 constraint.satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::File).into())
259 );
260 assert!(
261 constraint.satisfied(
262 &Type::from(PairType::new(
263 PrimitiveType::Directory,
264 PrimitiveType::String
265 ))
266 .optional()
267 )
268 );
269 assert!(
270 !constraint.satisfied(
271 &Type::from(MapType::new(
272 PrimitiveType::String,
273 ArrayType::new(PrimitiveType::String)
274 ))
275 .optional()
276 )
277 );
278 assert!(
279 constraint.satisfied(
280 &MapType::new(
281 PrimitiveType::String,
282 Type::from(ArrayType::new(PrimitiveType::File)).optional()
283 )
284 .into()
285 )
286 );
287 assert!(
288 constraint.satisfied(
289 &Type::from(MapType::new(
290 PrimitiveType::Directory,
291 PrimitiveType::String
292 ))
293 .optional()
294 )
295 );
296 assert!(
297 !constraint.satisfied(&StructType::new("Foo", [("foo", PrimitiveType::String)]).into())
298 );
299 assert!(constraint.satisfied(
300 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::File)])).optional()
301 ));
302 assert!(
303 constraint
304 .satisfied(&StructType::new("Foo", [("foo", PrimitiveType::Directory,)]).into())
305 );
306 }
307
308 #[test]
309 fn test_struct_constraint() {
310 let constraint = StructConstraint;
311 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
312 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
313 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
314 assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
315 assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
316 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
317 assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
318 assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
319 assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
320 assert!(!constraint.satisfied(&PrimitiveType::String.into()));
321 assert!(!constraint.satisfied(&PrimitiveType::File.into()));
322 assert!(!constraint.satisfied(&PrimitiveType::Directory.into()));
323 assert!(!constraint.satisfied(&Type::OptionalObject));
324 assert!(!constraint.satisfied(&Type::Object));
325 assert!(!constraint.satisfied(&Type::Union));
326 assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
327 assert!(!constraint.satisfied(
328 &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
329 ));
330 assert!(
331 !constraint
332 .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
333 );
334 assert!(constraint.satisfied(
335 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
336 ));
337 }
338
339 #[test]
340 fn test_json_constraint() {
341 let constraint = JsonSerializableConstraint;
342 assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
343 assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
344 assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
345 assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
346 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
347 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
348 assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
349 assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
350 assert!(constraint.satisfied(&PrimitiveType::Float.into()));
351 assert!(constraint.satisfied(&PrimitiveType::String.into()));
352 assert!(constraint.satisfied(&PrimitiveType::File.into()));
353 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
354 assert!(constraint.satisfied(&Type::OptionalObject));
355 assert!(constraint.satisfied(&Type::Object));
356 assert!(constraint.satisfied(&Type::Union));
357 assert!(
358 constraint.satisfied(&Type::from(ArrayType::new(PrimitiveType::String)).optional())
359 );
360 assert!(
361 !constraint
362 .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String,).into())
363 );
364 assert!(constraint.satisfied(
365 &Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional()
366 ));
367 assert!(
368 !constraint
369 .satisfied(&MapType::new(PrimitiveType::Integer, PrimitiveType::String).into())
370 );
371 assert!(constraint.satisfied(
372 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
373 ));
374 }
375
376 #[test]
377 fn test_map_key_constraint() {
378 let constraint = MapKeyConstraint;
379 assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
380 assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
381 assert!(constraint.satisfied(&PrimitiveType::Float.into()));
382 assert!(constraint.satisfied(&PrimitiveType::String.into()));
383 assert!(constraint.satisfied(&PrimitiveType::File.into()));
384 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
385 assert!(constraint.satisfied(&Type::Union));
386 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
387 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
388 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
389 assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
390 assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
391 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
392 assert!(!constraint.satisfied(&Type::OptionalObject));
393 assert!(!constraint.satisfied(&Type::Object));
394 }
395
396 #[test]
397 fn test_primitive_constraint() {
398 let constraint = PrimitiveTypeConstraint;
399 assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
400 assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
401 assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
402 assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
403 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
404 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
405 assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
406 assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
407 assert!(constraint.satisfied(&PrimitiveType::Float.into()));
408 assert!(constraint.satisfied(&PrimitiveType::String.into()));
409 assert!(constraint.satisfied(&PrimitiveType::File.into()));
410 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
411 assert!(!constraint.satisfied(&Type::OptionalObject));
412 assert!(!constraint.satisfied(&Type::Object));
413 assert!(constraint.satisfied(&Type::Union));
414 assert!(constraint.satisfied(&Type::None));
415 assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
416 assert!(!constraint.satisfied(
417 &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
418 ));
419 assert!(
420 !constraint
421 .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
422 );
423 assert!(!constraint.satisfied(
424 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
425 ));
426 }
427}