Infer graphql "deprecation" from #[deprecated(note = "...")] in derive (and macros) (#269)

* Update object/iface macro with doc/deprecated attrs for fields

* Use the note from `#[deprecated]` by default in derived GraphQLType

* Update to support multiline raw-docstring format

* Support bare deprecated attribute

* Update arguments to support #[doc] for parity with previous ` as ` syntax
This commit is contained in:
Kevin Stenerson 2018-10-27 21:28:48 -06:00 committed by Christian Legnitto
parent f61fdb2063
commit 0f2a654471
13 changed files with 804 additions and 49 deletions

View file

@ -14,3 +14,73 @@
generic code. To retain the current behaviour use `DefaultScalarValue` as scalar value type
[#251](https://github.com/graphql-rust/juniper/pull/251)
- The `GraphQLObject` and `GraphQLEnum` derives will mark graphql fields as
`@deprecated` when struct fields or enum variants are marked with the
builtin `#[deprecated]` attribute.
The deprecation reason can be set using the `note = ...` meta item
(e.g. `#[deprecated(note = "Replaced by betterField")]`).
The `since` attribute is ignored.
[#269](https://github.com/graphql-rust/juniper/pull/269)
- There is an alternative syntax for setting a field's _description_ and
_deprecation reason_ in the `graphql_object!` and `graphql_interface!` macros.
To __deprecate__ a graphql field:
```rust
// Original syntax for setting deprecation reason
field deprecated "Reason" my_field() -> { ... }
// New alternative syntax for deprecation reason.
#[deprecated(note = "Reason")]
field my_field() -> { ... }
// You can now also deprecate without a reason.
#[deprecated]
field my_field() -> { ... }
```
To set the __description__ of a graphql field:
```rust
// Original syntax for field descriptions
field my_field() as "Description" -> { ... }
// Original syntax for argument descriptions
field my_field(
floops: i32 as "The number of starfish to be returned. \
Can't be more than 100.",
) -> {
...
}
// New alternative syntax for field descriptions
#[doc = "Description"]
field my_field() -> { ... }
// New alternative syntax for argument descriptions
field my_field(
#[doc = "The number of starfish to be returned. \
Can't be more than 100."]
arg: i32,
) -> {
...
}
// You can also use raw strings and const &'static str.
//
// Multiple docstrings will be collapsed into a single
// description separated by newlines.
#[doc = r#"
This is my field.
Make sure not to flitz the bitlet.
Flitzing without a bitlet has undefined behaviour.
"]
#[doc = my_consts::ADDED_IN_VERSION_XYZ]
field my_field() -> { ... }
```
[#269](https://github.com/graphql-rust/juniper/pull/269)

View file

@ -15,8 +15,8 @@ use value::Value;
use GraphQLError;
use schema::meta::{
Argument, EnumMeta, EnumValue, Field, InputObjectMeta, InterfaceMeta, ListMeta, MetaType,
NullableMeta, ObjectMeta, PlaceholderMeta, ScalarMeta, UnionMeta,
Argument, DeprecationStatus, EnumMeta, EnumValue, Field, InputObjectMeta, InterfaceMeta,
ListMeta, MetaType, NullableMeta, ObjectMeta, PlaceholderMeta, ScalarMeta, UnionMeta,
};
use schema::model::{RootNode, SchemaType, TypeType};
@ -729,7 +729,7 @@ where
description: None,
arguments: None,
field_type: self.get_type::<T>(info),
deprecation_reason: None,
deprecation_status: DeprecationStatus::Current,
}
}
@ -748,7 +748,7 @@ where
description: None,
arguments: None,
field_type: self.get_type::<I>(info),
deprecation_reason: None,
deprecation_status: DeprecationStatus::Current,
}
}

View file

