Skip to content

Commit

Permalink
Add support for interfaces that compose other base refit interfaces.
Browse files Browse the repository at this point in the history
  • Loading branch information
clairernovotny committed Jan 24, 2021
1 parent a1c68d6 commit 78a619c
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 3 deletions.
47 changes: 44 additions & 3 deletions InterfaceStubGenerator.Core/InterfaceStubGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,46 @@ sealed class PreserveAttribute : Attribute

var keyCount = new Dictionary<string, int>();

var interfaces = methodSymbols.GroupBy(m => m.ContainingType).ToDictionary(g => g.Key, v => v.ToList());




// Look through the candidate interfaces
var interfaceSymbols = new List<INamedTypeSymbol>();
foreach(var iface in receiver.CandidateInterfaces)
{
var model = compilation.GetSemanticModel(iface.SyntaxTree);

// get the symbol belonging to the interface
var ifaceSymbol = model.GetDeclaredSymbol(iface);

// See if we already know about it, might be a dup
if (ifaceSymbol is null || interfaces.ContainsKey(ifaceSymbol))
continue;

// The interface has no refit methods, but its base interfaces might
var hasDerivedRefit = ifaceSymbol.AllInterfaces
.SelectMany(i => i.GetMembers().OfType<IMethodSymbol>())
.Where(m => IsRefitMethod(m, httpMethodBaseAttributeSymbol))
.Any();

if(hasDerivedRefit)
{
// Add the interface to the generation list with an empty set of methods
// The logic already looks for base refit methods
interfaces.Add(ifaceSymbol, new List<IMethodSymbol>());
}
}

// group the fields by interface and generate the source
foreach (var group in methodSymbols.GroupBy(m => m.ContainingType))
foreach (var group in interfaces)
{
// each group is keyed by the Interface INamedTypeSymbol and contains the members
// with a refit attribute on them. Types may contain other members, without the attribute, which we'll
// need to check for and error out on

var classSource = ProcessInterface(group.Key, group.ToList(), preserveAttributeSymbol, disposableInterfaceSymbol, httpMethodBaseAttributeSymbol, context);
var classSource = ProcessInterface(group.Key, group.Value, preserveAttributeSymbol, disposableInterfaceSymbol, httpMethodBaseAttributeSymbol, context);

var keyName = group.Key.Name;
if(keyCount.TryGetValue(keyName, out var value))
Expand All @@ -123,7 +155,7 @@ sealed class PreserveAttribute : Attribute
keyCount[keyName] = value;

context.AddSource($"{keyName}_refit.cs", SourceText.From(classSource, Encoding.UTF8));
}
}

}

Expand Down Expand Up @@ -390,6 +422,8 @@ class SyntaxReceiver : ISyntaxReceiver
{
public List<MethodDeclarationSyntax> CandidateMethods { get; } = new();

public List<InterfaceDeclarationSyntax> CandidateInterfaces { get; } = new();

public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
// We're looking for methods with an attribute that are in an interfaces
Expand All @@ -399,6 +433,13 @@ methodDeclarationSyntax.Parent is InterfaceDeclarationSyntax &&
{
CandidateMethods.Add(methodDeclarationSyntax);
}

// We also look for interfaces that derive from others, so we can see if any base methods contain
// Refit methods
if(syntaxNode is InterfaceDeclarationSyntax iface && iface.BaseList is not null)
{
CandidateInterfaces.Add(iface);
}
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions Refit.Tests/InheritedInterfacesApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public interface IAmInterfaceF_RequireUsing
[Get("/get-requiring-using")]
Task<ResponseModel> Get(List<Guid> guids);
}

public interface IContainAandB : IAmInterfaceB, IAmInterfaceA
{

}
}

namespace Refit.Tests.SeparateNamespaceWithModel
Expand Down
25 changes: 25 additions & 0 deletions Refit.Tests/RestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1685,6 +1685,31 @@ public async Task InheritedMethodTest()
mockHttp.VerifyNoOutstandingExpectation();
}


[Fact]
public async Task InheritedInterfaceWithOnlyBaseMethodsTest()
{
var mockHttp = new MockHttpMessageHandler();

var settings = new RefitSettings
{
HttpMessageHandlerFactory = () => mockHttp
};

var fixture = RestService.For<IContainAandB>("https://httpbin.org", settings);

mockHttp.Expect(HttpMethod.Get, "https://httpbin.org/get").Respond("application/json", nameof(IAmInterfaceA.Ping));
var resp = await fixture.Ping();
Assert.Equal(nameof(IAmInterfaceA.Ping), resp);
mockHttp.VerifyNoOutstandingExpectation();

mockHttp.Expect(HttpMethod.Get, "https://httpbin.org/get")
.Respond("application/json", nameof(IAmInterfaceB.Pong));
resp = await fixture.Pong();
Assert.Equal(nameof(IAmInterfaceB.Pong), resp);
mockHttp.VerifyNoOutstandingExpectation();
}

[Fact]
public async Task DictionaryDynamicQueryparametersTest()
{
Expand Down

0 comments on commit 78a619c

Please sign in to comment.