Skip to content

Commit 723e948

Browse files
committed
graphql_query_derive: support aliases in queries
When querying a very abstract API (e.g., "find me database entry X"), the name of fields may be better named in the query side. Generated type names also have to use the alias because queries can bind different field selections of the same field to different aliases. Fixes #5.
1 parent ee0bf0d commit 723e948

File tree

7 files changed

+89
-5
lines changed

7 files changed

+89
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
4040

4141
- Improved some codegen error messages, giving more context. Thank @mathstuf!
4242

43+
- Aliases in queries are now supported.
44+
4345
### Fixed
4446

4547
- Handle all Rust keywords as field names in codegen by appending `_` to the generated names, so a field called `type` in a GraphQL query will become a `type_` field in the generated struct. Thanks to @scrogson!

graphql_query_derive/src/selection.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use graphql_parser::query::SelectionSet;
33

44
#[derive(Clone, Debug, PartialEq)]
55
pub struct SelectionField {
6+
pub alias: Option<String>,
67
pub name: String,
78
pub fields: Selection,
89
}
@@ -54,6 +55,7 @@ impl<'a> ::std::convert::From<&'a SelectionSet> for Selection {
5455
for item in &selection_set.items {
5556
let converted = match item {
5657
Selection::Field(f) => SelectionItem::Field(SelectionField {
58+
alias: f.alias.as_ref().map(|alias| alias.to_string()),
5759
name: f.name.to_string(),
5860
fields: (&f.selection_set).into(),
5961
}),
@@ -99,6 +101,7 @@ mod tests {
99101
rating
100102
}
101103
pawsCount
104+
aliased: sillyName
102105
}
103106
}
104107
"##;
@@ -123,34 +126,45 @@ mod tests {
123126
assert_eq!(
124127
selection,
125128
Selection(vec![SelectionItem::Field(SelectionField {
129+
alias: None,
126130
name: "animal".to_string(),
127131
fields: Selection(vec![
128132
SelectionItem::Field(SelectionField {
133+
alias: None,
129134
name: "isCat".to_string(),
130135
fields: Selection(Vec::new()),
131136
}),
132137
SelectionItem::Field(SelectionField {
138+
alias: None,
133139
name: "isHorse".to_string(),
134140
fields: Selection(Vec::new()),
135141
}),
136142
SelectionItem::FragmentSpread(SelectionFragmentSpread {
137143
fragment_name: "Timestamps".to_string(),
138144
}),
139145
SelectionItem::Field(SelectionField {
146+
alias: None,
140147
name: "barks".to_string(),
141148
fields: Selection(Vec::new()),
142149
}),
143150
SelectionItem::InlineFragment(SelectionInlineFragment {
144151
on: "Dog".to_string(),
145152
fields: Selection(vec![SelectionItem::Field(SelectionField {
153+
alias: None,
146154
name: "rating".to_string(),
147155
fields: Selection(Vec::new()),
148156
})]),
149157
}),
150158
SelectionItem::Field(SelectionField {
159+
alias: None,
151160
name: "pawsCount".to_string(),
152161
fields: Selection(Vec::new()),
153162
}),
163+
SelectionItem::Field(SelectionField {
164+
alias: Some("aliased".to_string()),
165+
name: "sillyName".to_string(),
166+
fields: Selection(Vec::new()),
167+
}),
154168
]),
155169
})])
156170
);

graphql_query_derive/src/shared.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,19 @@ pub(crate) fn field_impls_for_selection(
7272
.iter()
7373
.map(|selected| {
7474
if let SelectionItem::Field(selected) = selected {
75+
let name = &selected.name;
76+
let alias = selected.alias.as_ref().unwrap_or(name);
77+
7578
let ty = fields
7679
.iter()
77-
.find(|f| f.name == selected.name)
78-
.ok_or_else(|| format_err!("could not find field `{}`", selected.name))?
80+
.find(|f| &f.name == name)
81+
.ok_or_else(|| format_err!("could not find field `{}`", name))?
7982
.type_
8083
.inner_name_string();
8184
let prefix = format!(
8285
"{}{}",
8386
prefix.to_camel_case(),
84-
selected.name.to_camel_case()
87+
alias.to_camel_case()
8588
);
8689
context.maybe_expand_field(&ty, &selected.fields, &prefix)
8790
} else {
@@ -103,18 +106,19 @@ pub(crate) fn response_fields_for_selection(
103106
.map(|item| match item {
104107
SelectionItem::Field(f) => {
105108
let name = &f.name;
109+
let alias = f.alias.as_ref().unwrap_or(name);
106110

107111
let schema_field = &schema_fields
108112
.iter()
109113
.find(|field| field.name.as_str() == name.as_str())
110114
.ok_or_else(|| format_err!("Could not find field: {}", name.as_str()))?;
111115
let ty = schema_field.type_.to_rust(
112116
context,
113-
&format!("{}{}", prefix.to_camel_case(), name.to_camel_case()),
117+
&format!("{}{}", prefix.to_camel_case(), alias.to_camel_case()),
114118
);
115119

116120
Ok(render_object_field(
117-
name,
121+
alias,
118122
&ty,
119123
schema_field.description.as_ref().map(|s| s.as_str()),
120124
&schema_field.deprecation,

graphql_query_derive/src/unions.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,15 @@ mod tests {
151151
SelectionItem::InlineFragment(SelectionInlineFragment {
152152
on: "User".to_string(),
153153
fields: Selection(vec![SelectionItem::Field(SelectionField {
154+
alias: None,
154155
name: "firstName".to_string(),
155156
fields: Selection(vec![]),
156157
})]),
157158
}),
158159
SelectionItem::InlineFragment(SelectionInlineFragment {
159160
on: "Organization".to_string(),
160161
fields: Selection(vec![SelectionItem::Field(SelectionField {
162+
alias: None,
161163
name: "title".to_string(),
162164
fields: Selection(vec![]),
163165
})]),
@@ -236,19 +238,22 @@ mod tests {
236238
fn union_response_for_selection_works() {
237239
let fields = vec![
238240
SelectionItem::Field(SelectionField {
241+
alias: None,
239242
name: "__typename".to_string(),
240243
fields: Selection(vec![]),
241244
}),
242245
SelectionItem::InlineFragment(SelectionInlineFragment {
243246
on: "User".to_string(),
244247
fields: Selection(vec![SelectionItem::Field(SelectionField {
248+
alias: None,
245249
name: "firstName".to_string(),
246250
fields: Selection(vec![]),
247251
})]),
248252
}),
249253
SelectionItem::InlineFragment(SelectionInlineFragment {
250254
on: "Organization".to_string(),
251255
fields: Selection(vec![SelectionItem::Field(SelectionField {
256+
alias: None,
252257
name: "title".to_string(),
253258
fields: Selection(vec![]),
254259
})]),

tests/alias.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#[macro_use]
2+
extern crate graphql_client;
3+
extern crate serde;
4+
#[macro_use]
5+
extern crate serde_derive;
6+
#[macro_use]
7+
extern crate serde_json;
8+
9+
#[derive(GraphQLQuery)]
10+
#[graphql(
11+
query_path = "tests/alias/query.graphql",
12+
schema_path = "tests/alias/schema.graphql"
13+
)]
14+
#[allow(dead_code)]
15+
struct AliasQuery;
16+
17+
#[test]
18+
fn alias() {
19+
let valid_response = json!({
20+
"alias": "127.0.1.2",
21+
"outer_alias": {
22+
"inner_alias": "inner value",
23+
},
24+
});
25+
26+
let _type_name_test = alias_query::RustAliasQueryOuterAlias {
27+
inner_alias: None,
28+
};
29+
30+
let valid_alias =
31+
serde_json::from_value::<alias_query::ResponseData>(valid_response).unwrap();
32+
33+
assert_eq!(
34+
valid_alias.alias.unwrap(),
35+
"127.0.1.2"
36+
);
37+
assert_eq!(
38+
valid_alias.outer_alias.unwrap().inner_alias.unwrap(),
39+
"inner value"
40+
);
41+
}

tests/alias/query.graphql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
query AliasQuery {
2+
alias: address
3+
outer_alias: nested {
4+
inner_alias: inner
5+
}
6+
}

tests/alias/schema.graphql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
schema {
2+
query: QueryRoot
3+
}
4+
5+
type QueryNest {
6+
inner: String
7+
}
8+
9+
type QueryRoot {
10+
address: String
11+
nested: QueryNest
12+
}

0 commit comments

Comments
 (0)