Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions compiler/crates/graphql-ir/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,17 @@ pub enum ValidationMessage {
deprecation_reason: Option<StringKey>,
},

#[error("The enum value `{enum_value}` of type `{enum_name}` is deprecated.{}",
match deprecation_reason {
Some(reason) => format!(" Deprecation reason: \"{reason}\""),
None => "".to_string()
})]
DeprecatedEnumValue {
enum_value: StringKey,
enum_name: StringKey,
deprecation_reason: Option<StringKey>,
},

#[error("Missing required {}: `{}`",
if missing_arg_names.len() > 1 { "arguments" } else { "argument" },
missing_arg_names
Expand Down
106 changes: 73 additions & 33 deletions compiler/crates/relay-transforms/src/validations/deprecated_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use common::DiagnosticTag;
use common::DiagnosticsResult;
use common::WithLocation;
use graphql_ir::Argument;
use graphql_ir::ConstantValue;
use graphql_ir::Directive;
use graphql_ir::ExecutableDefinition;
use graphql_ir::LinkedField;
Expand All @@ -24,6 +25,7 @@ use graphql_ir::Value;
use schema::FieldID;
use schema::SDLSchema;
use schema::Schema;
use schema::Type;

use crate::fragment_alias_directive::FRAGMENT_DANGEROUSLY_UNALIAS_DIRECTIVE_NAME;

Expand Down Expand Up @@ -81,22 +83,42 @@ impl<'a> DeprecatedFields<'a> {
}

for arg in arguments {
if let Some(arg_definition) = field_definition.arguments.named(arg.name.item)
&& let Some(directive) = arg_definition.deprecated()
{
let parent_type = field_definition.parent_type.unwrap();
let parent_name = schema.get_type_name(parent_type);

self.warnings.push(Diagnostic::hint(
ValidationMessage::DeprecatedFieldArgument {
argument_name: arg.name.item,
field_name: field_definition.name.item,
parent_name,
deprecation_reason: directive.reason,
},
arg.name.location,
vec![DiagnosticTag::DEPRECATED],
));
if let Some(arg_definition) = field_definition.arguments.named(arg.name.item) {
if let Some(directive) = arg_definition.deprecated() {
let parent_type = field_definition.parent_type.unwrap();
let parent_name = schema.get_type_name(parent_type);

self.warnings.push(Diagnostic::hint(
ValidationMessage::DeprecatedFieldArgument {
argument_name: arg.name.item,
field_name: field_definition.name.item,
parent_name,
deprecation_reason: directive.reason,
},
arg.name.location,
vec![DiagnosticTag::DEPRECATED],
));
}

if let Type::Enum(enum_id) = arg_definition.type_.inner() {
let enum_def = schema.enum_(enum_id);
if let Value::Constant(ConstantValue::Enum(value_name)) = &arg.value.item {
if let Some(enum_value) =
enum_def.values.iter().find(|v| v.value == *value_name)
&& let Some(deprecation) = enum_value.deprecated()
{
self.warnings.push(Diagnostic::hint(
ValidationMessage::DeprecatedEnumValue {
enum_value: *value_name,
enum_name: enum_def.name.item.0,
deprecation_reason: deprecation.reason,
},
arg.value.location,
vec![DiagnosticTag::DEPRECATED],
));
}
}
}
}
}
}
Expand All @@ -121,11 +143,9 @@ impl Validator for DeprecatedFields<'_> {
}

fn validate_value(&mut self, value: &Value) -> DiagnosticsResult<()> {
// TODO: `@deprecated` is allowed on Enum values, so technically we
// should also be validating when someone uses a deprecated enum value
// as an argument, but that will require some additional methods on our
// Schema, and potentially some additional traversal in our validation
// trait to traverse into potentially deep constant objects/arrays.
// TODO: Deprecated enum values inside deeply nested constant objects/arrays
// are not yet validated. Top-level enum values in field and directive arguments
// are handled in validate_field and validate_directive respectively.
self.default_validate_value(value)
}

