private static string ScalarToNetType(GraphQlType baseType, string valueName, GraphQlTypeBase valueType)
        {
            switch (valueType.Name)
            {
            case GraphQlTypeBase.GraphQlTypeScalarInteger:
                return("int?");

            case GraphQlTypeBase.GraphQlTypeScalarByte:
                return("byte?");

            case GraphQlTypeBase.GraphQlTypeScalarString:
                return(GraphQlGeneratorConfiguration.CustomScalarFieldTypeMapping(baseType, valueType, valueName));

            case GraphQlTypeBase.GraphQlTypeScalarFloat:
                return("decimal?");

            case GraphQlTypeBase.GraphQlTypeScalarBoolean:
                return("bool?");

            case GraphQlTypeBase.GraphQlTypeScalarId:
                return("Guid?");

            case GraphQlTypeBase.GraphQlTypeScalarDateTime:
                return("DateTime?");

            default:
                return(GraphQlGeneratorConfiguration.CustomScalarFieldTypeMapping(baseType, valueType, valueName));
            }
        }
예제 #2
0
        private static string GetDataPropertyType(GraphQlType baseType, IGraphQlMember member)
        {
            string propertyType;
            var    fieldType = member.Type.UnwrapIfNonNull();

            switch (fieldType.Kind)
            {
            case GraphQlTypeKind.Object:
            case GraphQlTypeKind.Interface:
            case GraphQlTypeKind.Union:
            case GraphQlTypeKind.InputObject:
                var fieldTypeName = fieldType.Name;
                fieldTypeName = UseCustomClassNameIfDefined(fieldTypeName);
                propertyType  = $"{fieldTypeName}{GraphQlGeneratorConfiguration.ClassPostfix}";
                return(AddQuestionMarkIfNullableReferencesEnabled(propertyType));

            case GraphQlTypeKind.Enum:
                return(GraphQlGeneratorConfiguration.CustomScalarFieldTypeMapping(baseType, member.Type, member.Name));

            case GraphQlTypeKind.List:
                var itemTypeName = fieldType.OfType.UnwrapIfNonNull().Name;
                itemTypeName = UseCustomClassNameIfDefined(itemTypeName);
                var itemType         = IsUnknownObjectScalar(baseType, member.Name, fieldType.OfType) ? "object" : $"{itemTypeName}{GraphQlGeneratorConfiguration.ClassPostfix}";
                var suggestedNetType = ScalarToNetType(baseType, member.Name, fieldType.OfType).TrimEnd('?');
                if (!String.Equals(suggestedNetType, "object") && !String.Equals(suggestedNetType, "object?") && !suggestedNetType.TrimEnd().EndsWith("System.Object") && !suggestedNetType.TrimEnd().EndsWith("System.Object?"))
                {
                    itemType = suggestedNetType;
                }

                propertyType = $"ICollection<{itemType}>";

                return(AddQuestionMarkIfNullableReferencesEnabled(propertyType));

            case GraphQlTypeKind.Scalar:
                switch (fieldType.Name)
                {
                case GraphQlTypeBase.GraphQlTypeScalarInteger:
                    return(GetIntegerNetType(baseType, member.Type, member.Name));

                case GraphQlTypeBase.GraphQlTypeScalarString:
                    return(GetCustomScalarType(baseType, member.Type, member.Name));

                case GraphQlTypeBase.GraphQlTypeScalarFloat:
                    return(GetFloatNetType(baseType, member.Type, member.Name));

                case GraphQlTypeBase.GraphQlTypeScalarBoolean:
                    return(GetBooleanNetType(baseType, member.Type, member.Name));

                case GraphQlTypeBase.GraphQlTypeScalarId:
                    return(GetIdNetType(baseType, member.Type, member.Name));

                default:
                    return(GetCustomScalarType(baseType, member.Type, member.Name));
                }

            default:
                return(AddQuestionMarkIfNullableReferencesEnabled("string"));
            }
        }
        private static string GetIdNetType(GraphQlType baseType, GraphQlTypeBase valueType, string valueName)
        {
            switch (GraphQlGeneratorConfiguration.IdType)
            {
            case IdType.String: return("string");

            case IdType.Guid: return("Guid?");

            case IdType.Object: return("object");

            case IdType.Custom: return(GraphQlGeneratorConfiguration.CustomScalarFieldTypeMapping(baseType, valueType, valueName));

            default: throw new InvalidOperationException($"'{GraphQlGeneratorConfiguration.IdType}' not supported");
            }
        }
        private static string GetCustomScalarType(GraphQlType baseType, GraphQlTypeBase valueType, string valueName)
        {
            if (GraphQlGeneratorConfiguration.CustomScalarFieldTypeMapping == null)
            {
                throw new InvalidOperationException($"'{nameof(GraphQlGeneratorConfiguration.CustomScalarFieldTypeMapping)}' missing");
            }

            var netType = GraphQlGeneratorConfiguration.CustomScalarFieldTypeMapping(baseType, valueType, valueName);

            if (String.IsNullOrWhiteSpace(netType))
            {
                throw new InvalidOperationException($".NET type for '{baseType.Name}.{valueName}' ({valueType.Name}) cannot be resolved. Please check {nameof(GraphQlGeneratorConfiguration)}.{nameof(GraphQlGeneratorConfiguration.CustomScalarFieldTypeMapping)} implementation. ");
            }

            return(netType);
        }
