wdl_engine/stdlib/
find.rs

1//! Implements the `find` function from the WDL standard library.
2
3use regex::Regex;
4use wdl_analysis::types::Optional;
5use wdl_analysis::types::PrimitiveType;
6use wdl_analysis::types::Type;
7use wdl_ast::Diagnostic;
8
9use super::CallContext;
10use super::Callback;
11use super::Function;
12use super::Signature;
13use crate::PrimitiveValue;
14use crate::Value;
15use crate::diagnostics::function_call_failed;
16
17/// The name of the function defined in this file for use in diagnostics.
18const FUNCTION_NAME: &str = "find";
19
20/// Given two String parameters `input` and `pattern`, searches for the
21/// occurrence of `pattern` within `input` and returns the first match or `None`
22/// if there are no matches.
23///
24/// https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-find
25fn find(context: CallContext<'_>) -> Result<Value, Diagnostic> {
26    debug_assert_eq!(context.arguments.len(), 2);
27    debug_assert!(context.return_type_eq(Type::from(PrimitiveType::String).optional()));
28
29    let input = context
30        .coerce_argument(0, PrimitiveType::String)
31        .unwrap_string();
32    let pattern = context
33        .coerce_argument(1, PrimitiveType::String)
34        .unwrap_string();
35
36    let regex = Regex::new(pattern.as_str())
37        .map_err(|e| function_call_failed(FUNCTION_NAME, &e, context.arguments[1].span))?;
38
39    match regex.find(input.as_str()) {
40        Some(m) => Ok(PrimitiveValue::new_string(m.as_str()).into()),
41        None => Ok(Value::None),
42    }
43}
44
45/// Gets the function describing `find`.
46pub const fn descriptor() -> Function {
47    Function::new(
48        const {
49            &[Signature::new(
50                "(String, String) -> String?",
51                Callback::Sync(find),
52            )]
53        },
54    )
55}
56
57#[cfg(test)]
58mod test {
59    use pretty_assertions::assert_eq;
60    use wdl_ast::version::V1;
61
62    use crate::v1::test::TestEnv;
63    use crate::v1::test::eval_v1_expr;
64
65    #[tokio::test]
66    async fn find() {
67        let env = TestEnv::default();
68        let diagnostic = eval_v1_expr(&env, V1::Two, "find('foo bar baz', '?')")
69            .await
70            .unwrap_err();
71        assert_eq!(
72            diagnostic.message(),
73            "call to function `find` failed: regex parse error:\n    ?\n    ^\nerror: repetition \
74             operator missing expression"
75        );
76
77        let value = eval_v1_expr(&env, V1::Two, "find('hello world', 'e..o')")
78            .await
79            .unwrap();
80        assert_eq!(value.unwrap_string().as_str(), "ello");
81
82        let value = eval_v1_expr(&env, V1::Two, "find('hello world', 'goodbye')")
83            .await
84            .unwrap();
85        assert!(value.is_none());
86
87        let value = eval_v1_expr(&env, V1::Two, "find('hello\tBob', '\\t')")
88            .await
89            .unwrap();
90        assert_eq!(value.unwrap_string().as_str(), "\t");
91    }
92}