use async_graphql::async_trait::async_trait;
use async_graphql::{Context, CustomDirective, Directive, ResolveFut, ServerResult, Value};
use super::{MatcherError, ValueTypeName};
#[Directive(location = "Field", name = "shouldBeClose")]
pub fn should_be_close_to(
to: f64,
#[graphql(default = 2)] num_digits: i32,
) -> impl CustomDirective {
ShouldBeCloseTo { to, num_digits }
}
struct ShouldBeCloseTo {
to: f64,
num_digits: i32,
}
#[async_trait]
impl CustomDirective for ShouldBeCloseTo {
async fn resolve_field(
&self,
ctx: &Context<'_>,
resolve: ResolveFut<'_>,
) -> ServerResult<Option<Value>> {
match resolve.await {
Ok(None) => Ok(None),
Ok(Some(Value::Number(num))) => {
let expected_diff = 10f64.powi(-self.num_digits) / 2.;
let received_diff = (self.to - num.as_f64().unwrap()).abs();
if received_diff < expected_diff {
Ok(Some(Value::Number(num)))
} else {
Err(MatcherError::new(
ctx.item.pos,
format!(
"Expected: > {}\nExpected precision: {}\nReceived: {num}",
self.to, self.num_digits
),
))
}
}
Ok(Some(value)) => {
let type_name = ValueTypeName(&value);
Err(MatcherError::new(
ctx.item.pos,
format!(
"@shouldBeClose error: value must be numeric.\nReceived has type: {type_name}\nReceived has value: {value}"
),
))
}
Err(err) => Err(MatcherError::unexpected_error(err)),
}
}
}