Skip to content

kirides/refitter

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Build Smoke Tests NuGet

All Contributors

Refitter

Refitter is a tool for generating a C# REST API Client using the Refit library. Refitter can generate the Refit interface and contracts from OpenAPI specifications.

Refitter comes in 2 forms:

CLI Tool

Installation:

The tool is packaged as a .NET Tool and is published to nuget.org. You can install the latest version of this tool like this:

dotnet tool install --global Refitter

Usage:

$ refitter --help
USAGE:
    refitter [URL or input file] [OPTIONS]

EXAMPLES:
    refitter ./openapi.json
    refitter https://petstore3.swagger.io/api/v3/openapi.yaml
    refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --output ./GeneratedCode.cs
    refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --internal
    refitter ./openapi.json --output ./IGeneratedCode.cs --interface-only
    refitter ./openapi.json --use-api-response
    refitter ./openapi.json --cancellation-tokens
    refitter ./openapi.json --no-operation-headers
    refitter ./openapi.json --use-iso-date-format
    refitter ./openapi.json --additional-namespace "Your.Additional.Namespace" --additional-namespace "Your.Other.Additional.Namespace"
    refitter ./openapi.json --multiple-interfaces

ARGUMENTS:
    [URL or input file]    URL or file path to OpenAPI Specification file

OPTIONS:
                                      DEFAULT                                                                                                                       
    -h, --help                                         Prints help information                                                                                      
    -n, --namespace                   GeneratedCode    Default namespace to use for generated types                                                                 
    -o, --output                      Output.cs        Path to Output file                                                                                          
        --no-auto-generated-header                     Don't add <auto-generated> header to output file                                                             
        --interface-only                               Don't generate contract types                                                                                
        --use-api-response                             Return Task<IApiResponse<T>> instead of Task<T>                                                              
        --internal                                     Set the accessibility of the generated types to 'internal'                                                   
        --cancellation-tokens                          Use cancellation tokens                                                                                      
        --no-operation-headers                         Don't generate operation headers                                                                             
        --no-logging                                   Don't log errors or collect telemetry                                                                        
        --additional-namespace                         Add additional namespace to generated types                                                                  
        --use-iso-date-format                          Explicitly format date query string parameters in ISO 8601 standard date format using delimiters (2023-06-15)
        --multiple-interfaces                          Generate a Refit interface for each endpoint                                                                                                                       

To generate code from an OpenAPI specifications file, run the following:

$ refitter [path to OpenAPI spec file] --namespace "[Your.Namespace.Of.Choice.GeneratedCode]"

This will generate a file called Output.cs which contains the Refit interface and contract classes generated using NSwag

Source Generator

Refitter is available as a C# Source Generator that uses the Refitter.Core library for generating a REST API Client using the Refit library. Refitter can generate the Refit interface from OpenAPI specifications

Installation

The source generator is distributed as a NuGet package and should be installed to the project that will contain the generated code

dotnet add package Refitter.SourceGenerator

Usage

This source generator generates code based on any .refitter file included to the project as AdditionalFiles.

The generator can automatically detect all .refitter files inside the project that referenced the Refitter.SourceGenerator package and there is no need to include them manually as AdditionalFiles

.Refitter File format

The following is an example .refitter file