예제 #5
0
 public virtual void BeforeGeneration(GraphQlGeneratorConfiguration configuration) => Configuration = configuration;
        public void Execute(GeneratorExecutionContext context)
        {
            var compilation = context.Compilation as CSharpCompilation;
            if (compilation == null)
            {
                context.ReportDiagnostic(
                    Diagnostic.Create(
                        DescriptorParameterError,
                        Location.None,
                        "incompatible language: " + context.Compilation.Language));

                return;
            }

            try
            {
                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + "ServiceUrl", out var serviceUrl);
                var isServiceUrlMissing = String.IsNullOrWhiteSpace(serviceUrl);
                var graphQlSchemaFiles = context.AdditionalFiles.Where(f => String.Equals(Path.GetExtension(f.Path), ".json", StringComparison.OrdinalIgnoreCase)).ToList();
                var indexRegexScalarFieldTypeMappingProviderConfigurationFile =
                    graphQlSchemaFiles.FindIndex(f => String.Equals(Path.GetFileName(f.Path), FileNameRegexScalarFieldTypeMappingProviderConfiguration, StringComparison.OrdinalIgnoreCase));

                ICollection<RegexScalarFieldTypeMappingRule> regexScalarFieldTypeMappingProviderRules = null;
                if (indexRegexScalarFieldTypeMappingProviderConfigurationFile != -1)
                {
                    regexScalarFieldTypeMappingProviderRules =
                        JsonConvert.DeserializeObject<ICollection<RegexScalarFieldTypeMappingRule>>(
                            graphQlSchemaFiles[indexRegexScalarFieldTypeMappingProviderConfigurationFile].GetText().ToString());

                    graphQlSchemaFiles.RemoveAt(indexRegexScalarFieldTypeMappingProviderConfigurationFile);
                }

                var isSchemaFileSpecified = graphQlSchemaFiles.Any();
                if (isServiceUrlMissing && !isSchemaFileSpecified)
                {
                    context.ReportDiagnostic(
                        Diagnostic.Create(
                            DescriptorInfo,
                            Location.None,
                            "Neither \"GraphQlClientGenerator_ServiceUrl\" parameter nor GraphQL JSON schema additional file specified; terminating. "));

                    return;
                }

                if (!isServiceUrlMissing && isSchemaFileSpecified)
                {
                    context.ReportDiagnostic(
                        Diagnostic.Create(
                            DescriptorParameterError,
                            Location.None,
                            "\"GraphQlClientGenerator_ServiceUrl\" parameter and GraphQL JSON schema additional file are mutually exclusive. "));

                    return;
                }

                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + "Namespace", out var @namespace);
                if (String.IsNullOrWhiteSpace(@namespace))
                {
                    var root = (CompilationUnitSyntax)compilation.SyntaxTrees.FirstOrDefault()?.GetRoot();
                    var namespaceIdentifier = (IdentifierNameSyntax)root?.Members.OfType<NamespaceDeclarationSyntax>().FirstOrDefault()?.Name;
                    if (namespaceIdentifier == null)
                    {
                        context.ReportDiagnostic(
                            Diagnostic.Create(
                                DescriptorParameterError,
                                Location.None,
                                "\"GraphQlClientGenerator_Namespace\" required"));

                        return;
                    }

                    @namespace = namespaceIdentifier.Identifier.ValueText;

                    context.ReportDiagnostic(
                        Diagnostic.Create(
                            DescriptorInfo,
                            Location.None,
                            $"\"GraphQlClientGenerator_Namespace\" not specified; using \"{@namespace}\""));
                }

                var configuration = new GraphQlGeneratorConfiguration { TreatUnknownObjectAsScalar = true };

                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + "ClassPrefix", out var classPrefix);
                configuration.ClassPrefix = classPrefix;

                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + "ClassSuffix", out var classSuffix);
                configuration.ClassSuffix = classSuffix;

                if (compilation.LanguageVersion >= LanguageVersion.CSharp6)
                    configuration.CSharpVersion =
                        compilation.Options.NullableContextOptions == NullableContextOptions.Disable
                            ? CSharpVersion.Newest
                            : CSharpVersion.NewestWithNullableReferences;

                var currentParameterName = "IncludeDeprecatedFields";
                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + currentParameterName, out var includeDeprecatedFieldsRaw);
                configuration.IncludeDeprecatedFields = !String.IsNullOrWhiteSpace(includeDeprecatedFieldsRaw) && Convert.ToBoolean(includeDeprecatedFieldsRaw);

                currentParameterName = "CommentGeneration";
                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + currentParameterName, out var commentGenerationRaw);
                configuration.CommentGeneration =
                    String.IsNullOrWhiteSpace(commentGenerationRaw)
                        ? CommentGenerationOption.CodeSummary
                        : (CommentGenerationOption)Enum.Parse(typeof(CommentGenerationOption), commentGenerationRaw, true);

                currentParameterName = "FloatTypeMapping";
                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + currentParameterName, out var floatTypeMappingRaw);
                configuration.FloatTypeMapping =
                    String.IsNullOrWhiteSpace(floatTypeMappingRaw)
                        ? FloatTypeMapping.Decimal
                        : (FloatTypeMapping)Enum.Parse(typeof(FloatTypeMapping), floatTypeMappingRaw, true);

                currentParameterName = "BooleanTypeMapping";
                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + currentParameterName, out var booleanTypeMappingRaw);
                configuration.BooleanTypeMapping =
                    String.IsNullOrWhiteSpace(booleanTypeMappingRaw)
                        ? BooleanTypeMapping.Boolean
                        : (BooleanTypeMapping)Enum.Parse(typeof(BooleanTypeMapping), booleanTypeMappingRaw, true);

                currentParameterName = "IdTypeMapping";
                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + currentParameterName, out var idTypeMappingRaw);
                configuration.IdTypeMapping =
                    String.IsNullOrWhiteSpace(idTypeMappingRaw)
                        ? IdTypeMapping.Guid
                        : (IdTypeMapping)Enum.Parse(typeof(IdTypeMapping), idTypeMappingRaw, true);

                currentParameterName = "JsonPropertyGeneration";
                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + currentParameterName, out var jsonPropertyGenerationRaw);
                configuration.JsonPropertyGeneration =
                    String.IsNullOrWhiteSpace(jsonPropertyGenerationRaw)
                        ? JsonPropertyGenerationOption.CaseInsensitive
                        : (JsonPropertyGenerationOption)Enum.Parse(typeof(JsonPropertyGenerationOption), jsonPropertyGenerationRaw, true);

                currentParameterName = "CustomClassMapping";
                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + currentParameterName, out var customClassMappingRaw);
                if (!KeyValueParameterParser.TryGetCustomClassMapping(
                    customClassMappingRaw?.Split(new[] { '|', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries),
                    out var customMapping,
                    out var customMappingParsingErrorMessage))
                {
                    context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, customMappingParsingErrorMessage));
                    return;
                }

                foreach (var kvp in customMapping)
                    configuration.CustomClassNameMapping.Add(kvp);

                currentParameterName = "Headers";
                context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + currentParameterName, out var headersRaw);
                if (!KeyValueParameterParser.TryGetCustomHeaders(
                    headersRaw?.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries),
                    out var headers,
                    out var headerParsingErrorMessage))
                {
                    context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, headerParsingErrorMessage));
                    return;
                }

                currentParameterName = "ScalarFieldTypeMappingProvider";
                if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKeyPrefix + currentParameterName, out var scalarFieldTypeMappingProviderName))
                {
                    if (regexScalarFieldTypeMappingProviderRules != null)
                    {
                        context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, "\"GraphQlClientGenerator_ScalarFieldTypeMappingProvider\" and RegexScalarFieldTypeMappingProviderConfiguration are mutually exclusive"));
                        return;
                    }

                    if (String.IsNullOrWhiteSpace(scalarFieldTypeMappingProviderName))
                    {
                        context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, "\"GraphQlClientGenerator_ScalarFieldTypeMappingProvider\" value missing"));
                        return;
                    }

                    var scalarFieldTypeMappingProviderType = Type.GetType(scalarFieldTypeMappingProviderName);
                    if (scalarFieldTypeMappingProviderType == null)
                    {
                        context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, $"ScalarFieldTypeMappingProvider \"{scalarFieldTypeMappingProviderName}\" not found"));
                        return;
                    }

                    var scalarFieldTypeMappingProvider = (IScalarFieldTypeMappingProvider)Activator.CreateInstance(scalarFieldTypeMappingProviderType);
                    configuration.ScalarFieldTypeMappingProvider = scalarFieldTypeMappingProvider;
                }
                else if (regexScalarFieldTypeMappingProviderRules?.Count > 0)
                    configuration.ScalarFieldTypeMappingProvider = new RegexScalarFieldTypeMappingProvider(regexScalarFieldTypeMappingProviderRules);

                var graphQlSchemas = new List<(string TargetFileName, GraphQlSchema Schema)>();
                if (isSchemaFileSpecified)
                {
                    foreach (var schemaFile in graphQlSchemaFiles)
                    {
                        var targetFileName = Path.GetFileNameWithoutExtension(schemaFile.Path) + ".cs";
                        graphQlSchemas.Add((targetFileName, GraphQlGenerator.DeserializeGraphQlSchema(schemaFile.GetText().ToString())));
                    }
                }
                else
                {
                    graphQlSchemas.Add((FileNameGraphQlClientSource, GraphQlGenerator.RetrieveSchema(serviceUrl, headers).GetAwaiter().GetResult()));
                    context.ReportDiagnostic(
                        Diagnostic.Create(
                            DescriptorInfo,
                            Location.None,
                            "GraphQl schema fetched successfully from " + serviceUrl));
                }

                var generator = new GraphQlGenerator(configuration);

                foreach (var (targetFileName, schema) in graphQlSchemas)
                {
                    var builder = new StringBuilder();
                    using (var writer = new StringWriter(builder))
                        generator.WriteFullClientCSharpFile(schema, @namespace, writer);

                    context.AddSource(targetFileName, SourceText.From(builder.ToString(), Encoding.UTF8));
                }

                context.ReportDiagnostic(
                    Diagnostic.Create(
                        DescriptorInfo,
                        Location.None,
                        "GraphQlClientGenerator task completed successfully. "));
            }
            catch (Exception exception)
            {
                context.ReportDiagnostic(Diagnostic.Create(DescriptorGenerationError, Location.None, exception.Message));
            }
        }
        private static void GenerateDataProperty(GraphQlType baseType, IGraphQlMember member, bool isDeprecated, string deprecationReason, StringBuilder builder)
        {
            var propertyName = NamingHelper.ToPascalCase(member.Name);

            string propertyType;
            var    fieldType = UnwrapNonNull(member.Type);

            switch (fieldType.Kind)
            {
            case GraphQlTypeKindObject:
                propertyType = $"{fieldType.Name}{GraphQlGeneratorConfiguration.ClassPostfix}";
                break;

            case GraphQlTypeKindEnum:
                propertyType = $"{fieldType.Name}?";
                break;

            case GraphQlTypeKindList:
                var itemType         = IsObjectScalar(fieldType.OfType) ? "object" : $"{UnwrapNonNull(fieldType.OfType).Name}{GraphQlGeneratorConfiguration.ClassPostfix}";
                var suggestedNetType = ScalarToNetType(baseType, member.Name, UnwrapNonNull(fieldType.OfType)).TrimEnd('?');
                if (!String.Equals(suggestedNetType, "object"))
                {
                    itemType = suggestedNetType;
                }

                propertyType = $"ICollection<{itemType}>";
                break;

            case GraphQlTypeKindScalar:
                switch (fieldType.Name)
                {
                case "Int":
                    propertyType = "int?";
                    break;

                case "Byte":
                    propertyType = "byte?";
                    break;

                case "String":
                    propertyType = GraphQlGeneratorConfiguration.CustomScalarFieldTypeMapping(baseType, fieldType, member.Name);
                    break;

                case "Float":
                    propertyType = "decimal?";
                    break;

                case "Boolean":
                    propertyType = "bool?";
                    break;

                case "ID":
                    propertyType = "Guid?";
                    break;

                case "DateTime":
                    propertyType = "DateTime?";
                    break;

                default:
                    propertyType = GraphQlGeneratorConfiguration.CustomScalarFieldTypeMapping(baseType, fieldType, member.Name);
                    break;
                }

                break;

            default:
                propertyType = "string";
                break;
            }

            GenerateCodeComments(builder, member.Description);

            if (isDeprecated)
            {
                deprecationReason = String.IsNullOrWhiteSpace(deprecationReason) ? null : $"(@\"{deprecationReason.Replace("\"", "\"\"")}\")";
                builder.AppendLine($"    [Obsolete{deprecationReason}]");
            }

            builder.AppendLine($"    public {propertyType} {propertyName} {{ get; set; }}");
        }
