Skip to content

Commit 0f4462b

Browse files
authored
Merge pull request #90 from LegNeato/deprecated
Add support for `@deprecated`.
2 parents 9f8e412 + 175e9ce commit 0f4462b

File tree

15 files changed

+450
-14
lines changed

15 files changed

+450
-14
lines changed

CHANGELOG.md

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

88
## Unreleased
99

10+
- Support for `@deprecated` field annotations. You can configure how
11+
deprecations are handled via the `deprecated` argument in the `GraphQLQuery`
12+
derive:
13+
14+
```rust
15+
#[derive(GraphQLQuery)]
16+
#[graphql(
17+
schema_path = "src/graphql/schema.json",
18+
query_path = "src/graphql/queries/my_query.graphql",
19+
deprecated = "warn"
20+
)]
21+
pub struct MyQuery;
22+
```
23+
24+
Valid values are:
25+
26+
- `allow`: the response struct fields are not marked as deprecated.
27+
- `warn`: the response struct fields are marked as `#[deprecated]`.
28+
- `deny`: The struct fields are not included in the response struct and
29+
using them is a compile error.
30+
31+
The default is `warn`.
32+
1033
## [0.4.0] - 2018-08-23
1134

1235
There are a number of breaking changes due to the new features, read the `Added` section attentively if you are upgrading.

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ A typed GraphQL client library for Rust.
1616
- Arbitrary derives on the generated responses
1717
- Arbitrary custom scalars
1818
- Supports multiple operations per query document
19+
- Supports setting GraphQL fields as deprecated and having the Rust compiler check
20+
their use.
1921

2022
## Getting started
2123

@@ -97,6 +99,30 @@ struct SearchQuery;
9799

98100
The generated code will reference the scalar types as defined in the server schema. This means you have to provide matching rust types in the scope of the struct under derive. It can be as simple as declarations like `type Email = String;`. This gives you complete freedom on how to treat custom scalars, as long as they can be deserialized.
99101

102+
## Deprecations
103+
104+
The generated code has support for [`@deprecated`](http://facebook.github.io/graphql/June2018/#sec-Field-Deprecation)
105+
field annotations. You can configure how deprecations are handled via the `deprecated` argument in the `GraphQLQuery` derive:
106+
107+
```rust
108+
#[derive(GraphQLQuery)]
109+
#[graphql(
110+
schema_path = "src/graphql/schema.json",
111+
query_path = "src/graphql/queries/my_query.graphql",
112+
deprecated = "warn"
113+
)]
114+
pub struct MyQuery;
115+
```
116+
117+
Valid values are:
118+
119+
- `allow`: the response struct fields are not marked as deprecated.
120+
- `warn`: the response struct fields are marked as `#[deprecated]`.
121+
- `deny`: The struct fields are not included in the response struct and
122+
using them is a compile error.
123+
124+
The default is `warn`.
125+
100126
## Query documents with multiple operations
101127

102128
You can write multiple operations in one query document (one `.graphql` file). You can then select one by naming the struct you `#[derive(GraphQLQuery)]` on with the same name as one of the operations. This is neat, as it allows sharing fragments between operations.

graphql_query_derive/src/codegen.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use deprecation;
12
use failure;
23
use fragments::GqlFragment;
34
use graphql_parser::query;
@@ -12,8 +13,9 @@ pub(crate) fn response_for_query(
1213
query: query::Document,
1314
selected_operation: String,
1415
additional_derives: Option<String>,
16+
deprecation_strategy: deprecation::DeprecationStrategy,
1517
) -> Result<TokenStream, failure::Error> {
16-
let mut context = QueryContext::new(schema);
18+
let mut context = QueryContext::new(schema, deprecation_strategy);
1719

1820
if let Some(derives) = additional_derives {
1921
context.ingest_additional_derives(&derives).unwrap();

graphql_query_derive/src/constants.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use deprecation::DeprecationStatus;
12
use field_type::FieldType;
23
use objects::GqlObjectField;
34
use proc_macro2::{Ident, Span};
@@ -20,6 +21,7 @@ pub(crate) fn typename_field() -> GqlObjectField {
2021
/// Non-nullable, see spec:
2122
/// https://github.com/facebook/graphql/blob/master/spec/Section%204%20--%20Introspection.md
2223
type_: FieldType::Named(string_type()),
24+
deprecation: DeprecationStatus::Current,
2325
}
2426
}
2527

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use attributes;
2+
use failure;
3+
use syn;
4+
5+
static DEPRECATION_ERROR: &'static str = "deprecated must be one of 'allow', 'deny', or 'warn'";
6+
7+
#[derive(Debug, PartialEq, Hash, Clone)]
8+
pub enum DeprecationStatus {
9+
Current,
10+
Deprecated(Option<String>),
11+
}
12+
13+
#[derive(Debug, PartialEq)]
14+
pub(crate) enum DeprecationStrategy {
15+
Allow,
16+
Deny,
17+
Warn,
18+
}
19+
20+
pub(crate) fn extract_deprecation_strategy(
21+
ast: &syn::DeriveInput,
22+
) -> Result<DeprecationStrategy, failure::Error> {
23+
match attributes::extract_attr(&ast, "deprecated")?
24+
.to_lowercase()
25+
.as_str()
26+
{
27+
"allow" => Ok(DeprecationStrategy::Allow),
28+
"deny" => Ok(DeprecationStrategy::Deny),
29+
"warn" => Ok(DeprecationStrategy::Warn),
30+
_ => Err(format_err!("{}", DEPRECATION_ERROR))?,
31+
}
32+
}
33+
34+
#[cfg(test)]
35+
mod test {
36+
use super::*;
37+
38+
#[test]
39+
fn test_deprecation_strategy() {
40+
let input = "
41+
#[derive(GraphQLQuery)]
42+
#[graphql(
43+
schema_path = \"x\",
44+
query_path = \"x\",
45+
deprecated = \"warn\",
46+
)]
47+
struct MyQuery;
48+
";
49+
let parsed = syn::parse_str(input).unwrap();
50+
assert_eq!(
51+
extract_deprecation_strategy(&parsed).unwrap(),
52+
DeprecationStrategy::Warn
53+
);
54+
}
55+
56+
#[test]
57+
fn test_deprecation_strategy_is_case_insensitive() {
58+
let input = "
59+
#[derive(GraphQLQuery)]
60+
#[graphql(
61+
schema_path = \"x\",
62+
query_path = \"x\",
63+
deprecated = \"DeNy\",
64+
)]
65+
struct MyQuery;
66+
";
67+
let parsed = syn::parse_str(input).unwrap();
68+
assert_eq!(
69+
extract_deprecation_strategy(&parsed).unwrap(),
70+
DeprecationStrategy::Deny
71+
);
72+
}
73+
74+
#[test]
75+
fn test_invalid_deprecation_strategy() {
76+
let input = "
77+
#[derive(GraphQLQuery)]
78+
#[graphql(
79+
schema_path = \"x\",
80+
query_path = \"x\",
81+
deprecated = \"foo\",
82+
)]
83+
struct MyQuery;
84+
";
85+
let parsed = syn::parse_str(input).unwrap();
86+
match extract_deprecation_strategy(&parsed) {
87+
Ok(_) => panic!("parsed unexpectedly"),
88+
Err(e) => assert_eq!(&format!("{}", e), DEPRECATION_ERROR),
89+
};
90+
}
91+
}

