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::Hidden(_) | 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::Hidden(_) | 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::Hidden(_)
168 | Type::Call(_) => false,
169 }
170 }
171}
172
173#[cfg(test)]
174mod test {
175 use super::*;
176 use crate::types::ArrayType;
177 use crate::types::MapType;
178 use crate::types::Optional;
179 use crate::types::PairType;
180 use crate::types::PrimitiveType;
181 use crate::types::StructType;
182
183 #[test]
184 fn test_sizable_constraint() {
185 let constraint = SizeableConstraint;
186 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
187 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
188 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
189 assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
190 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
191 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
192 assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
193 assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
194 assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
195 assert!(!constraint.satisfied(&PrimitiveType::String.into()));
196 assert!(constraint.satisfied(&PrimitiveType::File.into()));
197 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
198 assert!(constraint.satisfied(&Type::OptionalObject));
199 assert!(constraint.satisfied(&Type::Object));
200 assert!(constraint.satisfied(&Type::Union));
201 assert!(!constraint.satisfied(&ArrayType::new(PrimitiveType::String).into()));
202 assert!(constraint.satisfied(&ArrayType::new(PrimitiveType::File).into()));
203 assert!(
204 !constraint
205 .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String).into())
206 );
207 assert!(
208 constraint.satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::File).into())
209 );
210 assert!(
211 constraint.satisfied(
212 &Type::from(PairType::new(
213 PrimitiveType::Directory,
214 PrimitiveType::String
215 ))
216 .optional()
217 )
218 );
219 assert!(
220 !constraint.satisfied(
221 &Type::from(MapType::new(
222 PrimitiveType::String,
223 ArrayType::new(PrimitiveType::String)
224 ))
225 .optional()
226 )
227 );
228 assert!(
229 constraint.satisfied(
230 &MapType::new(
231 PrimitiveType::String,
232 Type::from(ArrayType::new(PrimitiveType::File)).optional()
233 )
234 .into()
235 )
236 );
237 assert!(
238 constraint.satisfied(
239 &Type::from(MapType::new(
240 PrimitiveType::Directory,
241 PrimitiveType::String
242 ))
243 .optional()
244 )
245 );
246 assert!(
247 !constraint.satisfied(&StructType::new("Foo", [("foo", PrimitiveType::String)]).into())
248 );
249 assert!(constraint.satisfied(
250 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::File)])).optional()
251 ));
252 assert!(
253 constraint
254 .satisfied(&StructType::new("Foo", [("foo", PrimitiveType::Directory,)]).into())
255 );
256 }
257
258 #[test]
259 fn test_struct_constraint() {
260 let constraint = StructConstraint;
261 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
262 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
263 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
264 assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
265 assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
266 assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
267 assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
268 assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
269 assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
270 assert!(!constraint.satisfied(&PrimitiveType::String.into()));
271 assert!(!constraint.satisfied(&PrimitiveType::File.into()));
272 assert!(!constraint.satisfied(&PrimitiveType::Directory.into()));
273 assert!(!constraint.satisfied(&Type::OptionalObject));
274 assert!(!constraint.satisfied(&Type::Object));
275 assert!(!constraint.satisfied(&Type::Union));
276 assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
277 assert!(!constraint.satisfied(
278 &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
279 ));
280 assert!(
281 !constraint
282 .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
283 );
284 assert!(constraint.satisfied(
285 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
286 ));
287 }
288
289 #[test]
290 fn test_json_constraint() {
291 let constraint = JsonSerializableConstraint;
292 assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
293 assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
294 assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
295 assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
296 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
297 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
298 assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
299 assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
300 assert!(constraint.satisfied(&PrimitiveType::Float.into()));
301 assert!(constraint.satisfied(&PrimitiveType::String.into()));
302 assert!(constraint.satisfied(&PrimitiveType::File.into()));
303 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
304 assert!(constraint.satisfied(&Type::OptionalObject));
305 assert!(constraint.satisfied(&Type::Object));
306 assert!(constraint.satisfied(&Type::Union));
307 assert!(
308 constraint.satisfied(&Type::from(ArrayType::new(PrimitiveType::String)).optional())
309 );
310 assert!(
311 !constraint
312 .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String,).into())
313 );
314 assert!(constraint.satisfied(
315 &Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional()
316 ));
317 assert!(
318 !constraint
319 .satisfied(&MapType::new(PrimitiveType::Integer, PrimitiveType::String,).into())
320 );
321 assert!(constraint.satisfied(
322 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
323 ));
324 }
325
326 #[test]
327 fn test_primitive_constraint() {
328 let constraint = PrimitiveTypeConstraint;
329 assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
330 assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
331 assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
332 assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
333 assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
334 assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
335 assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
336 assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
337 assert!(constraint.satisfied(&PrimitiveType::Float.into()));
338 assert!(constraint.satisfied(&PrimitiveType::String.into()));
339 assert!(constraint.satisfied(&PrimitiveType::File.into()));
340 assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
341 assert!(!constraint.satisfied(&Type::OptionalObject));
342 assert!(!constraint.satisfied(&Type::Object));
343 assert!(constraint.satisfied(&Type::Union));
344 assert!(constraint.satisfied(&Type::None));
345 assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
346 assert!(!constraint.satisfied(
347 &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
348 ));
349 assert!(
350 !constraint
351 .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
352 );
353 assert!(!constraint.satisfied(
354 &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
355 ));
356 }
357}