Code generation is a neat tool which alleviates the hassle of query strings while providing full fledge types for query results. The issue arises when you want to build from the generated types.
Let's give an example scenario where this problem arises, lets imagine you have a User type in edgedb and you create 4 different edgeql files:
CreateUser.edgeql
GetUser.edgeql
UpdateUser.edgeql
DeleteUser.edgeql
The actual edgeql within these files doesn't matter, as long as their all targeting the User type.
Currently, the code generator will generate a result type for each of the files. This leaves us with 4 different dotnet types representing 1 edgeql one; not good! In our dotnet codebase consuming the generated files, we'd have to model a type wrapping the generated ones like so:
public class UserModel
{
public Guid Id { get; set; }
public string Email { get; set; }
public string Name { get; set; }
public UserModel(CreateUserResult model)
{
Id = model.Id;
Name = model.Name;
Email = model.Email;
}
public UserModel(GetUserResult model)
{
Id = model.Id;
Name = model.Name;
Email = model.Email;
}
public void Update(UpdateUserResult model)
{
Id = model.Id;
Name = model.Name;
Email = model.Email;
}
}
The amount of code scales with the amount of files that use the User type. Other bindings don't seem to have this problem due to less restrictive type systems.
The Proposal
My idea is to add an additional file to the output, called TypeManifest.yaml, which maps generated types to the return type of the generated functions, the code generator will then use this file to generate result types.
Example
Let's use this type manifest idea to solve the above problem:
The code generator can then create the UserModel type, infer the properties by the codecs of each query function, and apply it as the result each function defined on the functions property.
The result of the generation will look something like this:
// UserModel.g.cs
public partial class UserModel
{
public Guid Id { get; set; }
public string Email { get; set; }
public string Name { get; set; }
}
// GetUser.g.cs
public class GetUser
{
public static async Task<UserModel> ExecuteAsync(IEdgeDBQueryable client, Guid id, CancellationToken token)
{
...
}
}
// UpdateUser.g.cs
public class UpdateUser
{
public static async Task<UserModel> ExecuteAsync(IEdgeDBQueryable client, Guid id, CancellationToken token)
{
...
}
}
...
We can also use interfaces, if the name of the type starts with an "I" the code generator can create an interface which has only properties shared between all functions using the model. This system can be extended by preforming introspection of the entire schema to build abstract types if present.
A default type manifest will be generated with the current logic if one isn't present, mapping out the default generated types. If one is present, the code generator will read the type manifest and then build the types using it as the template.
With this approach we can also include support for type converters by defining a way to change property types via the type manifest.
The Problem
Code generation is a neat tool which alleviates the hassle of query strings while providing full fledge types for query results. The issue arises when you want to build from the generated types.
Let's give an example scenario where this problem arises, lets imagine you have a
User
type in edgedb and you create 4 different edgeql files:The actual edgeql within these files doesn't matter, as long as their all targeting the
User
type.Currently, the code generator will generate a result type for each of the files. This leaves us with 4 different dotnet types representing 1 edgeql one; not good! In our dotnet codebase consuming the generated files, we'd have to model a type wrapping the generated ones like so:
The amount of code scales with the amount of files that use the
User
type. Other bindings don't seem to have this problem due to less restrictive type systems.The Proposal
My idea is to add an additional file to the output, called
TypeManifest.yaml
, which maps generated types to the return type of the generated functions, the code generator will then use this file to generate result types.Example
Let's use this type manifest idea to solve the above problem:
The code generator can then create the
UserModel
type, infer the properties by the codecs of each query function, and apply it as the result each function defined on thefunctions
property.The result of the generation will look something like this:
We can also use interfaces, if the name of the type starts with an "I" the code generator can create an interface which has only properties shared between all functions using the model. This system can be extended by preforming introspection of the entire schema to build abstract types if present.
A default type manifest will be generated with the current logic if one isn't present, mapping out the default generated types. If one is present, the code generator will read the type manifest and then build the types using it as the template.
With this approach we can also include support for type converters by defining a way to change property types via the type manifest.