Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Refit Does Not Generate Implementations if 3+ Interfaces Share a Name #1654

Closed
chrstophr-wltrs opened this issue Feb 12, 2024 · 4 comments
Labels

Comments

@chrstophr-wltrs
Copy link

chrstophr-wltrs commented Feb 12, 2024

Describe the bug 🐞

Hi all 👋

I'm working on integration tests for a handful of distinct-yet-related APIs. These are ASP.NET applications, and they group endpoints into "controllers" based on their related functionality. Some of these APIs have controllers with conflicting names, even though they are in different namespaces (ex "Two APIs each have a UserController").

As I'm working on different APIs, sometimes I run into the following scenario, where I have different namespaces with the same Refit interface:

  • Application.csproj
    • Foo/
      • IUser.cs
    • Bar/
      • IUser.cs
    • Baz/
      • IUser.cs

Foo, Bar, and Baz being different ASP.NET applications in this case. The interfaces are different, they just all have to do with managing users.

If I try to use any of these interfaces, for example with the following code

using Scratch.Application.Foo;

namespace Scratch.Tests.Foo;

public class UserTest
{
	[Test]
	public async Task CanSearchUsers()
	{
		var api = RestService.For<IUser>("http://example.com");
		var response = await api.ViewAll();
	}
}

I get an exception:

System.InvalidOperationException : IUser doesn't look like a Refit interface. Make sure it has at least one method with a Refit HTTP method attribute and Refit is installed in the project.
   at Refit.RestService.GetGeneratedType(Type refitInterfaceType) in /_/Refit/RestService.cs:line 169
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Refit.RestService.For(Type refitInterfaceType, HttpClient client, IRequestBuilder builder) in /_/Refit/RestService.cs:line 76
   at Refit.RestService.For[T](HttpClient client, IRequestBuilder`1 builder) in /_/Refit/RestService.cs:line 20
   at Refit.RestService.For[T](HttpClient client, RefitSettings settings) in /_/Refit/RestService.cs:line 34
   at Refit.RestService.For[T](String hostUrl, RefitSettings settings) in /_/Refit/RestService.cs:line 54
   at Refit.RestService.For[T](String hostUrl) in /_/Refit/RestService.cs:line 65
   at Scratch.Tests.Foo.UserTest.CanSearchUsers() in C:\Users\i35531\development\xm8.services.e2e.tests\Scratch\Tests\Foo\UserTest.cs:line 10

The interesting thing is, if you drill down to look at the generated types, the class name includes the full namespace:

/* IUser1.g.cs */
namespace Refit.Implementation
{

    partial class Generated
    {

    /// <inheritdoc />
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    [global::System.Diagnostics.DebuggerNonUserCode]
    [global::ScratchRefitInternalGenerated.PreserveAttribute]
    [global::System.Reflection.Obfuscation(Exclude=true)]
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    partial class ScratchApplicationFooIUser
        : global::Scratch.Application.Foo.IUser

    {

...

Because of this, I would think that name collisions wouldn't be an issue, since the implementing class uses the full namespace of the interface as its name.

The easiest way I've found to track whether the types are being generated is a small annotation that's provided by my IDE, in this case JetBrains Rider:

image

Visual Studio has a similar feature, where you can view the implementations of an interface after building a project. If the annotation is there, then I know Refit is working. If it's absent, I know that something is broken.

Step to reproduce

Follow these steps within a single C# project.

  1. Create 3 or more separate folders with distinct names
  2. In each folder, add an interface with the same name
  3. Add at least one Refit method to each interface- the method names do not matter in this case
  4. Attempt to instantiate any of the classes (ex var api = RestService.For<IApi>(baseUrl);)
  5. Run your application
  6. Note the exception thrown when you attempt to instantiate a class

Reproduction repository

https://github.com/reactiveui/refit

Expected behavior

Each interface should be auto-generated without issue, and accessed by specifying a namespace.

IDE

Visual Studio 2022, Rider Windows

Version

.NET 7, .NET 6

Refit Version

7.0.0

@chrstophr-wltrs
Copy link
Author

Workarounds

There are, of course, a number of workarounds.

Separate Projects

The interfaces for each "API" could be made their own project, with a separate .csproj file.

  • Foo.csproj
    • IUser.cs
  • Bar.csproj
    • IUser.cs
  • Baz.csproj
    • IUser.cs

This could be less convenient, depending on the size of each API, and managing all the cross-project references could become pretty complicated pretty quickly.

Partial Interfaces

Instead of each file being its own interface, the endpoints are all combined onto a single interface.

  • Application.csproj
    • Foo/
      • User.cs
    • Bar/
      • User.cs
    • Baz/
      • User.cs
/* /Foo/User.cs */
public partial interface IFoo
{
	[Get("/user")]
	Task<IApiResponse<List<UserDetails>>> ViewAllUsers();
}

Each other "controller" follows this same pattern, where the endpoints are separated into different files based on related functionality, but they're all put onto the interface of IFoo. The endpoints all need to be renamed for specificity, since they'll be in the namespace. Additionally, Intellisense gets pretty crowded, since the interface has access to all of its hundreds of endpoints at the same time.

Unique Names

Of course, one can always just rename the interfaces in question, so that they no longer have the same name. The scheme my team opted for looked something like this:

  • Application.csproj
    • Foo/
      • User_Foo.cs
    • Bar/
      • User_Bar.cs
    • Baz/
      • User_Baz.cs

Each API interface is suffixed with the name of the API to which it belongs. Everything is kept in the same project so there's only one reference to track, and the namespace isn't as cluttered as partial interfaces, but speaking subjectively, this solution looks a little "ugly".

@mithileshz
Copy link
Contributor

mithileshz commented Sep 29, 2024

Looks like this was fixed with #1542
I am not able to reproduce this with 7.2.1

@ChrisPulman
Copy link
Member

Looks like this was fixed with #1542 I am not able to reproduce this with 7.2.1

Thank you for confirming this, I will now close this.

Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 16, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants