Skip to content

Commit e565732

Browse files
committed
WIP union selection fix
1 parent d79941c commit e565732

File tree

5 files changed

+160
-107
lines changed

5 files changed

+160
-107
lines changed

graphql_client/tests/interfaces/interface_with_fragment_query.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
fragment PublicStatus on Named {
2+
__typename
23
displayName
34
}
45

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use proc_macro2::{Ident, Span, TokenStream};
1+
use proc_macro2::TokenStream;
22
use query::QueryContext;
33
use selection::Selection;
44
use std::cell::Cell;
@@ -19,35 +19,16 @@ pub(crate) struct GqlFragment {
1919
impl GqlFragment {
2020
/// Generate all the Rust code required by the fragment's selection.
2121
pub(crate) fn to_rust(&self, context: &QueryContext) -> Result<TokenStream, ::failure::Error> {
22-
let derives = context.response_derives();
23-
let name_ident = Ident::new(&self.name, Span::call_site());
24-
let opt_object = context.schema.objects.get(&self.on);
25-
let (field_impls, fields) = if let Some(object) = opt_object {
26-
let field_impls =
27-
object.field_impls_for_selection(context, &self.selection, &self.name)?;
28-
let fields =
29-
object.response_fields_for_selection(context, &self.selection, &self.name)?;
30-
(field_impls, fields)
22+
if let Some(obj) = context.schema.objects.get(&self.on) {
23+
obj.response_for_selection(context, &self.selection, &self.name)
3124
} else if let Some(iface) = context.schema.interfaces.get(&self.on) {
32-
let field_impls =
33-
iface.field_impls_for_selection(context, &self.selection, &self.name)?;
34-
let fields =
35-
iface.response_fields_for_selection(context, &self.selection, &self.name)?;
36-
(field_impls, fields)
25+
iface.response_for_selection(context, &self.selection, &self.name)
3726
} else {
38-
panic!(
39-
"fragment '{}' cannot operate on unknown type '{}'",
40-
self.name, self.on
41-
);
42-
};
43-
44-
Ok(quote! {
45-
#derives
46-
pub struct #name_ident {
47-
#(#fields,)*
48-
}
49-
50-
#(#field_impls)*
51-
})
27+
Err(format_err!(
28+
"Fragment {} is defined on unknown type: {}",
29+
self.name,
30+
self.on
31+
))?
32+
}
5233
}
5334
}

graphql_client_codegen/src/query.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ impl QueryContext {
4848
}
4949
}
5050

51+
///
5152
pub(crate) fn maybe_expand_field(
5253
&self,
5354
ty: &str,
@@ -67,7 +68,7 @@ impl QueryContext {
6768
unn.is_required.set(true);
6869
unn.response_for_selection(self, &selection, prefix)
6970
} else {
70-
Ok(quote!())
71+
Err(format_err!("Unknown type: {}", ty))
7172
}
7273
}
7374

graphql_client_codegen/src/selection.rs

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
use constants::*;
22
use graphql_parser::query::SelectionSet;
3+
use std::collections::BTreeMap;
4+
use std::error::Error;
35

46
/// A single object field as part of a selection.
5-
#[derive(Clone, Debug, PartialEq)]
7+
#[derive(Clone, Debug, PartialEq, Eq)]
68
pub struct SelectionField {
79
pub alias: Option<String>,
810
pub name: String,
911
pub fields: Selection,
1012
}
1113

1214
/// A spread fragment in a selection (e.g. `...MyFragment`).
13-
#[derive(Clone, Debug, PartialEq)]
15+
#[derive(Clone, Debug, PartialEq, Eq)]
1416
pub struct SelectionFragmentSpread {
1517
pub fragment_name: String,
1618
}
1719

1820
/// An inline fragment as part of a selection (e.g. `...on MyThing { name }`).
19-
#[derive(Clone, Debug, PartialEq)]
21+
#[derive(Clone, Debug, PartialEq, Eq)]
2022
pub struct SelectionInlineFragment {
2123
pub on: String,
2224
pub fields: Selection,
2325
}
2426

2527
/// An element in a query selection.
26-
#[derive(Clone, Debug, PartialEq)]
28+
#[derive(Clone, Debug, PartialEq, Eq)]
2729
pub enum SelectionItem {
2830
Field(SelectionField),
2931
FragmentSpread(SelectionFragmentSpread),
@@ -41,7 +43,7 @@ impl SelectionItem {
4143
}
4244
}
4345

44-
#[derive(Clone, Debug, PartialEq)]
46+
#[derive(Clone, Debug, PartialEq, Eq)]
4547
pub struct Selection(pub Vec<SelectionItem>);
4648