{
  "openApiPath": "/path/to/your/openAPI", // Required
  "namespace": "Org.System.Service.Api.GeneratedCode", // Optional. Default=GeneratedCode
  "naming": {
    "useOpenApiTitle": false, // Optional. Default=true
    "interfaceName": "MyApiClient" // Optional. Default=ApiClient
  },
  "generateContracts": true, // Optional. Default=true
  "generateXmlDocCodeComments": true, // Optional. Default=true
  "addAutoGeneratedHeader": true, // Optional. Default=true
  "returnIApiResponse": false, // Optional. Default=false
  "generateOperationHeaders": true, // Optional. Default=true
  "typeAccessibility": "Public", // Optional. Values=Public|Internal. Default=Public
  "useCancellationTokens": false, // Optional. Default=false
  "useIsoDateFormat": false, // Optional. Default=false
  "multipleInterfaces": false, // Optional. Default=false
  "additionalNamespaces": [ // Optional
    "Namespace1",
    "Namespace2"
  ]
}
  • openApiPath - points to the OpenAPI Specifications file. This can be the path to a file stored on disk, relative to the .refitter file. This can also be a URL to a remote file that will be downloaded over HTTP/HTTPS
  • namespace - the namespace used in the generated code. If not specified, this defaults to GeneratedCode
  • naming.useOpenApiTitle - a boolean indicating whether the OpenApi title should be used. Default is true
  • naming.interfaceName - the name of the generated interface. The generated code will automatically prefix this with I so if this set to MyApiClient then the generated interface is called IMyApiClient. Default is ApiClient
  • generateContracts - a boolean indicating whether contracts should be generated. A use case for this is several API clients use the same contracts. Default is true
  • generateXmlDocCodeComments - a boolean indicating whether XML doc comments should be generated. Default is true
  • addAutoGeneratedHeader - a boolean indicating whether XML doc comments should be generated. Default is true
  • returnIApiResponse - a boolean indicating whether to return IApiResponse<T> objects. Default is false
  • generateOperationHeaders - a boolean indicating whether to use operation headers in the generated methods. Default is true
  • typeAccessibility - the generated type accessibility. Possible values are Public and Internal. Default is Public
  • useCancellationTokens - Use cancellation tokens in the generated methods. Default is false
  • useIsoDateFormat - Set to true to explicitly format date query string parameters in ISO 8601 standard date format using delimiters (for example: 2023-06-15). Default is false
  • multipleInterfaces - Set to true to generate an interface for each endpoint. Default is false
  • additionalNamespaces - A collection of additional namespaces to include in the generated file. A use case for this is when you want to reuse contracts from a different namespace than the generated code. Default is empty

Using the generated code

Here's an example generated output from the Swagger Petstore example using the default settings

CLI Tool

$ refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode"

Source Generator .refitter file

{
  "openApiPath": "./openapi.json",
  "namespace": "Your.Namespace.Of.Choice.GeneratedCode"
}

Output

using Refit;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace Your.Namespace.Of.Choice.GeneratedCode
{
    public interface ISwaggerPetstore
    {
        /// <summary>
        /// Update an existing pet by Id
        /// </summary>
        [Put("/pet")]
        Task<Pet> UpdatePet([Body] Pet body);

        /// <summary>
        /// Add a new pet to the store
        /// </summary>
        [Post("/pet")]
        Task<Pet> AddPet([Body] Pet body);

        /// <summary>
        /// Multiple status values can be provided with comma separated strings
        /// </summary>
        [Get("/pet/findByStatus")]
        Task<ICollection<Pet>> FindPetsByStatus([Query] Status? status);

        /// <summary>
        /// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
        /// </summary>
        [Get("/pet/findByTags")]
        Task<ICollection<Pet>> FindPetsByTags([Query(CollectionFormat.Multi)] IEnumerable<string> tags);

        /// <summary>
        /// Returns a single pet
        /// </summary>
        [Get("/pet/{petId}")]
        Task<Pet> GetPetById(long petId);

        [Post("/pet/{petId}")]
        Task UpdatePetWithForm(long petId, [Query] string name, [Query] string status);

        [Delete("/pet/{petId}")]
        Task DeletePet(long petId, [Header("api_key")] string api_key);

        [Post("/pet/{petId}/uploadImage")]
        Task<ApiResponse> UploadFile(long petId, [Query] string additionalMetadata, StreamPart body);

        /// <summary>
        /// Returns a map of status codes to quantities
        /// </summary>
        [Get("/store/inventory")]
        Task<IDictionary<string, int>> GetInventory();

        /// <summary>
        /// Place a new order in the store
        /// </summary>
        [Post("/store/order")]
        Task<Order> PlaceOrder([Body] Order body);

        /// <summary>
        /// For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
        /// </summary>
        [Get("/store/order/{orderId}")]
        Task<Order> GetOrderById(long orderId);

        /// <summary>
        /// For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
        /// </summary>
        [Delete("/store/order/{orderId}")]
        Task DeleteOrder(long orderId);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Post("/user")]
        Task CreateUser([Body] User body);

        /// <summary>
        /// Creates list of users with given input array
        /// </summary>
        [Post("/user/createWithList")]
        Task<User> CreateUsersWithListInput([Body] IEnumerable<User> body);

        [Get("/user/login")]
        Task<string> LoginUser([Query] string username, [Query] string password);

        [Get("/user/logout")]
        Task LogoutUser();

        [Get("/user/{username}")]
        Task<User> GetUserByName(string username);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Put("/user/{username}")]
        Task UpdateUser(string username, [Body] User body);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Delete("/user/{username}")]
        Task DeleteUser(string username);
    }
}

