-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2829 from apollographql/jake/fed-duplicate-enums
Handle duplicate enums across services
- Loading branch information
Showing
13 changed files
with
874 additions
and
205 deletions.
There are no files selected for viewing
353 changes: 156 additions & 197 deletions
353
packages/apollo-federation/src/composition/__tests__/compose.test.ts
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,16 @@ | ||
import { specifiedSDLRules } from 'graphql/validation/specifiedRules'; | ||
|
||
export const compositionRules = specifiedSDLRules.filter( | ||
rule => rule.name !== 'UniqueDirectivesPerLocation', | ||
); | ||
import { | ||
UniqueTypeNamesWithoutEnumsOrScalars, | ||
MatchingEnums, | ||
} from './validate/sdl'; | ||
|
||
const omit = [ | ||
'UniqueDirectivesPerLocation', | ||
'UniqueTypeNames', | ||
'UniqueEnumValueNames', | ||
]; | ||
|
||
export const compositionRules = specifiedSDLRules | ||
.filter(rule => !omit.includes(rule.name)) | ||
.concat([UniqueTypeNamesWithoutEnumsOrScalars, MatchingEnums]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
...ederation/src/composition/validate/preComposition/__tests__/duplicateEnumOrScalar.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import gql from 'graphql-tag'; | ||
import { duplicateEnumOrScalar as validateDuplicateEnumOrScalar } from '../'; | ||
import { graphqlErrorSerializer } from '../../../../snapshotSerializers'; | ||
|
||
expect.addSnapshotSerializer(graphqlErrorSerializer); | ||
|
||
describe('duplicateEnumOrScalar', () => { | ||
it('does not error with proper enum and scalar usage', () => { | ||
const serviceA = { | ||
typeDefs: gql` | ||
type Product @key(fields: "color { id value }") { | ||
sku: String! | ||
upc: String! | ||
shippingDate: Date | ||
type: ProductType | ||
} | ||
enum ProductType { | ||
BOOK | ||
FURNITURE | ||
} | ||
extend enum ProductType { | ||
DIGITAL | ||
} | ||
scalar Date | ||
`, | ||
name: 'serviceA', | ||
}; | ||
|
||
const warnings = validateDuplicateEnumOrScalar(serviceA); | ||
expect(warnings).toEqual([]); | ||
}); | ||
it('errors when there are multiple definitions of the same enum', () => { | ||
const serviceA = { | ||
typeDefs: gql` | ||
type Product @key(fields: "color { id value }") { | ||
sku: String! | ||
upc: String! | ||
color: Color! | ||
} | ||
type Color { | ||
id: ID! | ||
value: String! | ||
} | ||
enum ProductType { | ||
BOOK | ||
FURNITURE | ||
} | ||
enum ProductType { | ||
DIGITAL | ||
} | ||
`, | ||
name: 'serviceA', | ||
}; | ||
|
||
const warnings = validateDuplicateEnumOrScalar(serviceA); | ||
expect(warnings).toMatchInlineSnapshot(` | ||
Array [ | ||
Object { | ||
"code": "DUPLICATE_ENUM_DEFINITION", | ||
"message": "[serviceA] ProductType -> The enum, \`ProductType\` was defined multiple times in this service. Remove one of the definitions for \`ProductType\`", | ||
}, | ||
] | ||
`); | ||
}); | ||
|
||
it('errors when there are multiple definitions of the same scalar', () => { | ||
const serviceA = { | ||
typeDefs: gql` | ||
scalar Date | ||
type Product @key(fields: "color { id value }") { | ||
sku: String! | ||
upc: String! | ||
deliveryDate: Date | ||
} | ||
scalar Date | ||
`, | ||
name: 'serviceA', | ||
}; | ||
|
||
const warnings = validateDuplicateEnumOrScalar(serviceA); | ||
expect(warnings).toMatchInlineSnapshot(` | ||
Array [ | ||
Object { | ||
"code": "DUPLICATE_SCALAR_DEFINITION", | ||
"message": "[serviceA] Date -> The scalar, \`Date\` was defined multiple times in this service. Remove one of the definitions for \`Date\`", | ||
}, | ||
] | ||
`); | ||
}); | ||
}); |
74 changes: 74 additions & 0 deletions
74
...o-federation/src/composition/validate/preComposition/__tests__/duplicateEnumValue.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import gql from 'graphql-tag'; | ||
import { duplicateEnumValue as validateDuplicateEnumValue } from '../'; | ||
import { graphqlErrorSerializer } from '../../../../snapshotSerializers'; | ||
|
||
expect.addSnapshotSerializer(graphqlErrorSerializer); | ||
|
||
describe('duplicateEnumValue', () => { | ||
it('does not error with proper enum usage', () => { | ||
const serviceA = { | ||
typeDefs: gql` | ||
type Product @key(fields: "color { id value }") { | ||
sku: String! | ||
upc: String! | ||
color: Color! | ||
} | ||
type Color { | ||
id: ID! | ||
value: String! | ||
} | ||
enum ProductType { | ||
BOOK | ||
FURNITURE | ||
} | ||
extend enum ProductType { | ||
DIGITAL | ||
} | ||
`, | ||
name: 'serviceA', | ||
}; | ||
|
||
const warnings = validateDuplicateEnumValue(serviceA); | ||
expect(warnings).toEqual([]); | ||
}); | ||
it('errors when there are duplicate enum values in a single service', () => { | ||
const serviceA = { | ||
typeDefs: gql` | ||
type Product @key(fields: "color { id value }") { | ||
sku: String! | ||
upc: String! | ||
color: Color! | ||
} | ||
type Color { | ||
id: ID! | ||
value: String! | ||
} | ||
enum ProductType { | ||
BOOK | ||
FURNITURE | ||
} | ||
extend enum ProductType { | ||
DIGITAL | ||
BOOK | ||
} | ||
`, | ||
name: 'serviceA', | ||
}; | ||
|
||
const warnings = validateDuplicateEnumValue(serviceA); | ||
expect(warnings).toMatchInlineSnapshot(` | ||
Array [ | ||
Object { | ||
"code": "DUPLICATE_ENUM_VALUE", | ||
"message": "[serviceA] ProductType.BOOK -> The enum, \`ProductType\` has multiple definitions of the \`BOOK\` value.", | ||
}, | ||
] | ||
`); | ||
}); | ||
}); |
50 changes: 50 additions & 0 deletions
50
packages/apollo-federation/src/composition/validate/preComposition/duplicateEnumOrScalar.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { visit, GraphQLError } from 'graphql'; | ||
import { ServiceDefinition } from '../../types'; | ||
|
||
import { logServiceAndType, errorWithCode } from '../../utils'; | ||
|
||
export const duplicateEnumOrScalar = ({ | ||
name: serviceName, | ||
typeDefs, | ||
}: ServiceDefinition) => { | ||
const errors: GraphQLError[] = []; | ||
|
||
// keep track of every enum and scalar and error if there are ever duplicates | ||
const enums: string[] = []; | ||
const scalars: string[] = []; | ||
|
||
visit(typeDefs, { | ||
EnumTypeDefinition(definition) { | ||
const name = definition.name.value; | ||
if (enums.includes(name)) { | ||
errors.push( | ||
errorWithCode( | ||
'DUPLICATE_ENUM_DEFINITION', | ||
logServiceAndType(serviceName, name) + | ||
`The enum, \`${name}\` was defined multiple times in this service. Remove one of the definitions for \`${name}\``, | ||
), | ||
); | ||
return definition; | ||
} | ||
enums.push(name); | ||
return definition; | ||
}, | ||
ScalarTypeDefinition(definition) { | ||
const name = definition.name.value; | ||
if (scalars.includes(name)) { | ||
errors.push( | ||
errorWithCode( | ||
'DUPLICATE_SCALAR_DEFINITION', | ||
logServiceAndType(serviceName, name) + | ||
`The scalar, \`${name}\` was defined multiple times in this service. Remove one of the definitions for \`${name}\``, | ||
), | ||
); | ||
return definition; | ||
} | ||
scalars.push(name); | ||
return definition; | ||
}, | ||
}); | ||
|
||
return errors; | ||
}; |
72 changes: 72 additions & 0 deletions
72
packages/apollo-federation/src/composition/validate/preComposition/duplicateEnumValue.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { visit, GraphQLError } from 'graphql'; | ||
import { ServiceDefinition } from '../../types'; | ||
|
||
import { logServiceAndType, errorWithCode } from '../../utils'; | ||
|
||
export const duplicateEnumValue = ({ | ||
name: serviceName, | ||
typeDefs, | ||
}: ServiceDefinition) => { | ||
const errors: GraphQLError[] = []; | ||
|
||
const enums: { [name: string]: string[] } = {}; | ||
|
||
visit(typeDefs, { | ||
EnumTypeDefinition(definition) { | ||
const name = definition.name.value; | ||
const enumValues = | ||
definition.values && definition.values.map(value => value.name.value); | ||
|
||
if (!enumValues) return definition; | ||
|
||
if (enums[name] && enums[name].length) { | ||
enumValues.map(valueName => { | ||
if (enums[name].includes(valueName)) { | ||
errors.push( | ||
errorWithCode( | ||
'DUPLICATE_ENUM_VALUE', | ||
logServiceAndType(serviceName, name, valueName) + | ||
`The enum, \`${name}\` has multiple definitions of the \`${valueName}\` value.`, | ||
), | ||
); | ||
return; | ||
} | ||
enums[name].push(valueName); | ||
}); | ||
} else { | ||
enums[name] = enumValues; | ||
} | ||
|
||
return definition; | ||
}, | ||
EnumTypeExtension(definition) { | ||
const name = definition.name.value; | ||
const enumValues = | ||
definition.values && definition.values.map(value => value.name.value); | ||
|
||
if (!enumValues) return definition; | ||
|
||
if (enums[name] && enums[name].length) { | ||
enumValues.map(valueName => { | ||
if (enums[name].includes(valueName)) { | ||
errors.push( | ||
errorWithCode( | ||
'DUPLICATE_ENUM_VALUE', | ||
logServiceAndType(serviceName, name, valueName) + | ||
`The enum, \`${name}\` has multiple definitions of the \`${valueName}\` value.`, | ||
), | ||
); | ||
return; | ||
} | ||
enums[name].push(valueName); | ||
}); | ||
} else { | ||
enums[name] = enumValues; | ||
} | ||
|
||
return definition; | ||
}, | ||
}); | ||
|
||
return errors; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.