graphql_query_derive/src/inputs.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use deprecation::DeprecationStatus;
12
use failure;
23
use graphql_parser;
34
use heck::SnakeCase;
@@ -53,6 +54,7 @@ impl ::std::convert::From<graphql_parser::schema::InputObjectType> for GqlInput
5354
description: None,
5455
name: field.name,
5556
type_: field.value_type.into(),
57+
deprecation: DeprecationStatus::Current,
5658
};
5759
(name, field)
5860
}).collect(),
@@ -80,6 +82,7 @@ impl ::std::convert::From<introspection_response::FullType> for GqlInput {
8082
.type_
8183
.expect("type on input object field")
8284
.into(),
85+
deprecation: DeprecationStatus::Current,
8386
};
8487
(name, field)
8588
}).collect(),
@@ -105,6 +108,7 @@ mod tests {
105108
description: None,
106109
name: "pawsCount".to_string(),
107110
type_: FieldType::Named(float_type()),
111+
deprecation: DeprecationStatus::Current,
108112
},
109113
),
110114
(
@@ -116,6 +120,7 @@ mod tests {
116120
"Cat",
117121
Span::call_site(),
118122
)))),
123+
deprecation: DeprecationStatus::Current,
119124
},
120125
),
121126
(
@@ -127,6 +132,7 @@ mod tests {
127132
"CatRequirements",
128133
Span::call_site(),
129134
)))),
135+
deprecation: DeprecationStatus::Current,
130136
},
131137
),
132138
].into_iter()

graphql_query_derive/src/lib.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use proc_macro2::TokenStream;
1919
mod attributes;
2020
mod codegen;
2121
mod constants;
22+
mod deprecation;
2223
mod enums;
2324
mod field_type;
2425
mod fragments;
@@ -82,6 +83,10 @@ fn impl_gql_query(input: &syn::DeriveInput) -> Result<TokenStream, failure::Erro
8283
let schema_path = attributes::extract_attr(input, "schema_path")?;
8384
let response_derives = attributes::extract_attr(input, "response_derives").ok();
8485

86+
// The user can determine what to do about deprecations.
87+
let deprecation_strategy = deprecation::extract_deprecation_strategy(input)
88+
.unwrap_or(deprecation::DeprecationStrategy::Warn);
89+
8590
// We need to qualify the query with the path to the crate it is part of
8691
let query_path = format!("{}/{}", cargo_manifest_dir, query_path);
8792
let query_string = read_file(&query_path)?;
@@ -110,8 +115,13 @@ fn impl_gql_query(input: &syn::DeriveInput) -> Result<TokenStream, failure::Erro
110115

111116
let module_name = Ident::new(&input.ident.to_string().to_snake_case(), Span::call_site());
112117
let struct_name = &input.ident;
113-
let schema_output =
114-
codegen::response_for_query(schema, query, input.ident.to_string(), response_derives)?;
118+
let schema_output = codegen::response_for_query(
119+
schema,
120+
query,
121+
input.ident.to_string(),
122+
response_derives,
123+
deprecation_strategy,
124+
)?;
115125

116126
let result = quote!(
117127
pub mod #module_name {

0 commit comments

Comments
 (0)