Husqvik / GraphQlClientGenerator

GraphQL C# client generator
MIT License
213 stars 49 forks source link

Feature Request - Add Automatic Extraction of Interfaces to this library #134

Closed ohadcohen1111 closed 9 months ago

ohadcohen1111 commented 1 year ago

I've been using this library to generate a schema file for my GraphQL API, and it's been a great tool so far. However, I've noticed that the library only generates classes for the schema types, and I've had to manually extract these classes to interfaces.

To simplify this process, I've developed a tool that can automatically extract all the classes (in the data classes region by default) to interfaces. The tool requires some arguments such as the file to save and whether to save the interfaces declaration in the same file or in a separate file.

I think this tool could be beneficial for other developers who are using the graphQlClientGenerator library and facing similar challenges. Therefore, I would like to request that the library be enhanced to include an option to automatically extract interfaces from the generated schema classes.

Lastly, I would like to express my interest in hearing your opinion on this proposed feature. Your feedback and guidance would be invaluable in determining the feasibility and potential impact of this feature.

Thank you in advance for your time and consideration.

Husqvik commented 1 year ago

I believe the best approach would be to extend the BeforeDataClassGeneration method of GenerationContext to include full context of the generated class which can be simply used to generate anything else together with the object. So it solves your problem without adding any case specific code into the core of the generator.

ohadcohen1111 commented 1 year ago

Thank you for your prompt response to my feature request.

I appreciate your suggestion to extend the BeforeDataClassGeneration method of GenerationContext. However, I would appreciate it if you could provide more details on how you envision this approach to work.

Specifically, should I include the interface declaration in the BeforeDataClassGeneration method and write it immediately to the file?

Additionally, would I need to change the class signature to the class that implements the interface I created?

Thank you in advance for your guidance on this matter. Your expertise and support are greatly appreciated.

Husqvik commented 1 year ago

I refactored some of the internal helpers so they are now exposed from GenerationContext. Otherwise you would have to duplicate quite a lot code to match internal generator behavior.

Using the very last version (Nuget package not available yet) you can define your own generation context like this:

class ExtractInterfaceGenerationContext : SingleFileGenerationContext
{
    public ExtractInterfaceGenerationContext(GraphQlSchema schema, TextWriter writer, GeneratedObjectType objectTypes = GeneratedObjectType.All, byte indentationSize = 0)
        : base(schema, writer, objectTypes, indentationSize)
    {
    }

    public override void BeforeDataClassGeneration(ObjectGenerationContext context)
    {
        if (context.GraphQlType.Kind == GraphQlTypeKind.Object)
        {
            var interfaceTypeName = $"I{context.CSharpTypeName}";

            Writer.WriteLine();
            Writer.Write("public interface ");
            Writer.WriteLine(interfaceTypeName);
            Writer.WriteLine("{");

            var fieldsToGenerate = GetFieldsToGenerate(context.GraphQlType);
            foreach (var field in fieldsToGenerate)
            {
                var propertyTypeDescription = GetDataPropertyType(context.GraphQlType, field);
                var propertyTypeName = propertyTypeDescription.NetTypeName;

                Writer.Write("    ");
                Writer.Write(propertyTypeName);
                Writer.Write(" ");
                Writer.Write(NamingHelper.ToPascalCase(field.Name));
                Writer.WriteLine(" { get; }");
            }

            Writer.WriteLine("}");
            Writer.WriteLine();

            Writer.Write("public partial class ");
            Writer.Write(context.CSharpTypeName);
            Writer.Write(" : ");
            Writer.WriteLine(interfaceTypeName);
            Writer.WriteLine("{");
            Writer.WriteLine("}");
        }

        base.BeforeDataClassGeneration(context);
    }
}

and execute geneneration using

var generator = new GraphQlGenerator();
var builder = new StringBuilder();
using var writer = new StringWriter(builder);
generator.Generate(new ExtractInterfaceGenerationContext(schema, writer, GeneratedObjectType.BaseClasses | GeneratedObjectType.DataClasses, 0));
var csharpCode = builder.ToString();

the generated classes must be created as partial to be able to use multiple definitions needed to attach the newly defined interface.

ohadcohen1111 commented 1 year ago

Thank you for providing the code and helping me with my request to automatically extract interfaces from the generated schema classes. From what I understand, I don't need to add anything to your code, is that correct?

I also wanted to share with you a project I've been working on before you brought me your add-on. You can find it here: https://github.com/ohadcohen1111/GraphQlApiClientCodeGenerator

If there is anything else you would like me to add or change in your code, please let me know. Thank you again for your assistance.