Skip to content

Commit

Permalink
Merge pull request #472 from bryanmylee/master
Browse files Browse the repository at this point in the history
Implement field merging
  • Loading branch information
olirice authored Dec 26, 2023
2 parents b6a3458 + 3ac586a commit eecd43e
Show file tree
Hide file tree
Showing 8 changed files with 641 additions and 121 deletions.
179 changes: 100 additions & 79 deletions src/builder.rs

Large diffs are not rendered by default.

253 changes: 222 additions & 31 deletions src/parser_util.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::graphql::{EnumSource, __InputValue, __Type, ___Type};
use crate::graphql::*;
use crate::gson;
use graphql_parser::query::*;
use graphql_parser::Pos;
use std::collections::HashMap;

pub fn alias_or_name<'a, T>(query_field: &graphql_parser::query::Field<'a, T>) -> String
Expand All @@ -14,34 +15,221 @@ where
.unwrap_or_else(|| query_field.name.as_ref().to_string())
}

pub fn normalize_selection_set<'a, 'b, T>(
selection_set: &'b SelectionSet<'a, T>,
fragment_definitions: &'b Vec<FragmentDefinition<'a, T>>,
pub fn merge_fields<'a, T, I>(
target_fields: &mut Vec<Field<'a, T>>,
next_fields: I,
type_name: &str,
field_map: &HashMap<String, __Field>,
) -> Result<(), String>
where
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug + Clone,
I: IntoIterator<Item = Field<'a, T>>,
{
for field in next_fields {
merge_field(target_fields, field, type_name, field_map)?
}
Ok(())
}

pub fn merge_field<'a, T>(
target_fields: &mut Vec<Field<'a, T>>,
mut field: Field<'a, T>,
type_name: &str,
field_map: &HashMap<String, __Field>,
) -> Result<(), String>
where
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug + Clone,
{
let Some((matching_idx, matching_field)) = target_fields
.iter()
.enumerate()
.find(|(_, target)| alias_or_name(target) == alias_or_name(&field))
else {
target_fields.push(field);
return Ok(());
};

can_fields_merge(&matching_field, &field, type_name, field_map)?;

field.position = field.position.min(matching_field.position);

field.selection_set.span =
min_encapsulating_span(field.selection_set.span, matching_field.selection_set.span);

// Subfields will be normalized and properly merged on a later pass.
field
.selection_set
.items
.extend(matching_field.selection_set.items.clone());

target_fields[matching_idx] = field;

Ok(())
}

pub fn can_fields_merge<'a, T>(
field_a: &Field<'a, T>,
field_b: &Field<'a, T>,
type_name: &str,
field_map: &HashMap<String, __Field>,
) -> Result<(), String>
where
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug + Clone,
{
let Some(_field_a) = field_map.get(field_a.name.as_ref()) else {
return Err(format!(
"Unknown field '{}' on type '{}'",
field_a.name.as_ref(),
type_name
));
};
let Some(_field_b) = field_map.get(field_b.name.as_ref()) else {
return Err(format!(
"Unknown field '{}' on type '{}'",
field_b.name.as_ref(),
type_name
));
};

has_same_type_shape(
&alias_or_name(field_a),
type_name,
&_field_a.type_,
&_field_b.type_,
)?;

if field_a.name != field_b.name {
return Err(format!(
"Fields '{}' on type '{}' conflict because '{}' and '{}' are different fields",
alias_or_name(field_a),
type_name,
field_a.name.as_ref(),
field_b.name.as_ref(),
));
}

for (arg_a_name, arg_a_value) in field_a.arguments.iter() {
let arg_b_value = field_b.arguments.iter().find_map(|(name, value)| {
if name == arg_a_name {
Some(value)
} else {
None
}
});
let args_match = match arg_b_value {
None => false,
Some(arg_b_value) => arg_b_value == arg_a_value,
};
if !args_match {
return Err(format!(
"Fields '{}' on type '{}' conflict because they have differing arguments",
alias_or_name(field_a),
type_name,
));
}
}

Ok(())
}

pub fn has_same_type_shape(
field_name: &str,
type_name: &str,
type_a: &__Type,
type_b: &__Type,
) -> Result<(), String> {
let mut type_a = type_a;
let mut type_b = type_b;

if matches!(type_a, __Type::NonNull(_)) || matches!(type_b, __Type::NonNull(_)) {
if let (__Type::NonNull(nullable_type_a), __Type::NonNull(nullable_type_b)) =
(type_a, type_b)
{
type_a = nullable_type_a.type_.as_ref();
type_b = nullable_type_b.type_.as_ref();
} else {
return Err(format!(
"Fields '{}' on type '{}' conflict because only one is non nullable",
field_name, type_name,
));
}
}

if matches!(type_a, __Type::List(_)) || matches!(type_b, __Type::List(_)) {
if let (__Type::List(list_type_a), __Type::List(list_type_b)) = (type_a, type_b) {
type_a = list_type_a.type_.as_ref();
type_b = list_type_b.type_.as_ref();
} else {
return Err(format!(
"Fields '{}' on type '{}' conflict because only one is a list type",
field_name, type_name,
));
}

return has_same_type_shape(field_name, type_name, type_a, type_b);
}

if matches!(type_a, __Type::Enum(_))
|| matches!(type_b, __Type::Enum(_))
|| matches!(type_a, __Type::Scalar(_))
|| matches!(type_b, __Type::Scalar(_))
{
return if type_a == type_b {
Ok(())
} else {
Err(format!(
"Fields '{}' on type '{}' conflict due to mismatched types",
field_name, type_name,
))
};
}

// TODO handle composite types?

// Subfield type shapes will be checked on a later pass.
Ok(())
}