Here's an example generated output from the Swagger Petstore example configured to wrap the return type in IApiResponse<T>

CLI Tool

$ refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --use-api-response

Source Generator .refitter file

{
  "openApiPath": "./openapi.json",
  "namespace": "Your.Namespace.Of.Choice.GeneratedCode",
  "returnIApiResponse": true
}

Output

using Refit;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace Your.Namespace.Of.Choice.GeneratedCode.WithApiResponse
{
    public interface ISwaggerPetstore
    {
        /// <summary>
        /// Update an existing pet by Id
        /// </summary>
        [Put("/pet")]
        Task<IApiResponse<Pet>> UpdatePet([Body] Pet body);

        /// <summary>
        /// Add a new pet to the store
        /// </summary>
        [Post("/pet")]
        Task<IApiResponse<Pet>> AddPet([Body] Pet body);

        /// <summary>
        /// Multiple status values can be provided with comma separated strings
        /// </summary>
        [Get("/pet/findByStatus")]
        Task<IApiResponse<ICollection<Pet>>> FindPetsByStatus([Query] Status? status);

        /// <summary>
        /// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
        /// </summary>
        [Get("/pet/findByTags")]
        Task<IApiResponse<ICollection<Pet>>> FindPetsByTags([Query(CollectionFormat.Multi)] IEnumerable<string> tags);

        /// <summary>
        /// Returns a single pet
        /// </summary>
        [Get("/pet/{petId}")]
        Task<IApiResponse<Pet>> GetPetById(long petId);

        [Post("/pet/{petId}")]
        Task UpdatePetWithForm(long petId, [Query] string name, [Query] string status);

        [Delete("/pet/{petId}")]
        Task DeletePet(long petId, [Header("api_key")] string api_key);

        [Post("/pet/{petId}/uploadImage")]
        Task<IApiResponse<ApiResponse>> UploadFile(long petId, [Query] string additionalMetadata, StreamPart body);

        /// <summary>
        /// Returns a map of status codes to quantities
        /// </summary>
        [Get("/store/inventory")]
        Task<IApiResponse<IDictionary<string, int>>> GetInventory();

        /// <summary>
        /// Place a new order in the store
        /// </summary>
        [Post("/store/order")]
        Task<IApiResponse<Order>> PlaceOrder([Body] Order body);

        /// <summary>
        /// For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
        /// </summary>
        [Get("/store/order/{orderId}")]
        Task<IApiResponse<Order>> GetOrderById(long orderId);

        /// <summary>
        /// For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
        /// </summary>
        [Delete("/store/order/{orderId}")]
        Task DeleteOrder(long orderId);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Post("/user")]
        Task CreateUser([Body] User body);

        /// <summary>
        /// Creates list of users with given input array
        /// </summary>
        [Post("/user/createWithList")]
        Task<IApiResponse<User>> CreateUsersWithListInput([Body] IEnumerable<User> body);

        [Get("/user/login")]
        Task<IApiResponse<string>> LoginUser([Query] string username, [Query] string password);

        [Get("/user/logout")]
        Task LogoutUser();

        [Get("/user/{username}")]
        Task<IApiResponse<User>> GetUserByName(string username);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Put("/user/{username}")]
        Task UpdateUser(string username, [Body] User body);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Delete("/user/{username}")]
        Task DeleteUser(string username);
    }
}

Here's an example generated output from the Swagger Petstore example configured to generate an interface for each endpoint

CLI Tool

$ refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --multiple-interfaces

Source Generator .refitter file

{
  "openApiPath": "./openapi.json",
  "namespace": "Your.Namespace.Of.Choice.GeneratedCode",
  "multipleInterfaces": true
}

Output

/// <summary>
/// Update an existing pet
/// </summary>
public interface IUpdatePetEndpoint
{
    /// <summary>
    /// Update an existing pet by Id
    /// </summary>
    [Put("/pet")]
    Task<Pet> Execute([Body] Pet body);
}

