diff --git a/src/type/__tests__/definition-test.js b/src/type/__tests__/definition-test.js index 8003d86883..5eecd93893 100644 --- a/src/type/__tests__/definition-test.js +++ b/src/type/__tests__/definition-test.js @@ -259,6 +259,8 @@ describe('Type System: Objects', () => { description: undefined, type: ScalarType, defaultValue: undefined, + deprecationReason: undefined, + isDeprecated: false, extensions: undefined, astNode: undefined, }, @@ -750,6 +752,8 @@ describe('Type System: Input Objects', () => { description: undefined, type: ScalarType, defaultValue: undefined, + deprecationReason: undefined, + isDeprecated: false, extensions: undefined, astNode: undefined, }, @@ -770,6 +774,8 @@ describe('Type System: Input Objects', () => { type: ScalarType, defaultValue: undefined, extensions: undefined, + isDeprecated: false, + deprecationReason: undefined, astNode: undefined, }, }); diff --git a/src/type/__tests__/introspection-test.js b/src/type/__tests__/introspection-test.js index 5c60e8a071..a60d0ad30e 100644 --- a/src/type/__tests__/introspection-test.js +++ b/src/type/__tests__/introspection-test.js @@ -325,7 +325,17 @@ describe('Introspection', () => { }, { name: 'inputFields', - args: [], + args: [ + { + name: 'includeDeprecated', + type: { + kind: 'SCALAR', + name: 'Boolean', + ofType: null, + }, + defaultValue: 'false', + }, + ], type: { kind: 'LIST', name: null, @@ -441,7 +451,17 @@ describe('Introspection', () => { }, { name: 'args', - args: [], + args: [ + { + name: 'includeDeprecated', + type: { + kind: 'SCALAR', + name: 'Boolean', + ofType: null, + }, + defaultValue: 'false', + }, + ], type: { kind: 'NON_NULL', name: null, @@ -565,6 +585,32 @@ describe('Introspection', () => { isDeprecated: false, deprecationReason: null, }, + { + name: 'isDeprecated', + args: [], + type: { + kind: 'NON_NULL', + name: null, + ofType: { + kind: 'SCALAR', + name: 'Boolean', + ofType: null, + }, + }, + isDeprecated: false, + deprecationReason: null, + }, + { + name: 'deprecationReason', + args: [], + type: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, + isDeprecated: false, + deprecationReason: null, + }, ], inputFields: null, interfaces: [], @@ -880,7 +926,12 @@ describe('Introspection', () => { { name: 'deprecated', isRepeatable: false, - locations: ['FIELD_DEFINITION', 'ENUM_VALUE'], + locations: [ + 'FIELD_DEFINITION', + 'ENUM_VALUE', + 'ARGUMENT_DEFINITION', + 'INPUT_FIELD_DEFINITION', + ], args: [ { defaultValue: '"No longer supported"', diff --git a/src/type/definition.d.ts b/src/type/definition.d.ts index df62c66743..cd28b027a9 100644 --- a/src/type/definition.d.ts +++ b/src/type/definition.d.ts @@ -493,6 +493,8 @@ export interface GraphQLArgumentConfig { description?: Maybe; type: GraphQLInputType; defaultValue?: any; + isDeprecated?: boolean; + deprecationReason?: Maybe; extensions?: Maybe>>; astNode?: Maybe; } diff --git a/src/type/definition.js b/src/type/definition.js index 2d5a15117d..1cb05558f4 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -825,6 +825,8 @@ function defineFieldMap( type: argConfig.type, defaultValue: argConfig.defaultValue, extensions: argConfig.extensions && toObjMap(argConfig.extensions), + isDeprecated: argConfig.deprecationReason != null, + deprecationReason: argConfig.deprecationReason, astNode: argConfig.astNode, })); @@ -873,6 +875,8 @@ export function argsToArgsConfig( description: arg.description, type: arg.type, defaultValue: arg.defaultValue, + deprecationReason: arg.deprecationReason, + isDeprecated: arg.deprecationReason != null, extensions: arg.extensions, astNode: arg.astNode, }), @@ -949,6 +953,8 @@ export type GraphQLArgumentConfig = {| type: GraphQLInputType, defaultValue?: mixed, extensions?: ?ReadOnlyObjMapLike, + deprecationReason?: ?string, + isDeprecated?: boolean, astNode?: ?InputValueDefinitionNode, |}; @@ -978,6 +984,8 @@ export type GraphQLArgument = {| description: ?string, type: GraphQLInputType, defaultValue: mixed, + isDeprecated?: boolean, + deprecationReason?: ?string, extensions: ?ReadOnlyObjMap, astNode: ?InputValueDefinitionNode, |}; @@ -1517,17 +1525,25 @@ function defineInputFieldMap( isPlainObj(fieldMap), `${config.name} fields must be an object with field names as keys or a function which returns such an object.`, ); + return mapValue(fieldMap, (fieldConfig, fieldName) => { devAssert( !('resolve' in fieldConfig), `${config.name}.${fieldName} field has a resolve property, but Input Types cannot define resolvers.`, ); + devAssert( + !('isDeprecated' in fieldConfig), + `${config.name}.${fieldName} should provide "deprecationReason" ` + + 'instead of "isDeprecated".', + ); return { name: fieldName, description: fieldConfig.description, type: fieldConfig.type, defaultValue: fieldConfig.defaultValue, + deprecationReason: fieldConfig.deprecationReason, + isDeprecated: fieldConfig.deprecationReason != null, extensions: fieldConfig.extensions && toObjMap(fieldConfig.extensions), astNode: fieldConfig.astNode, }; @@ -1547,6 +1563,7 @@ export type GraphQLInputFieldConfig = {| description?: ?string, type: GraphQLInputType, defaultValue?: mixed, + deprecationReason?: ?string, extensions?: ?ReadOnlyObjMapLike, astNode?: ?InputValueDefinitionNode, |}; @@ -1558,6 +1575,8 @@ export type GraphQLInputField = {| description: ?string, type: GraphQLInputType, defaultValue: mixed, + isDeprecated?: boolean, + deprecationReason?: ?string, extensions: ?ReadOnlyObjMap, astNode: ?InputValueDefinitionNode, |}; diff --git a/src/type/directives.js b/src/type/directives.js index d3ed6fdae9..54053fdf6f 100644 --- a/src/type/directives.js +++ b/src/type/directives.js @@ -181,7 +181,12 @@ export const DEFAULT_DEPRECATION_REASON = 'No longer supported'; export const GraphQLDeprecatedDirective = new GraphQLDirective({ name: 'deprecated', description: 'Marks an element of a GraphQL schema as no longer supported.', - locations: [DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.ENUM_VALUE], + locations: [ + DirectiveLocation.FIELD_DEFINITION, + DirectiveLocation.ENUM_VALUE, + DirectiveLocation.ARGUMENT_DEFINITION, + DirectiveLocation.INPUT_FIELD_DEFINITION, + ], args: { reason: { type: GraphQLString, diff --git a/src/type/introspection.js b/src/type/introspection.js index 043685475f..c50067f1f0 100644 --- a/src/type/introspection.js +++ b/src/type/introspection.js @@ -288,9 +288,19 @@ export const __Type = new GraphQLObjectType({ }, inputFields: { type: GraphQLList(GraphQLNonNull(__InputValue)), - resolve(type) { + args: { + includeDeprecated: { + type: GraphQLBoolean, + defaultValue: false, + }, + }, + resolve(type, { includeDeprecated }) { if (isInputObjectType(type)) { - return objectValues(type.getFields()); + let values = objectValues(type.getFields()); + if (!includeDeprecated) { + values = values.filter(value => !value.deprecationReason); + } + return values; } }, }, @@ -317,7 +327,22 @@ export const __Field = new GraphQLObjectType({ }, args: { type: GraphQLNonNull(GraphQLList(GraphQLNonNull(__InputValue))), - resolve: field => field.args, + args: { + includeDeprecated: { + type: GraphQLBoolean, + defaultValue: false, + }, + }, + // resolve: field => field.args || [], + resolve(field, { includeDeprecated }) { + let args = field.args || []; + + if (!includeDeprecated) { + args = args.filter(arg => !arg.deprecationReason); + } + + return args; + }, }, type: { type: GraphQLNonNull(__Type), @@ -362,6 +387,14 @@ export const __InputValue = new GraphQLObjectType({ return valueAST ? print(valueAST) : null; }, }, + isDeprecated: { + type: GraphQLNonNull(GraphQLBoolean), + resolve: obj => obj.isDeprecated, + }, + deprecationReason: { + type: GraphQLString, + resolve: obj => obj.deprecationReason, + }, }: GraphQLFieldConfigMap), }); diff --git a/src/utilities/__tests__/buildASTSchema-test.js b/src/utilities/__tests__/buildASTSchema-test.js index 2238294e14..e0578b0f34 100644 --- a/src/utilities/__tests__/buildASTSchema-test.js +++ b/src/utilities/__tests__/buildASTSchema-test.js @@ -732,10 +732,19 @@ describe('Schema Builder', () => { OTHER_VALUE @deprecated(reason: "Terrible reasons") } + input MyInput { + oldInput: String @deprecated + otherInput: String @deprecated(reason: "Use newInput") + newInput: String + } + type Query { field1: String @deprecated field2: Int @deprecated(reason: "Because I said so") enum: MyEnum + field3(oldArg: String @deprecated, arg: String): String + field4(oldArg: String @deprecated(reason: "Why not?"), arg: String): String + field5(arg: MyInput): String } `; expect(cycleSDL(sdl)).to.equal(sdl); @@ -768,6 +777,39 @@ describe('Schema Builder', () => { isDeprecated: true, deprecationReason: 'Because I said so', }); + + const inputFields = assertInputObjectType( + schema.getType('MyInput'), + ).getFields(); + + const newInput = inputFields.newInput; + expect(newInput).to.include({ + isDeprecated: false, + }); + + const oldInput = inputFields.oldInput; + expect(oldInput).to.include({ + isDeprecated: true, + deprecationReason: 'No longer supported', + }); + + const otherInput = inputFields.otherInput; + expect(otherInput).to.include({ + isDeprecated: true, + deprecationReason: 'Use newInput', + }); + + const field3OldArg = rootFields.field3.args[0]; + expect(field3OldArg).to.include({ + isDeprecated: true, + deprecationReason: 'No longer supported', + }); + + const field4OldArg = rootFields.field4.args[0]; + expect(field4OldArg).to.include({ + isDeprecated: true, + deprecationReason: 'Why not?', + }); }); it('Correctly extend scalar type', () => { diff --git a/src/utilities/__tests__/schemaPrinter-test.js b/src/utilities/__tests__/schemaPrinter-test.js index ebfe7f7ff9..cbdb318150 100644 --- a/src/utilities/__tests__/schemaPrinter-test.js +++ b/src/utilities/__tests__/schemaPrinter-test.js @@ -637,7 +637,7 @@ describe('Type System Printer', () => { Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/). """ reason: String = "No longer supported" - ) on FIELD_DEFINITION | ENUM_VALUE + ) on FIELD_DEFINITION | ENUM_VALUE | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION """ A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations. @@ -678,7 +678,7 @@ describe('Type System Printer', () => { interfaces: [__Type!] possibleTypes: [__Type!] enumValues(includeDeprecated: Boolean = false): [__EnumValue!] - inputFields: [__InputValue!] + inputFields(includeDeprecated: Boolean = false): [__InputValue!] ofType: __Type } @@ -721,7 +721,7 @@ describe('Type System Printer', () => { type __Field { name: String! description: String - args: [__InputValue!]! + args(includeDeprecated: Boolean = false): [__InputValue!]! type: __Type! isDeprecated: Boolean! deprecationReason: String @@ -739,6 +739,8 @@ describe('Type System Printer', () => { A GraphQL-formatted string representing the default value for this input value. """ defaultValue: String + isDeprecated: Boolean! + deprecationReason: String } """ @@ -851,7 +853,7 @@ describe('Type System Printer', () => { directive @deprecated( # Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/). reason: String = "No longer supported" - ) on FIELD_DEFINITION | ENUM_VALUE + ) on FIELD_DEFINITION | ENUM_VALUE | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION # A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations. type __Schema { @@ -884,7 +886,7 @@ describe('Type System Printer', () => { interfaces: [__Type!] possibleTypes: [__Type!] enumValues(includeDeprecated: Boolean = false): [__EnumValue!] - inputFields: [__InputValue!] + inputFields(includeDeprecated: Boolean = false): [__InputValue!] ofType: __Type } @@ -919,7 +921,7 @@ describe('Type System Printer', () => { type __Field { name: String! description: String - args: [__InputValue!]! + args(includeDeprecated: Boolean = false): [__InputValue!]! type: __Type! isDeprecated: Boolean! deprecationReason: String @@ -933,6 +935,8 @@ describe('Type System Printer', () => { # A GraphQL-formatted string representing the default value for this input value. defaultValue: String + isDeprecated: Boolean! + deprecationReason: String } # One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string. diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index 0246eca461..b262df677a 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -501,6 +501,7 @@ export function extendSchemaImpl( type, description: getDescription(arg, options), defaultValue: valueFromAST(arg.defaultValue, type), + deprecationReason: getDeprecationReason(arg), astNode: arg, }; } @@ -527,6 +528,7 @@ export function extendSchemaImpl( type, description: getDescription(field, options), defaultValue: valueFromAST(field.defaultValue, type), + deprecationReason: getDeprecationReason(field), astNode: field, }; } @@ -694,7 +696,10 @@ const stdTypeMap = keyMap( * deprecation reason. */ function getDeprecationReason( - node: EnumValueDefinitionNode | FieldDefinitionNode, + node: + | EnumValueDefinitionNode + | FieldDefinitionNode + | InputValueDefinitionNode, ): ?string { const deprecated = getDirectiveValues(GraphQLDeprecatedDirective, node); return (deprecated?.reason: any); diff --git a/src/utilities/printSchema.js b/src/utilities/printSchema.js index 674d5b26a5..4afceae663 100644 --- a/src/utilities/printSchema.js +++ b/src/utilities/printSchema.js @@ -294,7 +294,7 @@ function printInputValue(arg) { if (defaultAST) { argDecl += ` = ${print(defaultAST)}`; } - return argDecl; + return argDecl + printDeprecated(arg); } function printDirective(directive, options) { @@ -310,7 +310,7 @@ function printDirective(directive, options) { } function printDeprecated(fieldOrEnumVal) { - if (!fieldOrEnumVal.isDeprecated) { + if (fieldOrEnumVal.deprecationReason == null) { return ''; } const reason = fieldOrEnumVal.deprecationReason;