Skip to content

Commit

Permalink
Introduce 'bare' mode on naming-convention transform (#4498)
Browse files Browse the repository at this point in the history
  • Loading branch information
santino committed Sep 14, 2022
1 parent 9e72c83 commit ee1cb6f
Show file tree
Hide file tree
Showing 17 changed files with 711 additions and 160 deletions.
6 changes: 6 additions & 0 deletions .changeset/cyan-beans-bake.md
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
12 changes: 8 additions & 4 deletions packages/transforms/naming-convention/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,21 @@
"peerDependencies": {
"graphql": "*"
},
"devDependencies": {
"@graphql-mesh/cache-localforage": "^0.6.35",
"@graphql-tools/schema": "9.0.3"
},
"dependencies": {
"@graphql-mesh/types": "0.83.5",
"@graphql-mesh/utils": "0.41.10",
"@graphql-tools/wrap": "9.2.1",
"@graphql-tools/delegate": "9.0.6",
"@graphql-tools/utils": "8.12.0",
"@graphql-tools/wrap": "9.2.1",
"change-case": "4.1.2",
"upper-case": "2.0.2",
"lower-case": "2.0.2",
"graphql-scalars": "1.18.0",
"tslib": "^2.4.0"
"lower-case": "2.0.2",
"tslib": "2.4.0",
"upper-case": "2.0.2"
},
"publishConfig": {
"access": "public",
Expand Down
184 changes: 184 additions & 0 deletions packages/transforms/naming-convention/src/bareNamingConvention.ts
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];
},
}),
});
}
}
152 changes: 14 additions & 138 deletions packages/transforms/naming-convention/src/index.ts
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);

0 comments on commit ee1cb6f

Please sign in to comment.