use arcstr::ArcStr;
use indexmap::IndexMap;
use crate::{
FieldResult, GraphQLEnum, IntoFieldError,
ast::{Directive, FromInputValue, InputValue, Selection},
executor::{ExecutionResult, Executor, Registry, Variables},
parser::Spanning,
schema::meta::{Argument, MetaType},
value::{DefaultScalarValue, Object, ScalarValue, Value},
};
#[derive(Clone, Eq, PartialEq, Debug, GraphQLEnum)]
#[graphql(name = "__TypeKind", internal)]
pub enum TypeKind {
Scalar,
Object,
Interface,
Union,
Enum,
InputObject,
List,
NonNull,
}
#[derive(Debug)]
pub struct Arguments<'a, S = DefaultScalarValue> {
args: Option<IndexMap<&'a str, Spanning<InputValue<S>>>>,
}
impl<'a, S> Arguments<'a, S> {
#[doc(hidden)]
pub fn new(
mut args: Option<IndexMap<&'a str, Spanning<InputValue<S>>>>,
meta_args: &'a Option<Vec<Argument<S>>>,
) -> Self
where
S: Clone,
{
if meta_args.is_some() && args.is_none() {
args = Some(IndexMap::new());
}
if let (Some(args), Some(meta_args)) = (&mut args, meta_args) {
for arg in meta_args {
let arg_name = arg.name.as_str();
if args.get(arg_name).is_none() {
if let Some(val) = arg.default_value.as_ref() {
args.insert(arg_name, Spanning::unlocated(val.clone()));
}
}
}
}
Self { args }
}
pub fn get<T>(&self, name: &str) -> FieldResult<Option<T>, S>
where
T: FromInputValue<S>,
T::Error: IntoFieldError<S>,
{
self.args
.as_ref()
.and_then(|args| args.get(name))
.map(|spanning| &spanning.item)
.map(InputValue::convert)
.transpose()
.map_err(IntoFieldError::into_field_error)
}
pub fn get_input_value(&self, name: &str) -> Option<&Spanning<InputValue<S>>> {
self.args.as_ref().and_then(|args| args.get(name))
}
}
pub trait GraphQLValue<S = DefaultScalarValue>
where
S: ScalarValue,
{
type Context;
type TypeInfo;
fn type_name(&self, info: &Self::TypeInfo) -> Option<ArcStr>;
fn resolve_field(
&self,
_info: &Self::TypeInfo,
_field_name: &str,
_arguments: &Arguments<S>,
_executor: &Executor<Self::Context, S>,
) -> ExecutionResult<S> {
panic!("GraphQLValue::resolve_field() must be implemented by objects and interfaces");
}
fn resolve_into_type(
&self,
info: &Self::TypeInfo,
type_name: &str,
selection_set: Option<&[Selection<S>]>,
executor: &Executor<Self::Context, S>,
) -> ExecutionResult<S> {
if self.type_name(info).unwrap() == type_name {
self.resolve(info, selection_set, executor)
} else {
panic!(
"GraphQLValue::resolve_into_type() must be implemented by unions and interfaces"
);
}
}
#[expect(unused_variables, reason = "nice interface declaration")]
fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String {
panic!(
"GraphQLValue::concrete_type_name() must be implemented by unions, interfaces \
and objects",
);
}
fn resolve(
&self,
info: &Self::TypeInfo,
selection_set: Option<&[Selection<S>]>,
executor: &Executor<Self::Context, S>,
) -> ExecutionResult<S> {
if let Some(sel) = selection_set {
let mut res = Object::with_capacity(sel.len());
Ok(
if resolve_selection_set_into(self, info, sel, executor, &mut res) {
Value::Object(res)
} else {
Value::null()
},
)
} else {
panic!("GraphQLValue::resolve() must be implemented by non-object output types");
}
}
}
pub trait GraphQLType<S = DefaultScalarValue>: GraphQLValue<S>
where
S: ScalarValue,
{
fn name(info: &Self::TypeInfo) -> Option<ArcStr>;
fn meta(info: &Self::TypeInfo, registry: &mut Registry<S>) -> MetaType<S>;
}
pub(crate) fn resolve_selection_set_into<T, S>(
instance: &T,
info: &T::TypeInfo,
selection_set: &[Selection<S>],
executor: &Executor<T::Context, S>,
result: &mut Object<S>,
) -> bool
where
T: GraphQLValue<S> + ?Sized,
S: ScalarValue,
{
let meta_type = executor
.schema()
.concrete_type_by_name(
instance
.type_name(info)
.expect("Resolving named type's selection set"),
)
.expect("Type not found in schema");
for selection in selection_set {
match *selection {
Selection::Field(Spanning {
item: ref f,
ref span,
}) => {
if is_excluded(&f.directives, executor.variables()) {
continue;
}
let response_name = f.alias.as_ref().unwrap_or(&f.name).item;
if f.name.item == "__typename" {
result.add_field(
response_name,
Value::Scalar(instance.concrete_type_name(executor.context(), info).into()),
);
continue;
}
let meta_field = meta_type.field_by_name(f.name.item).unwrap_or_else(|| {
panic!(
"Field {} not found on type {:?}",
f.name.item,
meta_type.name(),
)
});
let exec_vars = executor.variables();
let sub_exec = executor.field_sub_executor(
response_name,
f.name.item,
span.start,
f.selection_set.as_ref().map(|v| &v[..]),
);
let field_result = instance.resolve_field(
info,
f.name.item,
&Arguments::new(
f.arguments.as_ref().map(|m| {
m.item
.iter()
.filter_map(|(k, v)| {
let val = v.item.clone().into_const(exec_vars)?;
Some((k.item, Spanning::new(v.span, val)))
})
.collect()
}),
&meta_field.arguments,
),
&sub_exec,
);
match field_result {
Ok(Value::Null) if meta_field.field_type.is_non_null() => return false,
Ok(v) => merge_key_into(result, response_name, v),
Err(e) => {
sub_exec.push_error_at(e, span.start);
if meta_field.field_type.is_non_null() {
return false;
}
result.add_field(response_name, Value::null());
}
}
}
Selection::FragmentSpread(Spanning {
item: ref spread,
span,
}) => {
if is_excluded(&spread.directives, executor.variables()) {
continue;
}
let fragment = &executor
.fragment_by_name(spread.name.item)
.expect("Fragment could not be found");
let sub_exec = executor.type_sub_executor(
Some(fragment.type_condition.item),
Some(&fragment.selection_set[..]),
);
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
let type_name = instance.type_name(info);
if executor
.schema()
.is_named_subtype(&concrete_type_name, fragment.type_condition.item)
|| Some(fragment.type_condition.item) == type_name.as_deref()
{
let sub_result = instance.resolve_into_type(
info,
&concrete_type_name,
Some(&fragment.selection_set[..]),
&sub_exec,
);
if let Ok(Value::Object(object)) = sub_result {
for (k, v) in object {
merge_key_into(result, &k, v);
}
} else {
if let Err(e) = sub_result {
sub_exec.push_error_at(e, span.start);
}
return false;
}
}
}
Selection::InlineFragment(Spanning {
item: ref fragment,
ref span,
}) => {
if is_excluded(&fragment.directives, executor.variables()) {
continue;
}
let sub_exec = executor.type_sub_executor(
fragment.type_condition.as_ref().map(|c| c.item),
Some(&fragment.selection_set[..]),
);
if let Some(ref type_condition) = fragment.type_condition {
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
if executor
.schema()
.is_named_subtype(&concrete_type_name, type_condition.item)
{
let sub_result = instance.resolve_into_type(
info,
&concrete_type_name,
Some(&fragment.selection_set[..]),
&sub_exec,
);
if let Ok(Value::Object(object)) = sub_result {
for (k, v) in object {
merge_key_into(result, &k, v);
}
} else {
if let Err(e) = sub_result {
sub_exec.push_error_at(e, span.start);
}
return false;
}
}
} else if !resolve_selection_set_into(
instance,
info,
&fragment.selection_set[..],
&sub_exec,
result,
) {
return false;
}
}
}
}
true
}
pub(super) fn is_excluded<S>(
directives: &Option<Vec<Spanning<Directive<S>>>>,
vars: &Variables<S>,
) -> bool
where
S: ScalarValue,
{
if let Some(directives) = directives {
for Spanning {
item: directive, ..
} in directives
{
let condition: bool = directive
.arguments
.iter()
.flat_map(|m| m.item.get("if"))
.filter_map(|v| v.item.clone().into_const(vars)?.convert().ok())
.next()
.unwrap();
if (directive.name.item == "skip" && condition)
|| (directive.name.item == "include" && !condition)
{
return true;
}
}
}
false
}
pub(crate) fn merge_key_into<S>(result: &mut Object<S>, response_name: &str, value: Value<S>) {
if let Some(v) = result.get_mut_field_value(response_name) {
match v {
Value::Object(dest_obj) => {
if let Value::Object(src_obj) = value {
merge_maps(dest_obj, src_obj);
}
}
Value::List(dest_list) => {
if let Value::List(src_list) = value {
dest_list.iter_mut().zip(src_list).for_each(|(d, s)| {
if let Value::Object(d_obj) = d {
if let Value::Object(s_obj) = s {
merge_maps(d_obj, s_obj);
}
}
});
}
}
_ => {}
}
return;
}
result.add_field(response_name, value);
}
fn merge_maps<S>(dest: &mut Object<S>, src: Object<S>) {
for (key, value) in src {
if dest.contains_field(&key) {
merge_key_into(dest, &key, value);
} else {
dest.add_field(key, value);
}
}
}