@ -398,7 +398,76 @@ macro_rules! __juniper_parse_field_list {
);
};
(
success_callback = $success_callback: ident,
additional_parser = {$($additional:tt)*},
meta = {$($meta:tt)*},
items = [$({$($items: tt)*},)*],
rest = $(#[doc = $desc: tt])*
#[deprecated $(( $(since = $since: tt,)* note = $reason: tt ))* ]
field $name: ident (
$(&$executor: tt)* $(,)*
$($(#[doc = $arg_desc: expr])* $arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty),* $(,)*
) -> $return_ty: ty $body: block
$($rest:tt)*
) => {
__juniper_parse_field_list!(
success_callback = $success_callback,
additional_parser = {$($additional)*},
meta = {$($meta)*},
items = [$({$($items)*},)* {
name = $name,
body = $body,
return_ty = $return_ty,
args = [
$({
arg_name = $arg_name,
arg_ty = $arg_ty,
$(arg_default = $arg_default,)*
$(arg_docstring = $arg_desc,)*
},)*
],
$(docstring = $desc,)*
deprecated = None$(.unwrap_or(Some($reason)))*,
$(executor_var = $executor,)*
},],
rest = $($rest)*
);
};
(
success_callback = $success_callback: ident,
additional_parser = {$($additional:tt)*},
meta = {$($meta:tt)*},
items = [$({$($items: tt)*},)*],
rest = $(#[doc = $desc: tt])*
field $name: ident (
$(&$executor: ident)* $(,)*
$($(#[doc = $arg_desc: expr])* $arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty),* $(,)*
) -> $return_ty: ty $body: block
$($rest:tt)*
) => {
__juniper_parse_field_list!(
success_callback = $success_callback,
additional_parser = {$($additional)*},
meta = {$($meta)*},
items = [$({$($items)*},)* {
name = $name,
body = $body,
return_ty = $return_ty,
args = [
$({
arg_name = $arg_name,
arg_ty = $arg_ty,
$(arg_default = $arg_default,)*
$(arg_docstring = $arg_desc,)*
},)*
],
$(docstring = $desc,)*
$(executor_var = $executor,)*
},],
rest = $($rest)*
);
};
(
success_callback = $success_callback: ident,
additional_parser = {$($additional:tt)*},
@ -406,7 +475,7 @@ macro_rules! __juniper_parse_field_list {
items = [$({$($items: tt)*},)*],
rest = field deprecated $reason:tt $name: ident (
$(&$executor: tt)* $(,)*
$($arg_name:ident $(= $default_value: tt)* : $arg_ty: ty $(as $arg_des: expr)*),* $(,)*
$($arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty $(as $arg_desc: expr)*),* $(,)*
) -> $return_ty: ty $(as $desc: tt)* $body: block
$($rest:tt)*
) => {
@ -422,12 +491,12 @@ macro_rules! __juniper_parse_field_list {
$({
arg_name = $arg_name,
arg_ty = $arg_ty,
$(arg_default = $arg_default,)*
$(arg_description = $arg_desc,)*
$(arg_default = $default_value,)*
},)*
],
$(decs = $desc,)*
deprecated = $reason,
deprecated = Some($reason),
$(executor_var = $executor,)*
},],
rest = $($rest)*
@ -440,7 +509,7 @@ macro_rules! __juniper_parse_field_list {
items = [$({$($items: tt)*},)*],
rest = field $name: ident (
$(&$executor: ident)* $(,)*
$($arg_name:ident $(= $default_value: tt)* : $arg_ty: ty $(as $arg_desc: expr)*),* $(,)*
$($arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty $(as $arg_desc: expr)*),* $(,)*
) -> $return_ty: ty $(as $desc: tt)* $body: block
$($rest:tt)*
) => {
@ -456,8 +525,8 @@ macro_rules! __juniper_parse_field_list {
$({
arg_name = $arg_name,
arg_ty = $arg_ty,
$(arg_default = $arg_default,)*
$(arg_description = $arg_desc,)*
$(arg_default = $default_value,)*
},)*
],
$(decs = $desc,)*
@ -601,12 +670,14 @@ macro_rules! __juniper_create_arg {
arg_ty = $arg_ty: ty,
arg_name = $arg_name: ident,
$(description = $arg_description: expr,)*
$(docstring = $arg_docstring: expr,)*
) => {
$reg.arg::<$arg_ty>(
&$crate::to_camel_case(stringify!($arg_name)),
$info,
)
$(.description($arg_description))*
$(.push_docstring($arg_docstring))*
};
(
@ -614,8 +685,9 @@ macro_rules! __juniper_create_arg {
info = $info: ident,
arg_ty = $arg_ty: ty,
arg_name = $arg_name: ident,
$(description = $arg_description: expr,)*
default = $arg_default: expr,
$(description = $arg_description: expr,)*
$(docstring = $arg_docstring: expr,)*
) => {
$reg.arg_with_default::<$arg_ty>(
&$crate::to_camel_case(stringify!($arg_name)),
@ -623,5 +695,6 @@ macro_rules! __juniper_create_arg {
$info,
)
$(.description($arg_description))*
$(.push_docstring($arg_docstring))*
};
}

View file

@ -117,10 +117,12 @@ macro_rules! graphql_interface {
args = [$({
arg_name = $arg_name : ident,
arg_ty = $arg_ty: ty,
$(arg_description = $arg_description: expr,)*
$(arg_default = $arg_default: expr,)*
$(arg_description = $arg_description: expr,)*
$(arg_docstring = $arg_docstring: expr,)*
},)*],
$(decs = $fn_description: expr,)*
$(docstring = $docstring: expr,)*
$(deprecated = $deprecated: expr,)*
$(executor_var = $executor: ident,)*
},)*],
@ -151,6 +153,7 @@ macro_rules! graphql_interface {
info
)
$(.description($fn_description))*
$(.push_docstring($docstring))*
$(.deprecated($deprecated))*
$(.argument(
__juniper_create_arg!(
@ -158,8 +161,9 @@ macro_rules! graphql_interface {
info = info,
arg_ty = $arg_ty,
arg_name = $arg_name,
$(description = $arg_description,)*
$(default = $arg_default,)*
$(description = $arg_description,)*
$(docstring = $arg_docstring,)*
)
))*,
)*];

View file

@ -63,6 +63,45 @@ graphql_object!(User: () |&self| {
# fn main() { }
```
**Alternatively,** descriptions can be added with the builtin `doc` attribute.
Consecutive `#[doc = "..."]` attributes will be collapsed into a single description
where the docstrings are separated by newlines.
```rust
# #[macro_use] extern crate juniper;
struct User { id: String, name: String, group_ids: Vec<String> }
graphql_object!(User: () |&self| {
description: "A user in the database"
#[doc = "The user's unique identifier"]
field id() -> &String {
&self.id
}
#[doc = "The user's name"]
field name() -> &String {
&self.name
}
#[doc = r#"
Test if a user is member of a group.
This may return a flitzbit if the floop is twizled.
Make sure not to rumblejumble the cog-rotater.
"#]
#[doc = "Added in vX.Y.44"]
field member_of_group(
#[doc = "The group id you want to test membership against"]
group_id: String,
) -> bool {
self.group_ids.iter().any(|gid| gid == &group_id)
}
});
# fn main() { }
```
## Generics and lifetimes
You can expose generic or pointer types by prefixing the type with the necessary
@ -232,6 +271,24 @@ Defines a field on the object. The name is converted to camel case, e.g.
`user_name` is exposed as `userName`. The `as "Field description"` adds the
string as documentation on the field.
A field's description and deprecation can also be set using the
builtin `doc` and `deprecated` attributes.
```text
#[doc = "Field description"]
field name(args...) -> Type { }
#[deprecated] // no reason required
field name(args...) -> Type { }
#[deprecated(note = "Reason")]
field name(args...) -> Type { }
#[doc = "Field description"]
#[deprecated(note = "Reason")] // deprecated must come after doc
field deprecated "Reason" name(args...) -> Type { }
```
### Field arguments
```text
@ -269,6 +326,13 @@ arg_name = (Point { x: 1, y: 2 }): Point
arg_name = ("default".to_owned()): String
```
A description can also be provided using the builtin `doc` attribute.
```text
#[doc = "Argument description"]
arg_name: ArgType
```
[1]: struct.Executor.html
*/
@ -295,10 +359,12 @@ macro_rules! graphql_object {
args = [$({
arg_name = $arg_name : ident,
arg_ty = $arg_ty: ty,
$(arg_description = $arg_description: expr,)*
$(arg_default = $arg_default: expr,)*
$(arg_description = $arg_description: expr,)*
$(arg_docstring = $arg_docstring: expr,)*
},)*],
$(decs = $fn_description: expr,)*
$(docstring = $docstring: expr,)*
$(deprecated = $deprecated: expr,)*
$(executor_var = $executor: ident,)*
},)*],
@ -325,6 +391,7 @@ macro_rules! graphql_object {
info
)
$(.description($fn_description))*
$(.push_docstring($docstring))*
$(.deprecated($deprecated))*
$(.argument(
__juniper_create_arg!(
@ -332,8 +399,9 @@ macro_rules! graphql_object {
info = info,
arg_ty = $arg_ty,
arg_name = $arg_name,
$(description = $arg_description,)*
$(default = $arg_default,)*
$(description = $arg_description,)*
$(docstring = $arg_docstring,)*
)
))*,
)*];

View file

@ -49,6 +49,13 @@ graphql_object!(Root: () |&self| {
arg2: i32 as "The second arg",
) -> i32 { 0 }
field attr_arg_descr(#[doc = "The arg"] arg: i32) -> i32 { 0 }
field attr_arg_descr_collapse(
#[doc = "The arg"]
#[doc = "and more details"]
arg: i32,
) -> i32 { 0 }
field arg_with_default(arg = 123: i32) -> i32 { 0 }
field multi_args_with_default(
arg1 = 123: i32,
@ -499,6 +506,72 @@ fn introspect_field_multi_args_descr_trailing_comma() {
});
}
#[test]
fn introspect_field_attr_arg_descr() {
run_args_info_query("attrArgDescr", |args| {
assert_eq!(args.len(), 1);
assert!(
args.contains(&Value::object(
vec![
("name", Value::scalar("arg")),
("description", Value::scalar("The arg")),
("defaultValue", Value::null()),
(
"type",
Value::object(
vec![
("name", Value::null()),
(
"ofType",
Value::object(
vec![("name", Value::scalar("Int"))].into_iter().collect(),
),
),
].into_iter()
.collect(),
),
),
].into_iter()
.collect(),
))
);
});
}
#[test]
fn introspect_field_attr_arg_descr_collapse() {
run_args_info_query("attrArgDescrCollapse", |args| {
assert_eq!(args.len(), 1);
assert!(
args.contains(&Value::object(
vec![
("name", Value::scalar("arg")),
("description", Value::scalar("The arg\nand more details")),
("defaultValue", Value::null()),
(
"type",
Value::object(
vec![
("name", Value::null()),
(
"ofType",
Value::object(
vec![("name", Value::scalar("Int"))].into_iter().collect(),
),
),
].into_iter()
.collect(),
),
),
].into_iter()
.collect(),
))
);
});
}
#[test]
fn introspect_field_arg_with_default() {
run_args_info_query("argWithDefault", |args| {

View file

@ -31,6 +31,31 @@ graphql_object!(Root: () |&self| {
field deprecated "Deprecation reason"
deprecated_descr() -> i32 as "Field description" { 0 }
#[doc = "Field description"]
field attr_description() -> i32 { 0 }
#[doc = "Field description"]
#[doc = "with `collapse_docs` behavior"] // https://doc.rust-lang.org/rustdoc/the-doc-attribute.html
field attr_description_collapse() -> i32 { 0 }
#[doc = r#"
Get the i32 representation of 0.
- This comment is longer.
- These two lines are rendered as bullets by GraphiQL.
"#]
field attr_description_long() -> i32 { 0 }
#[deprecated]
field attr_deprecated() -> i32 { 0 }
#[deprecated(note = "Deprecation reason")]
field attr_deprecated_reason() -> i32 { 0 }
#[doc = "Field description"]
#[deprecated(note = "Deprecation reason")]
field attr_deprecated_descr() -> i32 { 0 }
field with_field_result() -> FieldResult<i32> { Ok(0) }
field with_return() -> i32 { return 0; }
@ -51,6 +76,31 @@ graphql_interface!(Interface: () |&self| {
field deprecated "Deprecation reason"
deprecated_descr() -> i32 as "Field description" { 0 }
#[doc = "Field description"]
field attr_description() -> i32 { 0 }
#[doc = "Field description"]
#[doc = "with `collapse_docs` behavior"] // https://doc.rust-lang.org/rustdoc/the-doc-attribute.html
field attr_description_collapse() -> i32 { 0 }
#[doc = r#"
Get the i32 representation of 0.
- This comment is longer.
- These two lines are rendered as bullets by GraphiQL.
"#]
field attr_description_long() -> i32 { 0 }
#[deprecated]
field attr_deprecated() -> i32 { 0 }
#[deprecated(note = "Deprecation reason")]
field attr_deprecated_reason() -> i32 { 0 }
#[doc = "Field description"]
#[deprecated(note = "Deprecation reason")]
field attr_deprecated_descr() -> i32 { 0 }
instance_resolvers: |&_| {
Root => Some(Root {}),
}
@ -280,3 +330,255 @@ fn introspect_interface_field_deprecated_descr() {
);
});
}
#[test]
fn introspect_object_field_attr_description() {
run_field_info_query("Root", "attrDescription", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDescription"))
);
assert_eq!(
field.get_field_value("description"),
Some(&Value::scalar("Field description"))
);
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(false))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::null())
);
});
}
#[test]
fn introspect_interface_field_attr_description() {
run_field_info_query("Interface", "attrDescription", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDescription"))
);
assert_eq!(
field.get_field_value("description"),
Some(&Value::scalar("Field description"))
);
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(false))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::null())
);
});
}
#[test]
fn introspect_object_field_attr_description_long() {
run_field_info_query("Root", "attrDescriptionLong", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDescriptionLong"))
);
assert_eq!(
field.get_field_value("description"),
Some(&Value::scalar("Get the i32 representation of 0.\n\n- This comment is longer.\n- These two lines are rendered as bullets by GraphiQL."))
);
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(false))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::null())
);
});
}
#[test]
fn introspect_interface_field_attr_description_long() {
run_field_info_query("Interface", "attrDescriptionLong", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDescriptionLong"))
);
assert_eq!(
field.get_field_value("description"),
Some(&Value::scalar("Get the i32 representation of 0.\n\n- This comment is longer.\n- These two lines are rendered as bullets by GraphiQL."))
);
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(false))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::null())
);
});
}
#[test]
fn introspect_object_field_attr_description_collapse() {
run_field_info_query("Root", "attrDescriptionCollapse", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDescriptionCollapse"))
);
assert_eq!(
field.get_field_value("description"),
Some(&Value::scalar("Field description\nwith `collapse_docs` behavior"))
);
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(false))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::null())
);
});
}
#[test]
fn introspect_interface_field_attr_description_collapse() {
run_field_info_query("Interface", "attrDescriptionCollapse", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDescriptionCollapse"))
);
assert_eq!(
field.get_field_value("description"),
Some(&Value::scalar("Field description\nwith `collapse_docs` behavior"))
);
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(false))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::null())
);
});
}
#[test]
fn introspect_object_field_attr_deprecated() {
run_field_info_query("Root", "attrDeprecated", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDeprecated"))
);
assert_eq!(field.get_field_value("description"), Some(&Value::null()));
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(true))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::null())
);
});
}
#[test]
fn introspect_interface_field_attr_deprecated() {
run_field_info_query("Interface", "attrDeprecated", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDeprecated"))
);
assert_eq!(field.get_field_value("description"), Some(&Value::null()));
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(true))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::null())
);
});
}
#[test]
fn introspect_object_field_attr_deprecated_reason() {
run_field_info_query("Root", "attrDeprecatedReason", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDeprecatedReason"))
);
assert_eq!(field.get_field_value("description"), Some(&Value::null()));
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(true))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::scalar("Deprecation reason"))
);
});
}
#[test]
fn introspect_interface_field_attr_deprecated_reason() {
run_field_info_query("Interface", "attrDeprecatedReason", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDeprecatedReason"))
);
assert_eq!(field.get_field_value("description"), Some(&Value::null()));
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(true))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::scalar("Deprecation reason"))
);
});
}
#[test]
fn introspect_object_field_attr_deprecated_descr() {
run_field_info_query("Root", "attrDeprecatedDescr", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDeprecatedDescr"))
);
assert_eq!(
field.get_field_value("description"),
Some(&Value::scalar("Field description"))
);
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(true))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::scalar("Deprecation reason"))
);
});
}
#[test]
fn introspect_interface_field_attr_deprecated_descr() {
run_field_info_query("Interface", "attrDeprecatedDescr", |field| {
assert_eq!(
field.get_field_value("name"),
Some(&Value::scalar("attrDeprecatedDescr"))
);
assert_eq!(
field.get_field_value("description"),
Some(&Value::scalar("Field description"))
);
assert_eq!(
field.get_field_value("isDeprecated"),
Some(&Value::scalar(true))
);
assert_eq!(
field.get_field_value("deprecationReason"),
Some(&Value::scalar("Deprecation reason"))
);
});
}

