From 7aebf2dbf78e0678b2d32614448c8b9fa10692a6 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Wed, 15 Nov 2017 16:12:56 -0500 Subject: [PATCH 01/28] initial porting --- src/Utils/FindBreakingChanges.php | 609 ++++++++++++++++++++++++++++++ 1 file changed, 609 insertions(+) create mode 100644 src/Utils/FindBreakingChanges.php diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php new file mode 100644 index 0000000..e2b5446 --- /dev/null +++ b/src/Utils/FindBreakingChanges.php @@ -0,0 +1,609 @@ + { + if (!newTypeMap[typeName]) { + breakingChanges.push({ + type: BreakingChangeType.TYPE_REMOVED, + description: `${typeName} was removed.`, + }); + } + }); + return breakingChanges;*/ + } + + /** + * Given two schemas, returns an Array containing descriptions of any breaking + * changes in the newSchema related to changing the type of a type. + */ + public function findTypesThatChangedKind( + $oldSchema, $newSchema + ) { + /*const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + + const breakingChanges = []; + Object.keys(oldTypeMap).forEach(typeName => { + if (!newTypeMap[typeName]) { + return; + } + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if (!(oldType instanceof newType.constructor)) { + breakingChanges.push({ + type: BreakingChangeType.TYPE_CHANGED_KIND, + description: `${typeName} changed from ` + + `${typeKindName(oldType)} to ${typeKindName(newType)}.` + }); + } + }); + return breakingChanges; + */ + } + + /** + * Given two schemas, returns an Array containing descriptions of any + * breaking or dangerous changes in the newSchema related to arguments + * (such as removal or change of type of an argument, or a change in an + * argument's default value). + */ + public function findArgChanges( + $oldSchema, $newSchema + ) { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + + const breakingChanges = []; + const dangerousChanges = []; + + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if ( + !(oldType instanceof GraphQLObjectType || + oldType instanceof GraphQLInterfaceType) || + !(newType instanceof oldType.constructor) + ) { + return; + } + + const oldTypeFields: GraphQLFieldMap<*, *> = oldType.getFields(); + const newTypeFields: GraphQLFieldMap<*, *> = newType.getFields(); + + Object.keys(oldTypeFields).forEach(fieldName => { + if (!newTypeFields[fieldName]) { + return; + } + + oldTypeFields[fieldName].args.forEach(oldArgDef => { + const newArgs = newTypeFields[fieldName].args; + const newArgDef = newArgs.find( + arg => arg.name === oldArgDef.name + ); + + // Arg not present + if (!newArgDef) { + breakingChanges.push({ + type: BreakingChangeType.ARG_REMOVED, + description: `${oldType.name}.${fieldName} arg ` + + `${oldArgDef.name} was removed`, + }); + } else { + const isSafe = isChangeSafeForInputObjectFieldOrFieldArg( + oldArgDef.type, + newArgDef.type, + ); + if (!isSafe) { + breakingChanges.push({ + type: BreakingChangeType.ARG_CHANGED_KIND, + description: `${oldType.name}.${fieldName} arg ` + + `${oldArgDef.name} has changed type from ` + + `${oldArgDef.type.toString()} to ${newArgDef.type.toString()}`, + }); + } else if (oldArgDef.defaultValue !== undefined && + oldArgDef.defaultValue !== newArgDef.defaultValue) { + dangerousChanges.push({ + type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, + description: `${oldType.name}.${fieldName} arg ` + + `${oldArgDef.name} has changed defaultValue`, + }); + } + } + }); + // Check if a non-null arg was added to the field + newTypeFields[fieldName].args.forEach(newArgDef => { + const oldArgs = oldTypeFields[fieldName].args; + const oldArgDef = oldArgs.find( + arg => arg.name === newArgDef.name + ); + if (!oldArgDef && newArgDef.type instanceof GraphQLNonNull) { + breakingChanges.push({ + type: BreakingChangeType.NON_NULL_ARG_ADDED, + description: `A non-null arg ${newArgDef.name} on ` + + `${newType.name}.${fieldName} was added`, + }); + } + }); + }); + }); + + return { + breakingChanges, + dangerousChanges, + };*/ + } + + private static function typeKindName($type) { + /* if (type instanceof GraphQLScalarType) { + return 'a Scalar type'; + } + if (type instanceof GraphQLObjectType) { + return 'an Object type'; + } + if (type instanceof GraphQLInterfaceType) { + return 'an Interface type'; + } + if (type instanceof GraphQLUnionType) { + return 'a Union type'; + } + if (type instanceof GraphQLEnumType) { + return 'an Enum type'; + } + if (type instanceof GraphQLInputObjectType) { + return 'an Input type'; + } + throw new TypeError('Unknown type ' + type.constructor.name);*/ + } + + /** + * Given two schemas, returns an Array containing descriptions of any breaking + * changes in the newSchema related to the fields on a type. This includes if + * a field has been removed from a type, if a field has changed type, or if + * a non-null field is added to an input type. + */ + public static function findFieldsThatChangedType( + $oldSchema, $newSchema + ) { + /*return [ + ...findFieldsThatChangedTypeOnObjectOrInterfaceTypes(oldSchema, newSchema), + ...findFieldsThatChangedTypeOnInputObjectTypes(oldSchema, newSchema), + ];*/ + } + + private static function findFieldsThatChangedTypeOnObjectOrInterfaceTypes( + $oldSchema, $newSchema + ) { + /*const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + + const breakingFieldChanges = []; + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if ( + !(oldType instanceof GraphQLObjectType || + oldType instanceof GraphQLInterfaceType) || + !(newType instanceof oldType.constructor) + ) { + return; + } + + const oldTypeFieldsDef = oldType.getFields(); + const newTypeFieldsDef = newType.getFields(); + Object.keys(oldTypeFieldsDef).forEach(fieldName => { + // Check if the field is missing on the type in the new schema. + if (!(fieldName in newTypeFieldsDef)) { + breakingFieldChanges.push({ + type: BreakingChangeType.FIELD_REMOVED, + description: `${typeName}.${fieldName} was removed.`, + }); + } else { + const oldFieldType = oldTypeFieldsDef[fieldName].type; + const newFieldType = newTypeFieldsDef[fieldName].type; + const isSafe = + isChangeSafeForObjectOrInterfaceField(oldFieldType, newFieldType); + if (!isSafe) { + const oldFieldTypeString = isNamedType(oldFieldType) ? + oldFieldType.name : + oldFieldType.toString(); + const newFieldTypeString = isNamedType(newFieldType) ? + newFieldType.name : + newFieldType.toString(); + breakingFieldChanges.push({ + type: BreakingChangeType.FIELD_CHANGED_KIND, + description: `${typeName}.${fieldName} changed type from ` + + `${oldFieldTypeString} to ${newFieldTypeString}.`, + }); + } + } + }); + }); + return breakingFieldChanges;*/ + } + + public static function findFieldsThatChangedTypeOnInputObjectTypes( + $oldSchema, $newSchema + ) { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + + const breakingFieldChanges = []; + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if ( + !(oldType instanceof GraphQLInputObjectType) || + !(newType instanceof GraphQLInputObjectType) + ) { + return; + } + + const oldTypeFieldsDef = oldType.getFields(); + const newTypeFieldsDef = newType.getFields(); + Object.keys(oldTypeFieldsDef).forEach(fieldName => { + // Check if the field is missing on the type in the new schema. + if (!(fieldName in newTypeFieldsDef)) { + breakingFieldChanges.push({ + type: BreakingChangeType.FIELD_REMOVED, + description: `${typeName}.${fieldName} was removed.`, + }); + } else { + const oldFieldType = oldTypeFieldsDef[fieldName].type; + const newFieldType = newTypeFieldsDef[fieldName].type; + + const isSafe = + isChangeSafeForInputObjectFieldOrFieldArg(oldFieldType, newFieldType); + if (!isSafe) { + const oldFieldTypeString = isNamedType(oldFieldType) ? + oldFieldType.name : + oldFieldType.toString(); + const newFieldTypeString = isNamedType(newFieldType) ? + newFieldType.name : + newFieldType.toString(); + breakingFieldChanges.push({ + type: BreakingChangeType.FIELD_CHANGED_KIND, + description: `${typeName}.${fieldName} changed type from ` + + `${oldFieldTypeString} to ${newFieldTypeString}.`, + }); + } + } + }); + // Check if a non-null field was added to the input object type + Object.keys(newTypeFieldsDef).forEach(fieldName => { + if ( + !(fieldName in oldTypeFieldsDef) && + newTypeFieldsDef[fieldName].type instanceof GraphQLNonNull + ) { + breakingFieldChanges.push({ + type: BreakingChangeType.NON_NULL_INPUT_FIELD_ADDED, + description: `A non-null field ${fieldName} on ` + + `input type ${newType.name} was added.`, + }); + } + }); + }); + return breakingFieldChanges;*/ + } + + private static function isChangeSafeForObjectOrInterfaceField( + $oldType, $newType + ) { + /*if (isNamedType(oldType)) { + return ( + // if they're both named types, see if their names are equivalent + isNamedType(newType) && oldType.name === newType.name + ) || + ( + // moving from nullable to non-null of the same underlying type is safe + newType instanceof GraphQLNonNull && + isChangeSafeForObjectOrInterfaceField( + oldType, + newType.ofType, + ) + ); + } else if (oldType instanceof GraphQLList) { + return ( + // if they're both lists, make sure the underlying types are compatible + newType instanceof GraphQLList && + isChangeSafeForObjectOrInterfaceField( + oldType.ofType, + newType.ofType, + ) + ) || + ( + // moving from nullable to non-null of the same underlying type is safe + newType instanceof GraphQLNonNull && + isChangeSafeForObjectOrInterfaceField( + oldType, + newType.ofType, + ) + ); + } else if (oldType instanceof GraphQLNonNull) { + // if they're both non-null, make sure the underlying types are compatible + return newType instanceof GraphQLNonNull && + isChangeSafeForObjectOrInterfaceField( + oldType.ofType, + newType.ofType, + ); + } + return false;*/ + } + + private static function isChangeSafeForInputObjectFieldOrFieldArg( + $oldType, $newType + ) { + /* if (isNamedType(oldType)) { + // if they're both named types, see if their names are equivalent + return isNamedType(newType) && oldType.name === newType.name; + } else if (oldType instanceof GraphQLList) { + // if they're both lists, make sure the underlying types are compatible + return newType instanceof GraphQLList && + isChangeSafeForInputObjectFieldOrFieldArg( + oldType.ofType, + newType.ofType, + ); + } else if (oldType instanceof GraphQLNonNull) { + return ( + // if they're both non-null, make sure the underlying types are + // compatible + newType instanceof GraphQLNonNull && + isChangeSafeForInputObjectFieldOrFieldArg( + oldType.ofType, + newType.ofType, + ) + ) || + ( + // moving from non-null to nullable of the same underlying type is safe + !(newType instanceof GraphQLNonNull) && + isChangeSafeForInputObjectFieldOrFieldArg( + oldType.ofType, + newType, + ) + ); + } + return false;*/ + } + + /** + * Given two schemas, returns an Array containing descriptions of any breaking + * changes in the newSchema related to removing types from a union type. + */ + public static function findTypesRemovedFromUnions( + $oldSchema, $newSchema + ) { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + + const typesRemovedFromUnion = []; + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if (!(oldType instanceof GraphQLUnionType) || + !(newType instanceof GraphQLUnionType)) { + return; + } + const typeNamesInNewUnion = Object.create(null); + newType.getTypes().forEach(type => { + typeNamesInNewUnion[type.name] = true; + }); + oldType.getTypes().forEach(type => { + if (!typeNamesInNewUnion[type.name]) { + typesRemovedFromUnion.push({ + type: BreakingChangeType.TYPE_REMOVED_FROM_UNION, + description: `${type.name} was removed from union type ${typeName}.` + }); + } + }); + }); + return typesRemovedFromUnion;*/ + } + + /** + * Given two schemas, returns an Array containing descriptions of any dangerous + * changes in the newSchema related to adding types to a union type. + */ + public static function findTypesAddedToUnions( + $oldSchema, $newSchema + ) { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + + const typesAddedToUnion = []; + Object.keys(newTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if (!(oldType instanceof GraphQLUnionType) || + !(newType instanceof GraphQLUnionType)) { + return; + } + const typeNamesInOldUnion = Object.create(null); + oldType.getTypes().forEach(type => { + typeNamesInOldUnion[type.name] = true; + }); + newType.getTypes().forEach(type => { + if (!typeNamesInOldUnion[type.name]) { + typesAddedToUnion.push({ + type: DangerousChangeType.TYPE_ADDED_TO_UNION, + description: `${type.name} was added to union type ${typeName}.` + }); + } + }); + }); + return typesAddedToUnion;*/ + } + + /** + * Given two schemas, returns an Array containing descriptions of any breaking + * changes in the newSchema related to removing values from an enum type. + */ + public static function findValuesRemovedFromEnums( + $oldSchema, $newSchema + ) { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + + const valuesRemovedFromEnums = []; + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if (!(oldType instanceof GraphQLEnumType) || + !(newType instanceof GraphQLEnumType)) { + return; + } + const valuesInNewEnum = Object.create(null); + newType.getValues().forEach(value => { + valuesInNewEnum[value.name] = true; + }); + oldType.getValues().forEach(value => { + if (!valuesInNewEnum[value.name]) { + valuesRemovedFromEnums.push({ + type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM, + description: `${value.name} was removed from enum type ${typeName}.` + }); + } + }); + }); + return valuesRemovedFromEnums;*/ + } + + /** + * Given two schemas, returns an Array containing descriptions of any dangerous + * changes in the newSchema related to adding values to an enum type. + */ + public static function findValuesAddedToEnums( + $oldSchema, $newSchema + ) { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + + const valuesAddedToEnums = []; + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if (!(oldType instanceof GraphQLEnumType) || + !(newType instanceof GraphQLEnumType)) { + return; + } + + const valuesInOldEnum = Object.create(null); + oldType.getValues().forEach(value => { + valuesInOldEnum[value.name] = true; + }); + newType.getValues().forEach(value => { + if (!valuesInOldEnum[value.name]) { + valuesAddedToEnums.push({ + type: DangerousChangeType.VALUE_ADDED_TO_ENUM, + description: `${value.name} was added to enum type ${typeName}.` + }); + } + }); + }); + return valuesAddedToEnums;*/ + } + + public static function findInterfacesRemovedFromObjectTypes( + $oldSchema, $newSchema + ) { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + const breakingChanges = []; + + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if ( + !(oldType instanceof GraphQLObjectType) || + !(newType instanceof GraphQLObjectType) + ) { + return; + } + + const oldInterfaces = oldType.getInterfaces(); + const newInterfaces = newType.getInterfaces(); + oldInterfaces.forEach(oldInterface => { + if (!newInterfaces.some(int => int.name === oldInterface.name)) { + breakingChanges.push({ + type: BreakingChangeType.INTERFACE_REMOVED_FROM_OBJECT, + description: `${typeName} no longer implements interface ` + + `${oldInterface.name}.` + }); + } + }); + }); + return breakingChanges;*/ + } +} \ No newline at end of file From 3811181f4914ab2391b3b16d539cefc88ea6f579 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Thu, 16 Nov 2017 13:53:01 -0500 Subject: [PATCH 02/28] some functions converted over --- src/Utils/FindBreakingChanges.php | 1298 ++++++++++++++++------------- 1 file changed, 726 insertions(+), 572 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index e2b5446..fa140b6 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -5,605 +5,759 @@ namespace GraphQL\Utils; +use GraphQL\Type\Definition\EnumType; +use GraphQL\Type\Definition\InputObjectType; +use GraphQL\Type\Definition\InterfaceType; +use GraphQL\Type\Definition\ListOfType; +use GraphQL\Type\Definition\NonNull; +use GraphQL\Type\Definition\ObjectType; +use GraphQL\Type\Definition\ScalarType; +use GraphQL\Type\Definition\Type; +use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Schema; -class FindBreakingChanges { +class FindBreakingChanges +{ - const BREAKING_CHANGE_FIELD_CHANGED = 'FIELD_CHANGED_KIND'; - const BREAKING_CHANGE_FIELD_REMOVED = 'FIELD_REMOVED'; - const BREAKING_CHANGE_TYPE_CHANGED = 'TYPE_CHANGED_KIND'; - const BREAKING_CHANGE_TYPE_REMOVED = 'TYPE_REMOVED'; - const BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION = 'TYPE_REMOVED_FROM_UNION'; - const BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM = 'VALUE_REMOVED_FROM_ENUM'; - const BREAKING_CHANGE_ARG_REMOVED = 'ARG_REMOVED'; - const BREAKING_CHANGE_ARG_CHANGED = 'ARG_CHANGED_KIND'; - const BREAKING_CHANGE_NON_NULL_ARG_ADDED = 'NON_NULL_ARG_ADDED'; - const BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED = 'NON_NULL_INPUT_FIELD_ADDED'; - const BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT = 'INTERFACE_REMOVED_FROM_OBJECT'; - /* - export const DangerousChangeType = { - ARG_DEFAULT_VALUE_CHANGE: 'ARG_DEFAULT_VALUE_CHANGE', - VALUE_ADDED_TO_ENUM: 'VALUE_ADDED_TO_ENUM', - TYPE_ADDED_TO_UNION: 'TYPE_ADDED_TO_UNION', - };*/ + const BREAKING_CHANGE_FIELD_CHANGED = 'FIELD_CHANGED_KIND'; + const BREAKING_CHANGE_FIELD_REMOVED = 'FIELD_REMOVED'; + const BREAKING_CHANGE_TYPE_CHANGED = 'TYPE_CHANGED_KIND'; + const BREAKING_CHANGE_TYPE_REMOVED = 'TYPE_REMOVED'; + const BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION = 'TYPE_REMOVED_FROM_UNION'; + const BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM = 'VALUE_REMOVED_FROM_ENUM'; + const BREAKING_CHANGE_ARG_REMOVED = 'ARG_REMOVED'; + const BREAKING_CHANGE_ARG_CHANGED = 'ARG_CHANGED_KIND'; + const BREAKING_CHANGE_NON_NULL_ARG_ADDED = 'NON_NULL_ARG_ADDED'; + const BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED = 'NON_NULL_INPUT_FIELD_ADDED'; + const BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT = 'INTERFACE_REMOVED_FROM_OBJECT'; + /* + export const DangerousChangeType = { + ARG_DEFAULT_VALUE_CHANGE: 'ARG_DEFAULT_VALUE_CHANGE', + VALUE_ADDED_TO_ENUM: 'VALUE_ADDED_TO_ENUM', + TYPE_ADDED_TO_UNION: 'TYPE_ADDED_TO_UNION', + };*/ - /** - * Given two schemas, returns an Array containing descriptions of all the types - * of breaking changes covered by the other functions down below. - * - * @return array - */ - public function findBreakingChanges( - $oldSchema, $newSchema - ) { - return [ - /*...findRemovedTypes(oldSchema, newSchema), - ...findTypesThatChangedKind(oldSchema, newSchema), - ...findFieldsThatChangedType(oldSchema, newSchema), - ...findTypesRemovedFromUnions(oldSchema, newSchema), - ...findValuesRemovedFromEnums(oldSchema, newSchema), - ...findArgChanges(oldSchema, newSchema).breakingChanges, - ...findInterfacesRemovedFromObjectTypes(oldSchema, newSchema), - */ - ]; - } + /** + * Given two schemas, returns an Array containing descriptions of all the types + * of potentially dangerous changes covered by the other functions down below. + * + * @return array + */ + public function findDangerousChanges( + Schema $oldSchema, Schema $newSchema + ) + { + return [ + /* ...findArgChanges(oldSchema, newSchema).dangerousChanges, + ...findValuesAddedToEnums(oldSchema, newSchema), + ...findTypesAddedToUnions(oldSchema, newSchema) + */ + ]; + } - /** - * Given two schemas, returns an Array containing descriptions of all the types - * of potentially dangerous changes covered by the other functions down below. - * - * @return array - */ - public function findDangerousChanges( - $oldSchema, $newSchema - ) { - return [ - /* ...findArgChanges(oldSchema, newSchema).dangerousChanges, - ...findValuesAddedToEnums(oldSchema, newSchema), - ...findTypesAddedToUnions(oldSchema, newSchema) - */ - ]; - } + /** + * Given two schemas, returns an Array containing descriptions of all the types + * of breaking changes covered by the other functions down below. + * + * @return array + */ + public function findBreakingChanges( + $oldSchema, $newSchema + ) + { + return [ + /*...findRemovedTypes(oldSchema, newSchema), + ...findTypesThatChangedKind(oldSchema, newSchema), + ...findFieldsThatChangedType(oldSchema, newSchema), + ...findTypesRemovedFromUnions(oldSchema, newSchema), + ...findValuesRemovedFromEnums(oldSchema, newSchema), + ...findArgChanges(oldSchema, newSchema).breakingChanges, + ...findInterfacesRemovedFromObjectTypes(oldSchema, newSchema), + */ + ]; + } - /** - * Given two schemas, returns an Array containing descriptions of any breaking - * changes in the newSchema related to removing an entire type. - */ - public function findRemovedTypes( - $oldSchema, $newSchema - ) { - /*const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); + /** + * Given two schemas, returns an Array containing descriptions of any breaking + * changes in the newSchema related to removing an entire type. + */ + public function findRemovedTypes( + Schema $oldSchema, Schema $newSchema + ) + { + $oldTypeMap = $oldSchema->getTypeMap(); + $newTypeMap = $newSchema->getTypeMap(); - const breakingChanges = []; - Object.keys(oldTypeMap).forEach(typeName => { - if (!newTypeMap[typeName]) { - breakingChanges.push({ - type: BreakingChangeType.TYPE_REMOVED, - description: `${typeName} was removed.`, - }); - } - }); - return breakingChanges;*/ - } - - /** - * Given two schemas, returns an Array containing descriptions of any breaking - * changes in the newSchema related to changing the type of a type. - */ - public function findTypesThatChangedKind( - $oldSchema, $newSchema - ) { - /*const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); - - const breakingChanges = []; - Object.keys(oldTypeMap).forEach(typeName => { - if (!newTypeMap[typeName]) { - return; - } - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if (!(oldType instanceof newType.constructor)) { - breakingChanges.push({ - type: BreakingChangeType.TYPE_CHANGED_KIND, - description: `${typeName} changed from ` + - `${typeKindName(oldType)} to ${typeKindName(newType)}.` - }); - } - }); - return breakingChanges; - */ - } - - /** - * Given two schemas, returns an Array containing descriptions of any - * breaking or dangerous changes in the newSchema related to arguments - * (such as removal or change of type of an argument, or a change in an - * argument's default value). - */ - public function findArgChanges( - $oldSchema, $newSchema - ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); - - const breakingChanges = []; - const dangerousChanges = []; - - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if ( - !(oldType instanceof GraphQLObjectType || - oldType instanceof GraphQLInterfaceType) || - !(newType instanceof oldType.constructor) - ) { - return; - } - - const oldTypeFields: GraphQLFieldMap<*, *> = oldType.getFields(); - const newTypeFields: GraphQLFieldMap<*, *> = newType.getFields(); - - Object.keys(oldTypeFields).forEach(fieldName => { - if (!newTypeFields[fieldName]) { - return; - } - - oldTypeFields[fieldName].args.forEach(oldArgDef => { - const newArgs = newTypeFields[fieldName].args; - const newArgDef = newArgs.find( - arg => arg.name === oldArgDef.name - ); - - // Arg not present - if (!newArgDef) { - breakingChanges.push({ - type: BreakingChangeType.ARG_REMOVED, - description: `${oldType.name}.${fieldName} arg ` + - `${oldArgDef.name} was removed`, - }); - } else { - const isSafe = isChangeSafeForInputObjectFieldOrFieldArg( - oldArgDef.type, - newArgDef.type, - ); - if (!isSafe) { - breakingChanges.push({ - type: BreakingChangeType.ARG_CHANGED_KIND, - description: `${oldType.name}.${fieldName} arg ` + - `${oldArgDef.name} has changed type from ` + - `${oldArgDef.type.toString()} to ${newArgDef.type.toString()}`, - }); - } else if (oldArgDef.defaultValue !== undefined && - oldArgDef.defaultValue !== newArgDef.defaultValue) { - dangerousChanges.push({ - type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, - description: `${oldType.name}.${fieldName} arg ` + - `${oldArgDef.name} has changed defaultValue`, - }); - } - } - }); - // Check if a non-null arg was added to the field - newTypeFields[fieldName].args.forEach(newArgDef => { - const oldArgs = oldTypeFields[fieldName].args; - const oldArgDef = oldArgs.find( - arg => arg.name === newArgDef.name - ); - if (!oldArgDef && newArgDef.type instanceof GraphQLNonNull) { - breakingChanges.push({ - type: BreakingChangeType.NON_NULL_ARG_ADDED, - description: `A non-null arg ${newArgDef.name} on ` + - `${newType.name}.${fieldName} was added`, - }); - } - }); - }); - }); - - return { - breakingChanges, - dangerousChanges, - };*/ - } - - private static function typeKindName($type) { - /* if (type instanceof GraphQLScalarType) { - return 'a Scalar type'; - } - if (type instanceof GraphQLObjectType) { - return 'an Object type'; - } - if (type instanceof GraphQLInterfaceType) { - return 'an Interface type'; - } - if (type instanceof GraphQLUnionType) { - return 'a Union type'; - } - if (type instanceof GraphQLEnumType) { - return 'an Enum type'; - } - if (type instanceof GraphQLInputObjectType) { - return 'an Input type'; - } - throw new TypeError('Unknown type ' + type.constructor.name);*/ - } - - /** - * Given two schemas, returns an Array containing descriptions of any breaking - * changes in the newSchema related to the fields on a type. This includes if - * a field has been removed from a type, if a field has changed type, or if - * a non-null field is added to an input type. - */ - public static function findFieldsThatChangedType( - $oldSchema, $newSchema - ) { - /*return [ - ...findFieldsThatChangedTypeOnObjectOrInterfaceTypes(oldSchema, newSchema), - ...findFieldsThatChangedTypeOnInputObjectTypes(oldSchema, newSchema), - ];*/ - } - - private static function findFieldsThatChangedTypeOnObjectOrInterfaceTypes( - $oldSchema, $newSchema - ) { - /*const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); - - const breakingFieldChanges = []; - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if ( - !(oldType instanceof GraphQLObjectType || - oldType instanceof GraphQLInterfaceType) || - !(newType instanceof oldType.constructor) - ) { - return; - } - - const oldTypeFieldsDef = oldType.getFields(); - const newTypeFieldsDef = newType.getFields(); - Object.keys(oldTypeFieldsDef).forEach(fieldName => { - // Check if the field is missing on the type in the new schema. - if (!(fieldName in newTypeFieldsDef)) { - breakingFieldChanges.push({ - type: BreakingChangeType.FIELD_REMOVED, - description: `${typeName}.${fieldName} was removed.`, - }); - } else { - const oldFieldType = oldTypeFieldsDef[fieldName].type; - const newFieldType = newTypeFieldsDef[fieldName].type; - const isSafe = - isChangeSafeForObjectOrInterfaceField(oldFieldType, newFieldType); - if (!isSafe) { - const oldFieldTypeString = isNamedType(oldFieldType) ? - oldFieldType.name : - oldFieldType.toString(); - const newFieldTypeString = isNamedType(newFieldType) ? - newFieldType.name : - newFieldType.toString(); - breakingFieldChanges.push({ - type: BreakingChangeType.FIELD_CHANGED_KIND, - description: `${typeName}.${fieldName} changed type from ` + - `${oldFieldTypeString} to ${newFieldTypeString}.`, - }); - } - } - }); - }); - return breakingFieldChanges;*/ - } - - public static function findFieldsThatChangedTypeOnInputObjectTypes( - $oldSchema, $newSchema - ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); - - const breakingFieldChanges = []; - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if ( - !(oldType instanceof GraphQLInputObjectType) || - !(newType instanceof GraphQLInputObjectType) - ) { - return; - } - - const oldTypeFieldsDef = oldType.getFields(); - const newTypeFieldsDef = newType.getFields(); - Object.keys(oldTypeFieldsDef).forEach(fieldName => { - // Check if the field is missing on the type in the new schema. - if (!(fieldName in newTypeFieldsDef)) { - breakingFieldChanges.push({ - type: BreakingChangeType.FIELD_REMOVED, - description: `${typeName}.${fieldName} was removed.`, - }); - } else { - const oldFieldType = oldTypeFieldsDef[fieldName].type; - const newFieldType = newTypeFieldsDef[fieldName].type; - - const isSafe = - isChangeSafeForInputObjectFieldOrFieldArg(oldFieldType, newFieldType); - if (!isSafe) { - const oldFieldTypeString = isNamedType(oldFieldType) ? - oldFieldType.name : - oldFieldType.toString(); - const newFieldTypeString = isNamedType(newFieldType) ? - newFieldType.name : - newFieldType.toString(); - breakingFieldChanges.push({ - type: BreakingChangeType.FIELD_CHANGED_KIND, - description: `${typeName}.${fieldName} changed type from ` + - `${oldFieldTypeString} to ${newFieldTypeString}.`, - }); + $breakingChanges = []; + foreach ($oldTypeMap as $typeName => $typeDefinition) { + if (!isset($newTypeMap[$typeName])) { + $breakingChanges[] = + ['type' => self::BREAKING_CHANGE_TYPE_REMOVED, 'description' => "${$typeName} was removed."]; } - } - }); - // Check if a non-null field was added to the input object type - Object.keys(newTypeFieldsDef).forEach(fieldName => { - if ( - !(fieldName in oldTypeFieldsDef) && - newTypeFieldsDef[fieldName].type instanceof GraphQLNonNull - ) { - breakingFieldChanges.push({ - type: BreakingChangeType.NON_NULL_INPUT_FIELD_ADDED, - description: `A non-null field ${fieldName} on ` + - `input type ${newType.name} was added.`, + } + + /*Object.keys(oldTypeMap).forEach(typeName => { + if (!newTypeMap[typeName]) { + breakingChanges.push({ + type: BreakingChangeType.TYPE_REMOVED, + description: `${typeName} was removed.`, }); } - }); - }); - return breakingFieldChanges;*/ - } + });*/ - private static function isChangeSafeForObjectOrInterfaceField( - $oldType, $newType - ) { - /*if (isNamedType(oldType)) { - return ( - // if they're both named types, see if their names are equivalent - isNamedType(newType) && oldType.name === newType.name - ) || - ( - // moving from nullable to non-null of the same underlying type is safe - newType instanceof GraphQLNonNull && - isChangeSafeForObjectOrInterfaceField( - oldType, - newType.ofType, - ) - ); - } else if (oldType instanceof GraphQLList) { - return ( - // if they're both lists, make sure the underlying types are compatible - newType instanceof GraphQLList && + return $breakingChanges; + } + + /** + * Given two schemas, returns an Array containing descriptions of any breaking + * changes in the newSchema related to changing the type of a type. + */ + public function findTypesThatChangedKind( + Schema $oldSchema, Schema $newSchema + ) + { + $oldTypeMap = $oldSchema->getTypeMap(); + $newTypeMap = $newSchema->getTypeMap(); + + $breakingChanges = []; + foreach ($oldTypeMap as $typeName => $typeDefinition) { + if (!isset($newTypeMap[$typeName])) { + continue; + } + $newTypeDefinition = $newTypeMap[$typeName]; + if (!($typeDefinition instanceof $newTypeDefinition)) { + $oldTypeKindName = self::typeKindName($typeDefinition); + $newTypeKindName = self::typeKindName($newTypeDefinition); + $breakingChanges[] = [ + 'type' => self::BREAKING_CHANGE_TYPE_CHANGED, + 'description' => "${$typeName} changed from ${oldTypeKindName} to ${newTypeKindName}." + ]; + } + } + + /* + Object.keys(oldTypeMap).forEach(typeName => { + if (!newTypeMap[typeName]) { + return; + } + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if (!(oldType instanceof newType.constructor)) { + breakingChanges.push({ + type: BreakingChangeType.TYPE_CHANGED_KIND, + description: `${typeName} changed from ` + + `${typeKindName(oldType)} to ${typeKindName(newType)}.` + }); + } + });*/ + + return $breakingChanges; + } + + /** + * Given two schemas, returns an Array containing descriptions of any + * breaking or dangerous changes in the newSchema related to arguments + * (such as removal or change of type of an argument, or a change in an + * argument's default value). + */ + public function findArgChanges( + Schema $oldSchema, Schema $newSchema + ) + { + $oldTypeMap = $oldSchema->getTypeMap(); + $newTypeMap = $newSchema->getTypeMap(); + + $breakingChanges = []; + $dangerousChanges = []; + foreach ($oldTypeMap as $oldTypeName => $oldTypeDefinition) { + $newTypeDefinition = isset($newTypeMap[$oldTypeName]) ? $newTypeMap[$oldTypeName] : null; + if (!($oldTypeDefinition instanceof ObjectType || $oldTypeDefinition instanceof InterfaceType) || + !($newTypeDefinition instanceof $oldTypeDefinition)) { + continue; + } + + $oldTypeFields = $oldTypeDefinition->getFields(); + $newTypeFields = $newTypeDefinition->getFields(); + + foreach ($oldTypeFields as $fieldName => $fieldDefinition) { + if (!isset($newTypeFields[$fieldName])) { + continue; + } + + foreach ($fieldDefinition->args as $oldArgName => $oldArgDef) { + $newArgs = $newTypeFields[$fieldName]->args; + $newArgDef = Utils::find( + $newArgs, function ($arg) use ($oldArgDef) { + return $arg->name === $oldArgDef->name; + } + ); + if (!$newArgDef) { + $breakingChanges[] = [ + 'type' => self::BREAKING_CHANGE_ARG_REMOVED, + 'description' => "${$oldTypeName}->${$fieldName} arg ${oldArgName} was removed" + ]; + } else { + $isSafe = self::isChangeSafeForInputObjectFieldOrFieldArg($oldArgDef->getType(), $newArgDef->getType()); + if (!$isSafe) { + $oldArgType = $oldArgDef->getType(); + $newArgType = $newArgDef->getType(); + $breakingChanges[] = [ + 'type' => self::BREAKING_CHANGE_ARG_CHANGED, + 'description' => "${oldTypeName}->${fieldName} arg ${oldArgName} has changed type from ${oldArgType} to ${newArgType}" + ]; + } elseif ($oldArgDef->defaultValueExists() && $oldArgDef->defaultValue !== $newArgDef->defaultValue) { + $dangerousChanges[] = []; // TODO + } + } + // Check if a non-null arg was added to the field + foreach ($newTypeFields[$fieldName]->args as $newArgName => $newArgDef) { + $oldArgs = $oldTypeFields[$fieldName]->args; + $oldArgDef = Utils::find( + $oldArgs, function ($arg) use ($newArgDef) { + return $arg->name === $newArgDef->name; + } + ); + + if (!$oldArgDef && $newArgDef->getType() instanceof NonNull) { + $newTypeName = $newTypeDefinition->name; + $breakingChanges[] = [ + 'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED, + 'description' => "A non-null arg ${$newArgName} on ${newTypeName}->${fieldName} was added." + ]; + } + } + } + } + } + + return ['breakingChanges' => $breakingChanges, 'dangerousChanges' => $dangerousChanges]; + /* + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if ( + !(oldType instanceof GraphQLObjectType || + oldType instanceof GraphQLInterfaceType) || + !(newType instanceof oldType.constructor) + ) { + return; + } + + const oldTypeFields: GraphQLFieldMap<*, *> = oldType.getFields(); + const newTypeFields: GraphQLFieldMap<*, *> = newType.getFields(); + + Object.keys(oldTypeFields).forEach(fieldName => { + if (!newTypeFields[fieldName]) { + return; + } + + oldTypeFields[fieldName].args.forEach(oldArgDef => { + const newArgs = newTypeFields[fieldName].args; + const newArgDef = newArgs.find( + arg => arg.name === oldArgDef.name + ); + + // Arg not present + if (!newArgDef) { + breakingChanges.push({ + type: BreakingChangeType.ARG_REMOVED, + description: `${oldType.name}.${fieldName} arg ` + + `${oldArgDef.name} was removed`, + }); + } else { + const isSafe = isChangeSafeForInputObjectFieldOrFieldArg( + oldArgDef.type, + newArgDef.type, + ); + if (!isSafe) { + breakingChanges.push({ + type: BreakingChangeType.ARG_CHANGED_KIND, + description: `${oldType.name}.${fieldName} arg ` + + `${oldArgDef.name} has changed type from ` + + `${oldArgDef.type.toString()} to ${newArgDef.type.toString()}`, + }); + } else if (oldArgDef.defaultValue !== undefined && + oldArgDef.defaultValue !== newArgDef.defaultValue) { + dangerousChanges.push({ + type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, + description: `${oldType.name}.${fieldName} arg ` + + `${oldArgDef.name} has changed defaultValue`, + }); + } + } + }); + // Check if a non-null arg was added to the field + newTypeFields[fieldName].args.forEach(newArgDef => { + const oldArgs = oldTypeFields[fieldName].args; + const oldArgDef = oldArgs.find( + arg => arg.name === newArgDef.name + ); + if (!oldArgDef && newArgDef.type instanceof GraphQLNonNull) { + breakingChanges.push({ + type: BreakingChangeType.NON_NULL_ARG_ADDED, + description: `A non-null arg ${newArgDef.name} on ` + + `${newType.name}.${fieldName} was added`, + }); + } + }); + }); + }); + + return { + breakingChanges, + dangerousChanges, + };*/ + } + + /** + * @param Type $type + * @return string + * + * @throws \TypeError + */ + private static function typeKindName(Type $type) + { + if ($type instanceof ScalarType) { + return 'a Scalar type'; + } elseif($type instanceof ObjectType) { + return 'an Object type'; + } elseif ($type instanceof InterfaceType) { + return 'an Interface type'; + } elseif ($type instanceof UnionType) { + return 'a Union type'; + } elseif ($type instanceof EnumType) { + return 'an Enum type'; + } elseif ($type instanceof InputObjectType) { + return 'an Input type'; + } + + throw new \TypeError('unknown type ' . $type->name); + } + + /** + * Given two schemas, returns an Array containing descriptions of any breaking + * changes in the newSchema related to the fields on a type. This includes if + * a field has been removed from a type, if a field has changed type, or if + * a non-null field is added to an input type. + */ + public static function findFieldsThatChangedType( + Schema $oldSchema, Schema $newSchema + ) + { + /*return [ + ...findFieldsThatChangedTypeOnObjectOrInterfaceTypes(oldSchema, newSchema), + ...findFieldsThatChangedTypeOnInputObjectTypes(oldSchema, newSchema), + ];*/ + } + + private static function findFieldsThatChangedTypeOnObjectOrInterfaceTypes( + $oldSchema, $newSchema + ) + { + /*const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + + const breakingFieldChanges = []; + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if ( + !(oldType instanceof GraphQLObjectType || + oldType instanceof GraphQLInterfaceType) || + !(newType instanceof oldType.constructor) + ) { + return; + } + + const oldTypeFieldsDef = oldType.getFields(); + const newTypeFieldsDef = newType.getFields(); + Object.keys(oldTypeFieldsDef).forEach(fieldName => { + // Check if the field is missing on the type in the new schema. + if (!(fieldName in newTypeFieldsDef)) { + breakingFieldChanges.push({ + type: BreakingChangeType.FIELD_REMOVED, + description: `${typeName}.${fieldName} was removed.`, + }); + } else { + const oldFieldType = oldTypeFieldsDef[fieldName].type; + const newFieldType = newTypeFieldsDef[fieldName].type; + const isSafe = + isChangeSafeForObjectOrInterfaceField(oldFieldType, newFieldType); + if (!isSafe) { + const oldFieldTypeString = isNamedType(oldFieldType) ? + oldFieldType.name : + oldFieldType.toString(); + const newFieldTypeString = isNamedType(newFieldType) ? + newFieldType.name : + newFieldType.toString(); + breakingFieldChanges.push({ + type: BreakingChangeType.FIELD_CHANGED_KIND, + description: `${typeName}.${fieldName} changed type from ` + + `${oldFieldTypeString} to ${newFieldTypeString}.`, + }); + } + } + }); + }); + return breakingFieldChanges;*/ + } + + public static function findFieldsThatChangedTypeOnInputObjectTypes( + Schema $oldSchema, Schema $newSchema + ) + { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + + const breakingFieldChanges = []; + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if ( + !(oldType instanceof GraphQLInputObjectType) || + !(newType instanceof GraphQLInputObjectType) + ) { + return; + } + + const oldTypeFieldsDef = oldType.getFields(); + const newTypeFieldsDef = newType.getFields(); + Object.keys(oldTypeFieldsDef).forEach(fieldName => { + // Check if the field is missing on the type in the new schema. + if (!(fieldName in newTypeFieldsDef)) { + breakingFieldChanges.push({ + type: BreakingChangeType.FIELD_REMOVED, + description: `${typeName}.${fieldName} was removed.`, + }); + } else { + const oldFieldType = oldTypeFieldsDef[fieldName].type; + const newFieldType = newTypeFieldsDef[fieldName].type; + + const isSafe = + isChangeSafeForInputObjectFieldOrFieldArg(oldFieldType, newFieldType); + if (!isSafe) { + const oldFieldTypeString = isNamedType(oldFieldType) ? + oldFieldType.name : + oldFieldType.toString(); + const newFieldTypeString = isNamedType(newFieldType) ? + newFieldType.name : + newFieldType.toString(); + breakingFieldChanges.push({ + type: BreakingChangeType.FIELD_CHANGED_KIND, + description: `${typeName}.${fieldName} changed type from ` + + `${oldFieldTypeString} to ${newFieldTypeString}.`, + }); + } + } + }); + // Check if a non-null field was added to the input object type + Object.keys(newTypeFieldsDef).forEach(fieldName => { + if ( + !(fieldName in oldTypeFieldsDef) && + newTypeFieldsDef[fieldName].type instanceof GraphQLNonNull + ) { + breakingFieldChanges.push({ + type: BreakingChangeType.NON_NULL_INPUT_FIELD_ADDED, + description: `A non-null field ${fieldName} on ` + + `input type ${newType.name} was added.`, + }); + } + }); + }); + return breakingFieldChanges;*/ + } + + private static function isChangeSafeForObjectOrInterfaceField( + Type $oldType, Type $newType + ) + { + if (self::isNamedType($oldType)) { + // if they're both named types, see if their names are equivalent + return (self::isNamedType($newType) && $oldType->name === $newType->name) + // moving from nullable to non-null of the same underlying type is safe + || ($newType instanceof NonNull + && self::isChangeSafeForObjectOrInterfaceField( + $oldType, $newType->getWrappedType() + )); + } elseif ($oldType instanceof ListOfType) { + // if they're both lists, make sure the underlying types are compatible + return ($newType instanceof ListOfType && + self::isChangeSafeForObjectOrInterfaceField($oldType->getWrappedType(), $newType->getWrappedType())) || + // moving from nullable to non-null of the same underlying type is safe + ($newType instanceof NonNull && + self::isChangeSafeForObjectOrInterfaceField($oldType, $newType->getWrappedType())); + } elseif ($oldType instanceof NonNull) { + // if they're both non-null, make sure the underlying types are compatible + return $newType instanceof NonNull && + self::isChangeSafeForObjectOrInterfaceField($oldType->getWrappedType(), $newType->getWrappedType()); + } + + return false; + /*if (isNamedType(oldType)) { + return ( + // if they're both named types, see if their names are equivalent + isNamedType(newType) && oldType.name === newType.name + ) || + ( + // moving from nullable to non-null of the same underlying type is safe + newType instanceof GraphQLNonNull && + isChangeSafeForObjectOrInterfaceField( + oldType, + newType.ofType, + ) + ); + } else if (oldType instanceof GraphQLList) { + return ( + // if they're both lists, make sure the underlying types are compatible + newType instanceof GraphQLList && + isChangeSafeForObjectOrInterfaceField( + oldType.ofType, + newType.ofType, + ) + ) || + ( + // moving from nullable to non-null of the same underlying type is safe + newType instanceof GraphQLNonNull && + isChangeSafeForObjectOrInterfaceField( + oldType, + newType.ofType, + ) + ); + } else if (oldType instanceof GraphQLNonNull) { + // if they're both non-null, make sure the underlying types are compatible + return newType instanceof GraphQLNonNull && isChangeSafeForObjectOrInterfaceField( oldType.ofType, newType.ofType, - ) - ) || - ( - // moving from nullable to non-null of the same underlying type is safe - newType instanceof GraphQLNonNull && - isChangeSafeForObjectOrInterfaceField( - oldType, - newType.ofType, - ) - ); - } else if (oldType instanceof GraphQLNonNull) { - // if they're both non-null, make sure the underlying types are compatible - return newType instanceof GraphQLNonNull && - isChangeSafeForObjectOrInterfaceField( - oldType.ofType, - newType.ofType, - ); + ); + } + return false;*/ } - return false;*/ - } - private static function isChangeSafeForInputObjectFieldOrFieldArg( - $oldType, $newType - ) { - /* if (isNamedType(oldType)) { - // if they're both named types, see if their names are equivalent - return isNamedType(newType) && oldType.name === newType.name; - } else if (oldType instanceof GraphQLList) { - // if they're both lists, make sure the underlying types are compatible - return newType instanceof GraphQLList && - isChangeSafeForInputObjectFieldOrFieldArg( - oldType.ofType, - newType.ofType, - ); - } else if (oldType instanceof GraphQLNonNull) { - return ( - // if they're both non-null, make sure the underlying types are - // compatible - newType instanceof GraphQLNonNull && + private static function isChangeSafeForInputObjectFieldOrFieldArg( + Schema $oldSchema, Schema $newSchema + ) + { + /* if (isNamedType(oldType)) { + // if they're both named types, see if their names are equivalent + return isNamedType(newType) && oldType.name === newType.name; + } else if (oldType instanceof GraphQLList) { + // if they're both lists, make sure the underlying types are compatible + return newType instanceof GraphQLList && isChangeSafeForInputObjectFieldOrFieldArg( oldType.ofType, newType.ofType, - ) - ) || - ( - // moving from non-null to nullable of the same underlying type is safe - !(newType instanceof GraphQLNonNull) && - isChangeSafeForInputObjectFieldOrFieldArg( - oldType.ofType, - newType, - ) - ); - } - return false;*/ - } + ); + } else if (oldType instanceof GraphQLNonNull) { + return ( + // if they're both non-null, make sure the underlying types are + // compatible + newType instanceof GraphQLNonNull && + isChangeSafeForInputObjectFieldOrFieldArg( + oldType.ofType, + newType.ofType, + ) + ) || + ( + // moving from non-null to nullable of the same underlying type is safe + !(newType instanceof GraphQLNonNull) && + isChangeSafeForInputObjectFieldOrFieldArg( + oldType.ofType, + newType, + ) + ); + } + return false;*/ + } - /** - * Given two schemas, returns an Array containing descriptions of any breaking - * changes in the newSchema related to removing types from a union type. - */ - public static function findTypesRemovedFromUnions( - $oldSchema, $newSchema - ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); + /** + * Given two schemas, returns an Array containing descriptions of any breaking + * changes in the newSchema related to removing types from a union type. + */ + public static function findTypesRemovedFromUnions( + Schema $oldSchema, Schema $newSchema + ) + { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); - const typesRemovedFromUnion = []; - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if (!(oldType instanceof GraphQLUnionType) || - !(newType instanceof GraphQLUnionType)) { - return; - } - const typeNamesInNewUnion = Object.create(null); - newType.getTypes().forEach(type => { - typeNamesInNewUnion[type.name] = true; - }); - oldType.getTypes().forEach(type => { - if (!typeNamesInNewUnion[type.name]) { - typesRemovedFromUnion.push({ - type: BreakingChangeType.TYPE_REMOVED_FROM_UNION, - description: `${type.name} was removed from union type ${typeName}.` + const typesRemovedFromUnion = []; + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if (!(oldType instanceof GraphQLUnionType) || + !(newType instanceof GraphQLUnionType)) { + return; + } + const typeNamesInNewUnion = Object.create(null); + newType.getTypes().forEach(type => { + typeNamesInNewUnion[type.name] = true; }); - } - }); - }); - return typesRemovedFromUnion;*/ - } + oldType.getTypes().forEach(type => { + if (!typeNamesInNewUnion[type.name]) { + typesRemovedFromUnion.push({ + type: BreakingChangeType.TYPE_REMOVED_FROM_UNION, + description: `${type.name} was removed from union type ${typeName}.` + }); + } + }); + }); + return typesRemovedFromUnion;*/ + } - /** - * Given two schemas, returns an Array containing descriptions of any dangerous - * changes in the newSchema related to adding types to a union type. - */ - public static function findTypesAddedToUnions( - $oldSchema, $newSchema - ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); + /** + * Given two schemas, returns an Array containing descriptions of any dangerous + * changes in the newSchema related to adding types to a union type. + */ + public static function findTypesAddedToUnions( + Schema $oldSchema, Schema $newSchema + ) + { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); - const typesAddedToUnion = []; - Object.keys(newTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if (!(oldType instanceof GraphQLUnionType) || - !(newType instanceof GraphQLUnionType)) { - return; - } - const typeNamesInOldUnion = Object.create(null); - oldType.getTypes().forEach(type => { - typeNamesInOldUnion[type.name] = true; - }); - newType.getTypes().forEach(type => { - if (!typeNamesInOldUnion[type.name]) { - typesAddedToUnion.push({ - type: DangerousChangeType.TYPE_ADDED_TO_UNION, - description: `${type.name} was added to union type ${typeName}.` + const typesAddedToUnion = []; + Object.keys(newTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if (!(oldType instanceof GraphQLUnionType) || + !(newType instanceof GraphQLUnionType)) { + return; + } + const typeNamesInOldUnion = Object.create(null); + oldType.getTypes().forEach(type => { + typeNamesInOldUnion[type.name] = true; }); - } - }); - }); - return typesAddedToUnion;*/ - } - - /** - * Given two schemas, returns an Array containing descriptions of any breaking - * changes in the newSchema related to removing values from an enum type. - */ - public static function findValuesRemovedFromEnums( - $oldSchema, $newSchema - ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); - - const valuesRemovedFromEnums = []; - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if (!(oldType instanceof GraphQLEnumType) || - !(newType instanceof GraphQLEnumType)) { - return; - } - const valuesInNewEnum = Object.create(null); - newType.getValues().forEach(value => { - valuesInNewEnum[value.name] = true; - }); - oldType.getValues().forEach(value => { - if (!valuesInNewEnum[value.name]) { - valuesRemovedFromEnums.push({ - type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM, - description: `${value.name} was removed from enum type ${typeName}.` + newType.getTypes().forEach(type => { + if (!typeNamesInOldUnion[type.name]) { + typesAddedToUnion.push({ + type: DangerousChangeType.TYPE_ADDED_TO_UNION, + description: `${type.name} was added to union type ${typeName}.` + }); + } }); - } - }); - }); - return valuesRemovedFromEnums;*/ - } + }); + return typesAddedToUnion;*/ + } - /** - * Given two schemas, returns an Array containing descriptions of any dangerous - * changes in the newSchema related to adding values to an enum type. - */ - public static function findValuesAddedToEnums( - $oldSchema, $newSchema - ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); + /** + * Given two schemas, returns an Array containing descriptions of any breaking + * changes in the newSchema related to removing values from an enum type. + */ + public static function findValuesRemovedFromEnums( + Schema $oldSchema, Schema $newSchema + ) + { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); - const valuesAddedToEnums = []; - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if (!(oldType instanceof GraphQLEnumType) || - !(newType instanceof GraphQLEnumType)) { - return; - } - - const valuesInOldEnum = Object.create(null); - oldType.getValues().forEach(value => { - valuesInOldEnum[value.name] = true; - }); - newType.getValues().forEach(value => { - if (!valuesInOldEnum[value.name]) { - valuesAddedToEnums.push({ - type: DangerousChangeType.VALUE_ADDED_TO_ENUM, - description: `${value.name} was added to enum type ${typeName}.` + const valuesRemovedFromEnums = []; + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if (!(oldType instanceof GraphQLEnumType) || + !(newType instanceof GraphQLEnumType)) { + return; + } + const valuesInNewEnum = Object.create(null); + newType.getValues().forEach(value => { + valuesInNewEnum[value.name] = true; }); - } - }); - }); - return valuesAddedToEnums;*/ - } - - public static function findInterfacesRemovedFromObjectTypes( - $oldSchema, $newSchema - ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); - const breakingChanges = []; - - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if ( - !(oldType instanceof GraphQLObjectType) || - !(newType instanceof GraphQLObjectType) - ) { - return; - } - - const oldInterfaces = oldType.getInterfaces(); - const newInterfaces = newType.getInterfaces(); - oldInterfaces.forEach(oldInterface => { - if (!newInterfaces.some(int => int.name === oldInterface.name)) { - breakingChanges.push({ - type: BreakingChangeType.INTERFACE_REMOVED_FROM_OBJECT, - description: `${typeName} no longer implements interface ` + - `${oldInterface.name}.` + oldType.getValues().forEach(value => { + if (!valuesInNewEnum[value.name]) { + valuesRemovedFromEnums.push({ + type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM, + description: `${value.name} was removed from enum type ${typeName}.` + }); + } }); - } - }); - }); - return breakingChanges;*/ - } + }); + return valuesRemovedFromEnums;*/ + } + + /** + * Given two schemas, returns an Array containing descriptions of any dangerous + * changes in the newSchema related to adding values to an enum type. + */ + public static function findValuesAddedToEnums( + Schema $oldSchema, Schema $newSchema + ) + { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + + const valuesAddedToEnums = []; + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if (!(oldType instanceof GraphQLEnumType) || + !(newType instanceof GraphQLEnumType)) { + return; + } + + const valuesInOldEnum = Object.create(null); + oldType.getValues().forEach(value => { + valuesInOldEnum[value.name] = true; + }); + newType.getValues().forEach(value => { + if (!valuesInOldEnum[value.name]) { + valuesAddedToEnums.push({ + type: DangerousChangeType.VALUE_ADDED_TO_ENUM, + description: `${value.name} was added to enum type ${typeName}.` + }); + } + }); + }); + return valuesAddedToEnums;*/ + } + + public static function findInterfacesRemovedFromObjectTypes( + Schema $oldSchema, Schema $newSchema + ) + { + /* const oldTypeMap = oldSchema.getTypeMap(); + const newTypeMap = newSchema.getTypeMap(); + const breakingChanges = []; + + Object.keys(oldTypeMap).forEach(typeName => { + const oldType = oldTypeMap[typeName]; + const newType = newTypeMap[typeName]; + if ( + !(oldType instanceof GraphQLObjectType) || + !(newType instanceof GraphQLObjectType) + ) { + return; + } + + const oldInterfaces = oldType.getInterfaces(); + const newInterfaces = newType.getInterfaces(); + oldInterfaces.forEach(oldInterface => { + if (!newInterfaces.some(int => int.name === oldInterface.name)) { + breakingChanges.push({ + type: BreakingChangeType.INTERFACE_REMOVED_FROM_OBJECT, + description: `${typeName} no longer implements interface ` + + `${oldInterface.name}.` + }); + } + }); + }); + return breakingChanges;*/ + } + + /** + * @param Type $type + * + * @return bool + */ + private static function isNamedType(Type $type) + { + return ( + $type instanceof ScalarType || + $type instanceof ObjectType || + $type instanceof InterfaceType || + $type instanceof UnionType || + $type instanceof EnumType || + $type instanceof InputObjectType + ); + } } \ No newline at end of file From e649ef307aa65a211224e74c4b6e91480640cd13 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Thu, 16 Nov 2017 14:15:39 -0500 Subject: [PATCH 03/28] couple more functions --- src/Utils/FindBreakingChanges.php | 254 +++++++----------------------- 1 file changed, 55 insertions(+), 199 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index fa140b6..6b15a27 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -96,15 +96,6 @@ class FindBreakingChanges } } - /*Object.keys(oldTypeMap).forEach(typeName => { - if (!newTypeMap[typeName]) { - breakingChanges.push({ - type: BreakingChangeType.TYPE_REMOVED, - description: `${typeName} was removed.`, - }); - } - });*/ - return $breakingChanges; } @@ -135,22 +126,6 @@ class FindBreakingChanges } } - /* - Object.keys(oldTypeMap).forEach(typeName => { - if (!newTypeMap[typeName]) { - return; - } - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if (!(oldType instanceof newType.constructor)) { - breakingChanges.push({ - type: BreakingChangeType.TYPE_CHANGED_KIND, - description: `${typeName} changed from ` + - `${typeKindName(oldType)} to ${typeKindName(newType)}.` - }); - } - });*/ - return $breakingChanges; } @@ -231,82 +206,6 @@ class FindBreakingChanges } return ['breakingChanges' => $breakingChanges, 'dangerousChanges' => $dangerousChanges]; - /* - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if ( - !(oldType instanceof GraphQLObjectType || - oldType instanceof GraphQLInterfaceType) || - !(newType instanceof oldType.constructor) - ) { - return; - } - - const oldTypeFields: GraphQLFieldMap<*, *> = oldType.getFields(); - const newTypeFields: GraphQLFieldMap<*, *> = newType.getFields(); - - Object.keys(oldTypeFields).forEach(fieldName => { - if (!newTypeFields[fieldName]) { - return; - } - - oldTypeFields[fieldName].args.forEach(oldArgDef => { - const newArgs = newTypeFields[fieldName].args; - const newArgDef = newArgs.find( - arg => arg.name === oldArgDef.name - ); - - // Arg not present - if (!newArgDef) { - breakingChanges.push({ - type: BreakingChangeType.ARG_REMOVED, - description: `${oldType.name}.${fieldName} arg ` + - `${oldArgDef.name} was removed`, - }); - } else { - const isSafe = isChangeSafeForInputObjectFieldOrFieldArg( - oldArgDef.type, - newArgDef.type, - ); - if (!isSafe) { - breakingChanges.push({ - type: BreakingChangeType.ARG_CHANGED_KIND, - description: `${oldType.name}.${fieldName} arg ` + - `${oldArgDef.name} has changed type from ` + - `${oldArgDef.type.toString()} to ${newArgDef.type.toString()}`, - }); - } else if (oldArgDef.defaultValue !== undefined && - oldArgDef.defaultValue !== newArgDef.defaultValue) { - dangerousChanges.push({ - type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, - description: `${oldType.name}.${fieldName} arg ` + - `${oldArgDef.name} has changed defaultValue`, - }); - } - } - }); - // Check if a non-null arg was added to the field - newTypeFields[fieldName].args.forEach(newArgDef => { - const oldArgs = oldTypeFields[fieldName].args; - const oldArgDef = oldArgs.find( - arg => arg.name === newArgDef.name - ); - if (!oldArgDef && newArgDef.type instanceof GraphQLNonNull) { - breakingChanges.push({ - type: BreakingChangeType.NON_NULL_ARG_ADDED, - description: `A non-null arg ${newArgDef.name} on ` + - `${newType.name}.${fieldName} was added`, - }); - } - }); - }); - }); - - return { - breakingChanges, - dangerousChanges, - };*/ } /** @@ -319,7 +218,7 @@ class FindBreakingChanges { if ($type instanceof ScalarType) { return 'a Scalar type'; - } elseif($type instanceof ObjectType) { + } elseif ($type instanceof ObjectType) { return 'an Object type'; } elseif ($type instanceof InterfaceType) { return 'an Interface type'; @@ -344,12 +243,18 @@ class FindBreakingChanges Schema $oldSchema, Schema $newSchema ) { - /*return [ - ...findFieldsThatChangedTypeOnObjectOrInterfaceTypes(oldSchema, newSchema), - ...findFieldsThatChangedTypeOnInputObjectTypes(oldSchema, newSchema), - ];*/ + return array_merge( + self::findFieldsThatChangedTypeOnObjectOrInterfaceTypes($oldSchema, $newSchema), + self::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema) + ); } + /** + * @param $oldSchema + * @param $newSchema + * + * @return array + */ private static function findFieldsThatChangedTypeOnObjectOrInterfaceTypes( $oldSchema, $newSchema ) @@ -402,6 +307,12 @@ class FindBreakingChanges return breakingFieldChanges;*/ } + /** + * @param Schema $oldSchema + * @param Schema $newSchema + * + * @return array + */ public static function findFieldsThatChangedTypeOnInputObjectTypes( Schema $oldSchema, Schema $newSchema ) @@ -493,81 +404,30 @@ class FindBreakingChanges } return false; - /*if (isNamedType(oldType)) { - return ( - // if they're both named types, see if their names are equivalent - isNamedType(newType) && oldType.name === newType.name - ) || - ( - // moving from nullable to non-null of the same underlying type is safe - newType instanceof GraphQLNonNull && - isChangeSafeForObjectOrInterfaceField( - oldType, - newType.ofType, - ) - ); - } else if (oldType instanceof GraphQLList) { - return ( - // if they're both lists, make sure the underlying types are compatible - newType instanceof GraphQLList && - isChangeSafeForObjectOrInterfaceField( - oldType.ofType, - newType.ofType, - ) - ) || - ( - // moving from nullable to non-null of the same underlying type is safe - newType instanceof GraphQLNonNull && - isChangeSafeForObjectOrInterfaceField( - oldType, - newType.ofType, - ) - ); - } else if (oldType instanceof GraphQLNonNull) { - // if they're both non-null, make sure the underlying types are compatible - return newType instanceof GraphQLNonNull && - isChangeSafeForObjectOrInterfaceField( - oldType.ofType, - newType.ofType, - ); - } - return false;*/ } + /** + * @param Type $oldType + * @param Schema $newSchema + * + * @return bool + */ private static function isChangeSafeForInputObjectFieldOrFieldArg( - Schema $oldSchema, Schema $newSchema + Type $oldType, Type $newType ) { - /* if (isNamedType(oldType)) { - // if they're both named types, see if their names are equivalent - return isNamedType(newType) && oldType.name === newType.name; - } else if (oldType instanceof GraphQLList) { - // if they're both lists, make sure the underlying types are compatible - return newType instanceof GraphQLList && - isChangeSafeForInputObjectFieldOrFieldArg( - oldType.ofType, - newType.ofType, - ); - } else if (oldType instanceof GraphQLNonNull) { - return ( - // if they're both non-null, make sure the underlying types are - // compatible - newType instanceof GraphQLNonNull && - isChangeSafeForInputObjectFieldOrFieldArg( - oldType.ofType, - newType.ofType, - ) - ) || - ( - // moving from non-null to nullable of the same underlying type is safe - !(newType instanceof GraphQLNonNull) && - isChangeSafeForInputObjectFieldOrFieldArg( - oldType.ofType, - newType, - ) - ); - } - return false;*/ + if (self::isNamedType($oldType)) { + return self::isNamedType($newType) && $oldType->name === $newType->name; + } elseif ($oldType instanceof ListOfType) { + return $newType instanceof ListOfType && self::isChangeSafeForInputObjectFieldOrFieldArg($oldType->getWrappedType(), $newType->getWrappedType()); + } elseif ($oldType instanceof NonNull) { + return ( + $newType instanceof NonNull && self::isChangeSafeForInputObjectFieldOrFieldArg($oldType->getWrappedType(), $newType->getWrappedType()) + ) || ( + !($newType instanceof NonNull) && self::isChangeSafeForInputObjectFieldOrFieldArg($oldType->getWrappedType(), $newType) + ); + } + return false; } /** @@ -578,31 +438,27 @@ class FindBreakingChanges Schema $oldSchema, Schema $newSchema ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); + $oldTypeMap = $oldSchema->getTypeMap(); + $newTypeMap = $newSchema->getTypeMap(); - const typesRemovedFromUnion = []; - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if (!(oldType instanceof GraphQLUnionType) || - !(newType instanceof GraphQLUnionType)) { - return; + $typesRemovedFromUnion = []; + foreach ($oldTypeMap as $typeName => $oldType) { + $newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; + if (!($oldType instanceof UnionType) || !($newType instanceof UnionType)) { + continue; } - const typeNamesInNewUnion = Object.create(null); - newType.getTypes().forEach(type => { - typeNamesInNewUnion[type.name] = true; - }); - oldType.getTypes().forEach(type => { - if (!typeNamesInNewUnion[type.name]) { - typesRemovedFromUnion.push({ - type: BreakingChangeType.TYPE_REMOVED_FROM_UNION, - description: `${type.name} was removed from union type ${typeName}.` - }); - } - }); - }); - return typesRemovedFromUnion;*/ + $typeNamesInNewUnion = []; + foreach ($newType->getTypes() as $type) { + $typeNamesInNewUnion[$type->name] = true; + } + foreach ($oldType->getTypes() as $type) { + if (!isset($typeNamesInNewUnion[$type->name])) { + $missingTypeName = $type->name; + $typesRemovedFromUnion[] = ['type' => self::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, 'description' => "${missingTypeName} was removed from union type ${typeName}"]; + } + } + } + return $typesRemovedFromUnion; } /** From 6e95b81aeeca3e7ecaac7c5554561588d2d34b3b Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Thu, 16 Nov 2017 14:17:21 -0500 Subject: [PATCH 04/28] dangerous changes consts --- src/Utils/FindBreakingChanges.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 6b15a27..114abb1 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -30,12 +30,10 @@ class FindBreakingChanges const BREAKING_CHANGE_NON_NULL_ARG_ADDED = 'NON_NULL_ARG_ADDED'; const BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED = 'NON_NULL_INPUT_FIELD_ADDED'; const BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT = 'INTERFACE_REMOVED_FROM_OBJECT'; - /* - export const DangerousChangeType = { - ARG_DEFAULT_VALUE_CHANGE: 'ARG_DEFAULT_VALUE_CHANGE', - VALUE_ADDED_TO_ENUM: 'VALUE_ADDED_TO_ENUM', - TYPE_ADDED_TO_UNION: 'TYPE_ADDED_TO_UNION', - };*/ + + const DANGEROUS_CHANGE_ARG_DEFAULT_VALUE = 'ARG_DEFAULT_VALUE_CHANGE'; + const DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM = 'VALUE_ADDED_TO_ENUM'; + const DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION = 'TYPE_ADDED_TO_UNION'; /** * Given two schemas, returns an Array containing descriptions of all the types From 55f6d6cf4777a50541331602e38f7a830f593518 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Thu, 16 Nov 2017 15:25:25 -0500 Subject: [PATCH 05/28] interfaces and enums --- src/Utils/FindBreakingChanges.php | 183 ++++++++++++++---------------- 1 file changed, 86 insertions(+), 97 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 114abb1..cff8875 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -467,31 +467,29 @@ class FindBreakingChanges Schema $oldSchema, Schema $newSchema ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); + $newTypeMap = $newSchema->getTypeMap(); - const typesAddedToUnion = []; - Object.keys(newTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if (!(oldType instanceof GraphQLUnionType) || - !(newType instanceof GraphQLUnionType)) { - return; - } - const typeNamesInOldUnion = Object.create(null); - oldType.getTypes().forEach(type => { - typeNamesInOldUnion[type.name] = true; - }); - newType.getTypes().forEach(type => { - if (!typeNamesInOldUnion[type.name]) { - typesAddedToUnion.push({ - type: DangerousChangeType.TYPE_ADDED_TO_UNION, - description: `${type.name} was added to union type ${typeName}.` - }); - } - }); - }); - return typesAddedToUnion;*/ + $typesAddedToUnion = []; + + foreach ($newTypeMap as $typeName => $oldType) { + $newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; + if (!($oldType instanceof UnionType) || !($newType instanceof UnionType)) { + continue; + } + + $typeNamesInOldUnion = []; + foreach ($oldType->getTypes() as $type) { + $typeNamesInOldUnion[$type->name] = true; + } + foreach ($newType->getTypes() as $type) { + if (!isset($typeNamesInOldUnion[$type->name])) { + $addedTypeName = $type->name; + $typesRemovedFromUnion[] = ['type' => self::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, 'description' => "${addedTypeName} was removed to union type ${typeName}"]; + } + } + } + + return $typesAddedToUnion; } /** @@ -502,31 +500,29 @@ class FindBreakingChanges Schema $oldSchema, Schema $newSchema ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); + $oldTypeMap = $oldSchema->getTypeMap(); + $newTypeMap = $newSchema->getTypeMap(); - const valuesRemovedFromEnums = []; - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if (!(oldType instanceof GraphQLEnumType) || - !(newType instanceof GraphQLEnumType)) { - return; - } - const valuesInNewEnum = Object.create(null); - newType.getValues().forEach(value => { - valuesInNewEnum[value.name] = true; - }); - oldType.getValues().forEach(value => { - if (!valuesInNewEnum[value.name]) { - valuesRemovedFromEnums.push({ - type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM, - description: `${value.name} was removed from enum type ${typeName}.` - }); - } - }); - }); - return valuesRemovedFromEnums;*/ + $valuesRemovedFromEnums = []; + + foreach ($oldTypeMap as $typeName => $oldType) { + $newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; + if (!($oldType instanceof EnumType) || !($newType instanceof EnumType)) { + continue; + } + $valuesInNewEnum = []; + foreach ($newType->getValues() as $value) { + $valuesInNewEnum[$value->name] = true; + } + foreach ($oldType->getValues() as $value) { + if (!isset($valuesInNewEnum[$value->name])) { + $valueName = $value->name; + $valuesRemovedFromEnums[] = ['type' => self::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM, 'description' => "${valueName} was removed from enum type ${typeName}"]; + } + } + } + + return $valuesRemovedFromEnums; } /** @@ -537,65 +533,58 @@ class FindBreakingChanges Schema $oldSchema, Schema $newSchema ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); + $oldTypeMap = $oldSchema->getTypeMap(); + $newTypeMap = $newSchema->getTypeMap(); - const valuesAddedToEnums = []; - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if (!(oldType instanceof GraphQLEnumType) || - !(newType instanceof GraphQLEnumType)) { - return; - } + $valuesAddedToEnums = []; + foreach ($oldTypeMap as $typeName => $oldType) { + $newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; + if (!($oldType instanceof EnumType) || !($newType instanceof EnumType)) { + continue; + } + $valuesInOldEnum = []; + foreach ($oldType->getValues() as $value) { + $valuesInOldEnum[$value->name] = true; + } + foreach ($newType->getValues() as $value) { + if (!isset($valuesInOldEnum[$value->name])) { + $valueName = $value->name; + $valuesAddedToEnums[] = ['type' => self::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM, 'description' => "${valueName} was added to enum type ${typeName}"]; + } + } + } - const valuesInOldEnum = Object.create(null); - oldType.getValues().forEach(value => { - valuesInOldEnum[value.name] = true; - }); - newType.getValues().forEach(value => { - if (!valuesInOldEnum[value.name]) { - valuesAddedToEnums.push({ - type: DangerousChangeType.VALUE_ADDED_TO_ENUM, - description: `${value.name} was added to enum type ${typeName}.` - }); - } - }); - }); - return valuesAddedToEnums;*/ + return $valuesAddedToEnums; } public static function findInterfacesRemovedFromObjectTypes( Schema $oldSchema, Schema $newSchema ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); - const breakingChanges = []; + $oldTypeMap = $oldSchema->getTypeMap(); + $newTypeMap = $newSchema->getTypeMap(); - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if ( - !(oldType instanceof GraphQLObjectType) || - !(newType instanceof GraphQLObjectType) - ) { - return; - } + $breakingChanges = []; + foreach ($oldTypeMap as $typeName => $oldType) { + $newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; + if (!($oldType instanceof ObjectType) || !($newType instanceof ObjectType)) { + continue; + } - const oldInterfaces = oldType.getInterfaces(); - const newInterfaces = newType.getInterfaces(); - oldInterfaces.forEach(oldInterface => { - if (!newInterfaces.some(int => int.name === oldInterface.name)) { - breakingChanges.push({ - type: BreakingChangeType.INTERFACE_REMOVED_FROM_OBJECT, - description: `${typeName} no longer implements interface ` + - `${oldInterface.name}.` - }); - } - }); - }); - return breakingChanges;*/ + $oldInterfaces = $oldType->getInterfaces(); + $newInterfaces = $newType->getInterfaces(); + foreach ($oldInterfaces as $oldInterface) { + if (!Utils::find($newInterfaces, function (InterfaceType $interface) use ($oldInterface) { + return $interface->name === $oldInterface->name; + })) { + $oldInterfaceName = $oldInterface->name; + $breakingChanges[] = ['type' => self::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT, + 'description' => "${typeName} no longer implements interface ${oldInterfaceName}" + ]; + } + } + } + return $breakingChanges; } /** From a1325eeb3ff255b54fd13d59d0d55c78744435cb Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Thu, 16 Nov 2017 15:53:20 -0500 Subject: [PATCH 06/28] top level API functions, docstrings --- src/Utils/FindBreakingChanges.php | 43 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index cff8875..60558b2 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -41,16 +41,12 @@ class FindBreakingChanges * * @return array */ - public function findDangerousChanges( - Schema $oldSchema, Schema $newSchema - ) + public function findDangerousChanges(Schema $oldSchema, Schema $newSchema) { - return [ - /* ...findArgChanges(oldSchema, newSchema).dangerousChanges, - ...findValuesAddedToEnums(oldSchema, newSchema), - ...findTypesAddedToUnions(oldSchema, newSchema) - */ - ]; + return array_merge(self::findArgChanges($oldSchema, $newSchema)['dangerousChanges'], + self::findValuesAddedToEnums($oldSchema, $newSchema), + self::findTypesAddedToUnions($oldSchema, $newSchema) + ); } /** @@ -59,25 +55,24 @@ class FindBreakingChanges * * @return array */ - public function findBreakingChanges( - $oldSchema, $newSchema - ) + public function findBreakingChanges(Schema $oldSchema, Schema $newSchema) { - return [ - /*...findRemovedTypes(oldSchema, newSchema), - ...findTypesThatChangedKind(oldSchema, newSchema), - ...findFieldsThatChangedType(oldSchema, newSchema), - ...findTypesRemovedFromUnions(oldSchema, newSchema), - ...findValuesRemovedFromEnums(oldSchema, newSchema), - ...findArgChanges(oldSchema, newSchema).breakingChanges, - ...findInterfacesRemovedFromObjectTypes(oldSchema, newSchema), - */ - ]; + return array_merge( + self::findRemovedTypes($oldSchema, $newSchema), + self::findTypesThatChangedKind($oldSchema, $newSchema), + self::findFieldsThatChangedType($oldSchema, $newSchema), + self::findTypesRemovedFromUnions($oldSchema, $newSchema), + self::findValuesRemovedFromEnums($oldSchema, $newSchema), + self::findArgChanges($oldSchema, $newSchema)['breakingChanges'], + self::findInterfacesRemovedFromObjectTypes($oldSchema, $newSchema) + ); } /** * Given two schemas, returns an Array containing descriptions of any breaking * changes in the newSchema related to removing an entire type. + * + * @return array */ public function findRemovedTypes( Schema $oldSchema, Schema $newSchema @@ -100,6 +95,8 @@ class FindBreakingChanges /** * Given two schemas, returns an Array containing descriptions of any breaking * changes in the newSchema related to changing the type of a type. + * + * @return array */ public function findTypesThatChangedKind( Schema $oldSchema, Schema $newSchema @@ -528,6 +525,8 @@ class FindBreakingChanges /** * Given two schemas, returns an Array containing descriptions of any dangerous * changes in the newSchema related to adding values to an enum type. + * + * @return array */ public static function findValuesAddedToEnums( Schema $oldSchema, Schema $newSchema From af60f1ee4de4e0a4836c9f24ea9f4669ad0927cf Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Thu, 16 Nov 2017 17:42:38 -0500 Subject: [PATCH 07/28] finish mechanical conversions --- src/Utils/FindBreakingChanges.php | 165 +++++++++++------------------- 1 file changed, 60 insertions(+), 105 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 60558b2..4a31590 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -245,61 +245,41 @@ class FindBreakingChanges } /** - * @param $oldSchema - * @param $newSchema + * @param Schema $oldSchema + * @param Schema $newSchema * * @return array */ - private static function findFieldsThatChangedTypeOnObjectOrInterfaceTypes( - $oldSchema, $newSchema - ) + private static function findFieldsThatChangedTypeOnObjectOrInterfaceTypes(Schema $oldSchema, Schema $newSchema) { - /*const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); + $oldTypeMap = $oldSchema->getTypeMap(); + $newTypeMap = $newSchema->getTypeMap(); - const breakingFieldChanges = []; - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if ( - !(oldType instanceof GraphQLObjectType || - oldType instanceof GraphQLInterfaceType) || - !(newType instanceof oldType.constructor) - ) { - return; - } - - const oldTypeFieldsDef = oldType.getFields(); - const newTypeFieldsDef = newType.getFields(); - Object.keys(oldTypeFieldsDef).forEach(fieldName => { - // Check if the field is missing on the type in the new schema. - if (!(fieldName in newTypeFieldsDef)) { - breakingFieldChanges.push({ - type: BreakingChangeType.FIELD_REMOVED, - description: `${typeName}.${fieldName} was removed.`, - }); - } else { - const oldFieldType = oldTypeFieldsDef[fieldName].type; - const newFieldType = newTypeFieldsDef[fieldName].type; - const isSafe = - isChangeSafeForObjectOrInterfaceField(oldFieldType, newFieldType); - if (!isSafe) { - const oldFieldTypeString = isNamedType(oldFieldType) ? - oldFieldType.name : - oldFieldType.toString(); - const newFieldTypeString = isNamedType(newFieldType) ? - newFieldType.name : - newFieldType.toString(); - breakingFieldChanges.push({ - type: BreakingChangeType.FIELD_CHANGED_KIND, - description: `${typeName}.${fieldName} changed type from ` + - `${oldFieldTypeString} to ${newFieldTypeString}.`, - }); - } + $breakingFieldChanges = []; + foreach ($oldTypeMap as $typeName => $oldType) { + $newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; + if (!($oldType instanceof ObjectType || $oldType instanceof InterfaceType) || !($newType instanceof $oldType)) { + continue; } - }); - }); - return breakingFieldChanges;*/ + $oldTypeFieldsDef = $oldType->getFields(); + $newTypeFieldsDef = $newType->getFields(); + foreach ($oldTypeFieldsDef as $fieldName => $fieldDefinition) { + if (!isset($newTypeFieldsDef[$fieldName])) { + $breakingFieldChanges[] = ['type' => self::BREAKING_CHANGE_FIELD_REMOVED, 'description' => "${typeName}->${fieldName} was removed."]; + } else { + $oldFieldType = $oldTypeFieldsDef[$fieldName]->getType(); + $newfieldType = $newTypeFieldsDef[$fieldName]->getType(); + $isSafe = self::isChangeSafeForObjectOrInterfaceField($oldFieldType, $newfieldType); + if (!$isSafe) { + + $oldFieldTypeString = self::isNamedType($oldFieldType) ? $oldFieldType->name : $oldFieldType; + $newFieldTypeString = self::isNamedType($newfieldType) ? $newfieldType->name : $newfieldType; + $breakingFieldChanges[] = ['type' => self::BREAKING_CHANGE_FIELD_CHANGED, 'description' => "${typeName}->${fieldName} changed type from ${oldFieldTypeString} to ${newFieldTypeString}"]; + } + } + } + } + return $breakingFieldChanges; } /** @@ -312,65 +292,40 @@ class FindBreakingChanges Schema $oldSchema, Schema $newSchema ) { - /* const oldTypeMap = oldSchema.getTypeMap(); - const newTypeMap = newSchema.getTypeMap(); + $oldTypeMap = $oldSchema->getTypeMap(); + $newTypeMap = $newSchema->getTypeMap(); - const breakingFieldChanges = []; - Object.keys(oldTypeMap).forEach(typeName => { - const oldType = oldTypeMap[typeName]; - const newType = newTypeMap[typeName]; - if ( - !(oldType instanceof GraphQLInputObjectType) || - !(newType instanceof GraphQLInputObjectType) - ) { - return; + $breakingFieldChanges = []; + foreach ($oldTypeMap as $typeName => $oldType) { + $newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; + if (!($oldType instanceof InputObjectType) || !($newType instanceof InputObjectType)) { + continue; } - - const oldTypeFieldsDef = oldType.getFields(); - const newTypeFieldsDef = newType.getFields(); - Object.keys(oldTypeFieldsDef).forEach(fieldName => { - // Check if the field is missing on the type in the new schema. - if (!(fieldName in newTypeFieldsDef)) { - breakingFieldChanges.push({ - type: BreakingChangeType.FIELD_REMOVED, - description: `${typeName}.${fieldName} was removed.`, - }); - } else { - const oldFieldType = oldTypeFieldsDef[fieldName].type; - const newFieldType = newTypeFieldsDef[fieldName].type; - - const isSafe = - isChangeSafeForInputObjectFieldOrFieldArg(oldFieldType, newFieldType); - if (!isSafe) { - const oldFieldTypeString = isNamedType(oldFieldType) ? - oldFieldType.name : - oldFieldType.toString(); - const newFieldTypeString = isNamedType(newFieldType) ? - newFieldType.name : - newFieldType.toString(); - breakingFieldChanges.push({ - type: BreakingChangeType.FIELD_CHANGED_KIND, - description: `${typeName}.${fieldName} changed type from ` + - `${oldFieldTypeString} to ${newFieldTypeString}.`, - }); + $oldTypeFieldsDef = $oldType->getFields(); + $newTypeFieldsDef = $newType->getFields(); + foreach ($oldTypeFieldsDef as $fieldName => $fieldDefinition) { + if (!isset($newTypeFieldsDef[$fieldName])) { + $breakingFieldChanges[] = ['type' => self::BREAKING_CHANGE_FIELD_REMOVED, 'description' => "${typeName}->${fieldName} was removed."]; + } else { + $oldFieldType = $oldTypeFieldsDef[$fieldName]->getType(); + $newfieldType = $newTypeFieldsDef[$fieldName]->getType(); + $isSafe = self::isChangeSafeForInputObjectFieldOrFieldArg($oldFieldType, $newfieldType); + if (!$isSafe) { + $oldFieldTypeString = self::isNamedType($oldFieldType) ? $oldFieldType->name : $oldFieldType; + $newFieldTypeString = self::isNamedType($newfieldType) ? $newfieldType->name : $newfieldType; + $breakingFieldChanges[] = ['type' => self::BREAKING_CHANGE_FIELD_CHANGED, 'description' => "${typeName}->${fieldName} changed type from ${oldFieldTypeString} to ${newFieldTypeString}"]; + } } - } - }); - // Check if a non-null field was added to the input object type - Object.keys(newTypeFieldsDef).forEach(fieldName => { - if ( - !(fieldName in oldTypeFieldsDef) && - newTypeFieldsDef[fieldName].type instanceof GraphQLNonNull - ) { - breakingFieldChanges.push({ - type: BreakingChangeType.NON_NULL_INPUT_FIELD_ADDED, - description: `A non-null field ${fieldName} on ` + - `input type ${newType.name} was added.`, - }); - } - }); - }); - return breakingFieldChanges;*/ + } + foreach ($newTypeFieldsDef as $fieldName => $fieldDef) { + if (!isset($oldTypeFieldsDef[$fieldName]) && $fieldDef->getType() instanceof NonNull) { + $newTypeName = $newType->name; + $breakingFieldChanges[] = ['type' => self::BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED, 'description' => "A non-null field ${fieldName} on input type ${$newTypeName} was added"]; + } + } + } + return $breakingFieldChanges; + } private static function isChangeSafeForObjectOrInterfaceField( From 6bdb7b7f806fd8a7f145a9573f1b11f335625246 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Thu, 16 Nov 2017 17:44:08 -0500 Subject: [PATCH 08/28] improve docstrings --- src/Utils/FindBreakingChanges.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 4a31590..c937a4f 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -129,6 +129,8 @@ class FindBreakingChanges * breaking or dangerous changes in the newSchema related to arguments * (such as removal or change of type of an argument, or a change in an * argument's default value). + * + * @return array */ public function findArgChanges( Schema $oldSchema, Schema $newSchema @@ -233,6 +235,8 @@ class FindBreakingChanges * changes in the newSchema related to the fields on a type. This includes if * a field has been removed from a type, if a field has changed type, or if * a non-null field is added to an input type. + * + * @return array */ public static function findFieldsThatChangedType( Schema $oldSchema, Schema $newSchema @@ -383,6 +387,8 @@ class FindBreakingChanges /** * Given two schemas, returns an Array containing descriptions of any breaking * changes in the newSchema related to removing types from a union type. + * + * @return array */ public static function findTypesRemovedFromUnions( Schema $oldSchema, Schema $newSchema @@ -414,6 +420,8 @@ class FindBreakingChanges /** * Given two schemas, returns an Array containing descriptions of any dangerous * changes in the newSchema related to adding types to a union type. + * + * @return array */ public static function findTypesAddedToUnions( Schema $oldSchema, Schema $newSchema @@ -447,6 +455,8 @@ class FindBreakingChanges /** * Given two schemas, returns an Array containing descriptions of any breaking * changes in the newSchema related to removing values from an enum type. + * + * @return array */ public static function findValuesRemovedFromEnums( Schema $oldSchema, Schema $newSchema @@ -511,6 +521,12 @@ class FindBreakingChanges return $valuesAddedToEnums; } + /** + * @param Schema $oldSchema + * @param Schema $newSchema + * + * @return array + */ public static function findInterfacesRemovedFromObjectTypes( Schema $oldSchema, Schema $newSchema ) From 4207adc098bb326cb9ba03d7117cc7953a387aeb Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Fri, 17 Nov 2017 10:54:18 -0500 Subject: [PATCH 09/28] change fns to static --- src/Utils/FindBreakingChanges.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index c937a4f..0894f0c 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -41,7 +41,7 @@ class FindBreakingChanges * * @return array */ - public function findDangerousChanges(Schema $oldSchema, Schema $newSchema) + public static function findDangerousChanges(Schema $oldSchema, Schema $newSchema) { return array_merge(self::findArgChanges($oldSchema, $newSchema)['dangerousChanges'], self::findValuesAddedToEnums($oldSchema, $newSchema), @@ -55,7 +55,7 @@ class FindBreakingChanges * * @return array */ - public function findBreakingChanges(Schema $oldSchema, Schema $newSchema) + public static function findBreakingChanges(Schema $oldSchema, Schema $newSchema) { return array_merge( self::findRemovedTypes($oldSchema, $newSchema), @@ -74,7 +74,7 @@ class FindBreakingChanges * * @return array */ - public function findRemovedTypes( + public static function findRemovedTypes( Schema $oldSchema, Schema $newSchema ) { @@ -98,7 +98,7 @@ class FindBreakingChanges * * @return array */ - public function findTypesThatChangedKind( + public static function findTypesThatChangedKind( Schema $oldSchema, Schema $newSchema ) { @@ -132,7 +132,7 @@ class FindBreakingChanges * * @return array */ - public function findArgChanges( + public static function findArgChanges( Schema $oldSchema, Schema $newSchema ) { From d9ce567cc8a9d25e7a32a0a2762afb189a46a1a3 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Fri, 17 Nov 2017 11:21:05 -0500 Subject: [PATCH 10/28] findRemovedTypes test --- src/Utils/FindBreakingChanges.php | 2 +- tests/Utils/FindBreakingChangesTest.php | 67 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 tests/Utils/FindBreakingChangesTest.php diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 0894f0c..7864508 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -85,7 +85,7 @@ class FindBreakingChanges foreach ($oldTypeMap as $typeName => $typeDefinition) { if (!isset($newTypeMap[$typeName])) { $breakingChanges[] = - ['type' => self::BREAKING_CHANGE_TYPE_REMOVED, 'description' => "${$typeName} was removed."]; + ['type' => self::BREAKING_CHANGE_TYPE_REMOVED, 'description' => "${typeName} was removed."]; } } diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php new file mode 100644 index 0000000..cb0c220 --- /dev/null +++ b/tests/Utils/FindBreakingChangesTest.php @@ -0,0 +1,67 @@ +queryType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string() + ] + ] + ]); + } + + public function testShouldDetectIfTypeWasRemoved() + { + $type1 = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => ['type' => Type::string()], + ] + ]); + $type2 = new ObjectType([ + 'name' => 'Type2', + 'fields' => [ + 'field1' => ['type' => Type::string()], + ] + ]); + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $type1, + 'type2' => $type2 + ] + ]) + ]); + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type2' => $type2 + ] + ]) + ]); + + $this->assertEquals(['type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, 'description' => 'Type1 was removed.'], + FindBreakingChanges::findRemovedTypes($oldSchema, $newSchema)[0] + ); + + $this->assertEquals([], FindBreakingChanges::findRemovedTypes($oldSchema, $oldSchema)); + } + +} \ No newline at end of file From b2b5d6f0805a308b035ff847c38c7b167c4a0d8b Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Fri, 17 Nov 2017 13:04:01 -0500 Subject: [PATCH 11/28] findTypesThatChangedKind test --- src/Utils/FindBreakingChanges.php | 2 +- tests/Utils/FindBreakingChangesTest.php | 47 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 7864508..51134c4 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -116,7 +116,7 @@ class FindBreakingChanges $newTypeKindName = self::typeKindName($newTypeDefinition); $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_TYPE_CHANGED, - 'description' => "${$typeName} changed from ${oldTypeKindName} to ${newTypeKindName}." + 'description' => "${typeName} changed from ${oldTypeKindName} to ${newTypeKindName}." ]; } } diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index cb0c220..e2cc9b2 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -5,8 +5,10 @@ namespace GraphQL\Tests\Utils; +use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Schema; use GraphQL\Utils\FindBreakingChanges; @@ -64,4 +66,49 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals([], FindBreakingChanges::findRemovedTypes($oldSchema, $oldSchema)); } + public function testShouldDetectTypeChanges() + { + $objectType = new ObjectType([ + 'name' => 'ObjectType', + 'fields' => [ + 'field1' => ['type' => Type::string()], + ] + ]); + + $interfaceType = new InterfaceType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => ['type' => Type::string()] + ] + ]); + + $unionType = new UnionType([ + 'name' => 'Type1', + 'types' => [new ObjectType(['name' => 'blah'])], + ]); + + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $interfaceType + ] + ]) + ]); + + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $unionType + ] + ]) + ]); + + $this->assertEquals( + ['type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_CHANGED, 'description' => 'Type1 changed from an Interface type to a Union type.'], + FindBreakingChanges::findTypesThatChangedKind($oldSchema, $newSchema)[0] + ); + } + } \ No newline at end of file From 68dbcc9ca385b2aca83fd0cea435456a49e882e5 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Fri, 17 Nov 2017 14:29:47 -0500 Subject: [PATCH 12/28] testShouldDetectFieldChangesAndDeletions test --- src/Utils/FindBreakingChanges.php | 4 +- tests/Utils/FindBreakingChangesTest.php | 147 ++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 2 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 51134c4..4077b1d 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -175,7 +175,7 @@ class FindBreakingChanges $newArgType = $newArgDef->getType(); $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_ARG_CHANGED, - 'description' => "${oldTypeName}->${fieldName} arg ${oldArgName} has changed type from ${oldArgType} to ${newArgType}" + 'description' => "${oldTypeName}->${fieldName} arg ${oldArgName} has changed type from ${oldArgType} to ${newArgType}." ]; } elseif ($oldArgDef->defaultValueExists() && $oldArgDef->defaultValue !== $newArgDef->defaultValue) { $dangerousChanges[] = []; // TODO @@ -278,7 +278,7 @@ class FindBreakingChanges $oldFieldTypeString = self::isNamedType($oldFieldType) ? $oldFieldType->name : $oldFieldType; $newFieldTypeString = self::isNamedType($newfieldType) ? $newfieldType->name : $newfieldType; - $breakingFieldChanges[] = ['type' => self::BREAKING_CHANGE_FIELD_CHANGED, 'description' => "${typeName}->${fieldName} changed type from ${oldFieldTypeString} to ${newFieldTypeString}"]; + $breakingFieldChanges[] = ['type' => self::BREAKING_CHANGE_FIELD_CHANGED, 'description' => "${typeName}->${fieldName} changed type from ${oldFieldTypeString} to ${newFieldTypeString}."]; } } } diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index e2cc9b2..34334c9 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -111,4 +111,151 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ); } + public function testShouldDetectFieldChangesAndDeletions() + { + $typeA1 = new ObjectType([ + 'name' => 'TypeA', + 'fields' => [ + 'field1' => ['type' => Type::string()], + ] + ]); + $typeA2 = new ObjectType([ + 'name' => 'TypeA', + 'fields' => [ + 'field1' => ['type' => Type::string()], + ] + ]); + $typeB = new ObjectType([ + 'name' => 'TypeB', + 'fields' => [ + 'field1' => ['type' => Type::string()], + ] + ]); + $oldType1 = new InterfaceType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => ['type' => $typeA1], + 'field2' => ['type' => Type::string()], + 'field3' => ['type' => Type::string()], + 'field4' => ['type' => $typeA1], + 'field6' => ['type' => Type::string()], + 'field7' => ['type' => Type::listOf(Type::string())], + 'field8' => ['type' => Type::int()], + 'field9' => ['type' => Type::nonNull(Type::int())], + 'field10' => ['type' => Type::nonNull(Type::listOf(Type::int()))], + 'field11' => ['type' => Type::int()], + 'field12' => ['type' => Type::listOf(Type::int())], + 'field13' => ['type' => Type::listOf(Type::nonNull(Type::int()))], + 'field14' => ['type' => Type::listOf(Type::int())], + 'field15' => ['type' => Type::listOf(Type::listOf(Type::int()))], + 'field16' => ['type' => Type::nonNull(Type::int())], + 'field17' => ['type' => Type::listOf(Type::int())], + 'field18' => [ + 'type' => Type::listOf(Type::nonNull( + Type::listOf(Type::nonNull(Type::int())))), + ], + ] + ]); + $newType1 = new InterfaceType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => ['type' => $typeA2], + 'field3' => ['type' => Type::boolean()], + 'field4' => ['type' => $typeB], + 'field5' => ['type' => Type::string()], + 'field6' => ['type' => Type::listOf(Type::string())], + 'field7' => ['type' => Type::string()], + 'field8' => ['type' => Type::nonNull(Type::int())], + 'field9' => ['type' => Type::int()], + 'field10' => ['type' => Type::listOf(Type::int())], + 'field11' => ['type' => Type::nonNull(Type::listOf(Type::int()))], + 'field12' => ['type' => Type::listOf(Type::nonNull(Type::int()))], + 'field13' => ['type' => Type::listOf(Type::int())], + 'field14' => ['type' => Type::listOf(Type::listOf(Type::int()))], + 'field15' => ['type' => Type::listOf(Type::int())], + 'field16' => ['type' => Type::nonNull(Type::listOf(Type::int()))], + 'field17' => ['type' => Type::nonNull(Type::listOf(Type::int()))], + 'field18' => [ + 'type' => Type::listOf( + Type::listOf(Type::nonNull(Type::int()))), + ], + ] + ]); + + $expectedFieldChanges = [ + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, + 'description' => 'Type1->field2 was removed.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field3 changed type from String to Boolean.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field4 changed type from TypeA to TypeB.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field6 changed type from String to [String].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field7 changed type from [String] to String.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field9 changed type from Int! to Int.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field10 changed type from [Int]! to [Int].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field11 changed type from Int to [Int]!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field13 changed type from [Int!] to [Int].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field14 changed type from [Int] to [[Int]].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field15 changed type from [[Int]] to [Int].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field16 changed type from Int! to [Int]!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'Type1->field18 changed type from [[Int!]!] to [[Int!]].', + ], + ]; + + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'Type1' => $oldType1 + ] + ]) + ]); + + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'Type1' => $newType1 + ] + ]) + ]); + + $this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedType($oldSchema, $newSchema)); + } + } \ No newline at end of file From cf4cccf4d6cfbeaa053f54ccac4052a2a39f8c26 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Fri, 17 Nov 2017 15:43:16 -0500 Subject: [PATCH 13/28] testShouldDetectInputFieldChanges --- src/Utils/FindBreakingChanges.php | 2 +- tests/Utils/FindBreakingChangesTest.php | 170 ++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 1 deletion(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 4077b1d..8f5a906 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -317,7 +317,7 @@ class FindBreakingChanges if (!$isSafe) { $oldFieldTypeString = self::isNamedType($oldFieldType) ? $oldFieldType->name : $oldFieldType; $newFieldTypeString = self::isNamedType($newfieldType) ? $newfieldType->name : $newfieldType; - $breakingFieldChanges[] = ['type' => self::BREAKING_CHANGE_FIELD_CHANGED, 'description' => "${typeName}->${fieldName} changed type from ${oldFieldTypeString} to ${newFieldTypeString}"]; + $breakingFieldChanges[] = ['type' => self::BREAKING_CHANGE_FIELD_CHANGED, 'description' => "${typeName}->${fieldName} changed type from ${oldFieldTypeString} to ${newFieldTypeString}."]; } } } diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 34334c9..ac792f4 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -5,6 +5,7 @@ namespace GraphQL\Tests\Utils; +use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; @@ -258,4 +259,173 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedType($oldSchema, $newSchema)); } + + public function testShouldDetectInputFieldChanges() + { + $oldInputType = new InputObjectType([ + 'name' => 'InputType1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + ], + 'field2' => [ + 'type' => Type::boolean(), + ], + 'field3' => [ + 'type' => Type::listOf(Type::string()) + ], + 'field4' => [ + 'type' => Type::nonNull(Type::string()), + ], + 'field5' => [ + 'type' => Type::string(), + ], + 'field6' => [ + 'type' => Type::listOf(Type::int()) + ], + 'field7' => [ + 'type' => Type::nonNull(Type::listOf(Type::int())) + ], + 'field8' => [ + 'type' => Type::int(), + ], + 'field9' => [ + 'type' => Type::listOf(Type::int()) + ], + 'field10' => [ + 'type' => Type::listOf(Type::nonNull(Type::int())) + ], + 'field11' => [ + 'type' => Type::listOf(Type::int()) + ], + 'field12' => [ + 'type' => Type::listOf(Type::listOf(Type::int())) + ], + 'field13' => [ + 'type' => Type::nonNull(Type::int()) + ], + 'field14' => [ + 'type' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))) + ], + 'field15' => [ + 'type' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))) + ] + ] + ]); + + $newInputType = new InputObjectType([ + 'name' => 'InputType1', + 'fields' => [ + 'field1' => [ + 'type' => Type::int(), + ], + 'field3' => [ + 'type' => Type::string() + ], + 'field4' => [ + 'type' => Type::string() + ], + 'field5' => [ + 'type' => Type::nonNull(Type::string()) + ], + 'field6' => [ + 'type' => Type::nonNull(Type::listOf(Type::int())) + ], + 'field7' => [ + 'type' => Type::listOf(Type::int()) + ], + 'field8' => [ + 'type' => Type::nonNull(Type::listOf(Type::int())) + ], + 'field9' => [ + 'type' => Type::listOf(Type::nonNull(Type::int())) + ], + 'field10' => [ + 'type' => Type::listOf(Type::int()) + ], + 'field11' => [ + 'type' => Type::listOf(Type::listOf(Type::int())) + ], + 'field12' => [ + 'type' => Type::listOf(Type::int()) + ], + 'field13' => [ + 'type' => Type::nonNull(Type::listOf(Type::int())) + ], + 'field14' => [ + 'type' => Type::listOf(Type::listOf(Type::int())) + ], + 'field15' => [ + 'type' => Type::listOf(Type::nonNull(Type::listOf(Type::nonNull(Type::int())))) + ] + ] + ]); + + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $oldInputType + ] + ]) + ]); + + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $newInputType + ] + ]) + ]); + + $expectedFieldChanges = [ + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'InputType1->field1 changed type from String to Int.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, + 'description' => 'InputType1->field2 was removed.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'InputType1->field3 changed type from [String] to String.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'InputType1->field5 changed type from String to String!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'InputType1->field6 changed type from [Int] to [Int]!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'InputType1->field8 changed type from Int to [Int]!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'InputType1->field9 changed type from [Int] to [Int!].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'InputType1->field11 changed type from [Int] to [[Int]].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'InputType1->field12 changed type from [[Int]] to [Int].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'InputType1->field13 changed type from Int! to [Int]!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'InputType1->field15 changed type from [[Int]!] to [[Int!]!].', + ], + ]; + + $this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedType($oldSchema, $newSchema)); + } } \ No newline at end of file From dde2747918ebb77e20472d4fd0f717eb12a52252 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Fri, 17 Nov 2017 15:56:53 -0500 Subject: [PATCH 14/28] testDetectsNonNullFieldAddedToInputType --- src/Utils/FindBreakingChanges.php | 2 +- tests/Utils/FindBreakingChangesTest.php | 45 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 8f5a906..c44d78d 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -324,7 +324,7 @@ class FindBreakingChanges foreach ($newTypeFieldsDef as $fieldName => $fieldDef) { if (!isset($oldTypeFieldsDef[$fieldName]) && $fieldDef->getType() instanceof NonNull) { $newTypeName = $newType->name; - $breakingFieldChanges[] = ['type' => self::BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED, 'description' => "A non-null field ${fieldName} on input type ${$newTypeName} was added"]; + $breakingFieldChanges[] = ['type' => self::BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED, 'description' => "A non-null field ${fieldName} on input type ${newTypeName} was added."]; } } } diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index ac792f4..ed87628 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -428,4 +428,49 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedType($oldSchema, $newSchema)); } + + public function testDetectsNonNullFieldAddedToInputType() + { + $oldInputType = new InputObjectType([ + 'name' => 'InputType1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $newInputType = new InputObjectType([ + 'name' => 'InputType1', + 'fields' => [ + 'field1' => Type::string(), + 'requiredField' => Type::nonNull(Type::int()), + 'optionalField' => Type::boolean() + ] + ]); + + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $oldInputType + ] + ]) + ]); + + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $newInputType + ] + ]) + ]); + + $this->assertEquals( + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED, + 'description' => 'A non-null field requiredField on input type InputType1 was added.' + ], + FindBreakingChanges::findFieldsThatChangedType($oldSchema, $newSchema)[0] + ); + } } \ No newline at end of file From 98ce1ccc69e059232bb66b6a4d354d503f5a6102 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Fri, 17 Nov 2017 16:08:44 -0500 Subject: [PATCH 15/28] testDetectsIfTypeWasRemovedFromUnion --- src/Utils/FindBreakingChanges.php | 2 +- tests/Utils/FindBreakingChangesTest.php | 72 +++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index c44d78d..aeb1f37 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -410,7 +410,7 @@ class FindBreakingChanges foreach ($oldType->getTypes() as $type) { if (!isset($typeNamesInNewUnion[$type->name])) { $missingTypeName = $type->name; - $typesRemovedFromUnion[] = ['type' => self::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, 'description' => "${missingTypeName} was removed from union type ${typeName}"]; + $typesRemovedFromUnion[] = ['type' => self::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, 'description' => "${missingTypeName} was removed from union type ${typeName}."]; } } } diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index ed87628..0600999 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -473,4 +473,76 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase FindBreakingChanges::findFieldsThatChangedType($oldSchema, $newSchema)[0] ); } + + public function testDetectsIfTypeWasRemovedFromUnion() + { + $type1 = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $type1a = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $type2 = new ObjectType([ + 'name' => 'Type2', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $type3 = new ObjectType([ + 'name' => 'Type3', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $oldUnionType = new UnionType([ + 'name' => 'UnionType1', + 'types' => [$type1, $type2], + 'resolveType' => function () { + } + ]); + + + $newUnionType = new UnionType([ + 'name' => 'UnionType1', + 'types' => [$type1a, $type3], + 'resolveType' => function () { + } + ]); + + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $oldUnionType + ] + ]) + ]); + + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $newUnionType + ] + ]) + ]); + + $this->assertEquals( + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, + 'description' => 'Type2 was removed from union type UnionType1.' + ], + FindBreakingChanges::findTypesRemovedFromUnions($oldSchema, $newSchema)[0] + ); + } } \ No newline at end of file From cac011246e9fbc43fd874b8356e9043957f00dfb Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Fri, 17 Nov 2017 16:24:04 -0500 Subject: [PATCH 16/28] testDetectsValuesRemovedFromEnum --- src/Utils/FindBreakingChanges.php | 2 +- tests/Utils/FindBreakingChangesTest.php | 47 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index aeb1f37..452430d 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -479,7 +479,7 @@ class FindBreakingChanges foreach ($oldType->getValues() as $value) { if (!isset($valuesInNewEnum[$value->name])) { $valueName = $value->name; - $valuesRemovedFromEnums[] = ['type' => self::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM, 'description' => "${valueName} was removed from enum type ${typeName}"]; + $valuesRemovedFromEnums[] = ['type' => self::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM, 'description' => "${valueName} was removed from enum type ${typeName}."]; } } } diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 0600999..77ad74c 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -5,6 +5,7 @@ namespace GraphQL\Tests\Utils; +use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; @@ -545,4 +546,50 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase FindBreakingChanges::findTypesRemovedFromUnions($oldSchema, $newSchema)[0] ); } + + public function testDetectsValuesRemovedFromEnum() + { + $oldEnumType = new EnumType([ + 'name' => 'EnumType1', + 'values' => [ + 'VALUE0' => 0, + 'VALUE1' => 1, + 'VALUE2' => 2 + ] + ]); + $newEnumType = new EnumType([ + 'name' => 'EnumType1', + 'values' => [ + 'VALUE0' => 0, + 'VALUE2' => 1, + 'VALUE3' => 2 + ] + ]); + + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $oldEnumType + ] + ]) + ]); + + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $newEnumType + ] + ]) + ]); + + $this->assertEquals( + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM, + 'description' => 'VALUE1 was removed from enum type EnumType1.' + ], + FindBreakingChanges::findValuesRemovedFromEnums($oldSchema, $newSchema)[0] + ); + } } \ No newline at end of file From fc9c5e85aa7f0e047924b92448ae909fecfb87e7 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Fri, 17 Nov 2017 17:18:26 -0500 Subject: [PATCH 17/28] testDetectsRemovalOfFieldArgument --- src/Utils/FindBreakingChanges.php | 3 +- tests/Utils/FindBreakingChangesTest.php | 93 +++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 452430d..a2cab12 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -164,9 +164,10 @@ class FindBreakingChanges } ); if (!$newArgDef) { + $argName = $oldArgDef->name; $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_ARG_REMOVED, - 'description' => "${$oldTypeName}->${$fieldName} arg ${oldArgName} was removed" + 'description' => "${oldTypeName}->${fieldName} arg ${argName} was removed" ]; } else { $isSafe = self::isChangeSafeForInputObjectFieldOrFieldArg($oldArgDef->getType(), $newArgDef->getType()); diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 77ad74c..3af4c83 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -592,4 +592,97 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase FindBreakingChanges::findValuesRemovedFromEnums($oldSchema, $newSchema)[0] ); } + + public function testDetectsRemovalOfFieldArgument() + { + + $oldType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'name' => Type::string() + ] + ] + ] + ]); + + + $inputType = new InputObjectType([ + 'name' => 'InputType1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $oldInterfaceType = new InterfaceType([ + 'name' => 'Interface1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'arg1' => Type::boolean(), + 'objectArg' => $inputType + ] + ] + ] + ]); + + $newType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [] + ] + ] + ]); + + $newInterfaceType = new InterfaceType([ + 'name' => 'Interface1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $oldType, + 'type2' => $oldInterfaceType + ], + 'types' => [$oldType, $oldInterfaceType] + ]) + ]); + + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $newType, + 'type2' => $newInterfaceType + ], + 'types' => [$newType, $newInterfaceType] + ]) + ]); + + $expectedChanges = [ + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, + 'description' => 'Type1->field1 arg name was removed', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, + 'description' => 'Interface1->field1 arg arg1 was removed', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, + 'description' => 'Interface1->field1 arg objectArg was removed', + ] + ]; + + $this->assertEquals($expectedChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); + } } \ No newline at end of file From 4ea6cbe839ad41e37840413fc83d6a463897ed69 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Fri, 17 Nov 2017 17:35:33 -0500 Subject: [PATCH 18/28] bugfix var ref --- src/Utils/FindBreakingChanges.php | 2 +- tests/Utils/FindBreakingChangesTest.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index a2cab12..11988fe 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -195,7 +195,7 @@ class FindBreakingChanges $newTypeName = $newTypeDefinition->name; $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED, - 'description' => "A non-null arg ${$newArgName} on ${newTypeName}->${fieldName} was added." + 'description' => "A non-null arg ${newArgName} on ${newTypeName}->${fieldName} was added." ]; } } diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 3af4c83..3ba80bb 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -685,4 +685,8 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } + + public function testDetectsFieldArgumentTypeChange() { + + } } \ No newline at end of file From 42d8ac07f98acb2a4035ca15fbff05abab01ad94 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Mon, 20 Nov 2017 12:52:49 -0500 Subject: [PATCH 19/28] testDetectsFieldArgumentTypeChange --- src/Utils/FindBreakingChanges.php | 3 +- tests/Utils/FindBreakingChangesTest.php | 125 +++++++++++++++++++++++- 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 11988fe..aa6a468 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -156,7 +156,7 @@ class FindBreakingChanges continue; } - foreach ($fieldDefinition->args as $oldArgName => $oldArgDef) { + foreach ($fieldDefinition->args as $oldArgDef) { $newArgs = $newTypeFields[$fieldName]->args; $newArgDef = Utils::find( $newArgs, function ($arg) use ($oldArgDef) { @@ -174,6 +174,7 @@ class FindBreakingChanges if (!$isSafe) { $oldArgType = $oldArgDef->getType(); $newArgType = $newArgDef->getType(); + $oldArgName = $oldArgDef->name; $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_ARG_CHANGED, 'description' => "${oldTypeName}->${fieldName} arg ${oldArgName} has changed type from ${oldArgType} to ${newArgType}." diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 3ba80bb..3d2c433 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -686,7 +686,130 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } - public function testDetectsFieldArgumentTypeChange() { + public function testDetectsFieldArgumentTypeChange() + { + $oldType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'arg1' => Type::string(), + 'arg2' => Type::string(), + 'arg3' => Type::listOf(Type::string()), + 'arg4' => Type::string(), + 'arg5' => Type::nonNull(Type::string()), + 'arg6' => Type::nonNull(Type::string()), + 'arg7' => Type::nonNull(Type::listOf(Type::int())), + 'arg8' => Type::int(), + 'arg9' => Type::listOf(Type::int()), + 'arg10' => Type::listOf(Type::nonNull(Type::int())), + 'arg11' => Type::listOf(Type::int()), + 'arg12' => Type::listOf(Type::listOf(Type::int())), + 'arg13' => Type::nonNull(Type::int()), + 'arg14' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))), + 'arg15' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))) + ] + ] + ] + ]); + + $newType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'arg1' => Type::int(), + 'arg2' => Type::listOf(Type::string()), + 'arg3' => Type::string(), + 'arg4' => Type::nonNull(Type::string()), + 'arg5' => Type::int(), + 'arg6' => Type::nonNull(Type::int()), + 'arg7' => Type::listOf(Type::int()), + 'arg8' => Type::nonNull(Type::listOf(Type::int())), + 'arg9' => Type::listOf(Type::nonNull(Type::int())), + 'arg10' => Type::listOf(Type::int()), + 'arg11' => Type::listOf(Type::listOf(Type::int())), + 'arg12' => Type::listOf(Type::int()), + 'arg13' => Type::nonNull(Type::listOf(Type::int())), + 'arg14' => Type::listOf(Type::listOf(Type::int())), + 'arg15' => Type::listOf(Type::nonNull(Type::listOf(Type::nonNull(Type::int())))) + ] + ] + ] + ]); + + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $oldType + ] + ]) + ]); + + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $newType + ] + ]) + ]); + + $expectedChanges = [ + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg1 has changed type from String to Int.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg2 has changed type from String to [String].' + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg3 has changed type from [String] to String.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg4 has changed type from String to String!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg5 has changed type from String! to Int.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg6 has changed type from String! to Int!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg8 has changed type from Int to [Int]!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg9 has changed type from [Int] to [Int!].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg11 has changed type from [Int] to [[Int]].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg12 has changed type from [[Int]] to [Int].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg13 has changed type from Int! to [Int]!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'Type1->field1 arg arg15 has changed type from [[Int]!] to [[Int!]!].', + ], + ]; + + $this->assertEquals($expectedChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } } \ No newline at end of file From 0fd5abc8338addece3e57936d4402b9d30c352aa Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Mon, 20 Nov 2017 13:48:21 -0500 Subject: [PATCH 20/28] testDetectsAdditionOfFieldArg --- src/Utils/FindBreakingChanges.php | 3 +- tests/Utils/FindBreakingChangesTest.php | 49 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index aa6a468..7754924 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -184,7 +184,7 @@ class FindBreakingChanges } } // Check if a non-null arg was added to the field - foreach ($newTypeFields[$fieldName]->args as $newArgName => $newArgDef) { + foreach ($newTypeFields[$fieldName]->args as $newArgDef) { $oldArgs = $oldTypeFields[$fieldName]->args; $oldArgDef = Utils::find( $oldArgs, function ($arg) use ($newArgDef) { @@ -194,6 +194,7 @@ class FindBreakingChanges if (!$oldArgDef && $newArgDef->getType() instanceof NonNull) { $newTypeName = $newTypeDefinition->name; + $newArgName = $newArgDef->name; $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED, 'description' => "A non-null arg ${newArgName} on ${newTypeName}->${fieldName} was added." diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 3d2c433..ed735be 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -812,4 +812,53 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } + + public function testDetectsAdditionOfFieldArg() + { + $oldType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'arg1' => Type::string() + ]] + ] + ]); + $newType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'arg1' => Type::string(), + 'newRequiredArg' => Type::nonNull(Type::string()), + 'newOptionalArg' => Type::int() + ]] + ] + ]); + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $oldType, + ] + ]) + ]); + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $newType + ] + ]) + ]); + + $this->assertEquals( + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_NON_NULL_ARG_ADDED, + 'description' => 'A non-null arg newRequiredArg on Type1->field1 was added.' + ], + FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges'][0]); + } } \ No newline at end of file From 90f35f26a29a595ddf371dd50638f2c917f965f1 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Mon, 20 Nov 2017 13:57:52 -0500 Subject: [PATCH 21/28] testDoesNotFlagArgsWithSameTypeSignature --- tests/Utils/FindBreakingChangesTest.php | 61 +++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index ed735be..b5f6991 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -861,4 +861,65 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges'][0]); } + + public function testDoesNotFlagArgsWithSameTypeSignature() { + $inputType1a = new InputObjectType([ + 'name' => 'InputType1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $inputType1b = new InputObjectType([ + 'name' => 'InputType1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $oldType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::int(), + 'args' => [ + 'arg1' => Type::nonNull(Type::int()), + 'arg2' => $inputType1a + ] + ] + ] + ]); + + $newType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::int(), + 'args' => [ + 'arg1' => Type::nonNull(Type::int()), + 'arg2' => $inputType1b + ] + ] + ] + ]); + + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $oldType, + ] + ]) + ]); + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $newType + ] + ]) + ]); + + $this->assertEquals([], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); + } } \ No newline at end of file From 0bb689d340d3db31e6b37915fb05bc604645451e Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Mon, 20 Nov 2017 14:12:26 -0500 Subject: [PATCH 22/28] testArgsThatMoveAwayFromNonNull --- tests/Utils/FindBreakingChangesTest.php | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index b5f6991..0050c38 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -922,4 +922,48 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals([], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } + + public function testArgsThatMoveAwayFromNonNull() { + $oldType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'arg1' => Type::nonNull(Type::string()), + ] + ] + ] + ]); + $newType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'arg1' => Type::string() + ] + ] + ] + ]); + + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $oldType, + ] + ]) + ]); + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $newType + ] + ]) + ]); + + $this->assertEquals([], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); + } } \ No newline at end of file From dbccf9b196c439fe9c82f8def08f3abfe96f95bb Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Mon, 20 Nov 2017 14:39:06 -0500 Subject: [PATCH 23/28] testDetectsRemovalOfInterfaces --- src/Utils/FindBreakingChanges.php | 2 +- tests/Utils/FindBreakingChangesTest.php | 55 ++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 7754924..0a8325c 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -552,7 +552,7 @@ class FindBreakingChanges })) { $oldInterfaceName = $oldInterface->name; $breakingChanges[] = ['type' => self::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT, - 'description' => "${typeName} no longer implements interface ${oldInterfaceName}" + 'description' => "${typeName} no longer implements interface ${oldInterfaceName}." ]; } } diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 0050c38..018e1bd 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -862,7 +862,8 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges'][0]); } - public function testDoesNotFlagArgsWithSameTypeSignature() { + public function testDoesNotFlagArgsWithSameTypeSignature() + { $inputType1a = new InputObjectType([ 'name' => 'InputType1', 'fields' => [ @@ -923,7 +924,8 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals([], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } - public function testArgsThatMoveAwayFromNonNull() { + public function testArgsThatMoveAwayFromNonNull() + { $oldType = new ObjectType([ 'name' => 'Type1', 'fields' => [ @@ -966,4 +968,53 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals([], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } + + public function testDetectsRemovalOfInterfaces() + { + $interface1 = new InterfaceType([ + 'name' => 'Interface1', + 'fields' => [ + 'field1' => Type::string() + ], + 'resolveType' => function () { + } + ]); + $oldType = new ObjectType([ + 'name' => 'Type1', + 'interfaces' => [$interface1], + 'fields' => [ + 'field1' => Type::string() + ] + ]); + $newType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $oldSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $oldType, + ] + ]) + ]); + $newSchema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'root', + 'fields' => [ + 'type1' => $newType + ] + ]) + ]); + + $this->assertEquals( + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT, + 'description' => 'Type1 no longer implements interface Interface1.' + ], + FindBreakingChanges::findInterfacesRemovedFromObjectTypes($oldSchema, $newSchema)[0]); + } } \ No newline at end of file From 3c0ed787ba5f9f49eb4d3771912c8755e1101df1 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Tue, 21 Nov 2017 11:33:45 -0500 Subject: [PATCH 24/28] testDetectsAllBreakingChanges --- tests/Utils/FindBreakingChangesTest.php | 211 ++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 018e1bd..4345ea1 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -17,6 +17,8 @@ use GraphQL\Utils\FindBreakingChanges; class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase { + private $queryType; + public function setUp() { $this->queryType = new ObjectType([ @@ -1017,4 +1019,213 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ], FindBreakingChanges::findInterfacesRemovedFromObjectTypes($oldSchema, $newSchema)[0]); } + + public function testDetectsAllBreakingChanges() + { + $typeThatGetsRemoved = new ObjectType([ + 'name' => 'TypeThatGetsRemoved', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $argThatChanges = new ObjectType([ + 'name' => 'ArgThatChanges', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'id' => Type::int() + ] + ] + ] + ]); + + $argChanged = new ObjectType([ + 'name' => 'ArgThatChanges', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'id' => Type::string() + ] + ] + ] + ]); + + $typeThatChangesTypeOld = new ObjectType([ + 'name' => 'TypeThatChangesType', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $typeThatChangesTypeNew = new InterfaceType([ + 'name' => 'TypeThatChangesType', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $typeThatHasBreakingFieldChangesOld = new InterfaceType([ + 'name' => 'TypeThatHasBreakingFieldChanges', + 'fields' => [ + 'field1' => Type::string(), + 'field2' => Type::string() + ] + ]); + + $typeThatHasBreakingFieldChangesNew = new InterfaceType([ + 'name' => 'TypeThatHasBreakingFieldChanges', + 'fields' => [ + 'field2' => Type::boolean() + ] + ]); + + $typeInUnion1 = new ObjectType([ + 'name' => 'TypeInUnion1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $typeInUnion2 = new ObjectType([ + 'name' => 'TypeInUnion2', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $unionTypeThatLosesATypeOld = new UnionType([ + 'name' => 'UnionTypeThatLosesAType', + 'types' => [$typeInUnion1, $typeInUnion2], + 'resolveType' => function () { + } + ]); + + $unionTypeThatLosesATypeNew = new UnionType([ + 'name' => 'UnionTypeThatLosesAType', + 'types' => [$typeInUnion1], + 'resolveType' => function () { + } + ]); + + $enumTypeThatLosesAValueOld = new EnumType([ + 'name' => 'EnumTypeThatLosesAValue', + 'values' => [ + 'VALUE0' => 0, + 'VALUE1' => 1, + 'VALUE2' => 2 + ] + ]); + + $enumTypeThatLosesAValueNew = new EnumType([ + 'name' => 'EnumTypeThatLosesAValue', + 'values' => [ + 'VALUE1' => 1, + 'VALUE2' => 2 + ] + ]); + + $interface1 = new InterfaceType([ + 'name' => 'Interface1', + 'fields' => [ + 'field1' => Type::string() + ], + 'resolveType' => function () { + } + ]); + + $typeThatLosesInterfaceOld = new ObjectType([ + 'name' => 'TypeThatLosesInterface1', + 'interfaces' => [$interface1], + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $typeThatLosesInterfaceNew = new ObjectType([ + 'name' => 'TypeThatLosesInterface1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $oldSchema = new Schema([ + 'query' => $this->queryType, + 'types' => + [ + 'TypeThatGetsRemoved' => $typeThatGetsRemoved, + 'TypeThatChangesType' => $typeThatChangesTypeOld, + 'TypeThatHasBreakingFieldChanges' => $typeThatHasBreakingFieldChangesOld, + 'UnionTypeThatLosesAType' => $unionTypeThatLosesATypeOld, + 'EnumTypeThatLosesAValue' => $enumTypeThatLosesAValueOld, + 'ArgThatChanges' => $argThatChanges, + 'TypeThatLosesInterface' => $typeThatLosesInterfaceOld + ] + ]); + + $newSchema = new Schema([ + 'query' => $this->queryType, + 'types' => + [ + 'TypeThatChangesType' => $typeThatChangesTypeNew, + 'TypeThatHasBreakingFieldChanges' => $typeThatHasBreakingFieldChangesNew, + 'UnionTypeThatLosesAType' => $unionTypeThatLosesATypeNew, + 'EnumTypeThatLosesAValue' => $enumTypeThatLosesAValueNew, + 'ArgThatChanges' => $argChanged, + 'TypeThatLosesInterface' => $typeThatLosesInterfaceNew, + 'Interface1' => $interface1 + ] + ]); + + $expectedBreakingChanges = [ + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, + 'description' => 'TypeThatGetsRemoved was removed.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, + 'description' => 'TypeInUnion2 was removed.', + ], + /* + // NB the below assertion is included in the graphql-js tests, but it makes no sense. + // Seriously, look for what `int` type was supposed to be removed between the two schemas. There is none. + // I honestly think it's a bug in the js implementation and was put into the test just to make it pass. + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, + 'description' => 'Int was removed.' + ],*/ + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_CHANGED, + 'description' => 'TypeThatChangesType changed from an Object type to an Interface type.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, + 'description' => 'TypeThatHasBreakingFieldChanges->field1 was removed.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, + 'description' => 'TypeThatHasBreakingFieldChanges->field2 changed type from String to Boolean.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, + 'description' => 'TypeInUnion2 was removed from union type UnionTypeThatLosesAType.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM, + 'description' => 'VALUE0 was removed from enum type EnumTypeThatLosesAValue.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, + 'description' => 'ArgThatChanges->field1 arg id has changed type from Int to String.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT, + 'description' => 'TypeThatLosesInterface1 no longer implements interface Interface1.', + ] + ]; + + $this->assertEquals($expectedBreakingChanges, FindBreakingChanges::findBreakingChanges($oldSchema, $newSchema)); + } } \ No newline at end of file From c4ae03454a53f76a117d1da0f620661fe710179e Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Tue, 21 Nov 2017 11:50:11 -0500 Subject: [PATCH 25/28] testFindDangerousArgChanges --- src/Utils/FindBreakingChanges.php | 9 ++-- tests/Utils/FindBreakingChangesTest.php | 59 ++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 0a8325c..3880dcb 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -171,16 +171,19 @@ class FindBreakingChanges ]; } else { $isSafe = self::isChangeSafeForInputObjectFieldOrFieldArg($oldArgDef->getType(), $newArgDef->getType()); + $oldArgType = $oldArgDef->getType(); + $oldArgName = $oldArgDef->name; if (!$isSafe) { - $oldArgType = $oldArgDef->getType(); $newArgType = $newArgDef->getType(); - $oldArgName = $oldArgDef->name; $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_ARG_CHANGED, 'description' => "${oldTypeName}->${fieldName} arg ${oldArgName} has changed type from ${oldArgType} to ${newArgType}." ]; } elseif ($oldArgDef->defaultValueExists() && $oldArgDef->defaultValue !== $newArgDef->defaultValue) { - $dangerousChanges[] = []; // TODO + $dangerousChanges[] = [ + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE, + 'description' => "${oldTypeName}->${fieldName} arg ${oldArgName} has changed defaultValue" + ]; } } // Check if a non-null arg was added to the field diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 4345ea1..1153e53 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -22,7 +22,7 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase public function setUp() { $this->queryType = new ObjectType([ - 'name' => 'Type1', + 'name' => 'Query', 'fields' => [ 'field1' => [ 'type' => Type::string() @@ -1228,4 +1228,61 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedBreakingChanges, FindBreakingChanges::findBreakingChanges($oldSchema, $newSchema)); } + + // findDangerousChanges tests below here + + public function testFindDangerousArgChanges() + { + $oldType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'name' => [ + 'type' => Type::string(), + 'defaultValue' => 'test' + ] + ] + ] + ] + ]); + + $newType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'name' => [ + 'type' => Type::string(), + 'defaultValue' => 'Testertest' + ] + ] + ] + ] + ]); + + $oldSchema = new Schema([ + 'query' => $this->queryType, + 'types' => [ + $oldType + ] + ]); + + $newSchema = new Schema([ + 'query' => $this->queryType, + 'types' => [ + $newType + ] + ]); + + $this->assertEquals( + [ + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE, + 'description' => 'Type1->field1 arg name has changed defaultValue' + ], + FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['dangerousChanges'][0] + ); + } } \ No newline at end of file From e0a63ec7927788099ac7816c30a01d800415d430 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Tue, 21 Nov 2017 11:53:14 -0500 Subject: [PATCH 26/28] testDetectsEnumValueAdditions --- tests/Utils/FindBreakingChangesTest.php | 45 +++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 1153e53..a15b093 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -1279,10 +1279,51 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals( [ - 'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE, - 'description' => 'Type1->field1 arg name has changed defaultValue' + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE, + 'description' => 'Type1->field1 arg name has changed defaultValue' ], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['dangerousChanges'][0] ); } + + public function testDetectsEnumValueAdditions() + { + $oldEnumType = new EnumType([ + 'name' => 'EnumType1', + 'values' => [ + 'VALUE0' => 0, + 'VALUE1' => 1, + ] + ]); + $newEnumType = new EnumType([ + 'name' => 'EnumType1', + 'values' => [ + 'VALUE0' => 0, + 'VALUE1' => 1, + 'VALUE2' => 2 + ] + ]); + + $oldSchema = new Schema([ + 'query' => $this->queryType, + 'types' => [ + $oldEnumType + ] + ]); + + $newSchema = new Schema([ + 'query' => $this->queryType, + 'types' => [ + $newEnumType + ] + ]); + + $this->assertEquals( + [ + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM, + 'description' => 'VALUE2 was added to enum type EnumType1' + ], + FindBreakingChanges::findValuesAddedToEnums($oldSchema, $newSchema)[0] + ); + } } \ No newline at end of file From 533b8b8b5f499ac06a54baf48be55b72ee3bef0f Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Tue, 21 Nov 2017 12:18:28 -0500 Subject: [PATCH 27/28] testDetectsAdditionsToUnionType --- src/Utils/FindBreakingChanges.php | 7 +-- tests/Utils/FindBreakingChangesTest.php | 59 +++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 3880dcb..c747a71 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -433,12 +433,13 @@ class FindBreakingChanges Schema $oldSchema, Schema $newSchema ) { + $oldTypeMap = $oldSchema->getTypeMap(); $newTypeMap = $newSchema->getTypeMap(); $typesAddedToUnion = []; - foreach ($newTypeMap as $typeName => $oldType) { - $newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; + foreach ($newTypeMap as $typeName => $newType) { + $oldType = isset($oldTypeMap[$typeName]) ? $oldTypeMap[$typeName] : null; if (!($oldType instanceof UnionType) || !($newType instanceof UnionType)) { continue; } @@ -450,7 +451,7 @@ class FindBreakingChanges foreach ($newType->getTypes() as $type) { if (!isset($typeNamesInOldUnion[$type->name])) { $addedTypeName = $type->name; - $typesRemovedFromUnion[] = ['type' => self::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, 'description' => "${addedTypeName} was removed to union type ${typeName}"]; + $typesAddedToUnion[] = ['type' => self::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, 'description' => "${addedTypeName} was added to union type ${typeName}"]; } } } diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index a15b093..0d461f4 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -1326,4 +1326,63 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase FindBreakingChanges::findValuesAddedToEnums($oldSchema, $newSchema)[0] ); } + + public function testDetectsAdditionsToUnionType() { + $type1 = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $type1a = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $type2 = new ObjectType([ + 'name' => 'Type2', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $oldUnionType = new UnionType([ + 'name' => 'UnionType1', + 'types' => [$type1], + 'resolveType' => function () { + } + ]); + + $newUnionType = new UnionType([ + 'name' => 'UnionType1', + 'types' => [$type1a, $type2], + 'resolveType' => function () { + } + ]); + + $oldSchema = new Schema([ + 'query' => $this->queryType, + 'types' => [ + $oldUnionType + ] + ]); + + $newSchema = new Schema([ + 'query' => $this->queryType, + 'types' => [ + $newUnionType + ] + ]); + + $this->assertEquals( + [ + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, + 'description' => 'Type2 was added to union type UnionType1' + ], + FindBreakingChanges::findTypesAddedToUnions($oldSchema, $newSchema)[0] + ); + } } \ No newline at end of file From b18dfd670ffa1891413044a9ea454242ba3307c8 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Tue, 21 Nov 2017 12:30:18 -0500 Subject: [PATCH 28/28] testFindsAllDangerousChanges --- tests/Utils/FindBreakingChangesTest.php | 119 +++++++++++++++++++++++- 1 file changed, 116 insertions(+), 3 deletions(-) diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 0d461f4..2fc0f6f 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -1327,7 +1327,8 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ); } - public function testDetectsAdditionsToUnionType() { + public function testDetectsAdditionsToUnionType() + { $type1 = new ObjectType([ 'name' => 'Type1', 'fields' => [ @@ -1379,10 +1380,122 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals( [ - 'type' => FindBreakingChanges::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, - 'description' => 'Type2 was added to union type UnionType1' + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, + 'description' => 'Type2 was added to union type UnionType1' ], FindBreakingChanges::findTypesAddedToUnions($oldSchema, $newSchema)[0] ); } + + public function testFindsAllDangerousChanges() + { + $enumThatGainsAValueOld = new EnumType([ + 'name' => 'EnumType1', + 'values' => [ + 'VALUE0' => 0, + 'VALUE1' => 1, + ] + ]); + $enumThatGainsAValueNew = new EnumType([ + 'name' => 'EnumType1', + 'values' => [ + 'VALUE0' => 0, + 'VALUE1' => 1, + 'VALUE2' => 2 + ] + ]); + + $oldType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'name' => [ + 'type' => Type::string(), + 'defaultValue' => 'test' + ] + ] + ] + ] + ]); + + $newType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => [ + 'type' => Type::string(), + 'args' => [ + 'name' => [ + 'type' => Type::string(), + 'defaultValue' => 'Testertest' + ] + ] + ] + ] + ]); + + $typeInUnion1 = new ObjectType([ + 'name' => 'TypeInUnion1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $typeInUnion2 = new ObjectType([ + 'name' => 'TypeInUnion2', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + + $unionTypeThatGainsATypeOld = new UnionType([ + 'name' => 'UnionType1', + 'types' => [$typeInUnion1], + 'resolveType' => function () { + } + ]); + + $unionTypeThatGainsATypeNew = new UnionType([ + 'name' => 'UnionType1', + 'types' => [$typeInUnion1, $typeInUnion2], + 'resolveType' => function () { + } + ]); + + $oldSchema = new Schema([ + 'query' => $this->queryType, + 'types' => [ + $oldType, + $enumThatGainsAValueOld, + $unionTypeThatGainsATypeOld + ] + ]); + + $newSchema = new Schema([ + 'query' => $this->queryType, + 'types' => [ + $newType, + $enumThatGainsAValueNew, + $unionTypeThatGainsATypeNew + ] + ]); + + $expectedDangerousChanges = [ + [ + 'description' => 'Type1->field1 arg name has changed defaultValue', + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE + ], + [ + 'description' => 'VALUE2 was added to enum type EnumType1', + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM + ], + [ + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, + 'description' => 'TypeInUnion2 was added to union type UnionType1', + ] + ]; + + $this->assertEquals($expectedDangerousChanges, FindBreakingChanges::findDangerousChanges($oldSchema, $newSchema)); + } } \ No newline at end of file