/// <summary>
/// Add a new pet to the store
/// </summary>
public interface IAddPetEndpoint
{
    /// <summary>
    /// Add a new pet to the store
    /// </summary>
    [Post("/pet")]
    Task<Pet> Execute([Body] Pet body);
}

/// <summary>
/// Finds Pets by status
/// </summary>
public interface IFindPetsByStatusEndpoint
{
    /// <summary>
    /// Multiple status values can be provided with comma separated strings
    /// </summary>
    [Get("/pet/findByStatus")]
    Task<ICollection<Pet>> Execute([Query] Status? status);
}

/// <summary>
/// Finds Pets by tags
/// </summary>
public interface IFindPetsByTagsEndpoint
{
    /// <summary>
    /// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
    /// </summary>
    [Get("/pet/findByTags")]
    Task<ICollection<Pet>> Execute([Query(CollectionFormat.Multi)] IEnumerable<string> tags);
}

/// <summary>
/// Find pet by ID
/// </summary>
public interface IGetPetByIdEndpoint
{
    /// <summary>
    /// Returns a single pet
    /// </summary>
    [Get("/pet/{petId}")]
    Task<Pet> Execute(long petId);
}

/// <summary>
/// Updates a pet in the store with form data
/// </summary>
public interface IUpdatePetWithFormEndpoint
{
    [Post("/pet/{petId}")]
    Task Execute(long petId, [Query] string name, [Query] string status);
}

/// <summary>
/// Deletes a pet
/// </summary>
public interface IDeletePetEndpoint
{
    [Delete("/pet/{petId}")]
    Task Execute(long petId, [Header("api_key")] string api_key);
}

/// <summary>
/// uploads an image
/// </summary>
public interface IUploadFileEndpoint
{
    [Post("/pet/{petId}/uploadImage")]
    Task<ApiResponse> Execute(long petId, [Query] string additionalMetadata, StreamPart body);
}

/// <summary>
/// Returns pet inventories by status
/// </summary>
public interface IGetInventoryEndpoint
{
    /// <summary>
    /// Returns a map of status codes to quantities
    /// </summary>
    [Get("/store/inventory")]
    Task<IDictionary<string, int>> Execute();
}

/// <summary>
/// Place an order for a pet
/// </summary>
public interface IPlaceOrderEndpoint
{
    /// <summary>
    /// Place a new order in the store
    /// </summary>
    [Post("/store/order")]
    Task<Order> Execute([Body] Order body);
}

/// <summary>
/// Find purchase order by ID
/// </summary>
public interface IGetOrderByIdEndpoint
{
    /// <summary>
    /// For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
    /// </summary>
    [Get("/store/order/{orderId}")]
    Task<Order> Execute(long orderId);
}

/// <summary>
/// Delete purchase order by ID
/// </summary>
public interface IDeleteOrderEndpoint
{
    /// <summary>
    /// For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
    /// </summary>
    [Delete("/store/order/{orderId}")]
    Task Execute(long orderId);
}

/// <summary>
/// Create user
/// </summary>
public interface ICreateUserEndpoint
{
    /// <summary>
    /// This can only be done by the logged in user.
    /// </summary>
    [Post("/user")]
    Task Execute([Body] User body);
}

/// <summary>
/// Creates list of users with given input array
/// </summary>
public interface ICreateUsersWithListInputEndpoint
{
    /// <summary>
    /// Creates list of users with given input array
    /// </summary>
    [Post("/user/createWithList")]
    Task<User> Execute([Body] IEnumerable<User> body);
}

/// <summary>
/// Logs user into the system
/// </summary>
public interface ILoginUserEndpoint
{
    [Get("/user/login")]
    Task<string> Execute([Query] string username, [Query] string password);
}

/// <summary>
/// Logs out current logged in user session
/// </summary>
public interface ILogoutUserEndpoint
{
    [Get("/user/logout")]
    Task Execute();
}

/// <summary>
/// Get user by user name
/// </summary>
public interface IGetUserByNameEndpoint
{
    [Get("/user/{username}")]
    Task<User> Execute(string username);
}

/// <summary>
/// Update user
/// </summary>
public interface IUpdateUserEndpoint
{
    /// <summary>
    /// This can only be done by the logged in user.
    /// </summary>
    [Put("/user/{username}")]
    Task Execute(string username, [Body] User body);
}