pub fn min_encapsulating_span(a: (Pos, Pos), b: (Pos, Pos)) -> (Pos, Pos) {
(a.0.min(b.0), a.1.max(b.1))
}

pub fn normalize_selection_set<'a, T>(
selection_set: &SelectionSet<'a, T>,
fragment_definitions: &Vec<FragmentDefinition<'a, T>>,
type_name: &String, // for inline fragments
variables: &serde_json::Value, // for directives
) -> Result<Vec<&'b Field<'a, T>>, String>
field_type: &__Type,
) -> Result<Vec<Field<'a, T>>, String>
where
T: Text<'a> + Eq + AsRef<str>,
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug + Clone,
{
let mut selections: Vec<&'b Field<'a, T>> = vec![];
let mut normalized_fields: Vec<Field<'a, T>> = vec![];

let field_map = field_map(&field_type.unmodified_type());

for selection in &selection_set.items {
let sel = selection;
match normalize_selection(sel, fragment_definitions, type_name, variables) {
Ok(sels) => selections.extend(sels),
match normalize_selection(
selection,
fragment_definitions,
type_name,
variables,
field_type,
) {
Ok(fields) => merge_fields(&mut normalized_fields, fields, type_name, &field_map)?,
Err(err) => return Err(err),
}
}
Ok(selections)
Ok(normalized_fields)
}

/// Combines @skip and @include
pub fn selection_is_skipped<'a, 'b, T>(
query_selection: &'b Selection<'a, T>,
pub fn selection_is_skipped<'a, T>(
query_selection: &Selection<'a, T>,
variables: &serde_json::Value,
) -> Result<bool, String>
where
T: Text<'a> + Eq + AsRef<str>,
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug,
{
let directives = match query_selection {
Selection::Field(x) => &x.directives,
Expand Down Expand Up @@ -130,24 +318,27 @@ where
}

/// Normalizes literal selections, fragment spreads, and inline fragments
pub fn normalize_selection<'a, 'b, T>(
query_selection: &'b Selection<'a, T>,
fragment_definitions: &'b Vec<FragmentDefinition<'a, T>>,
pub fn normalize_selection<'a, T>(
query_selection: &Selection<'a, T>,
fragment_definitions: &Vec<FragmentDefinition<'a, T>>,
type_name: &String, // for inline fragments
variables: &serde_json::Value, // for directives
) -> Result<Vec<&'b Field<'a, T>>, String>
field_type: &__Type, // for field merging shape check
) -> Result<Vec<Field<'a, T>>, String>
where
T: Text<'a> + Eq + AsRef<str>,
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug + Clone,
{
let mut selections: Vec<&Field<'a, T>> = vec![];
let mut normalized_fields: Vec<Field<'a, T>> = vec![];

if selection_is_skipped(query_selection, variables)? {
return Ok(selections);
return Ok(normalized_fields);
}

let field_map = field_map(&field_type.unmodified_type());

match query_selection {
Selection::Field(field) => {
selections.push(field);
merge_field(&mut normalized_fields, field.clone(), type_name, &field_map)?;
}
Selection::FragmentSpread(fragment_spread) => {
let frag_name = &fragment_spread.fragment_name;
Expand All @@ -173,14 +364,15 @@ where
};

// TODO handle directives?
let frag_selections = normalize_selection_set(
let frag_fields = normalize_selection_set(
&frag_def.selection_set,
fragment_definitions,
type_name,
variables,
field_type,
);
match frag_selections {
Ok(sels) => selections.extend(sels.iter()),
match frag_fields {
Ok(fields) => merge_fields(&mut normalized_fields, fields, type_name, &field_map)?,
Err(err) => return Err(err),
};
}
Expand All @@ -193,18 +385,19 @@ where
};

if inline_fragment_applies {
let infrag_selections = normalize_selection_set(
let infrag_fields = normalize_selection_set(
&inline_fragment.selection_set,
fragment_definitions,
type_name,
variables,
field_type,
)?;
selections.extend(infrag_selections.iter());
merge_fields(&mut normalized_fields, infrag_fields, type_name, &field_map)?;
}
}
}

Ok(selections)
Ok(normalized_fields)
}

pub fn to_gson<'a, T>(
Expand All @@ -213,7 +406,7 @@ pub fn to_gson<'a, T>(
variable_definitions: &Vec<VariableDefinition<'a, T>>,
) -> Result<gson::Value, String>
where
T: Text<'a> + AsRef<str>,
T: Text<'a> + AsRef<str> + std::fmt::Debug,
{
let result = match graphql_value {
Value::Null => gson::Value::Null,
Expand Down Expand Up @@ -273,7 +466,6 @@ where
}

pub fn validate_arg_from_type(type_: &__Type, value: &gson::Value) -> Result<gson::Value, String> {
use crate::graphql::Scalar;
use crate::gson::Number as GsonNumber;
use crate::gson::Value as GsonValue;

Expand Down Expand Up @@ -482,7 +674,6 @@ pub fn validate_arg_from_input_object(
input_type: &__Type,
value: &gson::Value,
) -> Result<gson::Value, String> {
use crate::graphql::__TypeKind;
use crate::gson::Value as GsonValue;

let input_type_name = input_type.name().unwrap_or_default();
Expand Down
Loading

0 comments on commit eecd43e

Please sign in to comment.