-
Notifications
You must be signed in to change notification settings - Fork 257
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Uniquify and sanitize enum values in the join__Graph enum
- Loading branch information
1 parent
0365de6
commit 0b133e5
Showing
3 changed files
with
180 additions
and
34 deletions.
There are no files selected for viewing
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,45 @@ | ||
import { fixtures } from 'apollo-federation-integration-testsuite'; | ||
import { getJoins } from "../joinSpec"; | ||
|
||
const questionableNamesRemap = { | ||
accounts: 'ServiceA', | ||
books: 'serviceA', | ||
documents: 'servicea_2', | ||
inventory: 'servicea_2_', | ||
product: '9product*!', | ||
reviews: 'reviews_9', | ||
}; | ||
|
||
const fixturesWithQuestionableServiceNames = fixtures.map((service) => ({ | ||
...service, | ||
name: questionableNamesRemap[service.name], | ||
})); | ||
|
||
describe('join__Graph enum', () => { | ||
it('correctly uniquifies and sanitizes service names', () => { | ||
const { sanitizedServiceNames } = getJoins( | ||
fixturesWithQuestionableServiceNames, | ||
); | ||
|
||
/** | ||
* Expectations | ||
* 1. Non-Alphanumeric characters are replaced with _ (9product*!) | ||
* 2. Numeric first characters are prefixed with _ (9product*!) | ||
* 3. Names ending in an underscore followed by numbers `_\d+` are suffixed with _ (reviews_9, servicea_2) | ||
* 4. Names are uppercased (all) | ||
* 5. After transformations 1-4, duplicates are suffixed with _{n} where {n} is number of times we've seen the dupe (ServiceA + serviceA, servicea_2 + servicea_2_) | ||
* | ||
* Miscellany | ||
* (serviceA) tests the edge case of colliding with a name we generated | ||
* (servicea_2_) tests a collision against (documents) post-transformation | ||
*/ | ||
expect(sanitizedServiceNames).toMatchObject({ | ||
'9product*!': '_9PRODUCT__', | ||
ServiceA: 'SERVICEA', | ||
This comment has been minimized.
Sorry, something went wrong. |
||
reviews_9: 'REVIEWS_9_', | ||
serviceA: 'SERVICEA_2', | ||
servicea_2: 'SERVICEA_2_', | ||
servicea_2_: 'SERVICEA_2__2', | ||
}); | ||
}) | ||
}) |
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 |
---|---|---|
|
@@ -25,16 +25,64 @@ const JoinGraphDirective = new GraphQLDirective({ | |
} | ||
}); | ||
|
||
/** | ||
* Expectations | ||
* 1. Non-Alphanumeric characters are replaced with _ (alphaNumericUnderscoreOnly) | ||
* 2. Numeric first characters are prefixed with _ (noNumericFirstChar) | ||
* 3. Names ending in an underscore followed by numbers `_\d+` are suffixed with _ (noUnderscoreNumericEnding) | ||
* 4. Names are uppercased (toUpper) | ||
* 5. After transformations 1-4, duplicates are suffixed with _{n} where {n} is number of times we've seen the dupe | ||
* | ||
* Note: Collisions with name's we've generated are also accounted for | ||
*/ | ||
function getJoinGraphEnum(serviceList: ServiceDefinition[]) { | ||
return new GraphQLEnumType({ | ||
name: 'join__Graph', | ||
values: Object.fromEntries( | ||
serviceList.map((service) => [ | ||
service.name.toUpperCase(), | ||
{ value: service }, | ||
]), | ||
), | ||
}); | ||
// Track whether we've seen a name and how many times | ||
const nameMap: Map<string, number> = new Map(); | ||
// Build a map of original service name to generated name | ||
const sanitizedServiceNames: Record<string, string> = Object.create(null); | ||
|
||
function uniquifyAndSanitizeGraphQLName(name: string) { | ||
// Transforms to ensure valid graphql `Name` | ||
const alphaNumericUnderscoreOnly = name.replace(/[^_a-zA-Z0-9]/g, '_'); | ||
const noNumericFirstChar = alphaNumericUnderscoreOnly.match(/^[0-9]/) | ||
? '_' + alphaNumericUnderscoreOnly | ||
: alphaNumericUnderscoreOnly; | ||
const noUnderscoreNumericEnding = noNumericFirstChar.match(/_[0-9]+$/) | ||
? noNumericFirstChar + '_' | ||
: noNumericFirstChar; | ||
|
||
// toUpper not really necessary but follows convention of enum values | ||
const toUpper = noUnderscoreNumericEnding.toLocaleUpperCase(); | ||
|
||
// Uniquifying post-transform | ||
const nameCount = nameMap.get(toUpper); | ||
if (nameCount) { | ||
// Collision - bump counter by one | ||
nameMap.set(toUpper, nameCount + 1); | ||
const uniquified = `${toUpper}_${nameCount + 1}`; | ||
// We also now need another entry for the name we just generated | ||
nameMap.set(uniquified, 1); | ||
This comment has been minimized.
Sorry, something went wrong.
glasser
Member
|
||
sanitizedServiceNames[name] = uniquified; | ||
return uniquified; | ||
} else { | ||
nameMap.set(toUpper, 1); | ||
sanitizedServiceNames[name] = toUpper; | ||
return toUpper; | ||
} | ||
} | ||
|
||
return { | ||
sanitizedServiceNames, | ||
JoinGraphEnum: new GraphQLEnumType({ | ||
name: 'join__Graph', | ||
values: Object.fromEntries( | ||
serviceList.map((service) => [ | ||
This comment has been minimized.
Sorry, something went wrong.
glasser
Member
|
||
uniquifyAndSanitizeGraphQLName(service.name), | ||
{ value: service }, | ||
]), | ||
), | ||
}), | ||
}; | ||
} | ||
|
||
function getJoinFieldDirective(JoinGraphEnum: GraphQLEnumType) { | ||
|
@@ -68,7 +116,7 @@ function getJoinOwnerDirective(JoinGraphEnum: GraphQLEnumType) { | |
} | ||
|
||
export function getJoins(serviceList: ServiceDefinition[]) { | ||
const JoinGraphEnum = getJoinGraphEnum(serviceList); | ||
const { sanitizedServiceNames, JoinGraphEnum } = getJoinGraphEnum(serviceList); | ||
const JoinFieldDirective = getJoinFieldDirective(JoinGraphEnum); | ||
const JoinOwnerDirective = getJoinOwnerDirective(JoinGraphEnum); | ||
|
||
|
@@ -87,11 +135,12 @@ export function getJoins(serviceList: ServiceDefinition[]) { | |
}); | ||
|
||
return { | ||
sanitizedServiceNames, | ||
FieldSetScalar, | ||
JoinTypeDirective, | ||
JoinFieldDirective, | ||
JoinOwnerDirective, | ||
JoinGraphEnum, | ||
JoinGraphDirective, | ||
} | ||
}; | ||
} |
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
I was imagining that this would become
SERVICEA_1
, though that does make the implementation slightly different in that you have to process the whole list before you can definitively assign names.