View file

@ -9,6 +9,33 @@ use schema::model::SchemaType;
use types::base::TypeKind;
use value::{DefaultScalarValue, ParseScalarValue, ScalarRefValue, ScalarValue};
/// Whether an item is deprecated, with context.
#[derive(Debug, PartialEq, Hash, Clone)]
pub enum DeprecationStatus {
/// The field/variant is not deprecated.
Current,
/// The field/variant is deprecated, with an optional reason
Deprecated(Option<String>),
}
impl DeprecationStatus {
/// If this deprecation status indicates the item is deprecated.
pub fn is_deprecated(&self) -> bool {
match self {
&DeprecationStatus::Current => false,
&DeprecationStatus::Deprecated(_) => true,
}
}
/// An optional reason for the deprecation, or none if `Current`.
pub fn reason(&self) -> Option<&String> {
match self {
&DeprecationStatus::Current => None,
&DeprecationStatus::Deprecated(ref reason) => reason.as_ref(),
}
}
}
/// Scalar type metadata
pub struct ScalarMeta<'a, S> {
#[doc(hidden)]
@ -135,7 +162,7 @@ pub struct Field<'a, S> {
#[doc(hidden)]
pub field_type: Type<'a>,
#[doc(hidden)]
pub deprecation_reason: Option<String>,
pub deprecation_status: DeprecationStatus,
}
/// Metadata for an argument to a field
@ -163,10 +190,8 @@ pub struct EnumValue {
/// Note: this is not the description of the enum itself; it's the
/// description of this enum _value_.
pub description: Option<String>,
/// The optional deprecation reason
///
/// If this is `Some`, the field will be considered `isDeprecated`.
pub deprecation_reason: Option<String>,
/// Whether the field is deprecated or not, with an optional reason.
pub deprecation_status: DeprecationStatus,
}
impl<'a, S> MetaType<'a, S> {
@ -580,6 +605,28 @@ impl<'a, S> Field<'a, S> {
self
}
/// Adds a (multi)line doc string to the description of the field.
/// Any leading or trailing newlines will be removed.
///
/// If the docstring contains newlines, repeated leading tab and space characters
/// will be removed from the beginning of each line.
///
/// If the description hasn't been set, the description is set to the provided line.
/// Otherwise, the doc string is added to the current description after a newline.
pub fn push_docstring(mut self, multiline: &str) -> Field<'a, S> {
let docstring = clean_docstring(multiline);
match &mut self.description {
&mut Some(ref mut desc) => {
desc.push('\n');
desc.push_str(&docstring);
}
desc @ &mut None => {
*desc = Some(docstring.to_string());
}
}
self
}
/// Add an argument to the field
///
/// Arguments are unordered and can't contain duplicates by name.
@ -596,11 +643,11 @@ impl<'a, S> Field<'a, S> {
self
}
/// Set the deprecation reason
/// Set the field to be deprecated with an optional reason.
///
/// This overwrites the deprecation reason if any was previously set.
pub fn deprecated(mut self, reason: &str) -> Self {
self.deprecation_reason = Some(reason.to_owned());
pub fn deprecated(mut self, reason: Option<&str>) -> Self {
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(|s| s.to_owned()));
self
}
}
@ -624,6 +671,28 @@ impl<'a, S> Argument<'a, S> {
self
}
/// Adds a (multi)line doc string to the description of the field.
/// Any leading or trailing newlines will be removed.
///
/// If the docstring contains newlines, repeated leading tab and space characters
/// will be removed from the beginning of each line.
///
/// If the description hasn't been set, the description is set to the provided line.
/// Otherwise, the doc string is added to the current description after a newline.
pub fn push_docstring(mut self, multiline: &str) -> Argument<'a, S> {
let docstring = clean_docstring(multiline);
match &mut self.description {
&mut Some(ref mut desc) => {
desc.push('\n');
desc.push_str(&docstring);
}
desc @ &mut None => {
*desc = Some(docstring.to_string());
}
}
self
}
/// Set the default value of the argument
///
/// This overwrites the description if any was previously set.
@ -639,7 +708,7 @@ impl EnumValue {
EnumValue {
name: name.to_owned(),
description: None,
deprecation_reason: None,
deprecation_status: DeprecationStatus::Current,
}
}
@ -651,11 +720,11 @@ impl EnumValue {
self
}
/// Set the deprecation reason for the enum value
/// Set the enum value to be deprecated with an optional reason.
///
/// This overwrites the deprecation reason if any was previously set.
pub fn deprecated(mut self, reason: &str) -> EnumValue {
self.deprecation_reason = Some(reason.to_owned());
pub fn deprecated(mut self, reason: Option<&str>) -> Self {
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(|s| s.to_owned()));
self
}
}
@ -696,3 +765,27 @@ where
{
<T as FromInputValue<S>>::from_input_value(v).is_some()
}
fn clean_docstring<'a>(multiline: &'a str) -> Cow<'a, str> {
let trim_start = multiline.split('\n')
.skip(1)
.filter_map(|ln| ln.chars().position(|ch| ch != ' ' && ch != '\t'))
.min();
if let Some(trim) = trim_start {
let trimmed = multiline
.split('\n')
.map(|ln| {
if !ln.starts_with(' ') && !ln.starts_with('\t') {
ln // skip trimming the first line
} else if ln.len() >= trim {
&ln[trim..]
} else {
""
}
})
.collect::<Vec<_>>();
Cow::from(trimmed.join("\n").trim_matches('\n').to_owned())
} else {
Cow::from(multiline.trim_matches('\n'))
}
}