예제 #8
0
 public override void BeforeGeneration(GraphQlGeneratorConfiguration configuration)
 {
     _enums = _directives = _queryBuilders = _dataClasses = 0;
     base.BeforeGeneration(configuration);
 }
예제 #9
0
        public static void GenerateDataClasses(GraphQlSchema schema, StringBuilder builder)
        {
            var inputTypes            = schema.Types.Where(t => t.Kind == GraphQlTypeKind.InputObject && !t.Name.StartsWith("__")).ToArray();
            var hasInputType          = inputTypes.Any();
            var referencedObjectTypes = new HashSet <string>();

            if (GraphQlGeneratorConfiguration.CSharpVersion == CSharpVersion.NewestWithNullableReferences)
            {
                builder.AppendLine("#nullable enable");
            }

            if (hasInputType)
            {
                builder.AppendLine();
                builder.AppendLine("#region input classes");

                for (var i = 0; i < inputTypes.Length; i++)
                {
                    var type = inputTypes[i];
                    FindAllReferencedObjectTypes(schema, type, referencedObjectTypes);
                    GenerateDataClass(type.Name, type.Description, "IGraphQlInputObject", builder, () => GenerateInputDataClassBody(type, type.InputFields.Cast <IGraphQlMember>().ToArray(), builder));

                    builder.AppendLine();

                    if (i < inputTypes.Length - 1)
                    {
                        builder.AppendLine();
                    }
                }

                builder.AppendLine("#endregion");
            }

            var complexTypes = schema.Types.Where(t => IsComplexType(t.Kind) && !t.Name.StartsWith("__")).ToArray();

            if (complexTypes.Any())
            {
                if (hasInputType)
                {
                    builder.AppendLine();
                }

                var complexTypeDictionary = complexTypes.ToDictionary(t => t.Name);

                builder.AppendLine("#region data classes");

                for (var i = 0; i < complexTypes.Length; i++)
                {
                    var type = complexTypes[i];
                    var hasInputReference = referencedObjectTypes.Contains(type.Name);
                    var fieldsToGenerate  = GetFieldsToGenerate(type, complexTypeDictionary);
                    var isInterface       = type.Kind == GraphQlTypeKind.Interface;

                    void GenerateBody(bool isInterfaceMember)
                    {
                        if (hasInputReference)
                        {
                            GenerateInputDataClassBody(type, (ICollection <IGraphQlMember>)fieldsToGenerate, builder);
                        }
                        else if (fieldsToGenerate != null)
                        {
                            var generateBackingFields = GraphQlGeneratorConfiguration.PropertyGeneration == PropertyGenerationOption.BackingField && !isInterfaceMember;
                            if (generateBackingFields)
                            {
                                foreach (var field in fieldsToGenerate)
                                {
                                    builder.Append("    private ");
                                    builder.Append(GetDataPropertyType(type, field));
                                    builder.Append(" ");
                                    builder.Append(GetBackingFieldName(field.Name));
                                    builder.AppendLine(";");
                                }

                                builder.AppendLine();
                            }

                            foreach (var field in fieldsToGenerate)
                            {
                                GenerateDataProperty(
                                    type,
                                    field,
                                    isInterfaceMember,
                                    field.IsDeprecated,
                                    field.DeprecationReason,
                                    true,
                                    (_, backingFieldName) =>
                                    builder.Append(generateBackingFields ? GraphQlGeneratorConfiguration.PropertyAccessorBodyWriter(backingFieldName, GetDataPropertyType(type, field)) : " { get; set; }"),
                                    builder);
                            }
                        }
                    }

                    var interfacesToImplement = new List <string>();
                    if (isInterface)
                    {
                        interfacesToImplement.Add(GenerateInterface("I" + type.Name, type.Description, builder, () => GenerateBody(true)));
                        builder.AppendLine();
                        builder.AppendLine();
                    }
                    else if (type.Interfaces?.Count > 0)
                    {
                        var fieldNames = new HashSet <string>(fieldsToGenerate.Select(f => f.Name));

                        foreach (var @interface in type.Interfaces)
                        {
                            interfacesToImplement.Add("I" + @interface.Name + GraphQlGeneratorConfiguration.ClassPostfix);

                            foreach (var interfaceField in complexTypeDictionary[@interface.Name].Fields.Where(FilterDeprecatedFields))
                            {
                                if (fieldNames.Add(interfaceField.Name))
                                {
                                    fieldsToGenerate.Add(interfaceField);
                                }
                            }
                        }
                    }

                    if (hasInputReference)
                    {
                        interfacesToImplement.Add("IGraphQlInputObject");
                    }

                    GenerateDataClass(type.Name, type.Description, String.Join(", ", interfacesToImplement), builder, () => GenerateBody(false));

                    builder.AppendLine();

                    if (i < complexTypes.Length - 1)
                    {
                        builder.AppendLine();
                    }
                }

                builder.AppendLine("#endregion");
            }

            if (GraphQlGeneratorConfiguration.CSharpVersion == CSharpVersion.NewestWithNullableReferences)
            {
                builder.AppendLine("#nullable disable");
            }
        }