4749
impl Selection {
@@ -71,6 +73,57 @@ impl Selection {
7173
.next()
7274
}
7375

76+
// Implementation helper for `selected_variants_on_union`.
77+
fn selected_variants_on_union_inner<'s>(
78+
&'s self,
79+
context: &'s crate::query::QueryContext,
80+
selected_variants: &mut BTreeMap<&'s str, Vec<&'s SelectionItem>>,
81+
) -> Result<(), Box<Error>> {
82+
for item in self.0.iter() {
83+
match item {
84+
SelectionItem::Field(_) => Err(format_err!("field selection on union"))?,
85+
SelectionItem::InlineFragment(inline_fragment) => {
86+
selected_variants
87+
.entry(inline_fragment.on.as_str())
88+
.and_modify(|entry| entry.extend(&inline_fragment.fields.0))
89+
.or_insert_with(|| {
90+
let mut set = Vec::new();
91+
set.extend(&inline_fragment.fields.0);
92+
set
93+
});
94+
}
95+
SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => {
96+
let fragment = context
97+
.fragments
98+
.get(fragment_name)
99+
.ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name))?;
100+
101+
fragment
102+
.selection
103+
.selected_variants_on_union_inner(context, selected_variants)?;
104+
}
105+
}
106+
}
107+
108+
Ok(())
109+
}
110+
111+
/// This method should only be invoked on selections on union fields. It returns the selected varianst and the
112+
///
113+
/// Importantly, it will "flatten" the fragments and handle multiple selections of the same variant.
114+
///
115+
/// The `context` argument is required so we can expand the fragments.
116+
pub(crate) fn selected_variants_on_union<'s>(
117+
&'s self,
118+
context: &'s crate::query::QueryContext,
119+
) -> Result<BTreeMap<&'s str, Vec<&'s SelectionItem>>, Box<Error>> {
120+
let mut selected_variants = BTreeMap::new();
121+
122+
self.selected_variants_on_union_inner(context, &mut selected_variants)?;
123+
124+
Ok(selected_variants)
125+
}
126+
74127
#[cfg(test)]
75128
pub(crate) fn new_empty() -> Selection {
76129
Selection(Vec::new())

graphql_client_codegen/src/unions.rs

Lines changed: 89 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::cell::Cell;
77
use std::collections::BTreeSet;
88

99
#[derive(Debug, Clone, PartialEq)]
10-
pub struct GqlUnion {
10+
pub(crate) struct GqlUnion {
1111
pub description: Option<String>,
1212
pub variants: BTreeSet<String>,
1313
pub is_required: Cell<bool>,
@@ -22,90 +22,104 @@ enum UnionError {
2222
MissingTypename { union_name: String },
2323
}
2424

25-
type UnionVariantResult = Result<(Vec<TokenStream>, Vec<TokenStream>, Vec<String>), failure::Error>;
26-
27-
pub(crate) fn union_variants(
28-
selection: &Selection,
29-
query_context: &QueryContext,
25+
type UnionVariantResult<'selection> =
26+
Result<(Vec<TokenStream>, Vec<TokenStream>, Vec<&'selection str>), failure::Error>;
27+
28+
/// Returns a triple.
29+
///
30+
/// - The first element is the union variants to be inserted directly into the `enum` declaration.
31+
/// - The second is the structs for each variant's sub-selection
32+
/// - The last one contains which fields have been selected on the union, so we can make the enum exhaustive by complementing with those missing.
33+
pub(crate) fn union_variants<'selection>(
34+
selection: &'selection Selection,
35+
context: &QueryContext,
3036
prefix: &str,
31-
) -> UnionVariantResult {
32-
let mut children_definitions = Vec::new();
33-
let mut used_variants = Vec::with_capacity(selection.0.len());
34-
35-
let variants: Result<Vec<TokenStream>, failure::Error> = selection
36-
.0
37-
.iter()
38-
// ignore __typename
39-
.filter(|item| {
40-
if let SelectionItem::Field(f) = item {
41-
f.name != TYPENAME_FIELD
42-
} else {
43-
true
44-
}
45-
})
46-
.map(|item| {
47-
let (on, fields) = match item {
48-
SelectionItem::Field(_) => Err(format_err!("field selection on union"))?,
49-
SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => {
50-
let fragment = query_context
51-
.fragments
52-
.get(fragment_name)
53-
.ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name))?;
54-
55-
(&fragment.on, &fragment.selection)
56-
}
57-
SelectionItem::InlineFragment(frag) => (&frag.on, &frag.fields),
58-
};
59-
let variant_name = Ident::new(&on, Span::call_site());
60-
used_variants.push(on.to_string());
61-
62-
let new_prefix = format!("{}On{}", prefix, on);
63-
64-
let variant_type = Ident::new(&new_prefix, Span::call_site());
65-
66-
let field_object_type = query_context
67-
.schema
68-
.objects
69-
.get(on)
70-
.map(|_f| query_context.maybe_expand_field(&on, &fields, &new_prefix));
71-
let field_interface = query_context
72-
.schema
73-
.interfaces
74-
.get(on)
75-
.map(|_f| query_context.maybe_expand_field(&on, &fields, &new_prefix));
76-
let field_union_type = query_context
77-
.schema
78-
.unions
79-
.get(on)
80-
.map(|_f| query_context.maybe_expand_field(&on, &fields, &new_prefix));
81-
82-
match field_object_type.or(field_interface).or(field_union_type) {
83-
Some(tokens) => children_definitions.push(tokens?),
84-
None => Err(UnionError::UnknownType { ty: on.to_string() })?,
85-
};
86-
87-
Ok(quote! {
88-
#variant_name(#variant_type)
89-
})
90-
})
91-
.collect();
37+
) -> UnionVariantResult<'selection> {
38+
let selection = selection.selected_variants_on_union(context)?;
39+
let used_variants = selection.keys().collect();
40+
let mut children_definitions = Vec::with_capacity(selection.size());
41+
let mut variants = Vec::with_capacity(selection.size());
42+
43+
for (on, fields) in selection.iter() {
44+
let variant_name = Ident::new(&on, Span::call_site());
45+
used_variants.push(on.to_string());
46+
47+
let new_prefix = format!("{}On{}", prefix, on);
48+
49+
let variant_type = Ident::new(&new_prefix, Span::call_site());
50+
51+
let field_object_type = context
52+
.schema
53+
.objects
54+
.get(on)
55+
.map(|_f| context.maybe_expand_field(&on, fields, &new_prefix));
56+
let field_interface = context
57+
.schema
58+
.interfaces
59+
.get(on)
60+
.map(|_f| context.maybe_expand_field(&on, fields, &new_prefix));
61+
let field_union_type = context
62+
.schema
63+
.unions
64+
.get(on)
65+
.map(|_f| context.maybe_expand_field(&on, fields, &new_prefix));
66+
67+
match field_object_type.or(field_interface).or(field_union_type) {
68+
Some(tokens) => children_definitions.push(tokens?),
69+
None => Err(UnionError::UnknownType { ty: on.to_string() })?,
70+
};
9271

93-
let variants = variants?;
72+
variants.push(quote! {
73+
#variant_name(#variant_type)
74+
})
75+
}
9476

9577
Ok((variants, children_definitions, used_variants))
78+
79+
// let variants: Result<Vec<TokenStream>, failure::Error> = selection
80+
// .0
81+
// .iter()
82+
// // ignore __typename
83+
// .filter(|item| {
84+
// if let SelectionItem::Field(f) = item {
85+
// f.name != TYPENAME_FIELD
86+
// } else {
87+
// true
88+
// }
89+
// })
90+
// // .flat_map(
91+
// // |item| -> impl ::std::iter::Iterator<Item = Result<(_, _), failure::Error>> {
92+
// // match item {
93+
// // SelectionItem::Field(_) => Err(format_err!("field selection on union"))?,
94+
// // SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => {
95+
// // let fragment = query_context
96+
// // .fragments
97+
// // .get(fragment_name)
98+
// // .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name))?;
99+
// // // found the bug! `on` doesn't mean the same here as in the inline fragments
100+
// // std::iter::once(Ok((&fragment.on, &fragment.selection)))
101+
// // }
102+
// // SelectionItem::InlineFragment(frag) => {
103+
// // std::iter::once(Ok((&frag.on, &frag.fields)))
104+
// // }
105+
// // }
106+
// // },
107+
// // )
108+
// // // .collect::<Result<_, _>>()?
109+
// .map(|result: Result<(_, _), failure::Error>| -> Result<_, _> {
110+
// let Ok((on, fields)) = result?;
111+
112+
// let variants = variants?;
96113
}
97114

98115
impl GqlUnion {
116+
/// Returns the code to deserialize this union in the response given the query selection.
99117
pub(crate) fn response_for_selection(
100118
&self,
101119
query_context: &QueryContext,
102120
selection: &Selection,
103121
prefix: &str,
104122
) -> Result<TokenStream, failure::Error> {
105-
let struct_name = Ident::new(prefix, Span::call_site());
106-
let derives = query_context.response_derives();
107-
108-
// TODO: do this inside fragments
109123
let typename_field = selection.extract_typename(query_context);
110124

111125
if typename_field.is_none() {
@@ -114,6 +128,9 @@ impl GqlUnion {
114128
})?;
115129
}
116130

131+
let struct_name = Ident::new(prefix, Span::call_site());
132+
let derives = query_context.response_derives();
133+
117134
let (mut variants, children_definitions, used_variants) =
118135
union_variants(selection, query_context, prefix)?;
119136

0 commit comments

Comments
 (0)