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