예제 #10
0
 public override void BeforeGeneration(GraphQlGeneratorConfiguration configuration)
 {
     _files.Clear();
     base.BeforeGeneration(configuration);
 }
예제 #11
0
        public static void GenerateDataClass(GraphQlType type, StringBuilder builder)
        {
            var className = $"{type.Name}{GraphQlGeneratorConfiguration.ClassPostfix}";

            ValidateClassName(className);

            builder.Append("public class ");
            builder.AppendLine(className);
            builder.AppendLine("{");

            foreach (var field in type.Fields)
            {
                var propertyName = NamingHelper.CapitalizeFirst(field.Name);

                string propertyType;
                var    fieldType = UnwrapNonNull(field.Type);
                switch (fieldType.Kind)
                {
                case GraphQlTypeKindObject:
                    propertyType = $"{fieldType.Name}{GraphQlGeneratorConfiguration.ClassPostfix}";
                    break;

                case GraphQlTypeKindEnum:
                    propertyType = $"{fieldType.Name}?";
                    break;

                case GraphQlTypeKindList:
                    var itemType         = IsObjectScalar(fieldType.OfType) ? "object" : $"{UnwrapNonNull(fieldType.OfType).Name}{GraphQlGeneratorConfiguration.ClassPostfix}";
                    var suggestedNetType = ScalarToNetType(UnwrapNonNull(fieldType.OfType).Name).TrimEnd('?');
                    if (!String.Equals(suggestedNetType, "object"))
                    {
                        itemType = suggestedNetType;
                    }

                    propertyType = $"ICollection<{itemType}>";
                    break;

                case GraphQlTypeKindScalar:
                    switch (fieldType.Name)
                    {
                    case "Int":
                        propertyType = "int?";
                        break;

                    case "String":
                        propertyType = GraphQlGeneratorConfiguration.CustomScalarFieldMapping(field);
                        break;

                    case "Float":
                        propertyType = "decimal?";
                        break;

                    case "Boolean":
                        propertyType = "bool?";
                        break;

                    case "ID":
                        propertyType = "Guid?";
                        break;

                    default:
                        propertyType = "object";
                        break;
                    }

                    break;

                default:
                    propertyType = "string";
                    break;
                }

                if (GraphQlGeneratorConfiguration.GenerateComments && !String.IsNullOrWhiteSpace(field.Description))
                {
                    builder.AppendLine("    /// <summary>");
                    builder.AppendLine($"    /// {field.Description}");
                    builder.AppendLine("    /// </summary>");
                }

                builder.AppendLine($"    public {propertyType} {propertyName} {{ get; set; }}");
            }

            builder.Append("}");
        }