View file

@ -132,7 +132,7 @@ graphql_object!(<'a> TypeType<'a, S>: SchemaType<'a, S> as "__Type"
TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) =>
Some(fields
.iter()
.filter(|f| include_deprecated || f.deprecation_reason.is_none())
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated())
.filter(|f| !f.name.starts_with("__"))
.collect()),
_ => None,
@ -201,7 +201,7 @@ graphql_object!(<'a> TypeType<'a, S>: SchemaType<'a, S> as "__Type"
TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) =>
Some(values
.iter()
.filter(|f| include_deprecated || f.deprecation_reason.is_none())
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated())
.collect()),
_ => None,
}
@ -228,11 +228,11 @@ graphql_object!(<'a> Field<'a, S>: SchemaType<'a, S> as "__Field"
}
field is_deprecated() -> bool {
self.deprecation_reason.is_some()
self.deprecation_status.is_deprecated()
}
field deprecation_reason() -> &Option<String> {
&self.deprecation_reason
field deprecation_reason() -> Option<&String> {
self.deprecation_status.reason()
}
});
@ -266,11 +266,11 @@ graphql_object!(EnumValue: () as "__EnumValue" where Scalar = <S> |&self| {
}
field is_deprecated() -> bool {
self.deprecation_reason.is_some()
self.deprecation_status.is_deprecated()
}
field deprecation_reason() -> &Option<String> {
&self.deprecation_reason
field deprecation_reason() -> Option<&String> {
self.deprecation_status.reason()
}
});

