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 EnumVariantConstraint;
191
192impl Constraint for EnumVariantConstraint {
193 fn description(&self) -> &'static str {
194 "any enum variant"
195 }
196
197 fn satisfied(&self, ty: &Type) -> bool {
198 matches!(
199 ty,
200 Type::Compound(CompoundType::Custom(CustomType::Enum(_)), _)
201 )
202 }
203}
204
205#[cfg(test)]
206mod test {
207 use super::*;
208 use crate::types::ArrayType;
209 use crate::types::MapType;
210 use crate::types::Optional;
211 use crate::types::PairType;
212 use crate::types::PrimitiveType;
213 use crate::types::StructType;
214
215 #[test]
216 fn test_sizable_constraint() {
217 let constraint = SizeableConstraint;
218 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
219 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
220 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
221 assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
222 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
223 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
224 assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
225 assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
226 assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
227 assert!(!constraint.satisfied(&PrimitiveType::String.into()));
228 assert!(constraint.satisfied(&PrimitiveType::File.into()));
229 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
230 assert!(constraint.satisfied(&Type::OptionalObject));
231 assert!(constraint.satisfied(&Type::Object));
232 assert!(constraint.satisfied(&Type::Union));
233 assert!(!constraint.satisfied(&ArrayType::new(PrimitiveType::String).into()));
234 assert!(constraint.satisfied(&ArrayType::new(PrimitiveType::File).into()));
235 assert!(
236 !constraint
237 .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String).into())
238 );
239 assert!(
240 constraint.satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::File).into())
241 );
242 assert!(
243 constraint.satisfied(
244 &Type::from(PairType::new(
245 PrimitiveType::Directory,
246 PrimitiveType::String
247 ))
248 .optional()
249 )
250 );
251 assert!(
252 !constraint.satisfied(
253 &Type::from(MapType::new(
254 PrimitiveType::String,
255 ArrayType::new(PrimitiveType::String)
256 ))
257 .optional()
258 )
259 );
260 assert!(
261 constraint.satisfied(
262 &MapType::new(
263 PrimitiveType::String,
264 Type::from(ArrayType::new(PrimitiveType::File)).optional()
265 )
266 .into()
267 )
268 );
269 assert!(
270 constraint.satisfied(
271 &Type::from(MapType::new(
272 PrimitiveType::Directory,
273 PrimitiveType::String
274 ))
275 .optional()
276 )
277 );
278 assert!(
279 !constraint.satisfied(&StructType::new("Foo", [("foo", PrimitiveType::String)]).into())
280 );
281 assert!(constraint.satisfied(
282 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::File)])).optional()
283 ));
284 assert!(
285 constraint
286 .satisfied(&StructType::new("Foo", [("foo", PrimitiveType::Directory,)]).into())
287 );
288 }
289
290 #[test]
291 fn test_struct_constraint() {
292 let constraint = StructConstraint;
293 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
294 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
295 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
296 assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
297 assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
298 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
299 assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
300 assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
301 assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
302 assert!(!constraint.satisfied(&PrimitiveType::String.into()));
303 assert!(!constraint.satisfied(&PrimitiveType::File.into()));
304 assert!(!constraint.satisfied(&PrimitiveType::Directory.into()));
305 assert!(!constraint.satisfied(&Type::OptionalObject));
306 assert!(!constraint.satisfied(&Type::Object));
307 assert!(!constraint.satisfied(&Type::Union));
308 assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
309 assert!(!constraint.satisfied(
310 &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
311 ));
312 assert!(
313 !constraint
314 .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
315 );
316 assert!(constraint.satisfied(
317 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
318 ));
319 }
320
321 #[test]
322 fn test_json_constraint() {
323 let constraint = JsonSerializableConstraint;
324 assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
325 assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
326 assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
327 assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
328 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
329 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
330 assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
331 assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
332 assert!(constraint.satisfied(&PrimitiveType::Float.into()));
333 assert!(constraint.satisfied(&PrimitiveType::String.into()));
334 assert!(constraint.satisfied(&PrimitiveType::File.into()));
335 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
336 assert!(constraint.satisfied(&Type::OptionalObject));
337 assert!(constraint.satisfied(&Type::Object));
338 assert!(constraint.satisfied(&Type::Union));
339 assert!(
340 constraint.satisfied(&Type::from(ArrayType::new(PrimitiveType::String)).optional())
341 );
342 assert!(
343 !constraint
344 .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String,).into())
345 );
346 assert!(constraint.satisfied(
347 &Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional()
348 ));
349 assert!(
350 !constraint
351 .satisfied(&MapType::new(PrimitiveType::Integer, PrimitiveType::String,).into())
352 );
353 assert!(constraint.satisfied(
354 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
355 ));
356 }
357
358 #[test]
359 fn test_primitive_constraint() {
360 let constraint = PrimitiveTypeConstraint;
361 assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
362 assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
363 assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
364 assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
365 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
366 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
367 assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
368 assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
369 assert!(constraint.satisfied(&PrimitiveType::Float.into()));
370 assert!(constraint.satisfied(&PrimitiveType::String.into()));
371 assert!(constraint.satisfied(&PrimitiveType::File.into()));
372 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
373 assert!(!constraint.satisfied(&Type::OptionalObject));
374 assert!(!constraint.satisfied(&Type::Object));
375 assert!(constraint.satisfied(&Type::Union));
376 assert!(constraint.satisfied(&Type::None));
377 assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
378 assert!(!constraint.satisfied(
379 &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
380 ));
381 assert!(
382 !constraint
383 .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
384 );
385 assert!(!constraint.satisfied(
386 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
387 ));
388 }
389}