Skip to main content

async_graphql_test/matchers/
should_match.rs

1use std::fmt::{Display, Formatter};
2
3use async_graphql::async_trait::async_trait;
4use async_graphql::{Context, CustomDirective, Directive, ResolveFut, ServerResult, Value};
5
6use super::{MatcherError, ValueTypeName};
7
8#[Directive(location = "Field", name = "shouldMatch")]
9pub fn should_match(string: Option<String>, regex: Option<String>) -> impl CustomDirective {
10    if let Some(string) = string {
11        return ShouldMatch::String(string);
12    }
13
14    if let Some(regex) = regex {
15        return ShouldMatch::Regex(
16            regex::Regex::new(&regex)
17                .map_err(|err| format!("incorrect regular expression: {err}"))
18                .unwrap(),
19        );
20    }
21
22    panic!("at least one of the arguments must be present");
23}
24
25enum ShouldMatch {
26    Regex(regex::Regex),
27    String(String),
28}
29
30impl ShouldMatch {
31    pub fn matches(&self, text: &str) -> bool {
32        match self {
33            ShouldMatch::String(sub) => text.contains(sub),
34            ShouldMatch::Regex(regex) => regex.is_match(text),
35        }
36    }
37}
38
39impl Display for ShouldMatch {
40    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
41        match self {
42            ShouldMatch::Regex(regex) => write!(f, "/{regex}/"),
43            ShouldMatch::String(sub) => sub.fmt(f),
44        }
45    }
46}
47
48#[async_trait]
49impl CustomDirective for ShouldMatch {
50    async fn resolve_field(
51        &self,
52        ctx: &Context<'_>,
53        resolve: ResolveFut<'_>,
54    ) -> ServerResult<Option<Value>> {
55        match resolve.await {
56            Ok(None) => Ok(None),
57            Ok(Some(Value::String(received))) => {
58                if self.matches(&received) {
59                    Ok(Some(Value::String(received)))
60                } else {
61                    Err(MatcherError::new(
62                        ctx.item.pos,
63                        format!("Expected value to match:\n  {self}\nReceived:\n  \"{received}\""),
64                    ))
65                }
66            }
67            Ok(Some(value)) => {
68                let type_name = ValueTypeName(&value);
69                Err(MatcherError::new(
70                    ctx.item.pos,
71                    format!(
72                        "@shouldMatch error: received value must be a string.\nReceived has type:  {type_name}\nReceived has value: {value}"
73                    ),
74                ))
75            }
76            Err(err) => Err(MatcherError::unexpected_error(err)),
77        }
78    }
79}