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)); } }
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); }
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; }}"); }
public override void BeforeGeneration(GraphQlGeneratorConfiguration configuration) { _enums = _directives = _queryBuilders = _dataClasses = 0; base.BeforeGeneration(configuration); }
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"); } }
public override void BeforeGeneration(GraphQlGeneratorConfiguration configuration) { _files.Clear(); base.BeforeGeneration(configuration); }
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("}"); }