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 matches!(
88 ty,
89 Type::Compound(CompoundType::Custom(CustomType::Struct(_)), _)
90 )
91 }
92}
93
94#[derive(Debug, Copy, Clone)]
97pub struct PrimitiveStructConstraint;
98
99impl Constraint for PrimitiveStructConstraint {
100 fn description(&self) -> &'static str {
101 "any structure containing only primitive types"
102 }
103
104 fn satisfied(&self, ty: &Type) -> bool {
105 if let Type::Compound(CompoundType::Custom(CustomType::Struct(ty)), _) = ty {
106 return ty
107 .members()
108 .values()
109 .all(|ty| matches!(ty, Type::Primitive(..)));
110 }
111
112 false
113 }
114}
115
116#[derive(Debug, Copy, Clone)]
118pub struct JsonSerializableConstraint;
119
120impl Constraint for JsonSerializableConstraint {
121 fn description(&self) -> &'static str {
122 "any JSON-serializable type"
123 }
124
125 fn satisfied(&self, ty: &Type) -> bool {
126 fn compound_type_is_serializable(ty: &CompoundType) -> bool {
128 match ty {
129 CompoundType::Array(ty) => type_is_serializable(ty.element_type()),
130 CompoundType::Pair(_) => false,
131 CompoundType::Map(ty) => {
132 ty.key_type().is_coercible_to(&PrimitiveType::String.into())
133 && type_is_serializable(ty.value_type())
134 }
135 CompoundType::Custom(CustomType::Struct(s)) => {
136 s.members().values().all(type_is_serializable)
137 }
138 CompoundType::Custom(CustomType::Enum(_)) => {
139 true
142 }
143 }
144 }
145
146 fn type_is_serializable(ty: &Type) -> bool {
148 match ty {
149 Type::Primitive(..)
151 | Type::Object
152 | Type::OptionalObject
153 | Type::Union
154 | Type::None => true,
155 Type::Compound(ty, _) => compound_type_is_serializable(ty),
156 Type::Hidden(_) | Type::Call(_) | Type::TypeNameRef(_) => false,
157 }
158 }
159
160 type_is_serializable(ty)
161 }
162}
163
164#[derive(Debug, Copy, Clone)]
166pub struct PrimitiveTypeConstraint;
167
168impl Constraint for PrimitiveTypeConstraint {
169 fn description(&self) -> &'static str {
170 "any primitive type"
171 }
172
173 fn satisfied(&self, ty: &Type) -> bool {
174 match ty {
175 Type::Primitive(..) => true,
176 Type::Union | Type::None => true,
178 Type::Compound(..)
179 | Type::Object
180 | Type::OptionalObject
181 | Type::Hidden(_)
182 | Type::Call(_)
183 | Type::TypeNameRef(_) => false,
184 }
185 }
186}
187
188#[derive(Debug, Copy, Clone)]
190pub struct MapKeyConstraint;
191
192impl Constraint for MapKeyConstraint {
193 fn description(&self) -> &'static str {
194 "any non-optional primitive type"
195 }
196
197 fn satisfied(&self, ty: &Type) -> bool {
198 match ty {
199 Type::Union | Type::Primitive(_, false) => true,
200 Type::Primitive(_, true)
201 | Type::Compound(..)
202 | Type::None
203 | Type::Object
204 | Type::OptionalObject
205 | Type::Hidden(_)
206 | Type::Call(_)
207 | Type::TypeNameRef(_) => false,
208 }
209 }
210}
211
212#[derive(Debug, Copy, Clone)]
214pub struct EnumVariantConstraint;
215
216impl Constraint for EnumVariantConstraint {
217 fn description(&self) -> &'static str {
218 "any enum variant"
219 }
220
221 fn satisfied(&self, ty: &Type) -> bool {
222 matches!(
223 ty,
224 Type::Compound(CompoundType::Custom(CustomType::Enum(_)), _)
225 )
226 }
227}
228
229#[cfg(test)]
230mod test {
231 use super::*;
232 use crate::types::ArrayType;
233 use crate::types::MapType;
234 use crate::types::Optional;
235 use crate::types::PairType;
236 use crate::types::PrimitiveType;
237 use crate::types::StructType;
238
239 #[test]
240 fn test_sizable_constraint() {
241 let constraint = SizeableConstraint;
242 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
243 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
244 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
245 assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
246 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
247 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
248 assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
249 assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
250 assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
251 assert!(!constraint.satisfied(&PrimitiveType::String.into()));
252 assert!(constraint.satisfied(&PrimitiveType::File.into()));
253 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
254 assert!(constraint.satisfied(&Type::OptionalObject));
255 assert!(constraint.satisfied(&Type::Object));
256 assert!(constraint.satisfied(&Type::Union));
257 assert!(!constraint.satisfied(&ArrayType::new(PrimitiveType::String).into()));
258 assert!(constraint.satisfied(&ArrayType::new(PrimitiveType::File).into()));
259 assert!(
260 !constraint
261 .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String).into())
262 );
263 assert!(
264 constraint.satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::File).into())
265 );
266 assert!(
267 constraint.satisfied(
268 &Type::from(PairType::new(
269 PrimitiveType::Directory,
270 PrimitiveType::String
271 ))
272 .optional()
273 )
274 );
275 assert!(
276 !constraint.satisfied(
277 &Type::from(MapType::new(
278 PrimitiveType::String,
279 ArrayType::new(PrimitiveType::String)
280 ))
281 .optional()
282 )
283 );
284 assert!(
285 constraint.satisfied(
286 &MapType::new(
287 PrimitiveType::String,
288 Type::from(ArrayType::new(PrimitiveType::File)).optional()
289 )
290 .into()
291 )
292 );
293 assert!(
294 constraint.satisfied(
295 &Type::from(MapType::new(
296 PrimitiveType::Directory,
297 PrimitiveType::String
298 ))
299 .optional()
300 )
301 );
302 assert!(
303 !constraint.satisfied(&StructType::new("Foo", [("foo", PrimitiveType::String)]).into())
304 );
305 assert!(constraint.satisfied(
306 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::File)])).optional()
307 ));
308 assert!(
309 constraint
310 .satisfied(&StructType::new("Foo", [("foo", PrimitiveType::Directory,)]).into())
311 );
312 }
313
314 #[test]
315 fn test_struct_constraint() {
316 let constraint = StructConstraint;
317 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
318 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
319 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
320 assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
321 assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
322 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
323 assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
324 assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
325 assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
326 assert!(!constraint.satisfied(&PrimitiveType::String.into()));
327 assert!(!constraint.satisfied(&PrimitiveType::File.into()));
328 assert!(!constraint.satisfied(&PrimitiveType::Directory.into()));
329 assert!(!constraint.satisfied(&Type::OptionalObject));
330 assert!(!constraint.satisfied(&Type::Object));
331 assert!(!constraint.satisfied(&Type::Union));
332 assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
333 assert!(!constraint.satisfied(
334 &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
335 ));
336 assert!(
337 !constraint
338 .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
339 );
340 assert!(constraint.satisfied(
341 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
342 ));
343 }
344
345 #[test]
346 fn test_json_constraint() {
347 let constraint = JsonSerializableConstraint;
348 assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
349 assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
350 assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
351 assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
352 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
353 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
354 assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
355 assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
356 assert!(constraint.satisfied(&PrimitiveType::Float.into()));
357 assert!(constraint.satisfied(&PrimitiveType::String.into()));
358 assert!(constraint.satisfied(&PrimitiveType::File.into()));
359 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
360 assert!(constraint.satisfied(&Type::OptionalObject));
361 assert!(constraint.satisfied(&Type::Object));
362 assert!(constraint.satisfied(&Type::Union));
363 assert!(
364 constraint.satisfied(&Type::from(ArrayType::new(PrimitiveType::String)).optional())
365 );
366 assert!(
367 !constraint
368 .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String,).into())
369 );
370 assert!(constraint.satisfied(
371 &Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional()
372 ));
373 assert!(
374 !constraint
375 .satisfied(&MapType::new(PrimitiveType::Integer, PrimitiveType::String).into())
376 );
377 assert!(constraint.satisfied(
378 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
379 ));
380 }
381
382 #[test]
383 fn test_map_key_constraint() {
384 let constraint = MapKeyConstraint;
385 assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
386 assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
387 assert!(constraint.satisfied(&PrimitiveType::Float.into()));
388 assert!(constraint.satisfied(&PrimitiveType::String.into()));
389 assert!(constraint.satisfied(&PrimitiveType::File.into()));
390 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
391 assert!(constraint.satisfied(&Type::Union));
392 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
393 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
394 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
395 assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
396 assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
397 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
398 assert!(!constraint.satisfied(&Type::OptionalObject));
399 assert!(!constraint.satisfied(&Type::Object));
400 }
401
402 #[test]
403 fn test_primitive_constraint() {
404 let constraint = PrimitiveTypeConstraint;
405 assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
406 assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
407 assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
408 assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
409 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
410 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
411 assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
412 assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
413 assert!(constraint.satisfied(&PrimitiveType::Float.into()));
414 assert!(constraint.satisfied(&PrimitiveType::String.into()));
415 assert!(constraint.satisfied(&PrimitiveType::File.into()));
416 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
417 assert!(!constraint.satisfied(&Type::OptionalObject));
418 assert!(!constraint.satisfied(&Type::Object));
419 assert!(constraint.satisfied(&Type::Union));
420 assert!(constraint.satisfied(&Type::None));
421 assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
422 assert!(!constraint.satisfied(
423 &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
424 ));
425 assert!(
426 !constraint
427 .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
428 );
429 assert!(!constraint.satisfied(
430 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
431 ));
432 }
433}