UNPKG

@graphql-codegen/c-sharp

Version:

GraphQL Code Generator plugin for generating CSharp code based on a GraphQL schema

333 lines (330 loc) • 14.7 kB
import { isEnumType, isInputObjectType, isScalarType, Kind, } from 'graphql'; import { C_SHARP_SCALARS, convertSafeName, CSharpDeclarationBlock, CSharpFieldType, getListInnerTypeNode, getListTypeField, getMemberNamingFunction, isValueType, transformComment, wrapFieldType, } from '@graphql-codegen/c-sharp-common'; import { BaseVisitor, buildScalarsFromConfig, getBaseTypeNode, indent, indentMultiline, } from '@graphql-codegen/visitor-plugin-common'; import { getJsonAttributeSourceConfiguration, } from './json-attributes.js'; export class CSharpResolversVisitor extends BaseVisitor { constructor(rawConfig, _schema) { var _a; super(rawConfig, { enumValues: rawConfig.enumValues || {}, listType: rawConfig.listType || 'List', namespaceName: rawConfig.namespaceName || 'GraphQLCodeGen', className: rawConfig.className || 'Types', emitRecords: rawConfig.emitRecords || false, emitJsonAttributes: (_a = rawConfig.emitJsonAttributes) !== null && _a !== void 0 ? _a : true, jsonAttributesSource: rawConfig.jsonAttributesSource || 'Newtonsoft.Json', scalars: buildScalarsFromConfig(_schema, rawConfig, C_SHARP_SCALARS), memberNamingFunction: getMemberNamingFunction(rawConfig), }); this._schema = _schema; if (this._parsedConfig.emitJsonAttributes) { this.jsonAttributesConfiguration = getJsonAttributeSourceConfiguration(this._parsedConfig.jsonAttributesSource); } } getImports() { const allImports = [ 'System', 'System.Collections.Generic', 'System.ComponentModel.DataAnnotations', ]; if (this._parsedConfig.emitJsonAttributes) { const jsonAttributesNamespace = this.jsonAttributesConfiguration.namespace; allImports.push(jsonAttributesNamespace); } return allImports.map(i => `using ${i};`).join('\n') + '\n'; } wrapWithNamespace(content) { return new CSharpDeclarationBlock() .asKind('namespace') .withName(this.config.namespaceName) .withBlock(indentMultiline(content)).string; } wrapWithClass(content) { return new CSharpDeclarationBlock() .access('public') .asKind('class') .withName(convertSafeName(this.config.className)) .withBlock(indentMultiline(content)).string; } getEnumValue(enumName, enumOption) { if (this.config.enumValues[enumName] && typeof this.config.enumValues[enumName] === 'object' && this.config.enumValues[enumName][enumOption]) { return this.config.enumValues[enumName][enumOption]; } return enumOption; } EnumValueDefinition(node) { return (enumName) => { const enumHeader = this.getFieldHeader(node); const enumOption = convertSafeName(node.name); return enumHeader + indent(this.getEnumValue(enumName, enumOption)); }; } EnumTypeDefinition(node) { const enumName = this.convertName(node.name); const enumValues = node.values .map(enumValue => enumValue(node.name.value)) .join(',\n'); const enumBlock = [enumValues].join('\n'); return new CSharpDeclarationBlock() .access('public') .asKind('enum') .withComment(node.description) .withName(enumName) .withBlock(enumBlock).string; } getFieldHeader(node, fieldType) { var _a; const attributes = []; const commentText = transformComment((_a = node.description) === null || _a === void 0 ? void 0 : _a.value); const deprecationDirective = node.directives.find(v => { var _a; return ((_a = v.name) === null || _a === void 0 ? void 0 : _a.value) === 'deprecated'; }); if (deprecationDirective) { const deprecationReason = this.getDeprecationReason(deprecationDirective); attributes.push(`[Obsolete("${deprecationReason}")]`); } if (this._parsedConfig.emitJsonAttributes && node.kind === Kind.FIELD_DEFINITION) { const jsonPropertyAttribute = this.jsonAttributesConfiguration.propertyAttribute; if (jsonPropertyAttribute != null) { attributes.push(`[${jsonPropertyAttribute}("${node.name.value}")]`); } } if (node.kind === Kind.INPUT_VALUE_DEFINITION && fieldType.isOuterTypeRequired) { // Should be always inserted for required fields to use in `GetInputObject()` when JSON attributes are not used // or there are no JSON attributes in selected attribute source that provides `JsonRequired` alternative attributes.push('[Required]'); if (this._parsedConfig.emitJsonAttributes) { const jsonRequiredAttribute = this.jsonAttributesConfiguration.requiredAttribute; if (jsonRequiredAttribute != null) { attributes.push(`[${jsonRequiredAttribute}]`); } } } if (commentText || attributes.length > 0) { const summary = commentText ? indentMultiline(commentText.trimRight()) + '\n' : ''; const attributeLines = attributes.length > 0 ? attributes .map(attr => indent(attr)) .concat('') .join('\n') : ''; return summary + attributeLines; } return ''; } getDeprecationReason(directive) { if (directive.name.value !== 'deprecated') { return ''; } const hasArguments = directive.arguments.length > 0; let reason = 'Field no longer supported'; if (hasArguments && directive.arguments[0].value.kind === Kind.STRING) { reason = directive.arguments[0].value.value; } return reason; } resolveInputFieldType(typeNode, hasDefaultValue = false) { const innerType = getBaseTypeNode(typeNode); const schemaType = this._schema.getType(innerType.name.value); const listType = getListTypeField(typeNode); const required = getListInnerTypeNode(typeNode).kind === Kind.NON_NULL_TYPE; let result = null; if (isScalarType(schemaType)) { if (this.scalars[schemaType.name]) { const baseType = this.scalars[schemaType.name]; result = new CSharpFieldType({ baseType: { type: baseType.input, required, valueType: isValueType(baseType.input), }, listType, }); } else { result = new CSharpFieldType({ baseType: { type: 'object', required, valueType: false, }, listType, }); } } else if (isInputObjectType(schemaType)) { result = new CSharpFieldType({ baseType: { type: `${this.convertName(schemaType.name)}`, required, valueType: false, }, listType, }); } else if (isEnumType(schemaType)) { result = new CSharpFieldType({ baseType: { type: this.convertName(schemaType.name), required, valueType: true, }, listType, }); } else { result = new 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; } buildRecord(name, description, inputValueArray, interfaces) { const classSummary = transformComment(description === null || description === void 0 ? void 0 : description.value); const interfaceImpl = interfaces && interfaces.length > 0 ? ` : ${interfaces.map(ntn => ntn.name.value).join(', ')}` : ''; const recordMembers = inputValueArray .map(arg => { const fieldType = this.resolveInputFieldType(arg.type); const fieldHeader = this.getFieldHeader(arg, fieldType); const fieldName = convertSafeName(this._parsedConfig.memberNamingFunction(this.convertName(arg.name))); const csharpFieldType = wrapFieldType(fieldType, fieldType.listType, this.config.listType); return (fieldHeader + indent(`public ${csharpFieldType} ${fieldName} { get; init; } = ${fieldName};`)); }) .join('\n\n'); const recordInitializer = inputValueArray .map(arg => { const fieldType = this.resolveInputFieldType(arg.type); const fieldName = convertSafeName(this._parsedConfig.memberNamingFunction(this.convertName(arg.name))); const csharpFieldType = wrapFieldType(fieldType, fieldType.listType, this.config.listType); return `${csharpFieldType} ${fieldName}`; }) .join(', '); return ` #region ${name} ${classSummary}public record ${convertSafeName(name)}(${recordInitializer})${interfaceImpl} { #region members ${recordMembers} #endregion } #endregion`; } buildClass(name, description, inputValueArray, interfaces) { const classSummary = transformComment(description === null || description === void 0 ? void 0 : description.value); const interfaceImpl = interfaces && interfaces.length > 0 ? ` : ${interfaces.map(ntn => ntn.name.value).join(', ')}` : ''; const classMembers = inputValueArray .map(arg => { const fieldType = this.resolveInputFieldType(arg.type); const fieldAttribute = this.getFieldHeader(arg, fieldType); const fieldName = convertSafeName(this._parsedConfig.memberNamingFunction(arg.name)); const csharpFieldType = wrapFieldType(fieldType, fieldType.listType, this.config.listType); return fieldAttribute + indent(`public ${csharpFieldType} ${fieldName} { get; set; }`); }) .join('\n\n'); return ` #region ${name} ${classSummary}public class ${convertSafeName(name)}${interfaceImpl} { #region members ${classMembers} #endregion } #endregion`; } buildInterface(name, description, inputValueArray) { const classSummary = transformComment(description === null || description === void 0 ? void 0 : description.value); const classMembers = inputValueArray .map(arg => { const fieldType = this.resolveInputFieldType(arg.type); const fieldHeader = this.getFieldHeader(arg, fieldType); let fieldName; let getterSetter; if (this.config.emitRecords) { // record fieldName = convertSafeName(this._parsedConfig.memberNamingFunction(this.convertName(arg.name))); getterSetter = '{ get; }'; } else { fieldName = convertSafeName(this._parsedConfig.memberNamingFunction(arg.name)); getterSetter = '{ get; set; }'; } const csharpFieldType = wrapFieldType(fieldType, fieldType.listType, this.config.listType); return fieldHeader + indent(`${csharpFieldType} ${fieldName} ${getterSetter}`); }) .join('\n\n'); return ` ${classSummary}public interface ${convertSafeName(name)} { ${classMembers} }`; } buildInputTransformer(name, description, inputValueArray) { const classSummary = transformComment(description === null || description === void 0 ? void 0 : description.value); const classMembers = inputValueArray .map(arg => { const fieldType = this.resolveInputFieldType(arg.type, !!arg.defaultValue); const fieldHeader = this.getFieldHeader(arg, fieldType); const fieldName = convertSafeName(this._parsedConfig.memberNamingFunction(arg.name)); const csharpFieldType = wrapFieldType(fieldType, fieldType.listType, this.config.listType); return fieldHeader + indent(`public ${csharpFieldType} ${fieldName} { get; set; }`); }) .join('\n\n'); return ` #region ${name} ${classSummary}public class ${convertSafeName(name)} { #region members ${classMembers} #endregion #region methods public dynamic GetInputObject() { IDictionary<string, object> d = new System.Dynamic.ExpandoObject(); var properties = GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); foreach (var propertyInfo in properties) { var value = propertyInfo.GetValue(this); var defaultValue = propertyInfo.PropertyType.IsValueType ? Activator.CreateInstance(propertyInfo.PropertyType) : null; ${this._parsedConfig.emitJsonAttributes && this.jsonAttributesConfiguration.requiredAttribute != null ? ` var requiredProp = propertyInfo.GetCustomAttributes(typeof(${this.jsonAttributesConfiguration.requiredAttribute}Attribute), false).Length > 0; ` : ` var requiredProp = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false).Length > 0; `} if (requiredProp || value != defaultValue) { d[propertyInfo.Name] = value; } } return d; } #endregion } #endregion`; } InputObjectTypeDefinition(node) { const name = `${this.convertName(node)}`; return this.buildInputTransformer(name, node.description, node.fields); } ObjectTypeDefinition(node) { if (this.config.emitRecords) { return this.buildRecord(node.name.value, node.description, node.fields, node.interfaces); } return this.buildClass(node.name.value, node.description, node.fields, node.interfaces); } InterfaceTypeDefinition(node) { return this.buildInterface(node.name.value, node.description, node.fields); } }