1/** 2 * Copyright (c) 2016-present, Facebook, Inc. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 * 7 * strict 8 */ 9 10import { isScalarType, isObjectType, isInterfaceType, isUnionType, isEnumType, isInputObjectType, isNonNullType, isListType, isNamedType } from '../type/definition'; 11 12import { GraphQLDirective } from '../type/directives'; 13import { GraphQLSchema } from '../type/schema'; 14import keyMap from '../jsutils/keyMap'; 15 16export var BreakingChangeType = { 17 FIELD_CHANGED_KIND: 'FIELD_CHANGED_KIND', 18 FIELD_REMOVED: 'FIELD_REMOVED', 19 TYPE_CHANGED_KIND: 'TYPE_CHANGED_KIND', 20 TYPE_REMOVED: 'TYPE_REMOVED', 21 TYPE_REMOVED_FROM_UNION: 'TYPE_REMOVED_FROM_UNION', 22 VALUE_REMOVED_FROM_ENUM: 'VALUE_REMOVED_FROM_ENUM', 23 ARG_REMOVED: 'ARG_REMOVED', 24 ARG_CHANGED_KIND: 'ARG_CHANGED_KIND', 25 NON_NULL_ARG_ADDED: 'NON_NULL_ARG_ADDED', 26 NON_NULL_INPUT_FIELD_ADDED: 'NON_NULL_INPUT_FIELD_ADDED', 27 INTERFACE_REMOVED_FROM_OBJECT: 'INTERFACE_REMOVED_FROM_OBJECT', 28 DIRECTIVE_REMOVED: 'DIRECTIVE_REMOVED', 29 DIRECTIVE_ARG_REMOVED: 'DIRECTIVE_ARG_REMOVED', 30 DIRECTIVE_LOCATION_REMOVED: 'DIRECTIVE_LOCATION_REMOVED', 31 NON_NULL_DIRECTIVE_ARG_ADDED: 'NON_NULL_DIRECTIVE_ARG_ADDED' 32}; 33 34export var DangerousChangeType = { 35 ARG_DEFAULT_VALUE_CHANGE: 'ARG_DEFAULT_VALUE_CHANGE', 36 VALUE_ADDED_TO_ENUM: 'VALUE_ADDED_TO_ENUM', 37 INTERFACE_ADDED_TO_OBJECT: 'INTERFACE_ADDED_TO_OBJECT', 38 TYPE_ADDED_TO_UNION: 'TYPE_ADDED_TO_UNION', 39 NULLABLE_INPUT_FIELD_ADDED: 'NULLABLE_INPUT_FIELD_ADDED', 40 NULLABLE_ARG_ADDED: 'NULLABLE_ARG_ADDED' 41}; 42 43/** 44 * Given two schemas, returns an Array containing descriptions of all the types 45 * of breaking changes covered by the other functions down below. 46 */ 47export function findBreakingChanges(oldSchema, newSchema) { 48 return [].concat(findRemovedTypes(oldSchema, newSchema), findTypesThatChangedKind(oldSchema, newSchema), findFieldsThatChangedTypeOnObjectOrInterfaceTypes(oldSchema, newSchema), findFieldsThatChangedTypeOnInputObjectTypes(oldSchema, newSchema).breakingChanges, findTypesRemovedFromUnions(oldSchema, newSchema), findValuesRemovedFromEnums(oldSchema, newSchema), findArgChanges(oldSchema, newSchema).breakingChanges, findInterfacesRemovedFromObjectTypes(oldSchema, newSchema), findRemovedDirectives(oldSchema, newSchema), findRemovedDirectiveArgs(oldSchema, newSchema), findAddedNonNullDirectiveArgs(oldSchema, newSchema), findRemovedDirectiveLocations(oldSchema, newSchema)); 49} 50 51/** 52 * Given two schemas, returns an Array containing descriptions of all the types 53 * of potentially dangerous changes covered by the other functions down below. 54 */ 55export function findDangerousChanges(oldSchema, newSchema) { 56 return [].concat(findArgChanges(oldSchema, newSchema).dangerousChanges, findValuesAddedToEnums(oldSchema, newSchema), findInterfacesAddedToObjectTypes(oldSchema, newSchema), findTypesAddedToUnions(oldSchema, newSchema), findFieldsThatChangedTypeOnInputObjectTypes(oldSchema, newSchema).dangerousChanges); 57} 58 59/** 60 * Given two schemas, returns an Array containing descriptions of any breaking 61 * changes in the newSchema related to removing an entire type. 62 */ 63export function findRemovedTypes(oldSchema, newSchema) { 64 var oldTypeMap = oldSchema.getTypeMap(); 65 var newTypeMap = newSchema.getTypeMap(); 66 67 var breakingChanges = []; 68 Object.keys(oldTypeMap).forEach(function (typeName) { 69 if (!newTypeMap[typeName]) { 70 breakingChanges.push({ 71 type: BreakingChangeType.TYPE_REMOVED, 72 description: typeName + ' was removed.' 73 }); 74 } 75 }); 76 return breakingChanges; 77} 78 79/** 80 * Given two schemas, returns an Array containing descriptions of any breaking 81 * changes in the newSchema related to changing the type of a type. 82 */ 83export function findTypesThatChangedKind(oldSchema, newSchema) { 84 var oldTypeMap = oldSchema.getTypeMap(); 85 var newTypeMap = newSchema.getTypeMap(); 86 87 var breakingChanges = []; 88 Object.keys(oldTypeMap).forEach(function (typeName) { 89 if (!newTypeMap[typeName]) { 90 return; 91 } 92 var oldType = oldTypeMap[typeName]; 93 var newType = newTypeMap[typeName]; 94 if (oldType.constructor !== newType.constructor) { 95 breakingChanges.push({ 96 type: BreakingChangeType.TYPE_CHANGED_KIND, 97 description: typeName + ' changed from ' + (typeKindName(oldType) + ' to ' + typeKindName(newType) + '.') 98 }); 99 } 100 }); 101 return breakingChanges; 102} 103 104/** 105 * Given two schemas, returns an Array containing descriptions of any 106 * breaking or dangerous changes in the newSchema related to arguments 107 * (such as removal or change of type of an argument, or a change in an 108 * argument's default value). 109 */ 110export function findArgChanges(oldSchema, newSchema) { 111 var oldTypeMap = oldSchema.getTypeMap(); 112 var newTypeMap = newSchema.getTypeMap(); 113 114 var breakingChanges = []; 115 var dangerousChanges = []; 116 117 Object.keys(oldTypeMap).forEach(function (typeName) { 118 var oldType = oldTypeMap[typeName]; 119 var newType = newTypeMap[typeName]; 120 if (!(isObjectType(oldType) || isInterfaceType(oldType)) || !(isObjectType(newType) || isInterfaceType(newType)) || newType.constructor !== oldType.constructor) { 121 return; 122 } 123 124 var oldTypeFields = oldType.getFields(); 125 var newTypeFields = newType.getFields(); 126 127 Object.keys(oldTypeFields).forEach(function (fieldName) { 128 if (!newTypeFields[fieldName]) { 129 return; 130 } 131 132 oldTypeFields[fieldName].args.forEach(function (oldArgDef) { 133 var newArgs = newTypeFields[fieldName].args; 134 var newArgDef = newArgs.find(function (arg) { 135 return arg.name === oldArgDef.name; 136 }); 137 138 // Arg not present 139 if (!newArgDef) { 140 breakingChanges.push({ 141 type: BreakingChangeType.ARG_REMOVED, 142 description: oldType.name + '.' + fieldName + ' arg ' + (oldArgDef.name + ' was removed') 143 }); 144 } else { 145 var isSafe = isChangeSafeForInputObjectFieldOrFieldArg(oldArgDef.type, newArgDef.type); 146 if (!isSafe) { 147 breakingChanges.push({ 148 type: BreakingChangeType.ARG_CHANGED_KIND, 149 description: oldType.name + '.' + fieldName + ' arg ' + (oldArgDef.name + ' has changed type from ') + (oldArgDef.type.toString() + ' to ' + newArgDef.type.toString()) 150 }); 151 } else if (oldArgDef.defaultValue !== undefined && oldArgDef.defaultValue !== newArgDef.defaultValue) { 152 dangerousChanges.push({ 153 type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, 154 description: oldType.name + '.' + fieldName + ' arg ' + (oldArgDef.name + ' has changed defaultValue') 155 }); 156 } 157 } 158 }); 159 // Check if a non-null arg was added to the field 160 newTypeFields[fieldName].args.forEach(function (newArgDef) { 161 var oldArgs = oldTypeFields[fieldName].args; 162 var oldArgDef = oldArgs.find(function (arg) { 163 return arg.name === newArgDef.name; 164 }); 165 if (!oldArgDef) { 166 if (isNonNullType(newArgDef.type)) { 167 breakingChanges.push({ 168 type: BreakingChangeType.NON_NULL_ARG_ADDED, 169 description: 'A non-null arg ' + newArgDef.name + ' on ' + (newType.name + '.' + fieldName + ' was added') 170 }); 171 } else { 172 dangerousChanges.push({ 173 type: DangerousChangeType.NULLABLE_ARG_ADDED, 174 description: 'A nullable arg ' + newArgDef.name + ' on ' + (newType.name + '.' + fieldName + ' was added') 175 }); 176 } 177 } 178 }); 179 }); 180 }); 181 182 return { 183 breakingChanges: breakingChanges, 184 dangerousChanges: dangerousChanges 185 }; 186} 187 188function typeKindName(type) { 189 if (isScalarType(type)) { 190 return 'a Scalar type'; 191 } 192 if (isObjectType(type)) { 193 return 'an Object type'; 194 } 195 if (isInterfaceType(type)) { 196 return 'an Interface type'; 197 } 198 if (isUnionType(type)) { 199 return 'a Union type'; 200 } 201 if (isEnumType(type)) { 202 return 'an Enum type'; 203 } 204 if (isInputObjectType(type)) { 205 return 'an Input type'; 206 } 207 throw new TypeError('Unknown type ' + type.constructor.name); 208} 209 210export function findFieldsThatChangedTypeOnObjectOrInterfaceTypes(oldSchema, newSchema) { 211 var oldTypeMap = oldSchema.getTypeMap(); 212 var newTypeMap = newSchema.getTypeMap(); 213 214 var breakingChanges = []; 215 Object.keys(oldTypeMap).forEach(function (typeName) { 216 var oldType = oldTypeMap[typeName]; 217 var newType = newTypeMap[typeName]; 218 if (!(isObjectType(oldType) || isInterfaceType(oldType)) || !(isObjectType(newType) || isInterfaceType(newType)) || newType.constructor !== oldType.constructor) { 219 return; 220 } 221 222 var oldTypeFieldsDef = oldType.getFields(); 223 var newTypeFieldsDef = newType.getFields(); 224 Object.keys(oldTypeFieldsDef).forEach(function (fieldName) { 225 // Check if the field is missing on the type in the new schema. 226 if (!(fieldName in newTypeFieldsDef)) { 227 breakingChanges.push({ 228 type: BreakingChangeType.FIELD_REMOVED, 229 description: typeName + '.' + fieldName + ' was removed.' 230 }); 231 } else { 232 var oldFieldType = oldTypeFieldsDef[fieldName].type; 233 var newFieldType = newTypeFieldsDef[fieldName].type; 234 var isSafe = isChangeSafeForObjectOrInterfaceField(oldFieldType, newFieldType); 235 if (!isSafe) { 236 var oldFieldTypeString = isNamedType(oldFieldType) ? oldFieldType.name : oldFieldType.toString(); 237 var newFieldTypeString = isNamedType(newFieldType) ? newFieldType.name : newFieldType.toString(); 238 breakingChanges.push({ 239 type: BreakingChangeType.FIELD_CHANGED_KIND, 240 description: typeName + '.' + fieldName + ' changed type from ' + (oldFieldTypeString + ' to ' + newFieldTypeString + '.') 241 }); 242 } 243 } 244 }); 245 }); 246 return breakingChanges; 247} 248 249export function findFieldsThatChangedTypeOnInputObjectTypes(oldSchema, newSchema) { 250 var oldTypeMap = oldSchema.getTypeMap(); 251 var newTypeMap = newSchema.getTypeMap(); 252 253 var breakingChanges = []; 254 var dangerousChanges = []; 255 Object.keys(oldTypeMap).forEach(function (typeName) { 256 var oldType = oldTypeMap[typeName]; 257 var newType = newTypeMap[typeName]; 258 if (!isInputObjectType(oldType) || !isInputObjectType(newType)) { 259 return; 260 } 261 262 var oldTypeFieldsDef = oldType.getFields(); 263 var newTypeFieldsDef = newType.getFields(); 264 Object.keys(oldTypeFieldsDef).forEach(function (fieldName) { 265 // Check if the field is missing on the type in the new schema. 266 if (!(fieldName in newTypeFieldsDef)) { 267 breakingChanges.push({ 268 type: BreakingChangeType.FIELD_REMOVED, 269 description: typeName + '.' + fieldName + ' was removed.' 270 }); 271 } else { 272 var oldFieldType = oldTypeFieldsDef[fieldName].type; 273 var newFieldType = newTypeFieldsDef[fieldName].type; 274 275 var isSafe = isChangeSafeForInputObjectFieldOrFieldArg(oldFieldType, newFieldType); 276 if (!isSafe) { 277 var oldFieldTypeString = isNamedType(oldFieldType) ? oldFieldType.name : oldFieldType.toString(); 278 var newFieldTypeString = isNamedType(newFieldType) ? newFieldType.name : newFieldType.toString(); 279 breakingChanges.push({ 280 type: BreakingChangeType.FIELD_CHANGED_KIND, 281 description: typeName + '.' + fieldName + ' changed type from ' + (oldFieldTypeString + ' to ' + newFieldTypeString + '.') 282 }); 283 } 284 } 285 }); 286 // Check if a field was added to the input object type 287 Object.keys(newTypeFieldsDef).forEach(function (fieldName) { 288 if (!(fieldName in oldTypeFieldsDef)) { 289 if (isNonNullType(newTypeFieldsDef[fieldName].type)) { 290 breakingChanges.push({ 291 type: BreakingChangeType.NON_NULL_INPUT_FIELD_ADDED, 292 description: 'A non-null field ' + fieldName + ' on ' + ('input type ' + newType.name + ' was added.') 293 }); 294 } else { 295 dangerousChanges.push({ 296 type: DangerousChangeType.NULLABLE_INPUT_FIELD_ADDED, 297 description: 'A nullable field ' + fieldName + ' on ' + ('input type ' + newType.name + ' was added.') 298 }); 299 } 300 } 301 }); 302 }); 303 return { 304 breakingChanges: breakingChanges, 305 dangerousChanges: dangerousChanges 306 }; 307} 308 309function isChangeSafeForObjectOrInterfaceField(oldType, newType) { 310 if (isNamedType(oldType)) { 311 return ( 312 // if they're both named types, see if their names are equivalent 313 isNamedType(newType) && oldType.name === newType.name || 314 // moving from nullable to non-null of the same underlying type is safe 315 isNonNullType(newType) && isChangeSafeForObjectOrInterfaceField(oldType, newType.ofType) 316 ); 317 } else if (isListType(oldType)) { 318 return ( 319 // if they're both lists, make sure the underlying types are compatible 320 isListType(newType) && isChangeSafeForObjectOrInterfaceField(oldType.ofType, newType.ofType) || 321 // moving from nullable to non-null of the same underlying type is safe 322 isNonNullType(newType) && isChangeSafeForObjectOrInterfaceField(oldType, newType.ofType) 323 ); 324 } else if (isNonNullType(oldType)) { 325 // if they're both non-null, make sure the underlying types are compatible 326 return isNonNullType(newType) && isChangeSafeForObjectOrInterfaceField(oldType.ofType, newType.ofType); 327 } 328 return false; 329} 330 331function isChangeSafeForInputObjectFieldOrFieldArg(oldType, newType) { 332 if (isNamedType(oldType)) { 333 // if they're both named types, see if their names are equivalent 334 return isNamedType(newType) && oldType.name === newType.name; 335 } else if (isListType(oldType)) { 336 // if they're both lists, make sure the underlying types are compatible 337 return isListType(newType) && isChangeSafeForInputObjectFieldOrFieldArg(oldType.ofType, newType.ofType); 338 } else if (isNonNullType(oldType)) { 339 return ( 340 // if they're both non-null, make sure the underlying types are 341 // compatible 342 isNonNullType(newType) && isChangeSafeForInputObjectFieldOrFieldArg(oldType.ofType, newType.ofType) || 343 // moving from non-null to nullable of the same underlying type is safe 344 !isNonNullType(newType) && isChangeSafeForInputObjectFieldOrFieldArg(oldType.ofType, newType) 345 ); 346 } 347 return false; 348} 349 350/** 351 * Given two schemas, returns an Array containing descriptions of any breaking 352 * changes in the newSchema related to removing types from a union type. 353 */ 354export function findTypesRemovedFromUnions(oldSchema, newSchema) { 355 var oldTypeMap = oldSchema.getTypeMap(); 356 var newTypeMap = newSchema.getTypeMap(); 357 358 var typesRemovedFromUnion = []; 359 Object.keys(oldTypeMap).forEach(function (typeName) { 360 var oldType = oldTypeMap[typeName]; 361 var newType = newTypeMap[typeName]; 362 if (!isUnionType(oldType) || !isUnionType(newType)) { 363 return; 364 } 365 var typeNamesInNewUnion = Object.create(null); 366 newType.getTypes().forEach(function (type) { 367 typeNamesInNewUnion[type.name] = true; 368 }); 369 oldType.getTypes().forEach(function (type) { 370 if (!typeNamesInNewUnion[type.name]) { 371 typesRemovedFromUnion.push({ 372 type: BreakingChangeType.TYPE_REMOVED_FROM_UNION, 373 description: type.name + ' was removed from union type ' + typeName + '.' 374 }); 375 } 376 }); 377 }); 378 return typesRemovedFromUnion; 379} 380 381/** 382 * Given two schemas, returns an Array containing descriptions of any dangerous 383 * changes in the newSchema related to adding types to a union type. 384 */ 385export function findTypesAddedToUnions(oldSchema, newSchema) { 386 var oldTypeMap = oldSchema.getTypeMap(); 387 var newTypeMap = newSchema.getTypeMap(); 388 389 var typesAddedToUnion = []; 390 Object.keys(newTypeMap).forEach(function (typeName) { 391 var oldType = oldTypeMap[typeName]; 392 var newType = newTypeMap[typeName]; 393 if (!isUnionType(oldType) || !isUnionType(newType)) { 394 return; 395 } 396 var typeNamesInOldUnion = Object.create(null); 397 oldType.getTypes().forEach(function (type) { 398 typeNamesInOldUnion[type.name] = true; 399 }); 400 newType.getTypes().forEach(function (type) { 401 if (!typeNamesInOldUnion[type.name]) { 402 typesAddedToUnion.push({ 403 type: DangerousChangeType.TYPE_ADDED_TO_UNION, 404 description: type.name + ' was added to union type ' + typeName + '.' 405 }); 406 } 407 }); 408 }); 409 return typesAddedToUnion; 410} 411/** 412 * Given two schemas, returns an Array containing descriptions of any breaking 413 * changes in the newSchema related to removing values from an enum type. 414 */ 415export function findValuesRemovedFromEnums(oldSchema, newSchema) { 416 var oldTypeMap = oldSchema.getTypeMap(); 417 var newTypeMap = newSchema.getTypeMap(); 418 419 var valuesRemovedFromEnums = []; 420 Object.keys(oldTypeMap).forEach(function (typeName) { 421 var oldType = oldTypeMap[typeName]; 422 var newType = newTypeMap[typeName]; 423 if (!isEnumType(oldType) || !isEnumType(newType)) { 424 return; 425 } 426 var valuesInNewEnum = Object.create(null); 427 newType.getValues().forEach(function (value) { 428 valuesInNewEnum[value.name] = true; 429 }); 430 oldType.getValues().forEach(function (value) { 431 if (!valuesInNewEnum[value.name]) { 432 valuesRemovedFromEnums.push({ 433 type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM, 434 description: value.name + ' was removed from enum type ' + typeName + '.' 435 }); 436 } 437 }); 438 }); 439 return valuesRemovedFromEnums; 440} 441 442/** 443 * Given two schemas, returns an Array containing descriptions of any dangerous 444 * changes in the newSchema related to adding values to an enum type. 445 */ 446export function findValuesAddedToEnums(oldSchema, newSchema) { 447 var oldTypeMap = oldSchema.getTypeMap(); 448 var newTypeMap = newSchema.getTypeMap(); 449 450 var valuesAddedToEnums = []; 451 Object.keys(oldTypeMap).forEach(function (typeName) { 452 var oldType = oldTypeMap[typeName]; 453 var newType = newTypeMap[typeName]; 454 if (!isEnumType(oldType) || !isEnumType(newType)) { 455 return; 456 } 457 458 var valuesInOldEnum = Object.create(null); 459 oldType.getValues().forEach(function (value) { 460 valuesInOldEnum[value.name] = true; 461 }); 462 newType.getValues().forEach(function (value) { 463 if (!valuesInOldEnum[value.name]) { 464 valuesAddedToEnums.push({ 465 type: DangerousChangeType.VALUE_ADDED_TO_ENUM, 466 description: value.name + ' was added to enum type ' + typeName + '.' 467 }); 468 } 469 }); 470 }); 471 return valuesAddedToEnums; 472} 473 474export function findInterfacesRemovedFromObjectTypes(oldSchema, newSchema) { 475 var oldTypeMap = oldSchema.getTypeMap(); 476 var newTypeMap = newSchema.getTypeMap(); 477 var breakingChanges = []; 478 479 Object.keys(oldTypeMap).forEach(function (typeName) { 480 var oldType = oldTypeMap[typeName]; 481 var newType = newTypeMap[typeName]; 482 if (!isObjectType(oldType) || !isObjectType(newType)) { 483 return; 484 } 485 486 var oldInterfaces = oldType.getInterfaces(); 487 var newInterfaces = newType.getInterfaces(); 488 oldInterfaces.forEach(function (oldInterface) { 489 if (!newInterfaces.some(function (int) { 490 return int.name === oldInterface.name; 491 })) { 492 breakingChanges.push({ 493 type: BreakingChangeType.INTERFACE_REMOVED_FROM_OBJECT, 494 description: typeName + ' no longer implements interface ' + (oldInterface.name + '.') 495 }); 496 } 497 }); 498 }); 499 return breakingChanges; 500} 501 502export function findInterfacesAddedToObjectTypes(oldSchema, newSchema) { 503 var oldTypeMap = oldSchema.getTypeMap(); 504 var newTypeMap = newSchema.getTypeMap(); 505 var interfacesAddedToObjectTypes = []; 506 507 Object.keys(newTypeMap).forEach(function (typeName) { 508 var oldType = oldTypeMap[typeName]; 509 var newType = newTypeMap[typeName]; 510 if (!isObjectType(oldType) || !isObjectType(newType)) { 511 return; 512 } 513 514 var oldInterfaces = oldType.getInterfaces(); 515 var newInterfaces = newType.getInterfaces(); 516 newInterfaces.forEach(function (newInterface) { 517 if (!oldInterfaces.some(function (int) { 518 return int.name === newInterface.name; 519 })) { 520 interfacesAddedToObjectTypes.push({ 521 type: DangerousChangeType.INTERFACE_ADDED_TO_OBJECT, 522 description: newInterface.name + ' added to interfaces implemented ' + ('by ' + typeName + '.') 523 }); 524 } 525 }); 526 }); 527 return interfacesAddedToObjectTypes; 528} 529 530export function findRemovedDirectives(oldSchema, newSchema) { 531 var removedDirectives = []; 532 533 var newSchemaDirectiveMap = getDirectiveMapForSchema(newSchema); 534 oldSchema.getDirectives().forEach(function (directive) { 535 if (!newSchemaDirectiveMap[directive.name]) { 536 removedDirectives.push({ 537 type: BreakingChangeType.DIRECTIVE_REMOVED, 538 description: directive.name + ' was removed' 539 }); 540 } 541 }); 542 543 return removedDirectives; 544} 545 546function findRemovedArgsForDirective(oldDirective, newDirective) { 547 var removedArgs = []; 548 var newArgMap = getArgumentMapForDirective(newDirective); 549 550 oldDirective.args.forEach(function (arg) { 551 if (!newArgMap[arg.name]) { 552 removedArgs.push(arg); 553 } 554 }); 555 556 return removedArgs; 557} 558 559export function findRemovedDirectiveArgs(oldSchema, newSchema) { 560 var removedDirectiveArgs = []; 561 var oldSchemaDirectiveMap = getDirectiveMapForSchema(oldSchema); 562 563 newSchema.getDirectives().forEach(function (newDirective) { 564 var oldDirective = oldSchemaDirectiveMap[newDirective.name]; 565 if (!oldDirective) { 566 return; 567 } 568 569 findRemovedArgsForDirective(oldDirective, newDirective).forEach(function (arg) { 570 removedDirectiveArgs.push({ 571 type: BreakingChangeType.DIRECTIVE_ARG_REMOVED, 572 description: arg.name + ' was removed from ' + newDirective.name 573 }); 574 }); 575 }); 576 577 return removedDirectiveArgs; 578} 579 580function findAddedArgsForDirective(oldDirective, newDirective) { 581 var addedArgs = []; 582 var oldArgMap = getArgumentMapForDirective(oldDirective); 583 584 newDirective.args.forEach(function (arg) { 585 if (!oldArgMap[arg.name]) { 586 addedArgs.push(arg); 587 } 588 }); 589 590 return addedArgs; 591} 592 593export function findAddedNonNullDirectiveArgs(oldSchema, newSchema) { 594 var addedNonNullableArgs = []; 595 var oldSchemaDirectiveMap = getDirectiveMapForSchema(oldSchema); 596 597 newSchema.getDirectives().forEach(function (newDirective) { 598 var oldDirective = oldSchemaDirectiveMap[newDirective.name]; 599 if (!oldDirective) { 600 return; 601 } 602 603 findAddedArgsForDirective(oldDirective, newDirective).forEach(function (arg) { 604 if (!isNonNullType(arg.type)) { 605 return; 606 } 607 608 addedNonNullableArgs.push({ 609 type: BreakingChangeType.NON_NULL_DIRECTIVE_ARG_ADDED, 610 description: 'A non-null arg ' + arg.name + ' on directive ' + (newDirective.name + ' was added') 611 }); 612 }); 613 }); 614 615 return addedNonNullableArgs; 616} 617 618export function findRemovedLocationsForDirective(oldDirective, newDirective) { 619 var removedLocations = []; 620 var newLocationSet = new Set(newDirective.locations); 621 622 oldDirective.locations.forEach(function (oldLocation) { 623 if (!newLocationSet.has(oldLocation)) { 624 removedLocations.push(oldLocation); 625 } 626 }); 627 628 return removedLocations; 629} 630 631export function findRemovedDirectiveLocations(oldSchema, newSchema) { 632 var removedLocations = []; 633 var oldSchemaDirectiveMap = getDirectiveMapForSchema(oldSchema); 634 635 newSchema.getDirectives().forEach(function (newDirective) { 636 var oldDirective = oldSchemaDirectiveMap[newDirective.name]; 637 if (!oldDirective) { 638 return; 639 } 640 641 findRemovedLocationsForDirective(oldDirective, newDirective).forEach(function (location) { 642 removedLocations.push({ 643 type: BreakingChangeType.DIRECTIVE_LOCATION_REMOVED, 644 description: location + ' was removed from ' + newDirective.name 645 }); 646 }); 647 }); 648 649 return removedLocations; 650} 651 652function getDirectiveMapForSchema(schema) { 653 return keyMap(schema.getDirectives(), function (dir) { 654 return dir.name; 655 }); 656} 657 658function getArgumentMapForDirective(directive) { 659 return keyMap(directive.args, function (arg) { 660 return arg.name; 661 }); 662}