async-graphql-test 1.0.0

A test framework for Rust GraphQL servers.
Documentation
use std::fmt::{Display, Formatter};

use async_graphql::async_trait::async_trait;
use async_graphql::{Context, CustomDirective, Directive, ResolveFut, ServerResult, Value};

use super::{MatcherError, ValueTypeName};

#[Directive(location = "Field", name = "shouldMatch")]
pub fn should_match(string: Option<String>, regex: Option<String>) -> impl CustomDirective {
    if let Some(string) = string {
        return ShouldMatch::String(string);
    }

    if let Some(regex) = regex {
        return ShouldMatch::Regex(
            regex::Regex::new(&regex)
                .map_err(|err| format!("incorrect regular expression: {err}"))
                .unwrap(),
        );
    }

    panic!("at least one of the arguments must be present");
}

enum ShouldMatch {
    Regex(regex::Regex),
    String(String),
}

impl ShouldMatch {
    pub fn matches(&self, text: &str) -> bool {
        match self {
            ShouldMatch::String(sub) => text.contains(sub),
            ShouldMatch::Regex(regex) => regex.is_match(text),
        }
    }
}

impl Display for ShouldMatch {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ShouldMatch::Regex(regex) => write!(f, "/{regex}/"),
            ShouldMatch::String(sub) => sub.fmt(f),
        }
    }
}

#[async_trait]
impl CustomDirective for ShouldMatch {
    async fn resolve_field(
        &self,
        ctx: &Context<'_>,
        resolve: ResolveFut<'_>,
    ) -> ServerResult<Option<Value>> {
        match resolve.await {
            Ok(None) => Ok(None),
            Ok(Some(Value::String(received))) => {
                if self.matches(&received) {
                    Ok(Some(Value::String(received)))
                } else {
                    Err(MatcherError::new(
                        ctx.item.pos,
                        format!("Expected value to match:\n  {self}\nReceived:\n  \"{received}\""),
                    ))
                }
            }
            Ok(Some(value)) => {
                let type_name = ValueTypeName(&value);
                Err(MatcherError::new(
                    ctx.item.pos,
                    format!(
                        "@shouldMatch error: received value must be a string.\nReceived has type:  {type_name}\nReceived has value: {value}"
                    ),
                ))
            }
            Err(err) => Err(MatcherError::unexpected_error(err)),
        }
    }
}