UNPKG

@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
"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 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;