Skip to content

Commit 8fb528a

Browse files
authored
Upgrade Syn and add cli option for extern enums (#520)
* update syn to v2, add external-enums support to the cli * remove dependencies added for testing, leaving syn 2.0
1 parent cb781ca commit 8fb528a

File tree

6 files changed

+96
-61
lines changed

6 files changed

+96
-61
lines changed

graphql_client_cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ serde = { version = "^1.0", features = ["derive"] }
2020
serde_json = "^1.0"
2121
log = "^0.4"
2222
env_logger = "^0.6"
23-
syn = "1.0"
23+
syn = { version = "^2.0", features = ["full"] }
2424

2525
[features]
2626
default = []

graphql_client_cli/src/generate.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::fs::File;
88
use std::io::Write as _;
99
use std::path::PathBuf;
1010
use std::process::Stdio;
11-
use syn::Token;
11+
use syn::{token::Paren, token::Pub, VisRestricted, Visibility};
1212

1313
pub(crate) struct CliCodegenParams {
1414
pub query_path: PathBuf,
@@ -22,6 +22,7 @@ pub(crate) struct CliCodegenParams {
2222
pub output_directory: Option<PathBuf>,
2323
pub custom_scalars_module: Option<String>,
2424
pub fragments_other_variant: bool,
25+
pub external_enums: Option<Vec<String>>,
2526
}
2627

2728
const WARNING_SUPPRESSION: &str = "#![allow(clippy::all, warnings)]";
@@ -39,18 +40,26 @@ pub(crate) fn generate_code(params: CliCodegenParams) -> CliResult<()> {
3940
selected_operation,
4041
custom_scalars_module,
4142
fragments_other_variant,
43+
external_enums,
4244
} = params;
4345

4446
let deprecation_strategy = deprecation_strategy.as_ref().and_then(|s| s.parse().ok());
4547

4648
let mut options = GraphQLClientCodegenOptions::new(CodegenMode::Cli);
4749

48-
options.set_module_visibility(
49-
syn::VisPublic {
50-
pub_token: <Token![pub]>::default(),
51-
}
52-
.into(),
53-
);
50+
options.set_module_visibility(match _module_visibility {
51+
Some(v) => match v.to_lowercase().as_str() {
52+
"pub" => Visibility::Public(Pub::default()),
53+
"inherited" => Visibility::Inherited,
54+
_ => Visibility::Restricted(VisRestricted {
55+
pub_token: Pub::default(),
56+
in_token: None,
57+
paren_token: Paren::default(),
58+
path: syn::parse_str(&v).unwrap(),
59+
}),
60+
},
61+
None => Visibility::Public(Pub::default()),
62+
});
5463

5564
options.set_fragments_other_variant(fragments_other_variant);
5665

@@ -70,6 +79,10 @@ pub(crate) fn generate_code(params: CliCodegenParams) -> CliResult<()> {
7079
options.set_deprecation_strategy(deprecation_strategy);
7180
}
7281

82+
if let Some(external_enums) = external_enums {
83+
options.set_extern_enums(external_enums);
84+
}
85+
7386
if let Some(custom_scalars_module) = custom_scalars_module {
7487
let custom_scalars_module = syn::parse_str(&custom_scalars_module)
7588
.map_err(|_| Error::message("Invalid custom scalar module path".to_owned()))?;

graphql_client_cli/src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ enum Cli {
8989
/// --fragments-other-variant
9090
#[clap(long = "fragments-other-variant")]
9191
fragments_other_variant: bool,
92+
/// List of externally defined enum types. Type names must match those used in the schema exactly
93+
#[clap(long = "external-enums", num_args(0..), action(clap::ArgAction::Append))]
94+
external_enums: Option<Vec<String>>,
9295
},
9396
}
9497

@@ -126,6 +129,7 @@ fn main() -> CliResult<()> {
126129
selected_operation,
127130
custom_scalars_module,
128131
fragments_other_variant,
132+
external_enums,
129133
} => generate::generate_code(generate::CliCodegenParams {
130134
query_path,
131135
schema_path,
@@ -138,6 +142,7 @@ fn main() -> CliResult<()> {
138142
output_directory,
139143
custom_scalars_module,
140144
fragments_other_variant,
145+
external_enums,
141146
}),
142147
}
143148
}

graphql_client_codegen/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ proc-macro2 = { version = "^1.0", features = [] }
1616
quote = "^1.0"
1717
serde_json = "1.0"
1818
serde = { version = "^1.0", features = ["derive"] }
19-
syn = "^1.0"
19+
syn = { version = "^2.0", features = [ "full" ] }

graphql_query_derive/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ edition = "2018"
1111
proc-macro = true
1212

1313
[dependencies]
14-
syn = { version = "^1.0", features = ["extra-traits"] }
14+
syn = { version = "^2.0", features = ["extra-traits"] }
1515
proc-macro2 = { version = "^1.0", features = [] }
1616
graphql_client_codegen = { path = "../graphql_client_codegen/", version = "0.14.0" }

graphql_query_derive/src/attributes.rs

Lines changed: 68 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,25 @@
1+
use proc_macro2::TokenTree;
12
use std::str::FromStr;
3+
use syn::Meta;
24

35
use graphql_client_codegen::deprecation::DeprecationStrategy;
46
use graphql_client_codegen::normalization::Normalization;
57

68
const DEPRECATION_ERROR: &str = "deprecated must be one of 'allow', 'deny', or 'warn'";
79
const NORMALIZATION_ERROR: &str = "normalization must be one of 'none' or 'rust'";
810

9-
/// The `graphql` attribute as a `syn::Path`.
10-
fn path_to_match() -> syn::Path {
11-
syn::parse_str("graphql").expect("`graphql` is a valid path")
12-
}
13-
1411
pub fn ident_exists(ast: &syn::DeriveInput, ident: &str) -> Result<(), syn::Error> {
15-
let graphql_path = path_to_match();
1612
let attribute = ast
1713
.attrs
1814
.iter()
19-
.find(|attr| attr.path == graphql_path)
15+
.find(|attr| attr.path().is_ident("graphql"))
2016
.ok_or_else(|| syn::Error::new_spanned(ast, "The graphql attribute is missing"))?;
2117

22-
if let syn::Meta::List(items) = &attribute.parse_meta().expect("Attribute is well formatted") {
23-
for item in items.nested.iter() {
24-
if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = item {
25-
if let Some(ident_) = path.get_ident() {
26-
if ident_ == ident {
27-
return Ok(());
28-
}
18+
if let Meta::List(list) = &attribute.meta {
19+
for item in list.tokens.clone().into_iter() {
20+
if let TokenTree::Ident(ident_) = item {
21+
if ident_ == ident {
22+
return Ok(());
2923
}
3024
}
3125
}
@@ -39,21 +33,21 @@ pub fn ident_exists(ast: &syn::DeriveInput, ident: &str) -> Result<(), syn::Erro
3933

4034
/// Extract an configuration parameter specified in the `graphql` attribute.
4135
pub fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result<String, syn::Error> {
42-
let attributes = &ast.attrs;
43-
let graphql_path = path_to_match();
44-
let attribute = attributes
36+
let attribute = ast
37+
.attrs
4538
.iter()
46-
.find(|attr| attr.path == graphql_path)
39+
.find(|a| a.path().is_ident("graphql"))
4740
.ok_or_else(|| syn::Error::new_spanned(ast, "The graphql attribute is missing"))?;
48-
if let syn::Meta::List(items) = &attribute.parse_meta().expect("Attribute is well formatted") {
49-
for item in items.nested.iter() {
50-
if let syn::NestedMeta::Meta(syn::Meta::NameValue(name_value)) = item {
51-
let syn::MetaNameValue { path, lit, .. } = name_value;
52-
if let Some(ident) = path.get_ident() {
53-
if ident == attr {
54-
if let syn::Lit::Str(lit) = lit {
55-
return Ok(lit.value());
56-
}
41+
42+
if let Meta::List(list) = &attribute.meta {
43+
let mut iter = list.tokens.clone().into_iter();
44+
while let Some(item) = iter.next() {
45+
if let TokenTree::Ident(ident) = item {
46+
if ident == attr {
47+
iter.next();
48+
if let Some(TokenTree::Literal(lit)) = iter.next() {
49+
let lit_str: syn::LitStr = syn::parse_str(&lit.to_string())?;
50+
return Ok(lit_str.value());
5751
}
5852
}
5953
}
@@ -68,38 +62,41 @@ pub fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result<String, syn::E
6862

6963
/// Extract a list of configuration parameter values specified in the `graphql` attribute.
7064
pub fn extract_attr_list(ast: &syn::DeriveInput, attr: &str) -> Result<Vec<String>, syn::Error> {
71-
let attributes = &ast.attrs;
72-
let graphql_path = path_to_match();
73-
let attribute = attributes
65+
let attribute = ast
66+
.attrs
7467
.iter()
75-
.find(|attr| attr.path == graphql_path)
68+
.find(|a| a.path().is_ident("graphql"))
7669
.ok_or_else(|| syn::Error::new_spanned(ast, "The graphql attribute is missing"))?;
77-
if let syn::Meta::List(items) = &attribute.parse_meta().expect("Attribute is well formatted") {
78-
for item in items.nested.iter() {
79-
if let syn::NestedMeta::Meta(syn::Meta::List(value_list)) = item {
80-
if let Some(ident) = value_list.path.get_ident() {
81-
if ident == attr {
82-
return value_list
83-
.nested
84-
.iter()
85-
.map(|lit| {
86-
if let syn::NestedMeta::Lit(syn::Lit::Str(lit)) = lit {
87-
Ok(lit.value())
88-
} else {
89-
Err(syn::Error::new_spanned(
90-
lit,
91-
"Attribute inside value list must be a literal",
92-
))
93-
}
94-
})
95-
.collect();
70+
71+
let mut result = Vec::new();
72+
73+
if let Meta::List(list) = &attribute.meta {
74+
let mut iter = list.tokens.clone().into_iter();
75+
while let Some(item) = iter.next() {
76+
if let TokenTree::Ident(ident) = item {
77+
if ident == attr {
78+
if let Some(TokenTree::Group(group)) = iter.next() {
79+
for token in group.stream() {
80+
if let TokenTree::Literal(lit) = token {
81+
let lit_str: syn::LitStr = syn::parse_str(&lit.to_string())?;
82+
result.push(lit_str.value());
83+
}
84+
}
85+
return Ok(result);
9686
}
9787
}
9888
}
9989
}
10090
}
10191

102-
Err(syn::Error::new_spanned(ast, "Attribute not found"))
92+
if result.is_empty() {
93+
Err(syn::Error::new_spanned(
94+
ast,
95+
format!("Attribute list `{}` not found or empty", attr),
96+
))
97+
} else {
98+
Ok(result)
99+
}
103100
}
104101

105102
/// Get the deprecation from a struct attribute in the derive case.
@@ -278,4 +275,24 @@ mod test {
278275
let parsed = syn::parse_str(input).unwrap();
279276
assert!(!extract_skip_serializing_none(&parsed));
280277
}
278+
279+
#[test]
280+
fn test_external_enums() {
281+
let input = r#"
282+
#[derive(Serialize, Deserialize, Debug)]
283+
#[derive(GraphQLQuery)]
284+
#[graphql(
285+
schema_path = "x",
286+
query_path = "x",
287+
extern_enums("Direction", "DistanceUnit"),
288+
)]
289+
struct MyQuery;
290+
"#;
291+
let parsed: syn::DeriveInput = syn::parse_str(input).unwrap();
292+
293+
assert_eq!(
294+
extract_attr_list(&parsed, "extern_enums").ok().unwrap(),
295+
vec!["Direction", "DistanceUnit"],
296+
);
297+
}
281298
}

0 commit comments

Comments
 (0)