View file

@ -44,7 +44,7 @@ impl EnumAttrs {
continue;
}
panic!(format!(
"Unknown attribute for #[derive(GraphQLEnum)]: {:?}",
"Unknown enum attribute for #[derive(GraphQLEnum)]: {:?}",
item
));
}
@ -57,7 +57,7 @@ impl EnumAttrs {
struct EnumVariantAttrs {
name: Option<String>,
description: Option<String>,
deprecation: Option<String>,
deprecation: Option<DeprecationAttr>,
}
impl EnumVariantAttrs {
@ -67,6 +67,9 @@ impl EnumVariantAttrs {
// Check doc comments for description.
res.description = get_doc_comment(&variant.attrs);
// Check builtin deprecated attribute for deprecation.
res.deprecation = get_deprecated(&variant.attrs);
// Check attributes for name and description.
if let Some(items) = get_graphql_attr(&variant.attrs) {
for item in items {
@ -90,13 +93,24 @@ impl EnumVariantAttrs {
continue;
}
if let Some(AttributeValue::String(val)) =
keyed_item_value(&item, "deprecated", AttributeValidation::String)
keyed_item_value(&item, "deprecation", AttributeValidation::String)
{
res.deprecation = Some(val);
res.deprecation = Some(DeprecationAttr { reason: Some(val) });
continue;
}
match keyed_item_value(&item, "deprecated", AttributeValidation::String) {
Some(AttributeValue::String(val)) => {
res.deprecation = Some(DeprecationAttr { reason: Some(val) });
continue;
}
Some(AttributeValue::Bare) => {
res.deprecation = Some(DeprecationAttr { reason: None });
continue;
}
None => {}
}
panic!(format!(
"Unknown attribute for #[derive(GraphQLEnum)]: {:?}",
"Unknown variant attribute for #[derive(GraphQLEnum)]: {:?}",
item
));
}
@ -151,14 +165,21 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> TokenStream {
None => quote!{ None },
};
let depr = match var_attrs.deprecation {
Some(s) => quote!{ Some(#s.to_string()) },
None => quote!{ None },
Some(DeprecationAttr { reason: Some(s) }) => quote!{
_juniper::meta::DeprecationStatus::Deprecated(Some(#s.to_string()))
},
Some(DeprecationAttr { reason: None }) => quote!{
_juniper::meta::DeprecationStatus::Deprecated(None)
},
None => quote!{
_juniper::meta::DeprecationStatus::Current
},
};
values.extend(quote!{
_juniper::meta::EnumValue{
name: #name.to_string(),
description: #descr,
deprecation_reason: #depr,
deprecation_status: #depr,
},
});

View file

@ -47,7 +47,7 @@ impl ObjAttrs {
continue;
}
panic!(format!(
"Unknown object attribute for #[derive(GraphQLObject)]: {:?}",
"Unknown struct attribute for #[derive(GraphQLObject)]: {:?}",
item
));
}
@ -60,7 +60,7 @@ impl ObjAttrs {
struct ObjFieldAttrs {
name: Option<String>,
description: Option<String>,
deprecation: Option<String>,
deprecation: Option<DeprecationAttr>,
skip: bool,
}
@ -71,6 +71,9 @@ impl ObjFieldAttrs {
// Check doc comments for description.
res.description = get_doc_comment(&variant.attrs);
// Check builtin deprecated attribute for deprecation.
res.deprecation = get_deprecated(&variant.attrs);
// Check attributes.
if let Some(items) = get_graphql_attr(&variant.attrs) {
for item in items {
@ -96,9 +99,20 @@ impl ObjFieldAttrs {
if let Some(AttributeValue::String(val)) =
keyed_item_value(&item, "deprecation", AttributeValidation::String)
{
res.deprecation = Some(val);
res.deprecation = Some(DeprecationAttr { reason: Some(val) });
continue;
}
match keyed_item_value(&item, "deprecated", AttributeValidation::String) {
Some(AttributeValue::String(val)) => {
res.deprecation = Some(DeprecationAttr { reason: Some(val) });
continue;
}
Some(AttributeValue::Bare) => {
res.deprecation = Some(DeprecationAttr { reason: None });
continue;
}
None => {}
}
if let Some(_) = keyed_item_value(&item, "skip", AttributeValidation::Bare) {
res.skip = true;
continue;
@ -167,7 +181,8 @@ pub fn impl_object(ast: &syn::DeriveInput) -> TokenStream {
};
let build_deprecation = match field_attrs.deprecation {
Some(s) => quote!{ field.deprecated(#s) },
Some(DeprecationAttr { reason: Some(s) }) => quote!{ field.deprecated(Some(#s)) },
Some(DeprecationAttr { reason: None }) => quote!{ field.deprecated(None) },
None => quote!{ field },
};

View file

@ -1,5 +1,5 @@
use regex::Regex;
use syn::{Attribute, Lit, Meta, MetaNameValue, NestedMeta};
use syn::{Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta};
pub enum AttributeValidation {
Any,
@ -12,6 +12,42 @@ pub enum AttributeValue {
String(String),
}
pub struct DeprecationAttr {
pub reason: Option<String>,
}
pub fn get_deprecated(attrs: &Vec<Attribute>) -> Option<DeprecationAttr> {
for attr in attrs {
match attr.interpret_meta() {
Some(Meta::List(ref list)) if list.ident == "deprecated" => {
return Some(get_deprecated_meta_list(list));
}
Some(Meta::Word(ref ident)) if ident == "deprecated" => {
return Some(DeprecationAttr { reason: None });
}
_ => {}
}
}
None
}
fn get_deprecated_meta_list(list: &MetaList) -> DeprecationAttr {
for meta in &list.nested {
match meta {
&NestedMeta::Meta(Meta::NameValue(ref nv)) if nv.ident == "note" => {
match &nv.lit {
&Lit::Str(ref strlit) => {
return DeprecationAttr { reason: Some(strlit.value().to_string()) };
}
_ => panic!("deprecated attribute note value only has string literal"),
}
}
_ => {}
}
}
DeprecationAttr { reason: None }
}
// Gets doc comment.
pub fn get_doc_comment(attrs: &Vec<Attribute>) -> Option<String> {
if let Some(items) = get_doc_attr(attrs) {

View file

@ -78,7 +78,7 @@ impl<'a> GraphQLType for &'a Fake {
&[juniper::meta::EnumValue {
name: "fake".to_string(),
description: None,
deprecation_reason: None,
deprecation_status: juniper::meta::DeprecationStatus::Current,
}],
);
meta.into_meta()