UNPKG

@graphql-codegen/c-sharp-operations

Version:

GraphQL Code Generator plugin for generating CSharp code based on GraphQL operations

431 lines (429 loc) • 20.9 kB
"use strict"; 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 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, scalars: (0, visitor_plugin_common_1.buildScalarsFromConfig)(schema, rawConfig, c_sharp_common_1.C_SHARP_SCALARS), typesafeOperation: rawConfig.typesafeOperation || false, }, documents); this._operationsToInclude = []; this.overruleConfigSettings(); (0, auto_bind_1.default)(this); this._schemaAST = (0, plugin_helpers_1.getCachedDocumentNodeFromSchema)(schema); } // 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 fragments = this._transformFragments(node); 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] || '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', 'Newtonsoft.Json', '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, required, valueType: (0, c_sharp_common_1.isValueType)(baseType), }, 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) { switch (node.kind) { case graphql_1.Kind.OPERATION_DEFINITION: { 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); }) .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'); return (0, visitor_plugin_common_1.indentMultiline)([ `[JsonProperty("${node.name.value}")]`, `public ${responseTypeName} ${(0, c_sharp_common_1.convertSafeName)(node.name.value)} { get; set; }`, ].join('\n') + '\n'); } const selectionBaseTypeName = `${responseType.baseType.type}Selection`; 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 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); }) .join('\n')).string; return (0, visitor_plugin_common_1.indentMultiline)([ innerClassDefinition, `[JsonProperty("${node.name.value}")]`, `public ${selectionTypeName} ${(0, c_sharp_common_1.convertSafeName)(node.name.value)} { get; set; }`, ].join('\n') + '\n'); } case graphql_1.Kind.FRAGMENT_SPREAD: { const fragmentSchema = this._fragments.find(f => f.name === node.name.value); if (!fragmentSchema) { throw new Error(`Fragment schema not found; ${node.name.value}`); } 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); }) .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); } _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'); return (0, visitor_plugin_common_1.indentMultiline)([ `[JsonProperty("${v.variable.name.value}")]`, `public ${inputTypeName} ${(0, c_sharp_common_1.convertSafeName)(v.variable.name.value)} { 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'); return (0, visitor_plugin_common_1.indentMultiline)([ `[JsonProperty("${f.name.value}")]`, `public ${inputTypeName} ${(0, c_sharp_common_1.convertSafeName)(f.name.value)} { 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 => v.name.value).join(',\n'))).string; return (0, visitor_plugin_common_1.indentMultiline)(enumDefinition, 2); } } exports.CSharpOperationsVisitor = CSharpOperationsVisitor;