//! Query processing.
//!
//! Parsing, formatting and manipulation of queries.
use std::collections::HashMap;
use std::collections::HashSet;
use apollo_parser::ast;
use derivative::Derivative;
use graphql::Error;
use serde_json_bytes::ByteString;
use tracing::level_filters::LevelFilter;
use crate::error::FetchError;
use crate::graphql::Request;
use crate::graphql::Response;
use crate::json_ext::Object;
use crate::json_ext::Path;
use crate::json_ext::PathElement;
use crate::json_ext::Value;
use crate::query_planner::fetch::OperationKind;
use crate::*;
pub(crate) const TYPENAME: &str = "__typename";
/// A GraphQL query.
#[derive(Debug, Derivative, Default)]
#[derivative(PartialEq, Hash, Eq)]
pub(crate) struct Query {
string: String,
#[derivative(PartialEq = "ignore", Hash = "ignore")]
fragments: Fragments,
#[derivative(PartialEq = "ignore", Hash = "ignore")]
pub(crate) operations: Vec<Operation>,
#[derivative(PartialEq = "ignore", Hash = "ignore")]
pub(crate) subselections: HashMap<(Option<Path>, String), Query>,
}
impl Query {
/// Re-format the response value to match this query.
///
/// This will discard unrequested fields and re-order the output to match the order of the
/// query.
#[tracing::instrument(skip_all, level = "trace")]
pub(crate) fn format_response(
&self,
response: &mut Response,
operation_name: Option<&str>,
is_deferred: bool,
variables: Object,
schema: &Schema,
) {
let data = std::mem::take(&mut response.data);
if let Some(Value::Object(mut input)) = data {
let operation = self.operation(operation_name);
if is_deferred {
if let Some(subselection) = &response.subselection {
// Get subselection from hashmap
match self.subselections.get(&(
//FIXME: we should not have optional paths at all in the subselections map
response.path.clone().or_else(|| Some(Path::default())),
subselection.clone(),
)) {
Some(subselection_query) => {
let mut output = Object::default();
let operation = &subselection_query.operations[0];
let mut parameters = FormatParameters {
variables: &variables,
schema,
errors: Vec::new(),
};
response.data = Some(
match self.apply_root_selection_set(
operation,
&mut parameters,
&mut input,
&mut output,
&mut Path::default(),
) {
Ok(()) => output.into(),
Err(InvalidValue) => Value::Null,
},
);
response.errors.extend(parameters.errors.into_iter());
return;
}
None => failfast_debug!("can't find subselection for {:?}", subselection),
}
// the primary query was empty, we return an empty object
} else {
response.data = Some(Value::Object(Object::default()));
return;
}
} else if let Some(operation) = operation {
let mut output = Object::default();
let all_variables = if operation.variables.is_empty() {
variables
} else {
operation
.variables
.iter()
.filter_map(|(k, (_, opt))| opt.as_ref().map(|v| (k, v)))
.chain(variables.iter())
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
};
let mut parameters = FormatParameters {
variables: &all_variables,
schema,
errors: Vec::new(),
};
response.data = Some(
match self.apply_root_selection_set(
operation,
&mut parameters,
&mut input,
&mut output,
&mut Path::default(),
) {
Ok(()) => output.into(),
Err(InvalidValue) => Value::Null,
},
);
response.errors.extend(parameters.errors.into_iter());
return;
} else {
failfast_debug!("can't find operation for {:?}", operation_name);
}
} else {
failfast_debug!("invalid type for data in response. data: {:#?}", data);
}
response.data = Some(Value::default());
}
pub(crate) fn parse(
query: impl Into<String>,
schema: &Schema,
configuration: &Configuration,
) -> Result<Self, SpecError> {
let string = query.into();
let parser = apollo_parser::Parser::with_recursion_limit(
string.as_str(),
configuration.server.experimental_parser_recursion_limit,
);
let tree = parser.parse();
// Trace log recursion limit data
let recursion_limit = tree.recursion_limit();
tracing::trace!(?recursion_limit, "recursion limit data");
let errors = tree
.errors()
.map(|err| format!("{:?}", err))
.collect::<Vec<_>>();
if !errors.is_empty() {
let errors = errors.join(", ");
failfast_debug!("parsing error(s): {}", errors);
return Err(SpecError::ParsingError(errors));
}
let document = tree.document();
let fragments = Fragments::from_ast(&document, schema)?;
let operations: Vec<Operation> = document
.definitions()
.filter_map(|definition| {
if let ast::Definition::OperationDefinition(operation) = definition {
Some(operation)
} else {
None
}
})
.map(|operation| Operation::from_ast(operation, schema))
.collect::<Result<Vec<_>, SpecError>>()?;
Ok(Query {
string,
fragments,
operations,
subselections: HashMap::new(),
})
}
#[allow(clippy::too_many_arguments)]
fn format_value(
&self,
parameters: &mut FormatParameters,
field_type: &FieldType,
input: &mut Value,
output: &mut Value,
path: &mut Path,
parent_type: &FieldType,
selection_set: &[Selection],
) -> Result<(), InvalidValue> {
// for every type, if we have an invalid value, we will replace it with null
// and return Ok(()), because values are optional by default
match field_type {
// for non null types, we validate with the inner type, then if we get an InvalidValue
// we set it to null and immediately return an error instead of Ok(()), because we
// want the error to go up until the next nullable parent
FieldType::NonNull(inner_type) => {
match self.format_value(
parameters,
inner_type,
input,
output,
path,
field_type,
selection_set,
) {
Err(_) => Err(InvalidValue),
Ok(_) => {
if output.is_null() {
let message = match path.last() {
Some(PathElement::Key(k)) => format!(
"Cannot return null for non-nullable field {parent_type}.{}",
k
),
Some(PathElement::Index(i)) => format!(
"Cannot return null for non-nullable array element of type {inner_type} at index {}",
i
),
_ => todo!(),
};
parameters.errors.push(Error {
message,
path: Some(path.clone()),
..Error::default()
});
Err(InvalidValue)
} else {
Ok(())
}
}
}
}
// if the list contains nonnullable types, we will receive a Err(InvalidValue)
// and should replace the entire list with null
// if the types are nullable, the inner call to filter_errors will take care
// of setting the current entry to null
FieldType::List(inner_type) => match input {
Value::Array(input_array) => {
if output.is_null() {
*output = Value::Array(
std::iter::repeat(Value::Null)
.take(input_array.len())
.collect(),
);
}
let output_array = output.as_array_mut().ok_or(InvalidValue)?;
match input_array
.iter_mut()
.enumerate()
.try_for_each(|(i, element)| {
path.push(PathElement::Index(i));
let res = self.format_value(
parameters,
inner_type,
element,
&mut output_array[i],
path,
field_type,
selection_set,
);
path.pop();
res
}) {
Err(InvalidValue) => {
*output = Value::Null;
Ok(())
}
Ok(()) => Ok(()),
}
}
_ => Ok(()),
},
FieldType::Named(type_name) | FieldType::Introspection(type_name) => {
// we cannot know about the expected format of custom scalars
// so we must pass them directly to the client
if parameters.schema.custom_scalars.contains(type_name) {
*output = input.clone();
return Ok(());
} else if let Some(enum_type) = parameters.schema.enums.get(type_name) {
return match input.as_str() {
Some(s) => {
if enum_type.contains(s) {
*output = input.clone();
Ok(())
} else {
*output = Value::Null;
Ok(())
}
}
None => {
*output = Value::Null;
Ok(())
}
};
}
match input {
Value::Object(ref mut input_object) => {
if let Some(input_type) =
input_object.get(TYPENAME).and_then(|val| val.as_str())
{
if !parameters.schema.object_types.contains_key(input_type) {
*output = Value::Null;
return Ok(());
}
}
if output.is_null() {
*output = Value::Object(Object::default());
}
let output_object = output.as_object_mut().ok_or(InvalidValue)?;
match self.apply_selection_set(
selection_set,
parameters,
input_object,
output_object,
path,
&FieldType::Named(type_name.to_string()),
) {
Ok(()) => Ok(()),
Err(InvalidValue) => {
*output = Value::Null;
Ok(())
}
}
}
_ => {
*output = Value::Null;
Ok(())
}
}
}
// the rest of the possible types just need to validate the expected value
FieldType::Int => {
let opt = if input.is_i64() {
input.as_i64().and_then(|i| i32::try_from(i).ok())
} else if input.is_u64() {
input.as_i64().and_then(|i| i32::try_from(i).ok())
} else {
None
};
// if the value is invalid, we do not insert it in the output object
// which is equivalent to inserting null
if opt.is_some() {
*output = input.clone();
} else {
*output = Value::Null;
}
Ok(())
}
FieldType::Float => {
if input.as_f64().is_some() {
*output = input.clone();
} else {
*output = Value::Null;
}
Ok(())
}
FieldType::Boolean => {
if input.as_bool().is_some() {
*output = input.clone();
} else {
*output = Value::Null;
}
Ok(())
}
FieldType::String => {
if input.as_str().is_some() {
*output = input.clone();
} else {
*output = Value::Null;
}
Ok(())
}
FieldType::Id => {
if input.is_string() || input.is_i64() || input.is_u64() || input.is_f64() {
*output = input.clone();
} else {
*output = Value::Null;
}
Ok(())
}
}
}
fn apply_selection_set(
&self,
selection_set: &[Selection],
parameters: &mut FormatParameters,
input: &mut Object,
output: &mut Object,
path: &mut Path,
parent_type: &FieldType,
) -> Result<(), InvalidValue> {
// For skip and include, using .unwrap_or is legit here because
// validate_variables should have already checked that
// the variable is present and it is of the correct type
for selection in selection_set {
match selection {
Selection::Field {
name,
alias,
selection_set,
field_type,
skip,
include,
} => {
let field_name = alias.as_ref().unwrap_or(name);
if skip.should_skip(parameters.variables).unwrap_or(false) {
continue;
}
if !include.should_include(parameters.variables).unwrap_or(true) {
continue;
}
if let Some(input_value) = input.get_mut(field_name.as_str()) {
// if there's already a value for that key in the output it means either:
// - the value is a scalar and was moved into output using take(), replacing
// the input value with Null
// - the value was already null and is already present in output
// if we expect an object or list at that key, output will already contain
// an object or list and then input_value cannot be null
if input_value.is_null() && output.contains_key(field_name.as_str()) {
continue;
}
let selection_set = selection_set.as_deref().unwrap_or_default();
let output_value =
output.entry((*field_name).clone()).or_insert(Value::Null);
if field_name.as_str() == TYPENAME {
if input_value.is_string() {
*output_value = input_value.clone();
}
} else {
path.push(PathElement::Key(field_name.as_str().to_string()));
let res = self.format_value(
parameters,
field_type,
input_value,
output_value,
path,
parent_type,
selection_set,
);
path.pop();
res?
}
} else {
if !output.contains_key(field_name.as_str()) {
output.insert((*field_name).clone(), Value::Null);
}
if field_type.is_non_null() {
parameters.errors.push(Error {
message: format!(
"Cannot return null for non-nullable field {parent_type}.{}",
field_name.as_str()
),
path: Some(path.clone()),
..Error::default()
});
return Err(InvalidValue);
}
}
}
Selection::InlineFragment {
type_condition,
selection_set,
skip,
include,
known_type,
} => {
if skip.should_skip(parameters.variables).unwrap_or(false) {
continue;
}
if !include.should_include(parameters.variables).unwrap_or(true) {
continue;
}
let is_apply = if let Some(input_type) =
input.get(TYPENAME).and_then(|val| val.as_str())
{
// check if the fragment matches the input type directly, and if not, check if the
// input type is a subtype of the fragment's type condition (interface, union)
input_type == type_condition.as_str()
|| parameters.schema.is_subtype(type_condition, input_type)
} else {
// known_type = true means that from the query's shape, we know
// we should get the right type here. But in the case we get a
// __typename field and it does not match, we should not apply
// that fragment
// If the type condition is an interface and the current known type implements it
known_type
.as_ref()
.map(|k| parameters.schema.is_subtype(type_condition, k))
.unwrap_or_default()
|| known_type.as_deref() == Some(type_condition.as_str())
};
if is_apply {
self.apply_selection_set(
selection_set,
parameters,
input,
output,
path,
parent_type,
)?;
}
}
Selection::FragmentSpread {
name,
known_type,
skip,
include,
} => {
if skip.should_skip(parameters.variables).unwrap_or(false) {
continue;
}
if !include.should_include(parameters.variables).unwrap_or(true) {
continue;
}
if let Some(fragment) = self.fragments.get(name) {
if fragment
.skip
.should_skip(parameters.variables)
.unwrap_or(false)
{
continue;
}
if !fragment
.include
.should_include(parameters.variables)
.unwrap_or(true)
{
continue;
}
let is_apply = if let Some(input_type) =
input.get(TYPENAME).and_then(|val| val.as_str())
{
// check if the fragment matches the input type directly, and if not, check if the
// input type is a subtype of the fragment's type condition (interface, union)
input_type == fragment.type_condition.as_str()
|| parameters
.schema
.is_subtype(&fragment.type_condition, input_type)
} else {
// If the type condition is an interface and the current known type implements it
known_type
.as_ref()
.map(|k| parameters.schema.is_subtype(&fragment.type_condition, k))
.unwrap_or_default()
|| known_type.as_deref() == Some(fragment.type_condition.as_str())
};
if is_apply {
self.apply_selection_set(
&fragment.selection_set,
parameters,
input,
output,
path,
parent_type,
)?;
}
} else {
// the fragment should have been already checked with the schema
failfast_debug!("missing fragment named: {}", name);
}
}
}
}
Ok(())
}
fn apply_root_selection_set(
&self,
operation: &Operation,
parameters: &mut FormatParameters,
input: &mut Object,
output: &mut Object,
path: &mut Path,
) -> Result<(), InvalidValue> {
for selection in &operation.selection_set {
match selection {
Selection::Field {
name,
alias,
selection_set,
field_type,
skip,
include,
} => {
// Using .unwrap_or is legit here because
// validate_variables should have already checked that
// the variable is present and it is of the correct type
if skip.should_skip(parameters.variables).unwrap_or(false) {
continue;
}
if !include.should_include(parameters.variables).unwrap_or(true) {
continue;
}
let field_name = alias.as_ref().unwrap_or(name);
let field_name_str = field_name.as_str();
if let Some(input_value) = input.get_mut(field_name_str) {
// if there's already a value for that key in the output it means either:
// - the value is a scalar and was moved into output using take(), replacing
// the input value with Null
// - the value was already null and is already present in output
// if we expect an object or list at that key, output will already contain
// an object or list and then input_value cannot be null
if input_value.is_null() && output.contains_key(field_name_str) {
continue;
}
let selection_set = selection_set.as_deref().unwrap_or_default();
let output_value =
output.entry((*field_name).clone()).or_insert(Value::Null);
path.push(PathElement::Key(field_name_str.to_string()));
let res = self.format_value(
parameters,
field_type,
input_value,
output_value,
path,
field_type,
selection_set,
);
path.pop();
res?
} else if field_name_str == TYPENAME {
if !output.contains_key(field_name_str) {
output.insert(
field_name.clone(),
Value::String(operation.kind.to_string().into()),
);
}
} else if field_type.is_non_null() {
parameters.errors.push(Error {
message: format!(
"Cannot return null for non-nullable field {}.{field_name_str}",
operation.kind
),
path: Some(path.clone()),
..Error::default()
});
return Err(InvalidValue);
} else {
output.insert(field_name.clone(), Value::Null);
}
}
Selection::InlineFragment {
type_condition,
selection_set,
..
} => {
// top level objects will not provide a __typename field
if type_condition.as_str()
!= parameters.schema.root_operation_name(operation.kind)
{
return Err(InvalidValue);
}
self.apply_selection_set(
selection_set,
parameters,
input,
output,
path,
&FieldType::Named(type_condition.clone()),
)?;
}
Selection::FragmentSpread {
name,
known_type: _,
skip: _,
include: _,
} => {
if let Some(fragment) = self.fragments.get(name) {
let operation_type_name =
parameters.schema.root_operation_name(operation.kind);
let is_apply = {
// check if the fragment matches the input type directly, and if not, check if the
// input type is a subtype of the fragment's type condition (interface, union)
operation_type_name == fragment.type_condition.as_str()
|| parameters
.schema
.is_subtype(&fragment.type_condition, operation_type_name)
};
if !is_apply {
return Err(InvalidValue);
}
self.apply_selection_set(
&fragment.selection_set,
parameters,
input,
output,
path,
&FieldType::Named(operation_type_name.into()),
)?;
} else {
// the fragment should have been already checked with the schema
failfast_debug!("missing fragment named: {}", name);
}
}
}
}
Ok(())
}
/// Validate a [`Request`]'s variables against this [`Query`] using a provided [`Schema`].
#[tracing::instrument(skip_all, level = "trace")]
pub(crate) fn validate_variables(
&self,
request: &Request,
schema: &Schema,
) -> Result<(), Response> {
let operation_name = request.operation_name.as_deref();
let operation_variable_types =
self.operations
.iter()
.fold(HashMap::new(), |mut acc, operation| {
if operation_name.is_none() || operation.name.as_deref() == operation_name {
acc.extend(operation.variables.iter().map(|(k, v)| (k.as_str(), v)))
}
acc
});
if LevelFilter::current() >= LevelFilter::DEBUG {
let known_variables = operation_variable_types.keys().cloned().collect();
let provided_variables = request
.variables
.keys()
.map(|k| k.as_str())
.collect::<HashSet<_>>();
let unknown_variables = provided_variables
.difference(&known_variables)
.collect::<Vec<_>>();
if !unknown_variables.is_empty() {
failfast_debug!(
"Received variable unknown to the query: {:?}",
unknown_variables,
);
}
}
let errors = operation_variable_types
.iter()
.filter_map(|(name, (ty, default_value))| {
let value = request
.variables
.get(*name)
.or(default_value.as_ref())
.unwrap_or(&Value::Null);
ty.validate_input_value(value, schema).err().map(|_| {
FetchError::ValidationInvalidTypeVariable {
name: name.to_string(),
}
.to_graphql_error(None)
})
})
.collect::<Vec<_>>();
if errors.is_empty() {
Ok(())
} else {
Err(Response::builder().errors(errors).build())
}
}
pub(crate) fn contains_only_typename(&self) -> bool {
self.operations.len() == 1 && self.operations[0].is_only_typename()
}
pub(crate) fn contains_introspection(&self) -> bool {
self.operations.iter().any(Operation::is_introspection)
}
pub(crate) fn variable_value<'a>(
&'a self,
operation_name: Option<&str>,
variable_name: &str,
variables: &'a Object,
) -> Option<&'a Value> {
variables
.get(variable_name)
.or_else(|| self.default_variable_value(operation_name, variable_name))
}
pub(crate) fn default_variable_value(
&self,
operation_name: Option<&str>,
variable_name: &str,
) -> Option<&Value> {
self.operation(operation_name).and_then(|op| {
op.variables
.get(variable_name)
.and_then(|(_, value)| value.as_ref())
})
}
fn operation(&self, operation_name: Option<&str>) -> Option<&Operation> {
match operation_name {
Some(name) => self
.operations
.iter()
// we should have an error if the only operation is anonymous but the query specifies a name
.find(|op| {
if let Some(op_name) = op.name.as_deref() {
op_name == name
} else {
false
}
}),
None => self.operations.get(0),
}
}
}
/// Intermediate structure for arguments passed through the entire formatting
struct FormatParameters<'a> {
variables: &'a Object,
errors: Vec<Error>,
schema: &'a Schema,
}
#[derive(Debug)]
pub(crate) struct Operation {
name: Option<String>,
kind: OperationKind,
selection_set: Vec<Selection>,
variables: HashMap<ByteString, (FieldType, Option<Value>)>,
}
impl Operation {
// clippy false positive due to the bytes crate
// ref: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type
#[allow(clippy::mutable_key_type)]
// Spec: https://spec.graphql.org/draft/#sec-Language.Operations
fn from_ast(operation: ast::OperationDefinition, schema: &Schema) -> Result<Self, SpecError> {
let name = operation.name().map(|x| x.text().to_string());
let kind = operation
.operation_type()
.and_then(|op| {
op.query_token()
.map(|_| OperationKind::Query)
.or_else(|| op.mutation_token().map(|_| OperationKind::Mutation))
.or_else(|| op.subscription_token().map(|_| OperationKind::Subscription))
})
.unwrap_or(OperationKind::Query);
let current_field_type = match kind {
OperationKind::Query => FieldType::Named("Query".to_string()),
OperationKind::Mutation => FieldType::Named("Mutation".to_string()),
OperationKind::Subscription => return Err(SpecError::SubscriptionNotSupported),
};
let selection_set = operation
.selection_set()
.ok_or_else(|| {
SpecError::ParsingError(
"the node SelectionSet is not optional in the spec".to_string(),
)
})?
.selections()
.map(|selection| Selection::from_ast(selection, ¤t_field_type, schema, 0))
.collect::<Result<Vec<Option<_>>, _>>()?
.into_iter()
.flatten()
.collect::<Vec<Selection>>();
let variables = operation
.variable_definitions()
.iter()
.flat_map(|x| x.variable_definitions())
.map(|definition| {
let name = definition
.variable()
.ok_or_else(|| {
SpecError::ParsingError(
"the node Variable is not optional in the spec".to_string(),
)
})?
.name()
.ok_or_else(|| {
SpecError::ParsingError(
"the node Name is not optional in the spec".to_string(),
)
})?
.text()
.to_string();
let ty = FieldType::try_from(definition.ty().ok_or_else(|| {
SpecError::ParsingError("the node Type is not optional in the spec".to_string())
})?)?;
Ok((
ByteString::from(name),
(ty, parse_default_value(&definition)),
))
})
.collect::<Result<_, _>>()?;
Ok(Operation {
selection_set,
name,
variables,
kind,
})
}
/// A query or mutation containing only `__typename` at the root level
fn is_only_typename(&self) -> bool {
self.selection_set.len() == 1
&& self
.selection_set
.get(0)
.map(|s| matches!(s, Selection::Field {name, ..} if name.as_str() == TYPENAME))
.unwrap_or_default()
}
fn is_introspection(&self) -> bool {
// If the only field is `__typename` it's considered as an introspection query
if self.is_only_typename() {
return true;
}
self.selection_set.iter().all(|sel| match sel {
Selection::Field { name, .. } => {
let name = name.as_str();
// `__typename` can only be resolved in runtime,
// so this query cannot be seen as an introspection query
name == "__schema" || name == "__type"
}
_ => false,
})
}
pub(crate) fn kind(&self) -> &OperationKind {
&self.kind
}
}
impl From<ast::OperationType> for OperationKind {
// Spec: https://spec.graphql.org/draft/#OperationType
fn from(operation_type: ast::OperationType) -> Self {
if operation_type.query_token().is_some() {
Self::Query
} else if operation_type.mutation_token().is_some() {
Self::Mutation
} else if operation_type.subscription_token().is_some() {
Self::Subscription
} else {
unreachable!(
"either the `query` token is provided, either the `mutation` token, \
either the `subscription` token; qed"
)
}
}
}
fn parse_default_value(definition: &ast::VariableDefinition) -> Option<Value> {
definition
.default_value()
.and_then(|v| v.value())
.and_then(|value| parse_value(&value))
}
fn parse_value(value: &ast::Value) -> Option<Value> {
match value {
ast::Value::Variable(_) => None,
ast::Value::StringValue(s) => Some(s.to_string().into()),
ast::Value::FloatValue(f) => f.to_string().parse::<f64>().ok().map(Into::into),
ast::Value::IntValue(i) => {
let s = i.to_string();
s.parse::<i64>()
.ok()
.map(Into::into)
.or_else(|| s.parse::<u64>().ok().map(Into::into))
}
ast::Value::BooleanValue(b) => {
match (b.true_token().is_some(), b.false_token().is_some()) {
(true, false) => Some(Value::Bool(true)),
(false, true) => Some(Value::Bool(false)),
_ => None,
}
}
ast::Value::NullValue(_) => Some(Value::Null),
ast::Value::EnumValue(e) => e.name().map(|n| n.text().to_string().into()),
ast::Value::ListValue(l) => l
.values()
.map(|v| parse_value(&v))
.collect::<Option<_>>()
.map(Value::Array),
ast::Value::ObjectValue(o) => o
.object_fields()
.map(|field| match (field.name(), field.value()) {
(Some(name), Some(value)) => {
parse_value(&value).map(|v| (name.text().to_string().into(), v))
}
_ => None,
})
.collect::<Option<_>>()
.map(Value::Object),
}
}
#[cfg(test)]
mod tests;