Expand All @@ -143,18 +163,38 @@ impl Validator for DeprecatedFields<'_> {
));
}
for arg in &directive.arguments {
if let Some(arg_definition) = directive_definition.arguments.named(arg.name.item)
&& let Some(deprecation) = arg_definition.deprecated()
{
self.warnings.push(Diagnostic::hint(
ValidationMessage::DeprecatedDirectiveArgument {
argument_name: arg.name.item,
directive_name: directive.name.item,
deprecation_reason: deprecation.reason,
},
arg.name.location,
vec![DiagnosticTag::DEPRECATED],
));
if let Some(arg_definition) = directive_definition.arguments.named(arg.name.item) {
if let Some(deprecation) = arg_definition.deprecated() {
self.warnings.push(Diagnostic::hint(
ValidationMessage::DeprecatedDirectiveArgument {
argument_name: arg.name.item,
directive_name: directive.name.item,
deprecation_reason: deprecation.reason,
},
arg.name.location,
vec![DiagnosticTag::DEPRECATED],
));
}

if let Type::Enum(enum_id) = arg_definition.type_.inner() {
let enum_def = self.schema.enum_(enum_id);
if let Value::Constant(ConstantValue::Enum(value_name)) = &arg.value.item {
if let Some(enum_value) =
enum_def.values.iter().find(|v| v.value == *value_name)
&& let Some(deprecation) = enum_value.deprecated()
{
self.warnings.push(Diagnostic::hint(
ValidationMessage::DeprecatedEnumValue {
enum_value: *value_name,
enum_name: enum_def.name.item.0,
deprecation_reason: deprecation.reason,
},
arg.value.location,
vec![DiagnosticTag::DEPRECATED],
));
}
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
==================================== INPUT ====================================
fragment Foo on MyNewType {
some_field(status: LEGACY)
}
%extensions%
enum Status {
ACTIVE
LEGACY @deprecated
}
type MyNewType {
some_field(status: Status): String
}
==================================== OUTPUT ===================================
ℹ The enum value `LEGACY` of type `Status` is deprecated.

deprecated_enum_value.graphql:2:22
1 │ fragment Foo on MyNewType {
2 │ some_field(status: LEGACY)
│ ^^^^^^
3 │ }
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fragment Foo on MyNewType {
some_field(status: LEGACY)
}
%extensions%
enum Status {
ACTIVE
LEGACY @deprecated
}
type MyNewType {
some_field(status: Status): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
==================================== INPUT ====================================
fragment Foo on MyNewType {
some_field(status: LEGACY)
}
%extensions%
enum Status {
ACTIVE
LEGACY @deprecated(reason: "Use ACTIVE instead.")
}
type MyNewType {
some_field(status: Status): String
}
==================================== OUTPUT ===================================
ℹ The enum value `LEGACY` of type `Status` is deprecated. Deprecation reason: "Use ACTIVE instead."

deprecated_enum_value_with_reason.graphql:2:22
1 │ fragment Foo on MyNewType {
2 │ some_field(status: LEGACY)
│ ^^^^^^
3 │ }
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fragment Foo on MyNewType {
some_field(status: LEGACY)
}
%extensions%
enum Status {
ACTIVE
LEGACY @deprecated(reason: "Use ACTIVE instead.")
}
type MyNewType {
some_field(status: Status): String
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<523e6262355cff63df80d9ea70e0d2ef>>
* @generated SignedSource<<617806504dd54b22a1692ec28a9db160>>
*/

mod validate_deprecated_fields;
Expand All @@ -26,6 +26,20 @@ async fn deprecated_directive_arg_with_reason() {
test_fixture(transform_fixture, file!(), "deprecated_directive_arg_with_reason.graphql", "validate_deprecated_fields/fixtures/deprecated_directive_arg_with_reason.expected", input, expected).await;
}

#[tokio::test]
async fn deprecated_enum_value() {
let input = include_str!("validate_deprecated_fields/fixtures/deprecated_enum_value.graphql");
let expected = include_str!("validate_deprecated_fields/fixtures/deprecated_enum_value.expected");
test_fixture(transform_fixture, file!(), "deprecated_enum_value.graphql", "validate_deprecated_fields/fixtures/deprecated_enum_value.expected", input, expected).await;
}

#[tokio::test]
async fn deprecated_enum_value_with_reason() {
let input = include_str!("validate_deprecated_fields/fixtures/deprecated_enum_value_with_reason.graphql");
let expected = include_str!("validate_deprecated_fields/fixtures/deprecated_enum_value_with_reason.expected");
test_fixture(transform_fixture, file!(), "deprecated_enum_value_with_reason.graphql", "validate_deprecated_fields/fixtures/deprecated_enum_value_with_reason.expected", input, expected).await;
}

#[tokio::test]
async fn deprecated_field_arg() {
let input = include_str!("validate_deprecated_fields/fixtures/deprecated_field_arg.graphql");
Expand Down
13 changes: 13 additions & 0 deletions compiler/crates/schema/src/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,19 @@ pub struct EnumValue {
pub description: Option<StringKey>,
}

impl EnumValue {
pub fn deprecated(&self) -> Option<Deprecation> {
self.directives
.named(*DIRECTIVE_DEPRECATED)
.map(|directive| Deprecation {
reason: directive
.arguments
.named(*ARGUMENT_REASON)
.and_then(|reason| reason.value.get_string_literal()),
})
}
}

#[derive(
Clone,
Eq,
Expand Down
Loading