graphql-platform icon indicating copy to clipboard operation
graphql-platform copied to clipboard

Add support for the default interface method implementation

Open livwvil opened this issue 3 months ago • 0 comments

Product

Hot Chocolate

Is your feature request related to a problem?

Lets imagine we want the Hot Chocolate to produce a schema like:

interface IChannel {
  id: ID!
  description: String!
  status: ChannelStatus!
}

union IAnotherChannel = ChannelTypeB | ChannelTypeC

type ChannelTypeA implements IChannel {
  id: ID!
  description: String!
  status: ChannelStatus!
  ip: String!
}

type ChannelTypeB implements IChannel {
  id: ID!
  description: String!
  status: ChannelStatus!
  port: String!
}

type ChannelTypeC implements IChannel {
  id: ID!
  description: String!
  status: ChannelStatus!
  adapter: String!
}

I have to use records in my project. So I did somethink like:

[GraphQLName("IChannel")]
public interface IChannelDto
{
    [ID]
    Guid Id { get; init; }

    string Description { get; init; }

    #region workaround
    // TODO: delete after Hot Chocolate Team fixes one of two issue:
    // - ExtendObjectType should extend interface and all derived classes
    // - interface default methods implementations must be used in derived classes
    Task<ChannelStatus> GetStatusAsync() { throw new NotImplementedException(); }
    #endregion
}

[ExtendObjectType(typeof(IChannelDto))]
public class ChannelResolvers
{
    public async Task<ChannelStatus> GetStatusAsync()
    {
        // Do some stuff
    }
}

[GraphQLName("ChannelTypeA")]
public record ChannelTypeADto
(
    [property: ID] Guid Id,
    string Description,
    string IP,
) : IChannelDto;

[UnionType("IAnotherChannel")]
public interface IAnotherChannelDto : IChannelDto { };

[GraphQLName("ChannelTypeB")]
public record ChannelTypeBDto
(
    [property: ID] Guid Id,
    string Description,
    string Port,
) : IAnotherChannelDto;

[GraphQLName("ChannelTypeC")]
public record ChannelTypeCDto
(
    [property: ID] Guid Id,
    string Description,
    string Adapter,
) : IAnotherChannelDto;

Here is a problem:

  1. The ExtendObjectType annotation does not extend interfaces by design but I need it, so I made a workaround. That stub function is never called and I only need it to create the schema.
  2. I can't just remove the class with the ExtendObjectType annotation and put the default implementation inside the interface. Hot chocolate want me to add a Status field inside all derived records.

I must use both the ExtendObjectType annotation and that stub default implementation in the interface. If I could replace records with classes, I would do something like:

[InterfaceType("IChannel")]
public abstract class ChannelDto
{
    [ID]
    public Guid Id { get; init; }

    public string Description { get; init; }

    public async Task<ChannelStatus> GetStatusAsync()
    {
        // Do some stuff
    }
}

[GraphQLName("ChannelTypeA")]
public class ChannelTypeADto : ChannelDto
{
    public string IP { get; init; }
}

[UnionType("IAnotherChannel")]
public abstract class AnotherChannelDto : ChannelDto { };

[GraphQLName("ChannelTypeB")]
public class ChannelTypeBDto : AnotherChannelDto
{
    public string Port { get; init; }
}

[GraphQLName("ChannelTypeC")]
public class ChannelTypeCDto : AnotherChannelDto
{ 
    public string Adapter { get; init; }
}

This works nice!

The solution you'd like

One of:

  1. Let the ExtendObjectType annotation extend interfaces.
  2. Add support for the default method implementation in the schema inference algorithm.

livwvil avatar Mar 29 '24 15:03 livwvil