@apollo/federation
Advanced tools
Comparing version 0.33.8 to 0.33.9
@@ -47,4 +47,6 @@ import { StringValueNode, NameNode, DocumentNode, DirectiveNode, GraphQLNamedType, GraphQLError, GraphQLSchema, GraphQLObjectType, GraphQLField, TypeDefinitionNode, TypeExtensionNode, ASTNode, DirectiveDefinitionNode, GraphQLDirective, OperationTypeNode, NonNullTypeNode, NamedTypeNode } from 'graphql'; | ||
}; | ||
inputValues: { | ||
[inputName: string]: string[]; | ||
fieldArgs: { | ||
[fieldName: string]: { | ||
[argumentName: string]: string[]; | ||
}; | ||
}; | ||
@@ -55,3 +57,3 @@ unionTypes: { | ||
locations: string[]; | ||
args: { | ||
directiveArgs: { | ||
[argumentName: string]: string[]; | ||
@@ -58,0 +60,0 @@ }; |
@@ -224,6 +224,6 @@ "use strict"; | ||
const fieldsDiff = Object.create(null); | ||
const inputValuesDiff = Object.create(null); | ||
const unionTypesDiff = Object.create(null); | ||
const locationsDiff = new Set(); | ||
const argumentsDiff = Object.create(null); | ||
const fieldArgsDiff = Object.create(null); | ||
const directiveArgsDiff = Object.create(null); | ||
const document = { | ||
@@ -233,3 +233,3 @@ kind: graphql_1.Kind.DOCUMENT, | ||
}; | ||
function fieldVisitor(node) { | ||
function inputFieldVisitor(node) { | ||
const fieldName = node.name.value; | ||
@@ -249,20 +249,48 @@ const type = (0, graphql_1.print)(node.type); | ||
} | ||
function inputValueVisitor(node) { | ||
function fieldVisitor(node) { | ||
const fieldName = node.name.value; | ||
const type = (0, graphql_1.print)(node.type); | ||
if (!inputValuesDiff[fieldName]) { | ||
inputValuesDiff[fieldName] = [type]; | ||
return; | ||
if (!fieldsDiff[fieldName]) { | ||
fieldsDiff[fieldName] = [type]; | ||
} | ||
const inputValueTypes = inputValuesDiff[fieldName]; | ||
if (inputValueTypes[0] === type) { | ||
delete inputValuesDiff[fieldName]; | ||
} | ||
else { | ||
inputValueTypes.push(type); | ||
const fieldTypes = fieldsDiff[fieldName]; | ||
if (fieldTypes[0] === type) { | ||
delete fieldsDiff[fieldName]; | ||
} | ||
else { | ||
fieldTypes.push(type); | ||
} | ||
} | ||
if (!fieldArgsDiff[fieldName]) { | ||
fieldArgsDiff[fieldName] = Object.create(null); | ||
} | ||
const argumentsDiff = fieldArgsDiff[fieldName]; | ||
const nodeArgs = Array.isArray(node.arguments) ? node.arguments : []; | ||
nodeArgs.forEach(argument => { | ||
const argumentName = argument.name.value; | ||
const printedType = (0, graphql_1.print)(argument.type); | ||
if (!argumentsDiff[argumentName]) { | ||
argumentsDiff[argumentName] = [printedType]; | ||
} | ||
else { | ||
if (printedType === argumentsDiff[argumentName][0]) { | ||
delete argumentsDiff[argumentName]; | ||
} | ||
else { | ||
argumentsDiff[argumentName].push(printedType); | ||
} | ||
} | ||
}); | ||
if (Object.keys(argumentsDiff).length === 0) { | ||
delete fieldArgsDiff[fieldName]; | ||
} | ||
} | ||
(0, graphql_1.visit)(document, { | ||
FieldDefinition: fieldVisitor, | ||
InputValueDefinition: inputValueVisitor, | ||
InputObjectTypeDefinition(node) { | ||
if (Array.isArray(node.fields)) { | ||
node.fields.forEach(inputFieldVisitor); | ||
} | ||
}, | ||
UnionTypeDefinition(node) { | ||
@@ -296,12 +324,12 @@ if (!node.types) | ||
const printedType = (0, graphql_1.print)(argument.type); | ||
if (argumentsDiff[argumentName]) { | ||
if (printedType === argumentsDiff[argumentName][0]) { | ||
delete argumentsDiff[argumentName]; | ||
if (directiveArgsDiff[argumentName]) { | ||
if (printedType === directiveArgsDiff[argumentName][0]) { | ||
delete directiveArgsDiff[argumentName]; | ||
} | ||
else { | ||
argumentsDiff[argumentName].push(printedType); | ||
directiveArgsDiff[argumentName].push(printedType); | ||
} | ||
} | ||
else { | ||
argumentsDiff[argumentName] = [printedType]; | ||
directiveArgsDiff[argumentName] = [printedType]; | ||
} | ||
@@ -319,6 +347,6 @@ }); | ||
fields: fieldsDiff, | ||
inputValues: inputValuesDiff, | ||
fieldArgs: fieldArgsDiff, | ||
unionTypes: unionTypesDiff, | ||
locations: Array.from(locationsDiff), | ||
args: argumentsDiff, | ||
directiveArgs: directiveArgsDiff | ||
}; | ||
@@ -328,10 +356,10 @@ } | ||
function typeNodesAreEquivalent(firstNode, secondNode) { | ||
const { name, kind, fields, inputValues, unionTypes, locations, args } = diffTypeNodes(firstNode, secondNode); | ||
const { name, kind, fields, fieldArgs, unionTypes, locations, directiveArgs } = diffTypeNodes(firstNode, secondNode); | ||
return (name.length === 0 && | ||
kind.length === 0 && | ||
Object.keys(fields).length === 0 && | ||
Object.keys(inputValues).length === 0 && | ||
Object.keys(fieldArgs).length === 0 && | ||
Object.keys(unionTypes).length === 0 && | ||
locations.length === 0 && | ||
Object.keys(args).length === 0); | ||
Object.keys(directiveArgs).length === 0); | ||
} | ||
@@ -338,0 +366,0 @@ exports.typeNodesAreEquivalent = typeNodesAreEquivalent; |
@@ -61,10 +61,13 @@ "use strict"; | ||
if (duplicateTypeNode) { | ||
const { fields, inputValues } = (0, utils_1.diffTypeNodes)(node, duplicateTypeNode); | ||
const { fields, fieldArgs } = (0, utils_1.diffTypeNodes)(node, duplicateTypeNode); | ||
if (Object.values(fields).every(diffEntry => diffEntry.length === 2)) { | ||
return false; | ||
} | ||
const inputValuesTypes = Object.values(inputValues); | ||
if (inputValuesTypes.length > 0 && | ||
inputValuesTypes.every((diffEntry) => diffEntry.length === 2)) { | ||
return false; | ||
const fieldArgDiffs = Object.values(fieldArgs); | ||
for (const argDiff of fieldArgDiffs) { | ||
const argTypes = Object.values(argDiff); | ||
if (argTypes.length > 0 && | ||
argTypes.every((diffEntry) => diffEntry.length === 2)) { | ||
return false; | ||
} | ||
} | ||
@@ -71,0 +74,0 @@ } |
@@ -34,3 +34,3 @@ "use strict"; | ||
const possibleErrors = []; | ||
const { kind, fields, inputValues } = (0, utils_1.diffTypeNodes)(node, duplicateTypeNode); | ||
const { kind, fields, fieldArgs } = (0, utils_1.diffTypeNodes)(node, duplicateTypeNode); | ||
const fieldsDiff = Object.entries(fields); | ||
@@ -52,10 +52,14 @@ if (kind.length > 0) { | ||
}); | ||
const inputValuesDiff = Object.entries(inputValues); | ||
const typesHaveSameInputValuesShape = inputValuesDiff.length === 0 || | ||
inputValuesDiff.every(([name, types]) => { | ||
if (types.length === 2) { | ||
possibleErrors.push((0, utils_1.errorWithCode)('VALUE_TYPE_INPUT_VALUE_MISMATCH', `${(0, utils_1.logServiceAndType)(duplicateTypeNode.serviceName, typeName)}A field's input type (\`${name}\`) was defined differently in different services. \`${duplicateTypeNode.serviceName}\` and \`${node.serviceName}\` define \`${name}\` as a ${types[1]} and ${types[0]} respectively. In order to define \`${typeName}\` in multiple places, the input values and their types must be identical.`, [node, duplicateTypeNode])); | ||
return true; | ||
} | ||
return false; | ||
const fieldArgDiffs = Object.values(fieldArgs); | ||
const typesHaveSameInputValuesShape = fieldArgDiffs.length === 0 || | ||
fieldArgDiffs.every((fieldArgDiff) => { | ||
const argTypeDiff = Object.entries(fieldArgDiff); | ||
return (argTypeDiff.length === 0 || | ||
argTypeDiff.every(([argName, types]) => { | ||
if (types.length === 2) { | ||
possibleErrors.push((0, utils_1.errorWithCode)('VALUE_TYPE_INPUT_VALUE_MISMATCH', `${(0, utils_1.logServiceAndType)(duplicateTypeNode.serviceName, typeName)}A field's input type (\`${argName}\`) was defined differently in different services. \`${duplicateTypeNode.serviceName}\` and \`${node.serviceName}\` define \`${argName}\` as a ${types[1]} and ${types[0]} respectively. In order to define \`${typeName}\` in multiple places, the input values and their types must be identical.`, [node, duplicateTypeNode])); | ||
return true; | ||
} | ||
return false; | ||
})); | ||
}); | ||
@@ -62,0 +66,0 @@ if (typesHaveSameFieldShape && typesHaveSameInputValuesShape) { |
{ | ||
"name": "@apollo/federation", | ||
"version": "0.33.8", | ||
"version": "0.33.9", | ||
"description": "Apollo Federation Utilities", | ||
@@ -34,3 +34,3 @@ "main": "dist/index.js", | ||
}, | ||
"gitHead": "caa378c31a136423f463bb1b5b83064671f0be7a" | ||
"gitHead": "1fbfe974a90823af37c908613652e8acf32ea572" | ||
} |
import { fixtures } from 'apollo-federation-integration-testsuite'; | ||
import { getJoinDefinitions } from "../joinSpec"; | ||
const questionableNamesRemap = { | ||
const questionableNamesRemap: Record<string, string> = { | ||
accounts: 'ServiceA', | ||
@@ -6,0 +6,0 @@ books: 'serviceA', |
import gql from 'graphql-tag'; | ||
import deepFreeze from 'deep-freeze'; | ||
import { stripExternalFieldsFromTypeDefs } from '../utils'; | ||
import { TypeDefinitionNode } from 'graphql'; | ||
import { stripExternalFieldsFromTypeDefs, diffTypeNodes } from '../utils'; | ||
import { astSerializer } from 'apollo-federation-integration-testsuite'; | ||
@@ -9,2 +10,576 @@ | ||
describe('Composition utility functions', () => { | ||
describe('diffTypeNodes', () => { | ||
it('should produce an empty diff for the same type', () => { | ||
const typeDefs = gql` | ||
type Product { | ||
name: String | ||
} | ||
`; | ||
const def = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const result = diffTypeNodes(def, def); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on two types with different names', () => { | ||
const typeDefs = gql` | ||
type A | ||
type B | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: ['A','B'], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on two different types', () => { | ||
const typeDefs = gql` | ||
type A | ||
input A | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: ['ObjectTypeDefinition', 'InputObjectTypeDefinition'], | ||
fields: {}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on types with different field names', () => { | ||
const typeDefs = gql` | ||
type A { | ||
x: String | ||
} | ||
type A { | ||
y: String | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: { | ||
x: ['String'], | ||
y: ['String'] | ||
}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on types with different field types', () => { | ||
const typeDefs = gql` | ||
type A { | ||
x: String | ||
} | ||
type A { | ||
x: String! | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: { | ||
x: ['String','String!'], | ||
}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on types with different field arguments', () => { | ||
const typeDefs = gql` | ||
type A { | ||
x(i: Int): String | ||
} | ||
type A { | ||
x(j: Int): String | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: { | ||
x: { | ||
i: ['Int'], | ||
j: ['Int'] | ||
} | ||
}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on types with different number of arguments', () => { | ||
const typeDefs = gql` | ||
type A { | ||
x(i: Int, k: Int): String | ||
} | ||
type A { | ||
x(i: Int): String | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: { | ||
x: { | ||
k: ['Int'] | ||
} | ||
}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on types with different field arguments', () => { | ||
const typeDefs = gql` | ||
type A { | ||
x(i: Int): String | ||
} | ||
type A { | ||
x(j: Int): String | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: { | ||
x: { | ||
i: ['Int'], | ||
j: ['Int'] | ||
} | ||
}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on types with field arguments of different types', () => { | ||
const typeDefs = gql` | ||
type A { | ||
x(i: Int): String | ||
} | ||
type A { | ||
x(i: Int!): String | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: { | ||
x: { | ||
i: ['Int','Int!'] | ||
} | ||
}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on inputs with different field names', () => { | ||
const typeDefs = gql` | ||
input A { | ||
x: String | ||
} | ||
input A { | ||
y: String | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: { | ||
x: ['String'], | ||
y: ['String'] | ||
}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on inputs with different field types', () => { | ||
const typeDefs = gql` | ||
input A { | ||
x: String | ||
} | ||
input A { | ||
x: String! | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: { | ||
x: ['String','String!'], | ||
}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on interfaces with different field names', () => { | ||
const typeDefs = gql` | ||
interface A { | ||
x: String | ||
} | ||
interface A { | ||
y: String | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: { | ||
x: ['String'], | ||
y: ['String'] | ||
}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on interface with different field types', () => { | ||
const typeDefs = gql` | ||
interface A { | ||
x: String | ||
} | ||
interface A { | ||
x: String! | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: { | ||
x: ['String','String!'], | ||
}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on interface with field with different arguments', () => { | ||
const typeDefs = gql` | ||
interface A { | ||
x(i: Int): String | ||
} | ||
interface A { | ||
x(j: Int): String | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: { | ||
x: { | ||
i: ['Int'], | ||
j: ['Int'] | ||
} | ||
}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on interface with field with different argument types', () => { | ||
const typeDefs = gql` | ||
interface A { | ||
x(i: Int): String | ||
} | ||
interface A { | ||
x(i: Int!): String | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: { | ||
x: { | ||
i: ['Int','Int!'] | ||
} | ||
}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on interface with field with different number of arguments', () => { | ||
const typeDefs = gql` | ||
interface A { | ||
x(i: Int): String | ||
} | ||
interface A { | ||
x(i: Int, j: String): String | ||
} | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: { | ||
x: { | ||
j: ['String'] | ||
} | ||
}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on two directives with different locations', () => { | ||
const typeDefs = gql` | ||
directive @custom on FIELD | ENUM | ||
directive @custom on FIELD | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [ | ||
'ENUM' | ||
], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
it('should produce a diff on two directives with different arguments', () => { | ||
const typeDefs = gql` | ||
directive @custom(a: String!) on FIELD | ||
directive @custom(b: String!) on FIELD | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: { | ||
a: ['String!'], | ||
b: ['String!'] | ||
} | ||
}); | ||
}); | ||
it('should produce a diff on two directives with different argument types', () => { | ||
const typeDefs = gql` | ||
directive @custom(a: String!) on FIELD | ||
directive @custom(a: String) on FIELD | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: { | ||
a: ['String!','String'] | ||
} | ||
}); | ||
}); | ||
it('should produce a diff on two directives with different number of arguments', () => { | ||
const typeDefs = gql` | ||
directive @custom(a: String!) on FIELD | ||
directive @custom(a: String!, b: Int) on FIELD | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: {}, | ||
unionTypes: {}, | ||
locations: [], | ||
directiveArgs: { | ||
b: ['Int'] | ||
} | ||
}); | ||
}); | ||
it('should produce a diff for difference union types', () => { | ||
const typeDefs = gql` | ||
union UnionA = A | B | ||
union UnionA = B | C | ||
`; | ||
const defA = typeDefs.definitions[0] as TypeDefinitionNode; | ||
const defB = typeDefs.definitions[1] as TypeDefinitionNode; | ||
const result = diffTypeNodes(defA, defB); | ||
expect(result).toEqual({ | ||
name: [], | ||
kind: [], | ||
fields: {}, | ||
fieldArgs: {}, | ||
unionTypes: { | ||
A: true, | ||
C: true | ||
}, | ||
locations: [], | ||
directiveArgs: {} | ||
}); | ||
}); | ||
}); | ||
describe('stripExternalFieldsFromTypeDefs', () => { | ||
@@ -11,0 +586,0 @@ it('returns a new DocumentNode with @external fields removed as well as information about the removed fields', () => { |
@@ -21,2 +21,3 @@ import { | ||
TypeDefinitionNode, | ||
InputObjectTypeDefinitionNode, | ||
InputValueDefinitionNode, | ||
@@ -465,2 +466,4 @@ TypeExtensionNode, | ||
) { | ||
// The fieldsDiff diff entries will contain both object type definitions | ||
// differences, and input object type definitions differences | ||
const fieldsDiff: { | ||
@@ -470,6 +473,2 @@ [fieldName: string]: string[]; | ||
const inputValuesDiff: { | ||
[inputName: string]: string[]; | ||
} = Object.create(null); | ||
const unionTypesDiff: { | ||
@@ -481,3 +480,15 @@ [typeName: string]: boolean; | ||
const argumentsDiff: { | ||
// Arguments need to be compared on per-field basis, so that arguments from | ||
// one field don't get conflated with arguments from a different field. Here | ||
// we setup an object-of-objects, where each top-level key is the field name, | ||
// and sub-keys are the argument name. | ||
const fieldArgsDiff: { | ||
[fieldName: string]: { | ||
[argumentName: string]: string[]; | ||
} | ||
} = Object.create(null); | ||
// Since directive arguments aren't field-scoped, we keep a diff of them | ||
// separate from the field arguments diff. | ||
const directiveArgsDiff: { | ||
[argumentName: string]: string[]; | ||
@@ -491,3 +502,6 @@ } = Object.create(null); | ||
function fieldVisitor(node: FieldDefinitionNode) { | ||
// Input objects fields aren't allowed to have arguments, | ||
// so we use a simpler version of `fieldVisitor` that | ||
// only looks at the name and types of the fields. | ||
function inputFieldVisitor(node: InputValueDefinitionNode) { | ||
const fieldName = node.name.value; | ||
@@ -502,4 +516,2 @@ | ||
// If we've seen this field twice and the types are the same, remove this | ||
// field from the diff result | ||
const fieldTypes = fieldsDiff[fieldName]; | ||
@@ -513,24 +525,60 @@ if (fieldTypes[0] === type) { | ||
/** Similar to fieldVisitor but specific for input values, so we don't store | ||
* fields and arguments in the same place. | ||
*/ | ||
function inputValueVisitor(node: InputValueDefinitionNode) { | ||
function fieldVisitor(node: FieldDefinitionNode) { | ||
const fieldName = node.name.value; | ||
const type = print(node.type); | ||
if (!inputValuesDiff[fieldName]) { | ||
inputValuesDiff[fieldName] = [type]; | ||
return; | ||
if (!fieldsDiff[fieldName]) { | ||
// If the field has no entry in the diff, it means we've never encountered | ||
// the field before. We'll add the field and type into our diff object | ||
fieldsDiff[fieldName] = [type]; | ||
} else { | ||
// If the field has already been seen, we need to compare the previously | ||
// recorded type with the current type. If the types match, we'll remove | ||
// the diff entry, to signal they are equivalent. Otherwise, we'll append | ||
// the newly recorded type into the array so it can be reported later. | ||
const fieldTypes = fieldsDiff[fieldName]; | ||
if (fieldTypes[0] === type) { | ||
delete fieldsDiff[fieldName]; | ||
} else { | ||
fieldTypes.push(type); | ||
} | ||
} | ||
// If we've seen this input value twice and the types are the same, | ||
// remove it from the diff result | ||
const inputValueTypes = inputValuesDiff[fieldName]; | ||
if (inputValueTypes[0] === type) { | ||
delete inputValuesDiff[fieldName]; | ||
} else { | ||
inputValueTypes.push(type); | ||
// Each field may have 1 or more arguments, and those arguments need to compared | ||
// on a per-field basis, so that arguments from 1 field aren't conflated with | ||
// arguments from other fields. Here we're setting the empty field-specific | ||
// diff object to track the arguments by name. | ||
if (!fieldArgsDiff[fieldName]) { | ||
fieldArgsDiff[fieldName] = Object.create(null); | ||
} | ||
const argumentsDiff = fieldArgsDiff[fieldName]; | ||
const nodeArgs = Array.isArray(node.arguments) ? node.arguments : []; | ||
nodeArgs.forEach(argument => { | ||
const argumentName = argument.name.value; | ||
const printedType = print(argument.type); | ||
if (!argumentsDiff[argumentName]) { | ||
// If this argument has never been seen on this field, we keep track of it's | ||
// name and type so it can be compared later. | ||
argumentsDiff[argumentName] = [printedType]; | ||
} else { | ||
// If the argument name has already been seen on this field, we need to compare | ||
// the types of the argument. If the types match, we remove the argument name | ||
// from the diff object, to signal they are equivalent. If they types don't match, | ||
// we append the type of the current field so they can be reported later | ||
if (printedType === argumentsDiff[argumentName][0]) { | ||
delete argumentsDiff[argumentName]; | ||
} else { | ||
argumentsDiff[argumentName].push(printedType); | ||
} | ||
} | ||
}); | ||
// If there are no entries in the arguments diff object for this specific field, | ||
// it means either the field had no arguments, or the arguments were equivalent. | ||
if (Object.keys(argumentsDiff).length === 0) { | ||
delete fieldArgsDiff[fieldName]; | ||
} | ||
} | ||
@@ -540,3 +588,7 @@ | ||
FieldDefinition: fieldVisitor, | ||
InputValueDefinition: inputValueVisitor, | ||
InputObjectTypeDefinition(node: InputObjectTypeDefinitionNode) { | ||
if (Array.isArray(node.fields)) { | ||
node.fields.forEach(inputFieldVisitor) | ||
} | ||
}, | ||
UnionTypeDefinition(node) { | ||
@@ -574,12 +626,12 @@ if (!node.types) return BREAK; | ||
const printedType = print(argument.type); | ||
if (argumentsDiff[argumentName]) { | ||
if (printedType === argumentsDiff[argumentName][0]) { | ||
if (directiveArgsDiff[argumentName]) { | ||
if (printedType === directiveArgsDiff[argumentName][0]) { | ||
// If the existing entry is equal to printedType, it means there's no | ||
// diff, so we can remove the entry from the diff object | ||
delete argumentsDiff[argumentName]; | ||
delete directiveArgsDiff[argumentName]; | ||
} else { | ||
argumentsDiff[argumentName].push(printedType); | ||
directiveArgsDiff[argumentName].push(printedType); | ||
} | ||
} else { | ||
argumentsDiff[argumentName] = [printedType]; | ||
directiveArgsDiff[argumentName] = [printedType]; | ||
} | ||
@@ -602,6 +654,6 @@ }); | ||
fields: fieldsDiff, | ||
inputValues: inputValuesDiff, | ||
fieldArgs: fieldArgsDiff, | ||
unionTypes: unionTypesDiff, | ||
locations: Array.from(locationsDiff), | ||
args: argumentsDiff, | ||
directiveArgs: directiveArgsDiff | ||
}; | ||
@@ -620,3 +672,3 @@ } | ||
) { | ||
const { name, kind, fields, inputValues, unionTypes, locations, args } = diffTypeNodes( | ||
const { name, kind, fields, fieldArgs, unionTypes, locations, directiveArgs } = diffTypeNodes( | ||
firstNode, | ||
@@ -630,6 +682,6 @@ secondNode, | ||
Object.keys(fields).length === 0 && | ||
Object.keys(inputValues).length === 0 && | ||
Object.keys(fieldArgs).length === 0 && | ||
Object.keys(unionTypes).length === 0 && | ||
locations.length === 0 && | ||
Object.keys(args).length === 0 | ||
Object.keys(directiveArgs).length === 0 | ||
); | ||
@@ -636,0 +688,0 @@ } |
@@ -153,3 +153,3 @@ import { | ||
if (duplicateTypeNode) { | ||
const { fields, inputValues } = diffTypeNodes(node, duplicateTypeNode); | ||
const { fields, fieldArgs } = diffTypeNodes(node, duplicateTypeNode); | ||
@@ -164,10 +164,11 @@ // This is the condition required for a *near* value type. At this point, we know the | ||
// not all types might have input values, we only want to check the diff if there's any | ||
const inputValuesTypes = Object.values(inputValues); | ||
if ( | ||
inputValuesTypes.length > 0 && | ||
inputValuesTypes.every((diffEntry) => diffEntry.length === 2) | ||
) { | ||
return false; | ||
const fieldArgDiffs = Object.values(fieldArgs); | ||
for (const argDiff of fieldArgDiffs) { | ||
const argTypes = Object.values(argDiff); | ||
if ( | ||
argTypes.length > 0 && | ||
argTypes.every((diffEntry) => diffEntry.length === 2) | ||
) { | ||
return false; | ||
} | ||
} | ||
@@ -174,0 +175,0 @@ } else { |
@@ -70,3 +70,3 @@ import { | ||
// i.e. { sku: [Int, String!], color: [String] } | ||
const { kind, fields, inputValues } = diffTypeNodes(node, duplicateTypeNode); | ||
const { kind, fields, fieldArgs } = diffTypeNodes(node, duplicateTypeNode); | ||
@@ -130,32 +130,37 @@ const fieldsDiff = Object.entries(fields); | ||
const inputValuesDiff = Object.entries(inputValues); | ||
const fieldArgDiffs = Object.values(fieldArgs); | ||
const typesHaveSameInputValuesShape = | ||
inputValuesDiff.length === 0 || | ||
inputValuesDiff.every(([name, types]) => { | ||
if (types.length === 2) { | ||
possibleErrors.push( | ||
errorWithCode( | ||
'VALUE_TYPE_INPUT_VALUE_MISMATCH', | ||
`${logServiceAndType( | ||
duplicateTypeNode.serviceName!, | ||
typeName, | ||
)}A field's input type (\`${name}\`) was defined differently in different services. \`${ | ||
duplicateTypeNode.serviceName | ||
}\` and \`${ | ||
node.serviceName | ||
}\` define \`${name}\` as a ${types[1]} and ${ | ||
types[0] | ||
} respectively. In order to define \`${typeName}\` in multiple places, the input values and their types must be identical.`, | ||
// TODO (Issue #705): when we can associate locations to service names | ||
// add locations for each service where this field was defined | ||
[node, duplicateTypeNode], | ||
), | ||
); | ||
return true; | ||
} | ||
return false; | ||
fieldArgDiffs.length === 0 || | ||
fieldArgDiffs.every((fieldArgDiff) => { | ||
const argTypeDiff = Object.entries(fieldArgDiff); | ||
return ( | ||
argTypeDiff.length === 0 || | ||
argTypeDiff.every(([argName, types]) => { | ||
if (types.length === 2) { | ||
possibleErrors.push( | ||
errorWithCode( | ||
'VALUE_TYPE_INPUT_VALUE_MISMATCH', | ||
`${logServiceAndType( | ||
duplicateTypeNode.serviceName!, | ||
typeName, | ||
)}A field's input type (\`${argName}\`) was defined differently in different services. \`${ | ||
duplicateTypeNode.serviceName | ||
}\` and \`${ | ||
node.serviceName | ||
}\` define \`${argName}\` as a ${types[1]} and ${ | ||
types[0] | ||
} respectively. In order to define \`${typeName}\` in multiple places, the input values and their types must be identical.`, | ||
// TODO (Issue #705): when we can associate locations to service names | ||
// add locations for each service where this field was defined | ||
[node, duplicateTypeNode], | ||
), | ||
); | ||
return true; | ||
} | ||
return false; | ||
}) | ||
); | ||
}); | ||
// Once we determined that types have the same shape (name, kind, and field | ||
@@ -162,0 +167,0 @@ // names), we can provide useful errors |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17062
763110
284