Skip to content

Commit b66bbeb

Browse files
committed
Enforce the presence of __typename on union selections
1 parent d10b5b4 commit b66bbeb

File tree

3 files changed

+58
-34
lines changed

3 files changed

+58
-34
lines changed

graphql_query_derive/src/objects.rs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,22 @@ impl GqlObject {
3535

3636
pub fn from_graphql_parser_object(obj: ::graphql_parser::schema::ObjectType) -> Self {
3737
let mut item = GqlObject::new(obj.name.into());
38-
item.fields.extend(obj.fields.iter()
39-
.map(|f| GqlObjectField {
40-
name: f.name.clone(),
41-
type_: FieldType::from(f.field_type.clone()),
42-
}));
38+
item.fields
39+
.extend(obj.fields.iter().map(|f| GqlObjectField {
40+
name: f.name.clone(),
41+
type_: FieldType::from(f.field_type.clone()),
42+
}));
4343
item
4444
}
4545

4646
pub fn from_introspected_schema_json(obj: &::introspection_response::FullType) -> Self {
4747
let mut item = GqlObject::new(obj.name.clone().expect("missing object name").into());
48-
let fields = obj
49-
.fields
50-
.clone()
51-
.unwrap()
52-
.into_iter()
53-
.filter_map(|t| {
54-
t.map(|t| GqlObjectField {
55-
name: t.name.expect("field name"),
56-
type_: FieldType::from(t.type_.expect("field type")),
57-
})
58-
});
48+
let fields = obj.fields.clone().unwrap().into_iter().filter_map(|t| {
49+
t.map(|t| GqlObjectField {
50+
name: t.name.expect("field name"),
51+
type_: FieldType::from(t.type_.expect("field type")),
52+
})
53+
});
5954

6055
item.fields.extend(fields);
6156

graphql_query_derive/src/schema.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,9 @@ impl ::std::convert::From<graphql_parser::schema::Document> for Schema {
192192
.or_insert_with(|| vec![name.clone()]);
193193
}
194194

195-
schema.objects.insert(
196-
obj.name.clone(),
197-
GqlObject::from_graphql_parser_object(obj),
198-
);
195+
schema
196+
.objects
197+
.insert(obj.name.clone(), GqlObject::from_graphql_parser_object(obj));
199198
}
200199
schema::TypeDefinition::Enum(enm) => {
201200
schema.enums.insert(
@@ -350,8 +349,8 @@ impl ::std::convert::From<::introspection_response::IntrospectionResponse> for S
350349

351350
#[cfg(test)]
352351
mod tests {
353-
use constants::*;
354352
use super::*;
353+
use constants::*;
355354
use proc_macro2::{Ident, Span};
356355

357356
#[test]

graphql_query_derive/src/unions.rs

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use constants::*;
12
use failure;
23
use heck::SnakeCase;
34
use proc_macro2::{Ident, Span, TokenStream};
@@ -13,6 +14,8 @@ pub struct GqlUnion(pub BTreeSet<String>);
1314
enum UnionError {
1415
#[fail(display = "Unknown type: {}", ty)]
1516
UnknownType { ty: String },
17+
#[fail(display = "Missing __typename in selection for {}", union_name)]
18+
MissingTypename { union_name: String }
1619
}
1720

1821
impl GqlUnion {
@@ -24,13 +27,34 @@ impl GqlUnion {
2427
) -> Result<TokenStream, failure::Error> {
2528
let struct_name = Ident::new(prefix, Span::call_site());
2629
let mut children_definitions: Vec<TokenStream> = Vec::new();
30+
31+
let typename_field = selection.0.iter().find(|f| {
32+
if let SelectionItem::Field(f) = f {
33+
f.name == TYPENAME_FIELD
34+
} else {
35+
false
36+
}
37+
});
38+
39+
if typename_field.is_none() {
40+
Err(UnionError::MissingTypename { union_name: prefix.into() })?;
41+
}
42+
2743
let variants: Result<Vec<TokenStream>, failure::Error> = selection
2844
.0
2945
.iter()
46+
// ignore __typename
47+
.filter(|item| {
48+
if let SelectionItem::Field(f) = item {
49+
f.name != TYPENAME_FIELD
50+
} else {
51+
true
52+
}
53+
})
3054
.map(|item| {
3155
match item {
32-
SelectionItem::Field(_) => unreachable!("field selection on union"),
33-
SelectionItem::FragmentSpread(_) => unreachable!("fragment spread on union"),
56+
SelectionItem::Field(f) => panic!("field selection on union"),
57+
SelectionItem::FragmentSpread(_) => panic!("fragment spread on union"),
3458
SelectionItem::InlineFragment(frag) => {
3559
let variant_name = Ident::new(&frag.on, Span::call_site());
3660

@@ -160,23 +184,23 @@ mod tests {
160184

161185
assert!(result.is_err());
162186

163-
assert_eq!(format!("{}", result.unwrap_err()), "");
187+
assert_eq!(format!("{}", result.unwrap_err()), "Missing __typename in selection for Meow");
164188
}
165189

166190
#[test]
167191
fn union_response_for_selection_works() {
168192
let fields = vec![
193+
SelectionItem::Field(SelectionField {
194+
name: "__typename".to_string(),
195+
fields: Selection(vec![]),
196+
}),
169197
SelectionItem::InlineFragment(SelectionInlineFragment {
170198
on: "User".to_string(),
171199
fields: Selection(vec![
172200
SelectionItem::Field(SelectionField {
173201
name: "first_name".to_string(),
174202
fields: Selection(vec![]),
175203
}),
176-
SelectionItem::Field(SelectionField {
177-
name: "__typename".to_string(),
178-
fields: Selection(vec![]),
179-
}),
180204
]),
181205
}),
182206
SelectionItem::InlineFragment(SelectionInlineFragment {
@@ -186,10 +210,6 @@ mod tests {
186210
name: "title".to_string(),
187211
fields: Selection(vec![]),
188212
}),
189-
SelectionItem::Field(SelectionField {
190-
name: "__typename".to_string(),
191-
fields: Selection(vec![]),
192-
}),
193213
]),
194214
}),
195215
];
@@ -207,13 +227,17 @@ mod tests {
207227
GqlObject {
208228
name: "User".to_string(),
209229
fields: vec![
230+
GqlObjectField {
231+
name: "__typename".to_string(),
232+
type_: FieldType::Named(string_type()),
233+
},
210234
GqlObjectField {
211235
name: "first_name".to_string(),
212-
type_: FieldType::Named(Ident::new("String", Span::call_site())),
236+
type_: FieldType::Named(string_type()),
213237
},
214238
GqlObjectField {
215239
name: "last_name".to_string(),
216-
type_: FieldType::Named(Ident::new("String", Span::call_site())),
240+
type_: FieldType::Named(string_type()),
217241
},
218242
GqlObjectField {
219243
name: "created_at".to_string(),
@@ -228,6 +252,10 @@ mod tests {
228252
GqlObject {
229253
name: "Organization".to_string(),
230254
fields: vec![
255+
GqlObjectField {
256+
name: "__typename".to_string(),
257+
type_: FieldType::Named(string_type()),
258+
},
231259
GqlObjectField {
232260
name: "title".to_string(),
233261
type_: FieldType::Named(Ident::new("String", Span::call_site())),
@@ -242,6 +270,8 @@ mod tests {
242270

243271
let result = union.response_for_selection(&context, &selection, &prefix);
244272

273+
println!("{:?}", result);
274+
245275
assert!(result.is_ok());
246276

247277
assert_eq!(

0 commit comments

Comments
 (0)