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(®ex)
.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)),
}
}
}