Skip to content

Commit

Permalink
Merge pull request #1035 from reactiveui/find-derived-interfaces
Browse files Browse the repository at this point in the history
Add support for interfaces that compose other base refit interfaces.
  • Loading branch information
clairernovotny authored Jan 24, 2021
2 parents a1c68d6 + 794aa17 commit a0c0698
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 15 deletions.
11 changes: 0 additions & 11 deletions Directory.Build.targets

This file was deleted.

44 changes: 41 additions & 3 deletions InterfaceStubGenerator.Core/InterfaceStubGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,47 @@ sealed class PreserveAttribute : Attribute
{
methodSymbols.Add(methodSymbol!);
}
}

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>());
}
}

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

// 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 +152,7 @@ sealed class PreserveAttribute : Attribute
keyCount[keyName] = value;

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

}

Expand Down Expand Up @@ -390,6 +419,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 +430,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
2 changes: 1 addition & 1 deletion Refit.Tests/Refit.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="1.3.0" PrivateAssets="all" />
<PackageReference Include="coverlet.collector" Version="3.0.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
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 a0c0698

Please sign in to comment.