-
Notifications
You must be signed in to change notification settings - Fork 348
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce 'bare' mode on naming-convention transform (#4498)
- Loading branch information
Showing
17 changed files
with
711 additions
and
160 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,6 @@ | ||
--- | ||
'@graphql-mesh/transform-naming-convention': minor | ||
'@graphql-mesh/types': minor | ||
--- | ||
|
||
Introduce 'bare' mode on naming-convention transform |
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
184 changes: 184 additions & 0 deletions
184
packages/transforms/naming-convention/src/bareNamingConvention.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,184 @@ | ||
import { defaultFieldResolver, GraphQLInputObjectType, GraphQLSchema, isInputObjectType, isEnumType } from 'graphql'; | ||
import { MeshTransform, YamlConfig, MeshTransformOptions } from '@graphql-mesh/types'; | ||
import { MapperKind, mapSchema, renameType } from '@graphql-tools/utils'; | ||
|
||
import { NAMING_CONVENTIONS, IGNORED_ROOT_FIELD_NAMES, IGNORED_TYPE_NAMES } from './shared'; | ||
|
||
const isObject = (input: any) => typeof input === 'object' && input !== null && !Array.isArray(input) && true; | ||
|
||
// Resolver composer mapping renamed field and arguments | ||
const defaultResolverComposer = | ||
( | ||
resolveFn = defaultFieldResolver, | ||
originalFieldName: string, | ||
argsMap: { [key: string]: string }, | ||
resultMap: { [key: string]: string } | ||
) => | ||
(root: any, args: any, context: any, info: any) => { | ||
const originalResult = resolveFn( | ||
root, | ||
// map renamed arguments to their original value | ||
argsMap | ||
? Object.keys(args).reduce((acc, key: string) => { | ||
if (!argsMap[key]) { | ||
return { ...acc, [key]: args[key] }; | ||
} | ||
|
||
const argKey = argsMap[key]; | ||
const mappedArgKeyIsObject = isObject(argKey); | ||
const newArgName = Object.keys(argKey)[0]; | ||
|
||
return { | ||
...acc, | ||
[mappedArgKeyIsObject ? newArgName : argKey]: mappedArgKeyIsObject | ||
? Object.entries(args[key]).reduce((acc, [key, value]) => { | ||
const oldInputFieldName = argKey[newArgName][key]; | ||
return { ...acc, [oldInputFieldName || key]: value }; | ||
}, {}) | ||
: args[key], | ||
}; | ||
}, {}) | ||
: args, | ||
context, | ||
// map renamed field name to its original value | ||
originalFieldName ? { ...info, fieldName: originalFieldName } : info | ||
); | ||
|
||
// map result values from original value to new renamed value | ||
return (resultMap && resultMap[originalResult as string]) || originalResult; | ||
}; | ||
|
||
export default class NamingConventionTransform implements MeshTransform { | ||
noWrap = true; | ||
config: Omit<YamlConfig.NamingConventionTransformConfig, 'mode'>; | ||
|
||
constructor(options: MeshTransformOptions<YamlConfig.NamingConventionTransformConfig>) { | ||
this.config = { ...options.config }; | ||
} | ||
|
||
transformSchema(schema: GraphQLSchema) { | ||
return mapSchema(schema, { | ||
...(this.config.typeNames && { | ||
[MapperKind.TYPE]: type => { | ||
const oldName = type.name; | ||
const namingConventionFn = NAMING_CONVENTIONS[this.config.typeNames]; | ||
const newName = IGNORED_TYPE_NAMES.includes(oldName) ? oldName : namingConventionFn(oldName); | ||
|
||
if (newName !== undefined && newName !== oldName) { | ||
return renameType(type, newName); | ||
} | ||
|
||
return undefined; | ||
}, | ||
}), | ||
...(this.config.enumValues && { | ||
[MapperKind.ENUM_VALUE]: (valueConfig, _typeName, _schema, externalValue) => { | ||
const namingConventionFn = NAMING_CONVENTIONS[this.config.enumValues]; | ||
const newEnumValue = namingConventionFn(externalValue); | ||
|
||
if (newEnumValue === externalValue) { | ||
return undefined; | ||
} | ||
|
||
return [ | ||
newEnumValue, | ||
{ | ||
...valueConfig, | ||
value: newEnumValue, | ||
astNode: { | ||
...valueConfig.astNode, | ||
name: { | ||
...valueConfig.astNode.name, | ||
value: newEnumValue, | ||
}, | ||
}, | ||
}, | ||
]; | ||
}, | ||
}), | ||
...((this.config.fieldNames || this.config.fieldArgumentNames) && { | ||
[MapperKind.COMPOSITE_FIELD]: (fieldConfig, fieldName) => { | ||
const enumNamingConventionFn = NAMING_CONVENTIONS[this.config.enumValues]; | ||
const fieldNamingConventionFn = this.config.fieldNames && NAMING_CONVENTIONS[this.config.fieldNames]; | ||
const argNamingConventionFn = | ||
this.config.fieldArgumentNames && NAMING_CONVENTIONS[this.config.fieldArgumentNames]; | ||
const argsMap = fieldConfig.args && {}; | ||
const newFieldName = | ||
this.config.fieldNames && | ||
!IGNORED_ROOT_FIELD_NAMES.includes(fieldName) && | ||
fieldNamingConventionFn(fieldName); | ||
const resultMap = | ||
this.config.enumValues && | ||
isEnumType(fieldConfig.type) && | ||
Object.keys(fieldConfig.type.toConfig().values).reduce((map, value) => { | ||
if (Number.isFinite(value)) { | ||
return map; | ||
} | ||
|
||
const newValue = enumNamingConventionFn(value as string); | ||
return newValue === value | ||
? map | ||
: { | ||
...map, | ||
[value]: newValue, | ||
}; | ||
}, {}); | ||
|
||
if (fieldConfig.args) { | ||
fieldConfig.args = Object.entries(fieldConfig.args).reduce((args, [argName, argConfig]) => { | ||
const newArgName = this.config.fieldArgumentNames && argNamingConventionFn(argName); | ||
const useArgName = newArgName || argName; | ||
const argIsInputObjectType = isInputObjectType(argConfig.type); | ||
|
||
if (argName === newArgName && !argIsInputObjectType) { | ||
return args; | ||
} | ||
|
||
// take advantage of the loop to map arg name from Old to New | ||
argsMap[useArgName] = !argIsInputObjectType | ||
? argName | ||
: { | ||
[argName]: Object.keys((argConfig.type as GraphQLInputObjectType).toConfig().fields).reduce( | ||
(inputFields, inputFieldName) => { | ||
if (Number.isFinite(inputFieldName)) return inputFields; | ||
|
||
const newInputFieldName = fieldNamingConventionFn(inputFieldName as string); | ||
return newInputFieldName === inputFieldName | ||
? inputFields | ||
: { | ||
...inputFields, | ||
[fieldNamingConventionFn(inputFieldName as string)]: inputFieldName, | ||
}; | ||
}, | ||
{} | ||
), | ||
}; | ||
|
||
return { | ||
...args, | ||
[useArgName]: argConfig, | ||
}; | ||
}, {}); | ||
} | ||
|
||
// Wrap resolve fn to handle mapping renamed field and argument names as well as results (for enums) | ||
fieldConfig.resolve = defaultResolverComposer(fieldConfig.resolve, fieldName, argsMap, resultMap); | ||
|
||
return [newFieldName || fieldName, fieldConfig]; | ||
}, | ||
}), | ||
...(this.config.fieldNames && { | ||
[MapperKind.INPUT_OBJECT_FIELD]: (inputFieldConfig, fieldName) => { | ||
const namingConventionFn = this.config.fieldNames && NAMING_CONVENTIONS[this.config.fieldNames]; | ||
const newName = namingConventionFn(fieldName); | ||
|
||
if (newName === fieldName) { | ||
return undefined; | ||
} | ||
|
||
return [newName, inputFieldConfig]; | ||
}, | ||
}), | ||
}); | ||
} | ||
} |
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,139 +1,15 @@ | ||
import { GraphQLSchema } from 'graphql'; | ||
import { MeshTransform, YamlConfig, MeshTransformOptions } from '@graphql-mesh/types'; | ||
import { | ||
RenameTypes, | ||
TransformEnumValues, | ||
RenameInterfaceFields, | ||
TransformObjectFields, | ||
RenameInputObjectFields, | ||
RenameObjectFieldArguments, | ||
} from '@graphql-tools/wrap'; | ||
import { ExecutionResult, ExecutionRequest } from '@graphql-tools/utils'; | ||
import { Transform, SubschemaConfig, DelegationContext } from '@graphql-tools/delegate'; | ||
import { applyRequestTransforms, applyResultTransforms, applySchemaTransforms } from '@graphql-mesh/utils'; | ||
|
||
import { | ||
camelCase, | ||
capitalCase, | ||
constantCase, | ||
dotCase, | ||
headerCase, | ||
noCase, | ||
paramCase, | ||
pascalCase, | ||
pathCase, | ||
sentenceCase, | ||
snakeCase, | ||
} from 'change-case'; | ||
|
||
import { upperCase } from 'upper-case'; | ||
import { lowerCase } from 'lower-case'; | ||
import { resolvers as scalarsResolversMap } from 'graphql-scalars'; | ||
|
||
type NamingConventionFn = (input: string) => string; | ||
type NamingConventionType = YamlConfig.NamingConventionTransformConfig['typeNames']; | ||
|
||
const NAMING_CONVENTIONS: Record<NamingConventionType, NamingConventionFn> = { | ||
camelCase, | ||
capitalCase, | ||
constantCase, | ||
dotCase, | ||
headerCase, | ||
noCase, | ||
paramCase, | ||
pascalCase, | ||
pathCase, | ||
sentenceCase, | ||
snakeCase, | ||
upperCase, | ||
lowerCase, | ||
}; | ||
|
||
// Ignore fields needed by Federation spec | ||
const IGNORED_ROOT_FIELD_NAMES = ['_service', '_entities']; | ||
|
||
const IGNORED_TYPE_NAMES = [ | ||
'date', | ||
'hostname', | ||
'regex', | ||
'json-pointer', | ||
'relative-json-pointer', | ||
'uri-reference', | ||
'uri-template', | ||
...Object.keys(scalarsResolversMap), | ||
]; | ||
|
||
export default class NamingConventionTransform implements MeshTransform { | ||
private transforms: Transform[] = []; | ||
|
||
constructor(options: MeshTransformOptions<YamlConfig.NamingConventionTransformConfig>) { | ||
if (options.config.typeNames) { | ||
const namingConventionFn = NAMING_CONVENTIONS[options.config.typeNames]; | ||
this.transforms.push( | ||
new RenameTypes(typeName => | ||
IGNORED_TYPE_NAMES.includes(typeName) ? typeName : namingConventionFn(typeName) || typeName | ||
) as any | ||
); | ||
} | ||
if (options.config.fieldNames) { | ||
const fieldNamingConventionFn = options.config.fieldNames | ||
? NAMING_CONVENTIONS[options.config.fieldNames] | ||
: (s: string) => s; | ||
this.transforms.push( | ||
new RenameInputObjectFields((_, fieldName) => fieldNamingConventionFn(fieldName) || fieldName) as any, | ||
new TransformObjectFields((_, fieldName, fieldConfig) => [ | ||
IGNORED_ROOT_FIELD_NAMES.includes(fieldName) ? fieldName : fieldNamingConventionFn(fieldName) || fieldName, | ||
fieldConfig, | ||
]) as any, | ||
new RenameInterfaceFields((_, fieldName) => fieldNamingConventionFn(fieldName) || fieldName) as any | ||
); | ||
} | ||
|
||
if (options.config.fieldArgumentNames) { | ||
const fieldArgNamingConventionFn = options.config.fieldArgumentNames | ||
? NAMING_CONVENTIONS[options.config.fieldArgumentNames] | ||
: (s: string) => s; | ||
|
||
this.transforms.push( | ||
new RenameObjectFieldArguments((_typeName, _fieldName, argName) => fieldArgNamingConventionFn(argName)) as any | ||
); | ||
} | ||
|
||
if (options.config.enumValues) { | ||
const namingConventionFn = NAMING_CONVENTIONS[options.config.enumValues]; | ||
|
||
this.transforms.push( | ||
new TransformEnumValues((typeName, externalValue, enumValueConfig) => { | ||
const newEnumValue = namingConventionFn(externalValue) || externalValue; | ||
return [ | ||
newEnumValue, | ||
{ | ||
...enumValueConfig, | ||
value: newEnumValue, | ||
}, | ||
]; | ||
}) as any | ||
); | ||
} | ||
} | ||
|
||
transformSchema( | ||
originalWrappingSchema: GraphQLSchema, | ||
subschemaConfig: SubschemaConfig, | ||
transformedSchema?: GraphQLSchema | ||
) { | ||
return applySchemaTransforms(originalWrappingSchema, subschemaConfig, transformedSchema, this.transforms); | ||
} | ||
|
||
transformRequest( | ||
originalRequest: ExecutionRequest, | ||
delegationContext: DelegationContext, | ||
transformationContext: Record<string, any> | ||
) { | ||
return applyRequestTransforms(originalRequest, delegationContext, transformationContext, this.transforms); | ||
} | ||
|
||
transformResult(originalResult: ExecutionResult, delegationContext: DelegationContext, transformationContext: any) { | ||
return applyResultTransforms(originalResult, delegationContext, transformationContext, this.transforms); | ||
} | ||
import { YamlConfig, MeshTransformOptions } from '@graphql-mesh/types'; | ||
|
||
import WrapNamingConvention from './wrapNamingConvention'; | ||
import BareNamingConvention from './bareNamingConvention'; | ||
interface NamingConventionTransformConstructor { | ||
new (options: MeshTransformOptions<YamlConfig.NamingConventionTransformConfig>): | ||
| WrapNamingConvention | ||
| BareNamingConvention; | ||
} | ||
|
||
export default (function NamingConventionTransform( | ||
options: MeshTransformOptions<YamlConfig.NamingConventionTransformConfig> | ||
) { | ||
return options.config.mode === 'bare' ? new BareNamingConvention(options) : new WrapNamingConvention(options); | ||
} as unknown as NamingConventionTransformConstructor); |
Oops, something went wrong.