@graphql-codegen/c-sharp-operations
Version:
GraphQL Code Generator plugin for generating CSharp code based on GraphQL operations
459 lines (457 loc) • 23.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.CSharpOperationsVisitor = void 0;
const tslib_1 = require("tslib");
const auto_bind_1 = tslib_1.__importDefault(require("auto-bind"));
const change_case_all_1 = require("change-case-all");
const graphql_1 = require("graphql");
const c_sharp_common_1 = require("@graphql-codegen/c-sharp-common");
const plugin_helpers_1 = require("@graphql-codegen/plugin-helpers");
const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common");
const defaultSuffix = 'GQL';
const R_NAME = /name:\s*"([^"]+)"/;
function R_DEF(directive) {
return new RegExp(`\\s+\\@${directive}\\([^)]+\\)`, 'gm');
}
class CSharpOperationsVisitor extends visitor_plugin_common_1.ClientSideBaseVisitor {
constructor(schema, fragments, rawConfig, documents) {
super(schema, fragments, rawConfig, {
namespaceName: rawConfig.namespaceName || 'GraphQLCodeGen',
namedClient: rawConfig.namedClient,
querySuffix: rawConfig.querySuffix || defaultSuffix,
mutationSuffix: rawConfig.mutationSuffix || defaultSuffix,
subscriptionSuffix: rawConfig.subscriptionSuffix || defaultSuffix,
jsonAttributesConfiguration: (0, c_sharp_common_1.getJsonAttributeSourceConfiguration)(rawConfig.jsonAttributesSource || 'Newtonsoft.Json'),
scalars: (0, visitor_plugin_common_1.buildScalarsFromConfig)(schema, rawConfig, c_sharp_common_1.C_SHARP_SCALARS),
typesafeOperation: rawConfig.typesafeOperation || false,
memberNamingFunction: (0, c_sharp_common_1.getMemberNamingFunction)(rawConfig),
}, documents);
this._operationsToInclude = [];
this.overruleConfigSettings();
(0, auto_bind_1.default)(this);
this._schemaAST = (0, plugin_helpers_1.getCachedDocumentNodeFromSchema)(schema);
this._fragmentList = fragments;
}
// Some settings aren't supported with C#, overruled here
overruleConfigSettings() {
if (this.config.documentMode === visitor_plugin_common_1.DocumentMode.graphQLTag) {
// C# operations does not (yet) support graphQLTag mode
this.config.documentMode = visitor_plugin_common_1.DocumentMode.documentNode;
}
}
_operationHasDirective(operation, directive) {
if (typeof operation === 'string') {
return operation.includes(`${directive}`);
}
let found = false;
(0, graphql_1.visit)(operation, {
Directive(node) {
if (node.name.value === directive) {
found = true;
}
},
});
return found;
}
_extractDirective(operation, directive) {
const directives = (0, graphql_1.print)(operation).match(R_DEF(directive));
if (directives.length > 1) {
throw new Error(`The ${directive} directive used multiple times in '${operation.name}' operation`);
}
return directives[0];
}
_namedClient(operation) {
let name;
if (this._operationHasDirective(operation, 'namedClient')) {
name = this._extractNamedClient(operation);
}
else if (this.config.namedClient) {
name = this.config.namedClient;
}
return name ? `client = '${name}';` : '';
}
_extractNamedClient(operation) {
const [, name] = this._extractDirective(operation, 'namedClient').match(R_NAME);
return name;
}
_gql(node) {
const includeNestedFragments = this.config.documentMode === visitor_plugin_common_1.DocumentMode.documentNode ||
this.config.documentMode === visitor_plugin_common_1.DocumentMode.string ||
(this.config.dedupeFragments && node.kind === 'OperationDefinition');
const fragmentNames = this._extractFragments(node, includeNestedFragments);
const fragments = this._transformFragments(fragmentNames);
const doc = this._prepareDocument([(0, graphql_1.print)(node), this._includeFragments(fragments, node.kind)].join('\n'));
return doc.replace(/"/g, '""');
}
_getDocumentNodeVariable(node, documentVariableName) {
return this.config.documentMode === visitor_plugin_common_1.DocumentMode.external
? `Operations.${node.name.value}`
: documentVariableName;
}
_gqlInputSignature(variable) {
const typeNode = variable.type;
const innerType = (0, visitor_plugin_common_1.getBaseTypeNode)(typeNode);
const schemaType = this._schema.getType(innerType.name.value);
const name = variable.variable.name.value;
const baseType = !(0, graphql_1.isScalarType)(schemaType)
? innerType.name.value
: this.scalars[schemaType.name].input || 'object';
const listType = (0, c_sharp_common_1.getListTypeField)(typeNode);
const required = (0, c_sharp_common_1.getListInnerTypeNode)(typeNode).kind === graphql_1.Kind.NON_NULL_TYPE;
return {
required: listType ? listType.required : required,
signature: !listType
? `${name}=(${baseType})`
: `${name}=(${baseType}${'[]'.repeat((0, c_sharp_common_1.getListTypeDepth)(listType))})`,
};
}
getCSharpImports() {
return ([
'System',
this.config.jsonAttributesConfiguration.namespace,
'GraphQL',
'GraphQL.Client.Abstractions',
]
.map(i => `using ${i};`)
.join('\n') + '\n');
}
_operationSuffix(operationType) {
switch (operationType) {
case 'query':
return this.config.querySuffix;
case 'mutation':
return this.config.mutationSuffix;
case 'subscription':
return this.config.subscriptionSuffix;
default:
return defaultSuffix;
}
}
resolveFieldType(typeNode, hasDefaultValue = false) {
const innerType = (0, visitor_plugin_common_1.getBaseTypeNode)(typeNode);
const schemaType = this._schema.getType(innerType.name.value);
const listType = (0, c_sharp_common_1.getListTypeField)(typeNode);
const required = (0, c_sharp_common_1.getListInnerTypeNode)(typeNode).kind === graphql_1.Kind.NON_NULL_TYPE;
let result = null;
if ((0, graphql_1.isScalarType)(schemaType)) {
if (this.scalars[schemaType.name]) {
const baseType = this.scalars[schemaType.name];
result = new c_sharp_common_1.CSharpFieldType({
baseType: {
type: baseType.output,
required,
valueType: (0, c_sharp_common_1.isValueType)(baseType.output),
},
listType,
});
}
else {
result = new c_sharp_common_1.CSharpFieldType({
baseType: {
type: 'object',
required,
valueType: false,
},
listType,
});
}
}
else if ((0, graphql_1.isInputObjectType)(schemaType)) {
result = new c_sharp_common_1.CSharpFieldType({
baseType: {
type: `${this.convertName(schemaType.name)}`,
required,
valueType: false,
},
listType,
});
}
else if ((0, graphql_1.isEnumType)(schemaType)) {
result = new c_sharp_common_1.CSharpFieldType({
baseType: {
type: this.convertName(schemaType.name),
required,
valueType: true,
},
listType,
});
}
else {
result = new c_sharp_common_1.CSharpFieldType({
baseType: {
type: `${schemaType.name}`,
required,
valueType: false,
},
listType,
});
}
if (hasDefaultValue) {
// Required field is optional when default value specified, see #4273
(result.listType || result.baseType).required = false;
}
return result;
}
_getResponseFieldRecursive(node, parentSchema, seenClasses) {
switch (node.kind) {
case graphql_1.Kind.OPERATION_DEFINITION: {
const classes = new Set();
return new c_sharp_common_1.CSharpDeclarationBlock()
.access('public')
.asKind('class')
.withName('Response')
.withBlock('\n' +
node.selectionSet.selections
.map(opr => {
if (opr.kind !== graphql_1.Kind.FIELD) {
throw new Error(`Unknown kind; ${opr.kind} in OperationDefinitionNode`);
}
return this._getResponseFieldRecursive(opr, parentSchema, classes);
})
.join('\n')).string;
}
case graphql_1.Kind.FIELD: {
const fieldSchema = parentSchema.fields.find(f => f.name.value === node.name.value);
if (!fieldSchema) {
throw new Error(`Field schema not found; ${node.name.value}`);
}
const responseType = this.resolveFieldType(fieldSchema.type);
if (!node.selectionSet) {
const responseTypeName = (0, c_sharp_common_1.wrapFieldType)(responseType, responseType.listType, 'System.Collections.Generic.List');
const propertyName = (0, c_sharp_common_1.convertSafeName)(this._parsedConfig.memberNamingFunction(node.name.value));
return (0, visitor_plugin_common_1.indentMultiline)([
`[${this.config.jsonAttributesConfiguration.propertyAttribute}("${node.name.value}")]`,
`public ${responseTypeName} ${propertyName} { get; set; }`,
].join('\n') + '\n');
}
// Earlier versions of this codegen generated a class name based on the
// base type for each selection set.
// Subsequent classes will use the selection name to prevent duplicates.
const baseTypeSelectionName = `${responseType.baseType.type}Selection`;
const selectionBaseTypeName = (seenClasses === null || seenClasses === void 0 ? void 0 : seenClasses.has(baseTypeSelectionName))
? (0, change_case_all_1.pascalCase)(`${node.name.value}Selection`)
: baseTypeSelectionName;
seenClasses.add(selectionBaseTypeName);
const selectionType = Object.assign(new c_sharp_common_1.CSharpFieldType(responseType), {
baseType: { type: selectionBaseTypeName },
});
const selectionTypeName = (0, c_sharp_common_1.wrapFieldType)(selectionType, selectionType.listType, 'System.Collections.Generic.List');
const innerClassSchema = this._schemaAST.definitions.find(d => d.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION && d.name.value === responseType.baseType.type);
const classes = new Set();
const innerClassDefinition = new c_sharp_common_1.CSharpDeclarationBlock()
.access('public')
.asKind('class')
.withName((0, c_sharp_common_1.convertSafeName)(selectionBaseTypeName))
.withBlock('\n' +
node.selectionSet.selections
.map(s => {
if (s.kind === graphql_1.Kind.INLINE_FRAGMENT) {
throw new Error(`Unsupported kind; ${node.name} ${s.kind}`);
}
return this._getResponseFieldRecursive(s, innerClassSchema, classes);
})
.join('\n')).string;
const propertyName = (0, c_sharp_common_1.convertSafeName)(this._parsedConfig.memberNamingFunction(node.name.value));
return (0, visitor_plugin_common_1.indentMultiline)([
innerClassDefinition,
`[${this.config.jsonAttributesConfiguration.propertyAttribute}("${node.name.value}")]`,
`public ${selectionTypeName} ${propertyName} { get; set; }`,
].join('\n') + '\n');
}
case graphql_1.Kind.FRAGMENT_SPREAD: {
const fragmentSchema = this._fragmentList.find(f => f.name === node.name.value);
if (!fragmentSchema) {
throw new Error(`Fragment schema not found; ${node.name.value}`);
}
const classes = new Set();
return fragmentSchema.node.selectionSet.selections
.map(s => {
if (s.kind === graphql_1.Kind.INLINE_FRAGMENT) {
throw new Error(`Unsupported kind; ${node.name} ${s.kind}`);
}
return this._getResponseFieldRecursive(s, parentSchema, classes);
})
.join('\n');
}
default: {
return '';
}
}
}
_getResponseClass(node) {
const operationSchema = this._schemaAST.definitions.find(s => s.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION && s.name.value.toLowerCase() === node.operation);
return this._getResponseFieldRecursive(node, operationSchema, undefined);
}
_getVariablesClass(node) {
var _a, _b;
if (!((_a = node.variableDefinitions) === null || _a === void 0 ? void 0 : _a.length)) {
return '';
}
return new c_sharp_common_1.CSharpDeclarationBlock()
.access('public')
.asKind('class')
.withName('Variables')
.withBlock('\n' +
((_b = node.variableDefinitions) === null || _b === void 0 ? void 0 : _b.map(v => {
const inputType = this.resolveFieldType(v.type);
const inputTypeName = (0, c_sharp_common_1.wrapFieldType)(inputType, inputType.listType, 'System.Collections.Generic.List');
const propertyName = (0, c_sharp_common_1.convertSafeName)(this._parsedConfig.memberNamingFunction(v.variable.name.value));
return (0, visitor_plugin_common_1.indentMultiline)([
`[${this.config.jsonAttributesConfiguration.propertyAttribute}("${v.variable.name.value}")]`,
`public ${inputTypeName} ${propertyName} { get; set; }`,
].join('\n') + '\n');
}).join('\n'))).string;
}
_getOperationMethod(node) {
var _a, _b, _c, _d;
const operationSchema = this._schemaAST.definitions.find(s => s.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION && s.name.value.toLowerCase() === node.operation);
if (!operationSchema) {
throw new Error(`Operation schema not found; ${node.operation}`);
}
const variablesArgument = ((_a = node.variableDefinitions) === null || _a === void 0 ? void 0 : _a.length) ? ', Variables variables' : '';
switch (node.operation) {
case 'query':
case 'mutation':
return [
`public static System.Threading.Tasks.Task<GraphQLResponse<Response>> Send${operationSchema.name.value}Async(IGraphQLClient client${variablesArgument}, System.Threading.CancellationToken cancellationToken = default) {`,
(0, visitor_plugin_common_1.indent)(`return client.Send${operationSchema.name.value}Async<Response>(Request(${((_b = node.variableDefinitions) === null || _b === void 0 ? void 0 : _b.length) ? 'variables' : ''}), cancellationToken);`),
`}`,
].join('\n');
case 'subscription': {
return [
`public static System.IObservable<GraphQLResponse<Response>> CreateSubscriptionStream(IGraphQLClient client${variablesArgument}) {`,
(0, visitor_plugin_common_1.indent)(`return client.CreateSubscriptionStream<Response>(Request(${((_c = node.variableDefinitions) === null || _c === void 0 ? void 0 : _c.length) ? 'variables' : ''}));`),
`}`,
'',
`public static System.IObservable<GraphQLResponse<Response>> CreateSubscriptionStream(IGraphQLClient client${variablesArgument}, System.Action<System.Exception> exceptionHandler) {`,
(0, visitor_plugin_common_1.indent)(`return client.CreateSubscriptionStream<Response>(Request(${((_d = node.variableDefinitions) === null || _d === void 0 ? void 0 : _d.length) ? 'variables' : ''}), exceptionHandler);`),
`}`,
].join('\n');
}
}
throw new Error(`Unexpected operation type: ${node.operation}`);
}
OperationDefinition(node) {
var _a;
if (!node.name || !node.name.value) {
return null;
}
this._collectedOperations.push(node);
const documentVariableName = this.convertName(node, {
suffix: this.config.documentVariableSuffix,
prefix: this.config.documentVariablePrefix,
useTypesPrefix: false,
});
let documentString = '';
if (this.config.documentMode !== visitor_plugin_common_1.DocumentMode.external) {
const gqlBlock = (0, visitor_plugin_common_1.indentMultiline)(this._gql(node), 4);
documentString = `${this.config.noExport ? '' : 'public'} static string ${(0, c_sharp_common_1.convertSafeName)(documentVariableName)} = @"\n${gqlBlock}";`;
}
const operationType = node.operation;
const operationTypeSuffix = this.config.dedupeOperationSuffix && node.name.value.toLowerCase().endsWith(node.operation)
? ''
: !operationType
? ''
: operationType;
const operationResultType = this.convertName(node, {
suffix: operationTypeSuffix + this._parsedConfig.operationResultSuffix,
});
const operationVariablesTypes = this.convertName(node, {
suffix: operationTypeSuffix + 'Variables',
});
const serviceName = `${this.convertName(node)}${this._operationSuffix(operationType)}`;
this._operationsToInclude.push({
node,
documentVariableName,
operationType,
operationResultType,
operationVariablesTypes,
});
const inputSignatures = (_a = node.variableDefinitions) === null || _a === void 0 ? void 0 : _a.map(v => this._gqlInputSignature(v));
const hasInputArgs = !!(inputSignatures === null || inputSignatures === void 0 ? void 0 : inputSignatures.length);
const inputArgsHint = hasInputArgs
? `
/// <para>Required variables:<br/> { ${inputSignatures
.filter(sig => sig.required)
.map(sig => sig.signature)
.join(', ')} }</para>
/// <para>Optional variables:<br/> { ${inputSignatures
.filter(sig => !sig.required)
.map(sig => sig.signature)
.join(', ')} }</para>`
: '';
// Should use ObsoleteAttribute but VS treats warnings as errors which would be super annoying so use remarks comment instead
const obsoleteMessage = '/// <remarks>This method is obsolete. Use Request instead.</remarks>';
let typesafeOperations = '';
if (this.config.typesafeOperation) {
typesafeOperations = `
${this._getVariablesClass(node)}
${this._getResponseClass(node)}
${this._getOperationMethod(node)}
`;
typesafeOperations = (0, visitor_plugin_common_1.indentMultiline)(typesafeOperations, 3);
}
const content = `
public class ${serviceName} {
/// <summary>
/// ${serviceName}.Request ${inputArgsHint}
/// </summary>
public static GraphQLRequest Request(${hasInputArgs ? 'object variables = null' : ''}) {
return new GraphQLRequest {
Query = ${this._getDocumentNodeVariable(node, documentVariableName)},
OperationName = "${node.name.value}"${hasInputArgs
? `,
Variables = variables`
: ''}
};
}
${obsoleteMessage}
public static GraphQLRequest get${serviceName}() {
return Request();
}
${this._namedClient(node)}
${documentString}
${typesafeOperations}
}
`;
return [content].filter(a => a).join('\n');
}
InputObjectTypeDefinition(node) {
var _a;
if (!this.config.typesafeOperation) {
return '';
}
const inputClass = new c_sharp_common_1.CSharpDeclarationBlock()
.access('public')
.asKind('class')
.withName((0, c_sharp_common_1.convertSafeName)(this.convertName(node)))
.withBlock('\n' +
((_a = node.fields) === null || _a === void 0 ? void 0 : _a.map(f => {
if (f.kind !== graphql_1.Kind.INPUT_VALUE_DEFINITION) {
return null;
}
const inputType = this.resolveFieldType(f.type);
const inputTypeName = (0, c_sharp_common_1.wrapFieldType)(inputType, inputType.listType, 'System.Collections.Generic.List');
const propertyName = (0, c_sharp_common_1.convertSafeName)(this._parsedConfig.memberNamingFunction(f.name.value));
return (0, visitor_plugin_common_1.indentMultiline)([
`[${this.config.jsonAttributesConfiguration.propertyAttribute}("${f.name.value}")]`,
`public ${inputTypeName} ${propertyName} { get; set; }`,
].join('\n') + '\n');
}).filter(f => !!f).join('\n'))).string;
return (0, visitor_plugin_common_1.indentMultiline)(inputClass, 2);
}
EnumTypeDefinition(node) {
var _a;
if (!this.config.typesafeOperation) {
return '';
}
const enumDefinition = new c_sharp_common_1.CSharpDeclarationBlock()
.access('public')
.asKind('enum')
.withName((0, c_sharp_common_1.convertSafeName)(this.convertName(node.name)))
.withBlock((0, visitor_plugin_common_1.indentMultiline)((_a = node.values) === null || _a === void 0 ? void 0 : _a.map(v => `${this.config.jsonAttributesConfiguration.enumConfiguration.enumMemberAttribute(v.name.value)}\n${this._parsedConfig.memberNamingFunction(v.name.value)}`).join(',\n')));
const enumWithAttributes = `${this.config.jsonAttributesConfiguration.enumConfiguration.decorator}\n${enumDefinition.string}`;
return (0, visitor_plugin_common_1.indentMultiline)(enumWithAttributes, 2);
}
}
exports.CSharpOperationsVisitor = CSharpOperationsVisitor;
;