/// <summary>
/// Delete user
/// </summary>
public interface IDeleteUserEndpoint
{
    /// <summary>
    /// This can only be done by the logged in user.
    /// </summary>
    [Delete("/user/{username}")]
    Task Execute(string username);
}

RestService

Here's an example usage of the generated code above

using Refit;
using System;
using System.Threading.Tasks;

namespace Your.Namespace.Of.Choice.GeneratedCode;

internal class Program
{
    private static async Task Main(string[] args)
    {
        var client = RestService.For<ISwaggerPetstore>("https://petstore3.swagger.io/api/v3");
        var pet = await client.GetPetById(1);

        Console.WriteLine("## Using Task<T> as return type ##");
        Console.WriteLine($"Name: {pet.Name}");
        Console.WriteLine($"Category: {pet.Category.Name}");
        Console.WriteLine($"Status: {pet.Status}");
        Console.WriteLine();

        var client2 = RestService.For<WithApiResponse.ISwaggerPetstore>("https://petstore3.swagger.io/api/v3");
        var response = await client2.GetPetById(2);

        Console.WriteLine("## Using Task<IApiResponse<T>> as return type ##");
        Console.WriteLine($"HTTP Status Code: {response.StatusCode}");
        Console.WriteLine($"Name: {response.Content.Name}");
        Console.WriteLine($"Category: {response.Content.Category.Name}");
        Console.WriteLine($"Status: {response.Content.Status}");
    }
}

The RestService class generates an implementation of ISwaggerPetstore that uses HttpClient to make its calls.

The code above when run will output something like this:

## Using Task<T> as return type ##
Name: Gatitotototo
Category: Chaucito
Status: Sold

## Using Task<IApiResponse<T>> as return type ##
HTTP Status Code: OK
Name: Gatitotototo
Category: Chaucito
Status: Sold

ASP.NET Core and HttpClientFactory

Here's an example Minimal API with the Refit.HttpClientFactory library:

using Refit;
using Your.Namespace.Of.Choice.GeneratedCode;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services
    .AddRefitClient<ISwaggerPetstore>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://petstore3.swagger.io/api/v3"));

var app = builder.Build();
app.MapGet(
        "/pet/{id:long}",
        async (ISwaggerPetstore petstore, long id) =>
        {
            try
            {
                return Results.Ok(await petstore.GetPetById(id));
            }
            catch (Refit.ApiException e)
            {
                return Results.StatusCode((int)e.StatusCode);
            }
        })
    .WithName("GetPetById")
    .WithOpenApi();

app.UseHttpsRedirection();
app.UseSwaggerUI();
app.UseSwagger();
app.Run();

.NET Core supports registering the generated ISwaggerPetstore interface via HttpClientFactory

The following request to the API above

$ curl -X 'GET' 'https://localhost:5001/pet/1' -H 'accept: application/json'

Returns a response that looks something like this:

{
  "id": 1,
  "name": "Special_char_owner_!@#$^&()`.testing",
  "photoUrls": [
    "https://petstore3.swagger.io/resources/photos/623389095.jpg"
  ],
  "tags": [],
  "status": "Sold"
}

System requirements

.NET 6.0 (LTS)

Contributors

Philip Cox
Philip Cox

πŸ’»
Cameron MacFarland
Cameron MacFarland

πŸ’»
kgame
kgame

πŸ’»
Thomas Pettersen / Yrki
Thomas Pettersen / Yrki

πŸ’»
1kvin
1kvin

πŸ›
m7clarke
m7clarke

πŸ›
kirides
kirides

πŸ›
guillaumeserale
guillaumeserale

πŸ’»
Dennis Brentjes
Dennis Brentjes

πŸ’»
Damian Hickey
Damian Hickey

πŸ›
richardhu-lmg
richardhu-lmg

πŸ›
brease-colin
brease-colin

πŸ›
angelofb
angelofb

πŸ’»
Dim Nogro
Dim Nogro

πŸ’»

For tips and tricks on software development, check out my blog

If you find this useful and feel a bit generous then feel free to buy me a coffee β˜•

About

Refit Client API Generator for OpenAPI

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C# 82.8%
  • PowerShell 17.2%