Skip to content

Commit eecd43e

Browse files
authored
Merge pull request #472 from bryanmylee/master
Implement field merging
2 parents b6a3458 + 3ac586a commit eecd43e

8 files changed

+641
-121
lines changed

src/builder.rs

Lines changed: 100 additions & 79 deletions
Large diffs are not rendered by default.

src/parser_util.rs

Lines changed: 222 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use crate::graphql::{EnumSource, __InputValue, __Type, ___Type};
1+
use crate::graphql::*;
22
use crate::gson;
33
use graphql_parser::query::*;
4+
use graphql_parser::Pos;
45
use std::collections::HashMap;
56

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

17-
pub fn normalize_selection_set<'a, 'b, T>(
18-
selection_set: &'b SelectionSet<'a, T>,
19-
fragment_definitions: &'b Vec<FragmentDefinition<'a, T>>,
18+
pub fn merge_fields<'a, T, I>(
19+
target_fields: &mut Vec<Field<'a, T>>,
20+
next_fields: I,
21+
type_name: &str,
22+
field_map: &HashMap<String, __Field>,
23+
) -> Result<(), String>
24+
where
25+
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug + Clone,
26+
I: IntoIterator<Item = Field<'a, T>>,
27+
{
28+
for field in next_fields {
29+
merge_field(target_fields, field, type_name, field_map)?
30+
}
31+
Ok(())
32+
}
33+
34+
pub fn merge_field<'a, T>(
35+
target_fields: &mut Vec<Field<'a, T>>,
36+
mut field: Field<'a, T>,
37+
type_name: &str,
38+
field_map: &HashMap<String, __Field>,
39+
) -> Result<(), String>
40+
where
41+
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug + Clone,
42+
{
43+
let Some((matching_idx, matching_field)) = target_fields
44+
.iter()
45+
.enumerate()
46+
.find(|(_, target)| alias_or_name(target) == alias_or_name(&field))
47+
else {
48+
target_fields.push(field);
49+
return Ok(());
50+
};
51+
52+
can_fields_merge(&matching_field, &field, type_name, field_map)?;
53+
54+
field.position = field.position.min(matching_field.position);
55+
56+
field.selection_set.span =
57+
min_encapsulating_span(field.selection_set.span, matching_field.selection_set.span);
58+
59+
// Subfields will be normalized and properly merged on a later pass.
60+
field
61+
.selection_set
62+
.items
63+
.extend(matching_field.selection_set.items.clone());
64+
65+
target_fields[matching_idx] = field;
66+
67+
Ok(())
68+
}
69+
70+
pub fn can_fields_merge<'a, T>(
71+
field_a: &Field<'a, T>,
72+
field_b: &Field<'a, T>,
73+
type_name: &str,
74+
field_map: &HashMap<String, __Field>,
75+
) -> Result<(), String>
76+
where
77+
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug + Clone,
78+
{
79+
let Some(_field_a) = field_map.get(field_a.name.as_ref()) else {
80+
return Err(format!(
81+
"Unknown field '{}' on type '{}'",
82+
field_a.name.as_ref(),
83+
type_name
84+
));
85+
};
86+
let Some(_field_b) = field_map.get(field_b.name.as_ref()) else {
87+
return Err(format!(
88+
"Unknown field '{}' on type '{}'",
89+
field_b.name.as_ref(),
90+
type_name
91+
));
92+
};
93+
94+
has_same_type_shape(
95+
&alias_or_name(field_a),
96+
type_name,
97+
&_field_a.type_,
98+
&_field_b.type_,
99+
)?;
100+
101+
if field_a.name != field_b.name {
102+
return Err(format!(
103+
"Fields '{}' on type '{}' conflict because '{}' and '{}' are different fields",
104+
alias_or_name(field_a),
105+
type_name,
106+
field_a.name.as_ref(),
107+
field_b.name.as_ref(),
108+
));
109+
}
110+
111+
for (arg_a_name, arg_a_value) in field_a.arguments.iter() {
112+
let arg_b_value = field_b.arguments.iter().find_map(|(name, value)| {
113+
if name == arg_a_name {
114+
Some(value)
115+
} else {
116+
None
117+
}
118+
});
119+
let args_match = match arg_b_value {
120+
None => false,
121+
Some(arg_b_value) => arg_b_value == arg_a_value,
122+
};
123+
if !args_match {
124+
return Err(format!(
125+
"Fields '{}' on type '{}' conflict because they have differing arguments",
126+
alias_or_name(field_a),
127+
type_name,
128+
));
129+
}
130+
}
131+
132+
Ok(())
133+
}
134+
135+
pub fn has_same_type_shape(
136+
field_name: &str,
137+
type_name: &str,
138+
type_a: &__Type,
139+
type_b: &__Type,
140+
) -> Result<(), String> {
141+
let mut type_a = type_a;
142+
let mut type_b = type_b;
143+
144+
if matches!(type_a, __Type::NonNull(_)) || matches!(type_b, __Type::NonNull(_)) {
145+
if let (__Type::NonNull(nullable_type_a), __Type::NonNull(nullable_type_b)) =
146+
(type_a, type_b)
147+
{
148+
type_a = nullable_type_a.type_.as_ref();
149+
type_b = nullable_type_b.type_.as_ref();
150+
} else {
151+
return Err(format!(
152+
"Fields '{}' on type '{}' conflict because only one is non nullable",
153+
field_name, type_name,
154+
));
155+
}
156+
}
157+
158+
if matches!(type_a, __Type::List(_)) || matches!(type_b, __Type::List(_)) {
159+
if let (__Type::List(list_type_a), __Type::List(list_type_b)) = (type_a, type_b) {
160+
type_a = list_type_a.type_.as_ref();
161+
type_b = list_type_b.type_.as_ref();
162+
} else {
163+
return Err(format!(
164+
"Fields '{}' on type '{}' conflict because only one is a list type",
165+
field_name, type_name,
166+
));
167+
}
168+
169+
return has_same_type_shape(field_name, type_name, type_a, type_b);
170+
}
171+
172+
if matches!(type_a, __Type::Enum(_))
173+
|| matches!(type_b, __Type::Enum(_))
174+
|| matches!(type_a, __Type::Scalar(_))
175+
|| matches!(type_b, __Type::Scalar(_))
176+
{
177+
return if type_a == type_b {
178+
Ok(())
179+
} else {
180+
Err(format!(
181+
"Fields '{}' on type '{}' conflict due to mismatched types",
182+
field_name, type_name,
183+
))
184+
};
185+
}
186+
187+
// TODO handle composite types?
188+
189+
// Subfield type shapes will be checked on a later pass.
190+
Ok(())
191+
}
192+
193+
pub fn min_encapsulating_span(a: (Pos, Pos), b: (Pos, Pos)) -> (Pos, Pos) {
194+
(a.0.min(b.0), a.1.max(b.1))
195+
}
196+
197+
pub fn normalize_selection_set<'a, T>(
198+
selection_set: &SelectionSet<'a, T>,
199+
fragment_definitions: &Vec<FragmentDefinition<'a, T>>,
20200
type_name: &String, // for inline fragments
21201
variables: &serde_json::Value, // for directives
22-
) -> Result<Vec<&'b Field<'a, T>>, String>
202+
field_type: &__Type,
203+
) -> Result<Vec<Field<'a, T>>, String>
23204
where
24-
T: Text<'a> + Eq + AsRef<str>,
205+
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug + Clone,
25206
{
26-
let mut selections: Vec<&'b Field<'a, T>> = vec![];
207+
let mut normalized_fields: Vec<Field<'a, T>> = vec![];
208+
209+
let field_map = field_map(&field_type.unmodified_type());
27210

28211
for selection in &selection_set.items {
29-
let sel = selection;
30-
match normalize_selection(sel, fragment_definitions, type_name, variables) {
31-
Ok(sels) => selections.extend(sels),
212+
match normalize_selection(
213+
selection,
214+
fragment_definitions,
215+
type_name,
216+
variables,
217+
field_type,
218+
) {
219+
Ok(fields) => merge_fields(&mut normalized_fields, fields, type_name, &field_map)?,
32220
Err(err) => return Err(err),
33221
}
34222
}
35-
Ok(selections)
223+
Ok(normalized_fields)
36224
}
37225

38226
/// Combines @skip and @include
39-
pub fn selection_is_skipped<'a, 'b, T>(
40-
query_selection: &'b Selection<'a, T>,
227+
pub fn selection_is_skipped<'a, T>(
228+
query_selection: &Selection<'a, T>,
41229
variables: &serde_json::Value,
42230
) -> Result<bool, String>
43231
where
44-
T: Text<'a> + Eq + AsRef<str>,
232+
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug,
45233
{
46234
let directives = match query_selection {
47235
Selection::Field(x) => &x.directives,
@@ -130,24 +318,27 @@ where
130318
}
131319

132320
/// Normalizes literal selections, fragment spreads, and inline fragments
133-
pub fn normalize_selection<'a, 'b, T>(
134-
query_selection: &'b Selection<'a, T>,
135-
fragment_definitions: &'b Vec<FragmentDefinition<'a, T>>,
321+
pub fn normalize_selection<'a, T>(
322+
query_selection: &Selection<'a, T>,
323+
fragment_definitions: &Vec<FragmentDefinition<'a, T>>,
136324
type_name: &String, // for inline fragments
137325
variables: &serde_json::Value, // for directives
138-
) -> Result<Vec<&'b Field<'a, T>>, String>
326+
field_type: &__Type, // for field merging shape check
327+
) -> Result<Vec<Field<'a, T>>, String>
139328
where
140-
T: Text<'a> + Eq + AsRef<str>,
329+
T: Text<'a> + Eq + AsRef<str> + std::fmt::Debug + Clone,
141330
{
142-
let mut selections: Vec<&Field<'a, T>> = vec![];
331+
let mut normalized_fields: Vec<Field<'a, T>> = vec![];
143332

144333
if selection_is_skipped(query_selection, variables)? {
145-
return Ok(selections);
334+
return Ok(normalized_fields);
146335
}
147336

337+
let field_map = field_map(&field_type.unmodified_type());
338+
148339
match query_selection {
149340
Selection::Field(field) => {
150-
selections.push(field);
341+
merge_field(&mut normalized_fields, field.clone(), type_name, &field_map)?;
151342
}
152343
Selection::FragmentSpread(fragment_spread) => {
153344
let frag_name = &fragment_spread.fragment_name;
@@ -173,14 +364,15 @@ where
173364
};
174365

175366
// TODO handle directives?
176-
let frag_selections = normalize_selection_set(
367+
let frag_fields = normalize_selection_set(
177368
&frag_def.selection_set,
178369
fragment_definitions,
179370
type_name,
180371
variables,
372+
field_type,
181373
);
182-
match frag_selections {
183-
Ok(sels) => selections.extend(sels.iter()),
374+
match frag_fields {
375+
Ok(fields) => merge_fields(&mut normalized_fields, fields, type_name, &field_map)?,
184376
Err(err) => return Err(err),
185377
};
186378
}
@@ -193,18 +385,19 @@ where
193385
};
194386

195387
if inline_fragment_applies {
196-
let infrag_selections = normalize_selection_set(
388+
let infrag_fields = normalize_selection_set(
197389
&inline_fragment.selection_set,
198390
fragment_definitions,
199391
type_name,
200392
variables,
393+
field_type,
201394
)?;
202-
selections.extend(infrag_selections.iter());
395+
merge_fields(&mut normalized_fields, infrag_fields, type_name, &field_map)?;
203396
}
204397
}
205398
}
206399

207-
Ok(selections)
400+
Ok(normalized_fields)
208401
}
209402

210403
pub fn to_gson<'a, T>(
@@ -213,7 +406,7 @@ pub fn to_gson<'a, T>(
213406
variable_definitions: &Vec<VariableDefinition<'a, T>>,
214407
) -> Result<gson::Value, String>
215408
where
216-
T: Text<'a> + AsRef<str>,
409+
T: Text<'a> + AsRef<str> + std::fmt::Debug,
217410
{
218411
let result = match graphql_value {
219412
Value::Null => gson::Value::Null,
@@ -273,7 +466,6 @@ where
273466
}
274467

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

@@ -482,7 +674,6 @@ pub fn validate_arg_from_input_object(
482674
input_type: &__Type,
483675
value: &gson::Value,
484676
) -> Result<gson::Value, String> {
485-
use crate::graphql::__TypeKind;
486677
use crate::gson::Value as GsonValue;
487678

488679
let input_type_name = input_type.name().unwrap_or_default();

0 commit comments

Comments
 (0)