Git Product home page Git Product logo

refitter's Introduction

Build Smoke Tests NuGet Quality Gate Status codecov

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 --settings-file ./openapi.refitter --output ./GeneratedCode.cs
    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 --no-accept-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 ByEndpoint
    refitter ./openapi.json --tag Pet --tag Store --tag User
    refitter ./openapi.json --match-path '^/pet/.*'
    refitter ./openapi.json --trim-unused-schema
    refitter ./openapi.json --trim-unused-schema  --keep-schema '^Model$' --keep-schema '^Person.+'
    refitter ./openapi.json --no-deprecated-operations
    refitter ./openapi.json --operation-name-template '{operationName}Async'
    refitter ./openapi.json --optional-nullable-parameters

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

OPTIONS:
                                                DEFAULT                                                                                                                                                    
    -h, --help                                                   Prints help information                                                                                                                   
    -v, --version                                                Prints version information                                                                                                                
    -s, --settings-file                                          Path to .refitter settings file. Specifying this will ignore all other settings (except for --output)                                     
    -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                                                                                          
        --no-accept-headers                                      Don't add <Accept> header to output file                                                                                                  
        --interface-only                                         Don't generate contract types                                                                                                             
        --use-api-response                                       Return Task<IApiResponse<T>> instead of Task<T>                                                                                           
        --use-observable-response                                Return IObservable instead of Task                                                                                                        
        --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. May be one of ByEndpoint, ByTag                                                             
        --match-path                                             Only include Paths that match the provided regular expression. May be set multiple times                                                  
        --tag                                                    Only include Endpoints that contain this tag. May be set multiple times and result in OR'ed evaluation                                    
        --skip-validation                                        Skip validation of the OpenAPI specification                                                                                              
        --no-deprecated-operations                               Don't generate deprecated operations                                                                                                      
        --operation-name-template                                Generate operation names using pattern. When using --multiple-interfaces ByEndpoint, this is name of the Execute() method in the interface
        --optional-nullable-parameters                           Generate nullable parameters as optional parameters                                                                                       
        --trim-unused-schema                                     Removes unreferenced components schema to keep the generated output to a minimum                                                          
        --keep-schema                                            Force to keep matching schema, uses regular expressions. Use together with "--trim-unused-schema". Can be set multiple times              
        --no-banner                                              Don't show donation banner                                                                                                                
        --skip-default-additional-properties                     Set to true to skip default additional properties                                                                                         
        --operation-name-generator              Default          The NSwag IOperationNameGenerator implementation to use.                                                                                  
                                                                 May be one of:                                                                                                                            
                                                                 - Default                                                                                                                                 
                                                                 - MultipleClientsFromOperationId                                                                                                          
                                                                 - MultipleClientsFromPathSegments                                                                                                         
                                                                 - MultipleClientsFromFirstTagAndOperationId                                                                                               
                                                                 - MultipleClientsFromFirstTagAndOperationName                                                                                             
                                                                 - MultipleClientsFromFirstTagAndPathSegments                                                                                              
                                                                 - SingleClientFromOperationId                                                                                                             
                                                                 - SingleClientFromPathSegments                                                                                                            
                                                                 See https://refitter.github.io/api/Refitter.Core.OperationNameGeneratorTypes.html for more information                                    

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

The Refitter source generator is a bit untraditional in a sense that it creates a folder called Generated in the same location as the .refitter file and generates files to disk under the Generated folder (can be changed with --outputFolder). The source generator output should be included in the project and committed to source control. This is done because there is no other way to trigger the Refit source generator to pickup the Refitter generated code

(Translation: I couldn't for the life of me figure how to get that to work, sorry)

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
  "generateStatusCodeComments": true, // Optional. Default=true
  "addAutoGeneratedHeader": true, // Optional. Default=true
  "addAcceptHeaders": true, // Optional. Default=true
  "returnIApiResponse": false, // Optional. Default=false
  "responseTypeOverride": { // Optional. Default={}
    "File_Upload": "IApiResponse",
    "File_Download": "System.Net.Http.HttpContent"
  },
  "generateOperationHeaders": true, // Optional. Default=true
  "typeAccessibility": "Public", // Optional. Values=Public|Internal. Default=Public
  "useCancellationTokens": false, // Optional. Default=false
  "useIsoDateFormat": false, // Optional. Default=false
  "multipleInterfaces": "ByEndpoint", // Optional. May be one of "ByEndpoint" or "ByTag"
  "generateDeprecatedOperations": false, // Optional. Default=true
  "operationNameTemplate": "{operationName}Async", // Optional. Must contain {operationName} when multipleInterfaces != ByEndpoint
  "optionalParameters": false, // Optional. Default=false
  "outputFolder": "../CustomOutput" // Optional. Default=./Generated
  "outputFilename": "RefitInterface.cs", // Optional. Default=Output.cs for CLI tool
  "additionalNamespaces": [ // Optional
    "Namespace1",
    "Namespace2"
  ],
  "includeTags": [ // Optional. OpenAPI Tag to include when generating code
    "Pet",
    "Store",
    "User"
  ],
  "includePathMatches": [ // Optional. Only include Paths that match the provided regular expression
    "^/pet/.*",
    "^/store/.*"
  ],
  "trimUnusedSchema": false, // Optional. Default=false
  "keepSchemaPatterns": [ // Optional. Force to keep matching schema, uses regular expressions. Use together with trimUnusedSchema=true
    "^Model$",
    "^Person.+"
  ],
  "generateDefaultAdditionalProperties": true, // Optional. default=true
  "operationNameGenerator": "Default", // Optional. May be one of Default, MultipleClientsFromOperationId, MultipleClientsFromPathSegments, MultipleClientsFromFirstTagAndOperationId, MultipleClientsFromFirstTagAndOperationName, MultipleClientsFromFirstTagAndPathSegments, SingleClientFromOperationId, SingleClientFromPathSegments
  "dependencyInjectionSettings": { // Optional
    "baseUrl": "https://petstore3.swagger.io/api/v3", // Optional. Leave this blank to set the base address manually
    "httpMessageHandlers": [ // Optional
        "AuthorizationMessageHandler", 
        "TelemetryMessageHandler" 
    ],
    "usePolly": true, // Optional. Set this to true, to configure Polly with a retry policy that uses a jittered backoff. Default=false
    "pollyMaxRetryCount": 3, // Optional. Default=6
    "firstBackoffRetryInSeconds": 0.5 // Optional. Default=1.0
  },
  "codeGeneratorSettings": { // Optional. Default settings are the values set in this example
    "requiredPropertiesMustBeDefined": true,
    "generateDataAnnotations": true,
    "anyType": "object",
    "dateType": "System.DateTimeOffset",
    "dateTimeType": "System.DateTimeOffset",
    "timeType": "System.TimeSpan",
    "timeSpanType": "System.TimeSpan",
    "arrayType": "System.Collections.Generic.ICollection",
    "dictionaryType": "System.Collections.Generic.IDictionary",
    "arrayInstanceType": "System.Collections.ObjectModel.Collection",
    "dictionaryInstanceType": "System.Collections.Generic.Dictionary",
    "arrayBaseType": "System.Collections.ObjectModel.Collection",
    "dictionaryBaseType": "System.Collections.Generic.Dictionary",
    "propertySetterAccessModifier": "",
    "generateImmutableArrayProperties": false,
    "generateImmutableDictionaryProperties": false,
    "handleReferences": false,
    "jsonSerializerSettingsTransformationMethod": null,
    "generateJsonMethods": false,
    "enforceFlagEnums": false,
    "inlineNamedDictionaries": false,
    "inlineNamedTuples": true,
    "inlineNamedArrays": false,
    "generateOptionalPropertiesAsNullable": false,
    "generateNullableReferenceTypes": false,
    "generateNativeRecords": false,
    "generateDefaultValues": true,
    "inlineNamedAny": false,
    "excludedTypeNames": [
      "ExcludedTypeFoo",
      "ExcludedTypeBar"
    ]
  }
}
  • 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
  • generateStatusCodeComments - a boolean indicating whether the XML docs for ApiException and IApiResponse contain detailed descriptions for every documented status code. Default is true
  • addAutoGeneratedHeader - a boolean indicating whether XML doc comments should be generated. Default is true
  • addAcceptHeaders - a boolean indicating whether to add accept headers [Headers("Accept: application/json")]. Default is true
  • returnIApiResponse - a boolean indicating whether to return IApiResponse<T> objects. Default is false
  • responseTypeOverride - a dictionary with operation ids (as specified in the OpenAPI document) and a particular return type to use. The types are wrapped in a task, but otherwise unmodified (so make sure to specify or import their namespaces). Default is {}
  • 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 ByEndpoint to generate an interface for each endpoint, or ByTag to group Endpoints by their Tag (like SwaggerUI groups them).
  • outputFolder - a string describing a relative path to a desired output folder. Default is ./Generated
  • outputFilename - Output filename. Default is Output.cs when used from the CLI tool, otherwise its the .refitter filename. So Petstore.refitter becomes Petstore.cs.
  • 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
  • includeTags - A collection of tags to use a filter for including endpoints that contain this tag.
  • includePathMatches - A collection of regular expressions used to filter paths.
  • generateDeprecatedOperations - a boolean indicating whether deprecated operations should be generated or skipped. Default is true
  • operationNameTemplate - Generate operation names using pattern. This must contain the string {operationName}. An example usage of this could be {operationName}Async to suffix all method names with Async
  • optionalParameters - Generate non-required parameters as nullable optional parameters
  • trimUnusedSchema - Removes unreferenced components schema to keep the generated output to a minimum
  • keepSchemaPatterns: A collection of regular expressions to force to keep matching schema. This is used together with trimUnusedSchema
  • generateDefaultAdditionalProperties: Set to false to skip default additional properties. Default is true
  • operationNameGenerator: The NSwag IOperationNameGenerator implementation to use. See https://refitter.github.io/api/Refitter.Core.OperationNameGeneratorTypes.html
  • dependencyInjectionSettings - Setting this will generated extension methods to IServiceCollection for configuring Refit clients
    • baseUrl - Used as the HttpClient base address. Leave this blank to manually set the base URL
    • httpMessageHandlers - A collection of HttpMessageHandler that is added to the HttpClient pipeline
    • usePolly - Set this to true to configure the HttpClient to use Polly using a retry policy with a jittered backoff
    • pollyMaxRetryCount - This is the max retry count used in the Polly retry policy. Default is 6
    • firstBackoffRetryInSeconds - This is the duration of the initial retry backoff. Default is 1 second
  • codeGeneratorSettings - Setting this allows customization of the NSwag generated types and contracts
    • requiredPropertiesMustBeDefined - Default is true,
    • generateDataAnnotations - Default is true,
    • anyType - Default is object,
    • dateType - Default is System.DateTimeOffset,
    • dateTimeType - Default is System.DateTimeOffset,
    • timeType - Default is System.TimeSpan,
    • timeSpanType - Default is System.TimeSpan,
    • arrayType - Default is System.Collections.Generic.ICollection,
    • dictionaryType - Default is System.Collections.Generic.IDictionary,
    • arrayInstanceType - Default is System.Collections.ObjectModel.Collection,
    • dictionaryInstanceType - Default is System.Collections.Generic.Dictionary,
    • arrayBaseType - Default is System.Collections.ObjectModel.Collection,
    • dictionaryBaseType - Default is System.Collections.Generic.Dictionary,
    • propertySetterAccessModifier - Default is ``,
    • generateImmutableArrayProperties - Default is false,
    • generateImmutableDictionaryProperties - Default is false,
    • handleReferences - Default is false,
    • jsonSerializerSettingsTransformationMethod - Default is null,
    • generateJsonMethods - Default is false,
    • enforceFlagEnums - Default is false,
    • inlineNamedDictionaries - Default is false,
    • inlineNamedTuples - Default is true,
    • inlineNamedArrays - Default is false,
    • generateOptionalPropertiesAsNullable - Default is false,
    • generateNullableReferenceTypes - Default is false,
    • generateNativeRecords - Default is false
    • generateDefaultValues - Default is true
    • inlineNamedAny - Default is false
    • excludedTypeNames - 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.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Your.Namespace.Of.Choice.GeneratedCode
{
    [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
    public partial interface ISwaggerPetstore
    {
        /// <summary>Update an existing pet</summary>
        /// <remarks>Update an existing pet by Id</remarks>
        /// <param name="body">Update an existent pet in the store</param>
        /// <returns>Successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Pet not found</description>
        /// </item>
        /// <item>
        /// <term>405</term>
        /// <description>Validation exception</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/xml, application/json")]
        [Put("/pet")]
        Task<Pet> UpdatePet([Body] Pet body);

        /// <summary>Add a new pet to the store</summary>
        /// <remarks>Add a new pet to the store</remarks>
        /// <param name="body">Create a new pet in the store</param>
        /// <returns>Successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>405</term>
        /// <description>Invalid input</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/xml, application/json")]
        [Post("/pet")]
        Task<Pet> AddPet([Body] Pet body);

        /// <summary>Finds Pets by status</summary>
        /// <remarks>Multiple status values can be provided with comma separated strings</remarks>
        /// <param name="status">Status values that need to be considered for filter</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid status value</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Get("/pet/findByStatus")]
        Task<ICollection<Pet>> FindPetsByStatus([Query] Status? status);

        /// <summary>Finds Pets by tags</summary>
        /// <remarks>Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.</remarks>
        /// <param name="tags">Tags to filter by</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid tag value</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Get("/pet/findByTags")]
        Task<ICollection<Pet>> FindPetsByTags([Query(CollectionFormat.Multi)] IEnumerable<string> tags);

        /// <summary>Find pet by ID</summary>
        /// <remarks>Returns a single pet</remarks>
        /// <param name="petId">ID of pet to return</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Pet not found</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/xml, application/json")]
        [Get("/pet/{petId}")]
        Task<Pet> GetPetById(long petId);

        /// <summary>Updates a pet in the store with form data</summary>
        /// <param name="petId">ID of pet that needs to be updated</param>
        /// <param name="name">Name of pet that needs to be updated</param>
        /// <param name="status">Status of pet that needs to be updated</param>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>405</term>
        /// <description>Invalid input</description>
        /// </item>
        /// </list>
        /// </exception>
        [Post("/pet/{petId}")]
        Task UpdatePetWithForm(long petId, [Query] string name, [Query] string status);

        /// <summary>Deletes a pet</summary>
        /// <param name="petId">Pet id to delete</param>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid pet value</description>
        /// </item>
        /// </list>
        /// </exception>
        [Delete("/pet/{petId}")]
        Task DeletePet(long petId, [Header("api_key")] string api_key);

        /// <summary>uploads an image</summary>
        /// <param name="petId">ID of pet to update</param>
        /// <param name="additionalMetadata">Additional Metadata</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>successful operation</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/json")]
        [Post("/pet/{petId}/uploadImage")]
        Task<ApiResponse> UploadFile(long petId, [Query] string additionalMetadata,  StreamPart body);

        /// <summary>Returns pet inventories by status</summary>
        /// <remarks>Returns a map of status codes to quantities</remarks>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
        [Headers("Accept: application/json")]
        [Get("/store/inventory")]
        Task<IDictionary<string, int>> GetInventory();

        /// <summary>Place an order for a pet</summary>
        /// <remarks>Place a new order in the store</remarks>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>405</term>
        /// <description>Invalid input</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Post("/store/order")]
        Task<Order> PlaceOrder([Body] Order body);

        /// <summary>Find purchase order by ID</summary>
        /// <remarks>For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions</remarks>
        /// <param name="orderId">ID of order that needs to be fetched</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Order not found</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Get("/store/order/{orderId}")]
        Task<Order> GetOrderById(long orderId);

        /// <summary>Delete purchase order by ID</summary>
        /// <remarks>For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors</remarks>
        /// <param name="orderId">ID of the order that needs to be deleted</param>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Order not found</description>
        /// </item>
        /// </list>
        /// </exception>
        [Delete("/store/order/{orderId}")]
        Task DeleteOrder(long orderId);

        /// <summary>Create user</summary>
        /// <remarks>This can only be done by the logged in user.</remarks>
        /// <param name="body">Created user object</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
        [Headers("Accept: application/json, application/xml")]
        [Post("/user")]
        Task CreateUser([Body] User body);

        /// <summary>Creates list of users with given input array</summary>
        /// <remarks>Creates list of users with given input array</remarks>
        /// <returns>Successful operation</returns>
        /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
        [Headers("Accept: application/xml, application/json")]
        [Post("/user/createWithList")]
        Task<User> CreateUsersWithListInput([Body] IEnumerable<User> body);

        /// <summary>Logs user into the system</summary>
        /// <param name="username">The user name for login</param>
        /// <param name="password">The password for login in clear text</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid username/password supplied</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Get("/user/login")]
        Task<string> LoginUser([Query] string username, [Query] string password);

        /// <summary>Logs out current logged in user session</summary>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
        [Get("/user/logout")]
        Task LogoutUser();

        /// <summary>Get user by user name</summary>
        /// <param name="username">The name that needs to be fetched. Use user1 for testing.</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid username supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>User not found</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Get("/user/{username}")]
        Task<User> GetUserByName(string username);

        /// <summary>Update user</summary>
        /// <remarks>This can only be done by the logged in user.</remarks>
        /// <param name="username">name that need to be deleted</param>
        /// <param name="body">Update an existent user in the store</param>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
        [Put("/user/{username}")]
        Task UpdateUser(string username, [Body] User body);

        /// <summary>Delete user</summary>
        /// <remarks>This can only be done by the logged in user.</remarks>
        /// <param name="username">The name that needs to be deleted</param>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid username supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>User not found</description>
        /// </item>
        /// </list>
        /// </exception>
        [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.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Your.Namespace.Of.Choice.GeneratedCode
{
    [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
    public partial interface ISwaggerPetstore
    {
        /// <summary>Update an existing pet</summary>
        /// <remarks>Update an existing pet by Id</remarks>
        /// <param name="body">Update an existent pet in the store</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>Successful operation</description>
        /// </item>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Pet not found</description>
        /// </item>
        /// <item>
        /// <term>405</term>
        /// <description>Validation exception</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/xml, application/json")]
        [Put("/pet")]
        Task<IApiResponse<Pet>> UpdatePet([Body] Pet body);

        /// <summary>Add a new pet to the store</summary>
        /// <remarks>Add a new pet to the store</remarks>
        /// <param name="body">Create a new pet in the store</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>Successful operation</description>
        /// </item>
        /// <item>
        /// <term>405</term>
        /// <description>Invalid input</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/xml, application/json")]
        [Post("/pet")]
        Task<IApiResponse<Pet>> AddPet([Body] Pet body);

        /// <summary>Finds Pets by status</summary>
        /// <remarks>Multiple status values can be provided with comma separated strings</remarks>
        /// <param name="status">Status values that need to be considered for filter</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>successful operation</description>
        /// </item>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid status value</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/json")]
        [Get("/pet/findByStatus")]
        Task<IApiResponse<ICollection<Pet>>> FindPetsByStatus([Query] Status? status);

        /// <summary>Finds Pets by tags</summary>
        /// <remarks>Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.</remarks>
        /// <param name="tags">Tags to filter by</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>successful operation</description>
        /// </item>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid tag value</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/json")]
        [Get("/pet/findByTags")]
        Task<IApiResponse<ICollection<Pet>>> FindPetsByTags([Query(CollectionFormat.Multi)] IEnumerable<string> tags);

        /// <summary>Find pet by ID</summary>
        /// <remarks>Returns a single pet</remarks>
        /// <param name="petId">ID of pet to return</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>successful operation</description>
        /// </item>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Pet not found</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/xml, application/json")]
        [Get("/pet/{petId}")]
        Task<IApiResponse<Pet>> GetPetById(long petId);

        /// <summary>Updates a pet in the store with form data</summary>
        /// <param name="petId">ID of pet that needs to be updated</param>
        /// <param name="name">Name of pet that needs to be updated</param>
        /// <param name="status">Status of pet that needs to be updated</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>405</term>
        /// <description>Invalid input</description>
        /// </item>
        /// </list>
        /// </returns>
        [Post("/pet/{petId}")]
        Task<IApiResponse> UpdatePetWithForm(long petId, [Query] string name, [Query] string status);

        /// <summary>Deletes a pet</summary>
        /// <param name="petId">Pet id to delete</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid pet value</description>
        /// </item>
        /// </list>
        /// </returns>
        [Delete("/pet/{petId}")]
        Task<IApiResponse> DeletePet(long petId, [Header("api_key")] string api_key);

        /// <summary>uploads an image</summary>
        /// <param name="petId">ID of pet to update</param>
        /// <param name="additionalMetadata">Additional Metadata</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>successful operation</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/json")]
        [Post("/pet/{petId}/uploadImage")]
        Task<IApiResponse<ApiResponse>> UploadFile(long petId, [Query] string additionalMetadata,  StreamPart body);

        /// <summary>Returns pet inventories by status</summary>
        /// <remarks>Returns a map of status codes to quantities</remarks>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
        [Headers("Accept: application/json")]
        [Get("/store/inventory")]
        Task<IApiResponse<IDictionary<string, int>>> GetInventory();

        /// <summary>Place an order for a pet</summary>
        /// <remarks>Place a new order in the store</remarks>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>successful operation</description>
        /// </item>
        /// <item>
        /// <term>405</term>
        /// <description>Invalid input</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/json")]
        [Post("/store/order")]
        Task<IApiResponse<Order>> PlaceOrder([Body] Order body);

        /// <summary>Find purchase order by ID</summary>
        /// <remarks>For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions</remarks>
        /// <param name="orderId">ID of order that needs to be fetched</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>successful operation</description>
        /// </item>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Order not found</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/json")]
        [Get("/store/order/{orderId}")]
        Task<IApiResponse<Order>> GetOrderById(long orderId);

        /// <summary>Delete purchase order by ID</summary>
        /// <remarks>For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors</remarks>
        /// <param name="orderId">ID of the order that needs to be deleted</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Order not found</description>
        /// </item>
        /// </list>
        /// </returns>
        [Delete("/store/order/{orderId}")]
        Task<IApiResponse> DeleteOrder(long orderId);

        /// <summary>Create user</summary>
        /// <remarks>This can only be done by the logged in user.</remarks>
        /// <param name="body">Created user object</param>
        /// <returns>A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result.</returns>
        [Headers("Accept: application/json, application/xml")]
        [Post("/user")]
        Task<IApiResponse> CreateUser([Body] User body);

        /// <summary>Creates list of users with given input array</summary>
        /// <remarks>Creates list of users with given input array</remarks>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>Successful operation</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/xml, application/json")]
        [Post("/user/createWithList")]
        Task<IApiResponse<User>> CreateUsersWithListInput([Body] IEnumerable<User> body);

        /// <summary>Logs user into the system</summary>
        /// <param name="username">The user name for login</param>
        /// <param name="password">The password for login in clear text</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>successful operation</description>
        /// </item>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid username/password supplied</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/json")]
        [Get("/user/login")]
        Task<IApiResponse<string>> LoginUser([Query] string username, [Query] string password);

        /// <summary>Logs out current logged in user session</summary>
        /// <returns>A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result.</returns>
        [Get("/user/logout")]
        Task<IApiResponse> LogoutUser();

        /// <summary>Get user by user name</summary>
        /// <param name="username">The name that needs to be fetched. Use user1 for testing.</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>successful operation</description>
        /// </item>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid username supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>User not found</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/json")]
        [Get("/user/{username}")]
        Task<IApiResponse<User>> GetUserByName(string username);

        /// <summary>Update user</summary>
        /// <remarks>This can only be done by the logged in user.</remarks>
        /// <param name="username">name that need to be deleted</param>
        /// <param name="body">Update an existent user in the store</param>
        /// <returns>A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result.</returns>
        [Put("/user/{username}")]
        Task<IApiResponse> UpdateUser(string username, [Body] User body);

        /// <summary>Delete user</summary>
        /// <remarks>This can only be done by the logged in user.</remarks>
        /// <param name="username">The name that needs to be deleted</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid username supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>User not found</description>
        /// </item>
        /// </list>
        /// </returns>
        [Delete("/user/{username}")]
        Task<IApiResponse> 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 ByEndpoint

Source Generator .refitter file

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

Output

/// <summary>Update an existing pet</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IUpdatePetEndpoint
{
    /// <summary>Update an existing pet</summary>
    /// <remarks>Update an existing pet by Id</remarks>
    /// <param name="body">Update an existent pet in the store</param>
    /// <returns>Successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid ID supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>Pet not found</description>
    /// </item>
    /// <item>
    /// <term>405</term>
    /// <description>Validation exception</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/xml, application/json")]
    [Put("/pet")]
    Task<Pet> Execute([Body] Pet body);
}

/// <summary>Add a new pet to the store</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IAddPetEndpoint
{
    /// <summary>Add a new pet to the store</summary>
    /// <remarks>Add a new pet to the store</remarks>
    /// <param name="body">Create a new pet in the store</param>
    /// <returns>Successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>405</term>
    /// <description>Invalid input</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/xml, application/json")]
    [Post("/pet")]
    Task<Pet> Execute([Body] Pet body);
}

/// <summary>Finds Pets by status</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IFindPetsByStatusEndpoint
{
    /// <summary>Finds Pets by status</summary>
    /// <remarks>Multiple status values can be provided with comma separated strings</remarks>
    /// <param name="status">Status values that need to be considered for filter</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid status value</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Get("/pet/findByStatus")]
    Task<ICollection<Pet>> Execute([Query] Status? status);
}

/// <summary>Finds Pets by tags</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IFindPetsByTagsEndpoint
{
    /// <summary>Finds Pets by tags</summary>
    /// <remarks>Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.</remarks>
    /// <param name="tags">Tags to filter by</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid tag value</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Get("/pet/findByTags")]
    Task<ICollection<Pet>> Execute([Query(CollectionFormat.Multi)] IEnumerable<string> tags);
}

/// <summary>Find pet by ID</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IGetPetByIdEndpoint
{
    /// <summary>Find pet by ID</summary>
    /// <remarks>Returns a single pet</remarks>
    /// <param name="petId">ID of pet to return</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid ID supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>Pet not found</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/xml, application/json")]
    [Get("/pet/{petId}")]
    Task<Pet> Execute(long petId);
}

/// <summary>Updates a pet in the store with form data</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IUpdatePetWithFormEndpoint
{
    /// <summary>Updates a pet in the store with form data</summary>
    /// <param name="petId">ID of pet that needs to be updated</param>
    /// <param name="name">Name of pet that needs to be updated</param>
    /// <param name="status">Status of pet that needs to be updated</param>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>405</term>
    /// <description>Invalid input</description>
    /// </item>
    /// </list>
    /// </exception>
    [Post("/pet/{petId}")]
    Task Execute(long petId, [Query] string name, [Query] string status);
}

/// <summary>Deletes a pet</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IDeletePetEndpoint
{
    /// <summary>Deletes a pet</summary>
    /// <param name="petId">Pet id to delete</param>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid pet value</description>
    /// </item>
    /// </list>
    /// </exception>
    [Delete("/pet/{petId}")]
    Task Execute(long petId, [Header("api_key")] string api_key);
}

/// <summary>uploads an image</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IUploadFileEndpoint
{
    /// <summary>uploads an image</summary>
    /// <param name="petId">ID of pet to update</param>
    /// <param name="additionalMetadata">Additional Metadata</param>
    /// <returns>
    /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>200</term>
    /// <description>successful operation</description>
    /// </item>
    /// </list>
    /// </returns>
    [Headers("Accept: application/json")]
    [Post("/pet/{petId}/uploadImage")]
    Task<ApiResponse> Execute(long petId, [Query] string additionalMetadata,  StreamPart body);
}

/// <summary>Returns pet inventories by status</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IGetInventoryEndpoint
{
    /// <summary>Returns pet inventories by status</summary>
    /// <remarks>Returns a map of status codes to quantities</remarks>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
    [Headers("Accept: application/json")]
    [Get("/store/inventory")]
    Task<IDictionary<string, int>> Execute();
}

/// <summary>Place an order for a pet</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IPlaceOrderEndpoint
{
    /// <summary>Place an order for a pet</summary>
    /// <remarks>Place a new order in the store</remarks>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>405</term>
    /// <description>Invalid input</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Post("/store/order")]
    Task<Order> Execute([Body] Order body);
}

/// <summary>Find purchase order by ID</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IGetOrderByIdEndpoint
{
    /// <summary>Find purchase order by ID</summary>
    /// <remarks>For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions</remarks>
    /// <param name="orderId">ID of order that needs to be fetched</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid ID supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>Order not found</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Get("/store/order/{orderId}")]
    Task<Order> Execute(long orderId);
}

/// <summary>Delete purchase order by ID</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IDeleteOrderEndpoint
{
    /// <summary>Delete purchase order by ID</summary>
    /// <remarks>For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors</remarks>
    /// <param name="orderId">ID of the order that needs to be deleted</param>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid ID supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>Order not found</description>
    /// </item>
    /// </list>
    /// </exception>
    [Delete("/store/order/{orderId}")]
    Task Execute(long orderId);
}

/// <summary>Create user</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface ICreateUserEndpoint
{
    /// <summary>Create user</summary>
    /// <remarks>This can only be done by the logged in user.</remarks>
    /// <param name="body">Created user object</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
    [Headers("Accept: application/json, application/xml")]
    [Post("/user")]
    Task Execute([Body] User body);
}

/// <summary>Creates list of users with given input array</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface ICreateUsersWithListInputEndpoint
{
    /// <summary>Creates list of users with given input array</summary>
    /// <remarks>Creates list of users with given input array</remarks>
    /// <returns>Successful operation</returns>
    /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
    [Headers("Accept: application/xml, application/json")]
    [Post("/user/createWithList")]
    Task<User> Execute([Body] IEnumerable<User> body);
}

/// <summary>Logs user into the system</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface ILoginUserEndpoint
{
    /// <summary>Logs user into the system</summary>
    /// <param name="username">The user name for login</param>
    /// <param name="password">The password for login in clear text</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid username/password supplied</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Get("/user/login")]
    Task<string> Execute([Query] string username, [Query] string password);
}

/// <summary>Logs out current logged in user session</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface ILogoutUserEndpoint
{
    /// <summary>Logs out current logged in user session</summary>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
    [Get("/user/logout")]
    Task Execute();
}

/// <summary>Get user by user name</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IGetUserByNameEndpoint
{
    /// <summary>Get user by user name</summary>
    /// <param name="username">The name that needs to be fetched. Use user1 for testing.</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid username supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>User not found</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Get("/user/{username}")]
    Task<User> Execute(string username);
}

/// <summary>Update user</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IUpdateUserEndpoint
{
    /// <summary>Update user</summary>
    /// <remarks>This can only be done by the logged in user.</remarks>
    /// <param name="username">name that need to be deleted</param>
    /// <param name="body">Update an existent user in the store</param>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
    [Put("/user/{username}")]
    Task Execute(string username, [Body] User body);
}

/// <summary>Delete user</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IDeleteUserEndpoint
{
    /// <summary>Delete user</summary>
    /// <remarks>This can only be done by the logged in user.</remarks>
    /// <param name="username">The name that needs to be deleted</param>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid username supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>User not found</description>
    /// </item>
    /// </list>
    /// </exception>
    [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

Refitter supports .NET versions with current long-term support (6.0 and 8.0)

Contributors

Philip Cox
Philip Cox

๐Ÿ’ป
Cameron MacFarland
Cameron MacFarland

๐Ÿ’ป
kgame
kgame

๐Ÿ’ป
Thomas Pettersen / Yrki
Thomas Pettersen / Yrki

๐Ÿ’ป
Artem
Artem

๐Ÿ›
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

๐Ÿ’ป
yadanilov19
yadanilov19

๐Ÿค” ๐Ÿ’ป
Daniel Powell
Daniel Powell

๐Ÿ›
Ekkeir
Ekkeir

๐Ÿ“– ๐Ÿ›
Waylon Martinez
Waylon Martinez

๐Ÿ›
vkmadupa
vkmadupa

๐Ÿ›
Noblix
Noblix

๐Ÿ’ป ๐Ÿค”
Attila Hajdrik
Attila Hajdrik

๐Ÿค”
bielik01
bielik01

๐Ÿ› ๐Ÿค”
naaeef
naaeef

๐Ÿค”
Alireza Habibi
Alireza Habibi

๐Ÿ›
Jeff Parker, PE
Jeff Parker, PE

๐Ÿ›
jods
jods

๐Ÿค” ๐Ÿ›
Edimarquez Medeiros
Edimarquez Medeiros

๐Ÿ’ป
safakkesikci
safakkesikci

๐Ÿ›
folbrecht
folbrecht

๐Ÿ›
mortenlaursen
mortenlaursen

๐Ÿ’ป
manuel-fernandez-rodriguez
manuel-fernandez-rodriguez

๐Ÿ›
Eli Yammine
Eli Yammine

๐Ÿ›
kami-poi
kami-poi

๐Ÿค”
Xeevis
Xeevis

๐Ÿ›
DJ4ddi
DJ4ddi

๐Ÿ’ป ๐Ÿค”
direncancatalkaya
direncancatalkaya

๐Ÿ’ป
Robert Palmqvist
Robert Palmqvist

๐Ÿค” ๐Ÿ’ป
Tim M
Tim M

๐Ÿ“–
janfolbrecht
janfolbrecht

๐Ÿค” ๐Ÿ’ป
Nick Seguin
Nick Seguin

๐Ÿ’ป
David Brink
David Brink

๐Ÿ› ๐Ÿ’ป
Stu Wilson
Stu Wilson

๐Ÿค” ๐Ÿ’ป
sharpzilla
sharpzilla

๐Ÿค”

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 โ˜•

refitter's People

Contributors

allcontributors[bot] avatar angelofb avatar christianhelle avatar david-brink-talogy avatar dependabot[bot] avatar direncancatalkaya avatar distantcam avatar gserale avatar guillaumeserale avatar kgamecarter avatar kirides avatar neogeneva avatar noblix avatar nogro avatar osc-nseguin avatar qodana-bot avatar renovate[bot] avatar robpalm avatar timothymakkison avatar yrki avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

refitter's Issues

Config parameter to add suffix to contract types

Is your feature request related to a problem? Please describe.
To match our existing coding conventions and to increase code readability, it would be great to have a config parameter that allows to set a suffix to the generated contract types (like Dto --> PetDto).

Additional context
Thank you for your help.

Error on build - dependency to Microsoft.Bcl.AsyncInterfaces

REFITTER000 System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.Bcl.AsyncInterfaces, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. Systรฉm nemลฏลพe nalรฉzt uvedenรฝ soubor.

I tried to install package Microsoft.Bcl.AsyncInterfaces, but it did not help. VS 2022, MAUI app project.
Thanks for help

Duplicate Accept Headers

Description
Duplicate accept headers like [Headers("Accept: application/json, application/json")] are generated when an endpoint has multiple responses with a payload. In this case, an endpoint can respond with HTTP 200 or 203 and both responses include a application/json payload in the response body

Support Key: ptbddgt

OpenAPI Specifications

openapi: '3.0.0'
info:
  version: 'v1'
  title: 'Test API'
servers:
  - url: 'https://test.host.com/api/v1'
paths:
  /jobs/{job-id}:
    get:
      tags:
      - 'Jobs'
      summary: 'Get job details'
      description: 'Get the details of the specified job.'
      parameters:
        - in: 'path'
          name: 'job-id'
          description: 'Job ID'
          required: true
          schema:
            type: 'string'
      responses:
        '200':
          description: 'successful operation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobResponse'
        '203':
          description: 'successful operation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobResponse'
components:
  schemas:
    JobResponse:
      type: 'object'
      properties:
        job:
          type: 'object'
          properties:
            start-date:
              type: 'string'
              format: 'date-time'
            details:
              type: 'string'

Generated code:

public interface ITestAPI
{
    /// <summary>
    /// Get the details of the specified job.
    /// </summary>
    [Headers("Accept: application/json, application/json")]
    [Get("/jobs/{job-id}")]
    Task<JobResponse> Jobs([AliasAs("job-id")] string job_id);
}

Parameters' casing in openAPI document are not honoured in Refit interface methods

When Refitter generates an operation, the parameter names in the methods are formatted in snakeCase.
Here is an example with Username and Password parameter:

OpenAPI Spec

{
  "/user/login": {
    "get": {
      "tags": [
        "user"
      ],
      "summary": "Logs user into the system",
      "description": "",
      "operationId": "loginUser",
      "parameters": [
        {
          "name": "Username",
          "in": "query",
          "description": "The user name for login",
          "required": false,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "Password",
          "in": "query",
          "description": "The password for login in clear text",
          "required": false,
          "schema": {
            "type": "string"
          }
        }
      ],
      ...
    }
  }
}

Generated Code

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

URL

http://swagger.io/user/login?username=XXXX&password=XXXX

This is causing issues in APIs that require the original casing for parameter names ( Username instead of username and Password instead of password)

Allow specifying access modifiers for generated classes

First off, sorry for feature bombing

It would be very handy to be able to specify the access modifier for the generated DTOs and Clients.

backstory:

In our project, we have modularized everything and try to keep our public Api surface as small as possible.
We define most of our classes as internal unless they explicitly are needed in other places.

All HTTP calls a module needs and all their DTOs reside in the module and never leave it's scope.

Add support for using .refitter file from CLI

Hi!

Is it possible for add a support for using file from CLI params?
Like this:

refitter ./openapi.json --settings-file cli.json

I saw you added .refitter file for setup source generation, but some of the parameters weren't supported in the CLI. For example "naming" param.

It would be soo cool to send just file to the command line instead of passing params.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

devcontainer
.devcontainer/devcontainer.json
  • mcr.microsoft.com/devcontainers/dotnet 1-6.0
  • ghcr.io/devcontainers/features/powershell 1
  • ghcr.io/devcontainers/features/dotnet 2
github-actions
.github/workflows/build.yml
  • actions/checkout v4
  • actions/upload-artifact v3
.github/workflows/changelog.yml
  • actions/checkout v4
  • ruby/setup-ruby v1
.github/workflows/codecov.yml
  • actions/checkout v4
  • actions/upload-artifact v3
  • codecov/codecov-action v4
.github/workflows/docfx.yml
  • actions/checkout v4
  • peaceiris/actions-gh-pages v4
.github/workflows/release-template.yml
  • actions/checkout v4
  • nelonoel/branch-name v1.0.1
  • actions/upload-artifact v3
  • actions/github-script v7
.github/workflows/template-source-generator.yml
  • actions/checkout v4
.github/workflows/template-url.yml
  • actions/checkout v4
.github/workflows/template.yml
  • actions/checkout v4
  • actions/upload-artifact v3
nuget
src/Directory.Build.props
  • SonarAnalyzer.CSharp 9.25.1.91650
src/Refitter.Core/Refitter.Core.csproj
  • OasReader 1.6.11.14
  • NSwag.Core.Yaml 14.0.7
  • NSwag.CodeGeneration.CSharp 14.0.7
  • Microsoft.SourceLink.GitHub 8.0.0
src/Refitter.SourceGenerator.Tests/Refitter.SourceGenerator.Tests.csproj
  • Polly.Contrib.WaitAndRetry 1.1.1
  • Microsoft.Extensions.Http.Polly 8.0.5
  • Refit.HttpClientFactory 7.0.0
  • coverlet.collector 6.0.2
  • xunit.runner.visualstudio 2.8.1
  • xunit 2.8.1
  • FluentAssertions 6.12.0
  • Microsoft.NET.Test.Sdk 17.10.0
src/Refitter.SourceGenerator/Refitter.SourceGenerator.csproj
  • Refit 7.0.0
  • OasReader 1.6.11.14
  • NSwag.Core.Yaml 14.0.7
  • NSwag.CodeGeneration.CSharp 14.0.7
  • H.Generators.Extensions 1.22.0
  • Microsoft.CodeAnalysis.CSharp 4.9.2
src/Refitter.Tests/Refitter.Tests.csproj
  • coverlet.collector 6.0.2
  • xunit.runner.visualstudio 2.8.1
  • xunit 2.8.1
  • Microsoft.NET.Test.Sdk 17.10.0
  • Atc.Test 1.0.89
src/Refitter/Refitter.csproj
  • Microsoft.SourceLink.GitHub 8.0.0
  • Spectre.Console.Cli 0.49.1
  • Exceptionless 6.0.4

  • Check this box to trigger a request for Renovate to run again on this repository

The using of StringEnumConverter end up generating unserializable data when different NamingPolicy is needed

Describe the bug
This problem is with the same API as in #175, they use snake_lower_case for enum values and because JsonConverter will instantiate the JsonStringEnumConverter with default options the deserialization of any type with enum members will fail.

Registering a JsonStringEnumConverter in json serialization settings does not help, because as JsonConverter says: "When placed on a property or field, the specified converter will always be used.".

When JsonConverter is placed on the type, if you've a compatible serializer in settings that will be used.

I see multiple solutions here, but one of them is simple:

  • Have an option to NOT to emit [JsonConverter(typeof(JsonStringEnumConverter))] for enum properties. With this settings will have to be configured but gives great flexibility. Cons: if no settings configured Json serialization will fail because there is nothing registered for enums.
  • Move the JsonConverter from properties to the enum types.

Now:

public enum Foo
{
    FooOne = 0,
    FooTwo = 1
}

public class FooClass
{
    [JsonConverter(typeof(JsonStringEnumConverter))]
    public Foo enumValue { get; set; }
}

With fix:

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Foo
{
    FooOne = 0,
    FooTwo = 1
}

public class FooClass
{
    public Foo enumValue { get; set; }
}

This way there is no requirement to have settings configured for enums, but gives the flexibility to have the behavior changed or register even typed enum converters there.

Support Key: [my-support-key]
N/A

OpenAPI Specifications
Same as in #175

Additional context
[EnumMember] is not used with System.Text.Json so their declaration can be omitted.

Member with the same signature is already declared

Describe the bug
The following command:

refitter https://api.hubspot.com/api-catalog-public/v1/apis/webhooks/v3 --namespace HubSpot --output ./GeneratedCode.cs

Generates the following:

    public interface IWebhooksAPI
    {

        [Get("/webhooks/v3/{appId}/settings")]
        Task<SettingsResponse> GetAll(int appId);

        [Put("/webhooks/v3/{appId}/settings")]
        Task<SettingsResponse> Configure(int appId, [Body] SettingsChangeRequest body);

        [Delete("/webhooks/v3/{appId}/settings")]
        Task Clear(int appId);

        [Get("/webhooks/v3/{appId}/subscriptions")]
        Task<SubscriptionListResponse> GetAll(int appId);

        [Post("/webhooks/v3/{appId}/subscriptions")]
        Task Create(int appId, [Body] SubscriptionCreateRequest body);

        [Post("/webhooks/v3/{appId}/subscriptions/batch/update")]
        Task<BatchResponseSubscriptionResponse> UpdateBatch(int appId, [Body] BatchInputSubscriptionBatchUpdateRequest body);

        [Get("/webhooks/v3/{appId}/subscriptions/{subscriptionId}")]
        Task<SubscriptionResponse> GetById(int subscriptionId, int appId);

        [Delete("/webhooks/v3/{appId}/subscriptions/{subscriptionId}")]
        Task Archive(int subscriptionId, int appId);

        [Patch("/webhooks/v3/{appId}/subscriptions/{subscriptionId}")]
        Task<SubscriptionResponse> Update(int subscriptionId, int appId, [Body] SubscriptionPatchRequest body);

    }

Here there are two GetAll methods defined that are clashing.

Support Key: kedfgny

OpenAPI Specifications
https://api.hubspot.com/api-catalog-public/v1/apis/webhooks/v3

Also happens with https://api.hubspot.com/api-catalog-public/v1/apis/crm/v3/properties

Base type not generated for types specified by oneOf in the schema

Describe the bug
I try to generate a client for the FusionAuth spec and it is using oneOf for IdentityProviderField and in the resulting source file no IdentityProvider base class is generated.

Support Key: [my-support-key]
N/A (did not find it in output)

OpenAPI Specifications
https://raw.githubusercontent.com/FusionAuth/fusionauth-openapi/main/openapi.yaml

Additional context
My quick fix were these:

  1. Create an IdentityProvider class:
public class IdentityProvider
{
}
  1. Change the method response to have a generic parameter:
    public partial class IdentityProviderResponse<T> where T: IdentityProvider
    {

        [JsonPropertyName("identityProvider")]

        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]   
        public T IdentityProvider { get; set; }

        [JsonPropertyName("identityProviders")]

        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]   
        public ICollection<T> IdentityProviders { get; set; }

        private IDictionary<string, object> _additionalProperties;

        [JsonExtensionData]
        public IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }
    }
  1. Change the method signature:
Task<IdentityProviderResponse<T>> RetrieveIdentityProviderWithIdAsync<T>(string identityProviderId, CancellationToken cancellationToken = default) where T : IdentityProvider;

Path to OpenAPI spec file is required in CLI command even when using a `--settings-file` parameter.

A new and a very convenient feature allows us to use refitter file via CLI with a --settings-file option in the following manner:
refitter ./openapi.json --settings-file ./openapi.refitter.

The path to OpenAPI spec file (./openapi.json) cannot be dropped from the CLI command as it results in Input file is required error.

At the same time refitter file has an openApiPath parameter with seemingly the same meaning. Thus it seems redundant and error-prone to have a mandatory path in a CLI command as it becomes unclear which of the paths would be used in the end - the one in CLI command or the one in .refitter file.

Proposal: filter generated interfaces

This would allow us to only generate a subset of the interfaces/methods we need.

e.g. App might need "Configuration"-Controller/Group Methods, but not others.

by allowing things like the following, the consumers can specify exactly what they need.
matches are based on regex

--include '^/api/v1/user/.*'
--include '^/api/(?!pet\b)' // include everything that is NOT /api/pet

For a less fine grained, but also useful way, we could also add filter for the "Tags" section of an openapi spec.

matches are based value, case-insensitive

--include-tag User \
--include-tag Configuration

NSwag contracts

Maybe it's there and I couldn't find it, but when generating contracts, I'd like to specify some NSwag options like dateType (I don't like DateTimeOffset) or arrayType.

Filter `--tag` broken

Describe the bug
Filtering a spec by tag using --tag A --tag B is currently broken as it only accepts the last filter entered.

Instead it should keep all paths that contain any of the tags.

Additional context

This is due to the following check constantly overriding previous results:
exclude = method.Value.Tags?.Exists(x => x == tag) != true;

The code may be rewritten, to remove the looping over includeTags by using the following:

var exclude = method.Value.Tags?.Exists(includeTags.Contains) != true;

Also, previously the enumerated collections were cloned, to not break the enumeration, after the rewrite the enumerators are reused (.ToArray() was removed)

7025d61#diff-275851d6772d6a0a03b9aaf63017c87331d0334e274deeebd199fe8cd07cc521R34

7025d61#diff-275851d6772d6a0a03b9aaf63017c87331d0334e274deeebd199fe8cd07cc521R37

deleting from an collection that is currently enumerated is undefined behavior

Add `CancellationToken cancellationToken = default` to generated Methods

Currently the generated code looks like this

[Post("/v1/Endpoint")]
Task EndpointPOST([Body] EndpointV1 body);

To support timeouts etc. it would be nice to get refitter to add the CancellationToken-parameter like this

[Post("/v1/Endpoint")]
Task EndpointPOST([Body] EndpointV1 body, CancellationToken cancellationToken = default);

Remove unused schema definitions (e.g. `--remove-unreferenced-schema` )

To reduce the amount of generated Contracts, we could walk the paths and remove and non-referenced schema

As we already are able to filter paths, we can make use of that to clean up the spec and generate less code.

Example code for cleaning up the schema definitions

// Uses Microsoft.OpenApi.Readers & SwashBuckle.AspNetCore for Reading and Writing specs

void Main()
{
    var rdr = new Microsoft.OpenApi.Readers.OpenApiStreamReader();
    using var specFile = File.OpenRead(@"C:\spec.json");

    // doc is a "filtered" OpenApi-spec
    var doc = rdr.Read(specFile, out var diagnostic);

    var schemaCleaner = new SchemaCleaner();
    schemaCleaner.RemoveUnreferencedSchema(doc);

    using var tw = File.CreateText(@"C:\clean_spec.yaml");
    var writer = new Microsoft.OpenApi.Writers.OpenApiYamlWriter(tw);
    doc.SerializeAsV3(writer);
}


public class SchemaCleaner
{
    public void RemoveUnreferencedSchema(OpenApiDocument doc)
    {
        var usage = FindUsedSchema(doc);

        var unused = doc.Components.Schemas.Where(s => !usage.Contains(s.Key))
            .ToArray();
        foreach (var unusedSchema in unused)
        {
            doc.Components.Schemas.Remove(unusedSchema);
        }
    }

    HashSet<string> FindUsedSchema(OpenApiDocument doc)
    {
        var toProcess = new Stack<OpenApiSchema>();

        foreach (var (_, pathItem) in doc.Paths)
        {
            foreach (var p in pathItem.Parameters)
            {
                TryPush(p.Schema, toProcess);
            }

            foreach (var (_, op) in pathItem.Operations)
            {
                foreach (var (_, resp) in op.Responses)
                {
                    foreach (var (_, header) in resp.Headers)
                    {
                        TryPush(header.Schema, toProcess);
                    }
                    foreach (var (_, content) in resp.Content)
                    {
                        TryPush(content.Schema, toProcess);
                    }
                }
            }
        }

        var seen = new HashSet<string>();
        while (toProcess.TryPop(out var schema))
        {
            if (schema.Reference?.Id is { } refId)
            {
                if (!seen.Add(refId))
                {
                    // prevent recursion
                    continue;
                }
            }
            foreach (var subSchema in EnumerateSchema(schema))
            {
                TryPush(subSchema, toProcess);
            }
        }

        return seen;
    }

    private void TryPush(OpenApiSchema? schema, Stack<OpenApiSchema> stack)
    {
        if (schema == null)
        {
            return;
        }
        stack.Push(schema);
    }

    IEnumerable<OpenApiSchema> EnumerateSchema(OpenApiSchema? schema)
    {
        if (schema is null)
        {
            return Enumerable.Empty<OpenApiSchema>();
        }

        return EnumerateInternal(schema)
            .Where(x => x != null)
            .Select(x => x!);

        static IEnumerable<OpenApiSchema?> EnumerateInternal(OpenApiSchema schema)
        {
            yield return schema.AdditionalProperties;
            yield return schema.Items;
            yield return schema.Not;

            foreach (var subSchema in schema.AllOf)
            {
                yield return subSchema;
            }
            foreach (var subSchema in schema.AnyOf)
            {
                yield return subSchema;
            }
            foreach (var subSchema in schema.OneOf)
            {
                yield return subSchema;
            }
            foreach (var (_, subSchema) in schema.Properties)
            {
                yield return subSchema;
            }
        }

    }
}

Unexpected initial token 'Boolean' when populating object

Describe the bug
refitter https://api.getport.io/json -o IPortApi.cs
Refitter v0.7.3.37
Support key: 6mxy0om

Error:
Unexpected initial token 'Boolean' when populating object. Expected JSON object or array. Path
'paths./v1/blueprints/{blueprint_identifier}/permissions.patch.requestBody.content.application/json.schema.properties.ad
ditionalProperties', line 1, position 3314.
Error: Could not find color or style 'T'.

Support Key: 6mxy0om

OpenAPI Specifications
https://api.getport.io/json

Single output

Why using CLI (reading settings from .reffiter file) I get two outputs?
One in outputFolder specified in settings file, but with name from that file name (i.e. test.refitter -> test.g.cs) and another one named Ouput.cs in directory from which I ran the command?

Please, add outputFileName to .reffiter settings so that only one output is produced.

Generated output has Task return type instead of expected Task<T>

Hi,

I have noticed the following with refitter 0.5.26. With OpenApi spec input:

openapi: 3.0.0
x-stoplight:
  id: w776sltk0h1bo
info:
  title: Gamenight
  version: '1.0'
  contact:
    name: Dennis Brentjes
    email: [email protected]
    url: 'https://brentj.es'
  description: Api specifaction for a Gamenight server
  license:
    name: MIT
servers:
  - url: 'http://localhost:8080'
    description: Gamenight
paths:
  /token:
    get:
      summary: ''
      operationId: get-token
      responses:
        '200':
          $ref: '#/components/responses/TokenResponse'
      requestBody:
        $ref: '#/components/requestBodies/LoginRequest'
      description: Submit your credentials to get a JWT-token to use with the rest of the api.
      parameters: []
components:
  schemas:
    Token:
      title: Token
      x-stoplight:
        id: 8pz19kigm1jer
      type: object
      properties:
        jwt_token:
          type: string
    Login:
      title: Login
      type: object
      properties:
        username:
          type: string
        password:
          type: string
      required:
        - username
        - password
  requestBodies:
    LoginRequest:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Login'
  responses:
    TokenResponse:
      description: Example response
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Token'

I get the following output, with Task missing their response output as Task result.

// <auto-generated>
//     This code was generated by Refitter.
// </auto-generated>

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

namespace GeneratedCode
{
    public interface IGamenight
    {

        /// <summary>
        /// Submit your credentials to get a JWT-token to use with the rest of the api.
        /// </summary>
        [Get("/token")]
        Task GetToken([Body] object body);

    }


}


//----------------------
// <auto-generated>
//     Generated using the NSwag toolchain v13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------

#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
#pragma warning disable 612 // Disable "CS0612 '...' is obsolete"
#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'"
#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant"
#pragma warning disable 8603 // Disable "CS8603 Possible null reference return"

namespace GeneratedCode
{
    using System = global::System;

    

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")]
    public partial class Token
    {

        [System.Text.Json.Serialization.JsonPropertyName("jwt_token")]

        [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)]   
        public string Jwt_token { get; set; }

        private System.Collections.Generic.IDictionary<string, object> _additionalProperties;

        [System.Text.Json.Serialization.JsonExtensionData]
        public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")]
    public partial class Login
    {

        [System.Text.Json.Serialization.JsonPropertyName("username")]

        [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)]   
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Username { get; set; }

        [System.Text.Json.Serialization.JsonPropertyName("password")]

        [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)]   
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Password { get; set; }

        private System.Collections.Generic.IDictionary<string, object> _additionalProperties;

        [System.Text.Json.Serialization.JsonExtensionData]
        public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }


}

#pragma warning restore  108
#pragma warning restore  114
#pragma warning restore  472
#pragma warning restore  612
#pragma warning restore 1573
#pragma warning restore 1591
#pragma warning restore 8073
#pragma warning restore 3016
#pragma warning restore 8603

When I apply this patch to refitter

diff --git a/src/Refitter.Core/RefitInterfaceGenerator.cs b/src/Refitter.Core/RefitInterfaceGenerator.cs
index ad588c4..0b19ab0 100644
--- a/src/Refitter.Core/RefitInterfaceGenerator.cs
+++ b/src/Refitter.Core/RefitInterfaceGenerator.cs
@@ -40,7 +40,7 @@ public class RefitInterfaceGenerator
                 var operation = operations.Value;
 
                 var returnTypeParameter = operation.Responses.ContainsKey("200")
-                    ? generator.GetTypeName(operation.Responses["200"].Schema, true, null)
+                    ? generator.GetTypeName(operation.Responses["200"].ActualResponse.Schema, true, null)
                     : null;
 
                 var returnType = GetReturnType(returnTypeParameter);

I get the expected output with Task returning their output.

// <auto-generated>
//     This code was generated by Refitter.
// </auto-generated>

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

namespace GeneratedCode
{
    public interface IGamenight
    {

        /// <summary>
        /// Submit your credentials to get a JWT-token to use with the rest of the api.
        /// </summary>
        [Get("/token")]
        Task<Token> GetToken([Body] object body);

    }


}


//----------------------
// <auto-generated>
//     Generated using the NSwag toolchain v13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------

#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
#pragma warning disable 612 // Disable "CS0612 '...' is obsolete"
#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'"
#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant"
#pragma warning disable 8603 // Disable "CS8603 Possible null reference return"

namespace GeneratedCode
{
    using System = global::System;

    

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")]
    public partial class Token
    {

        [System.Text.Json.Serialization.JsonPropertyName("jwt_token")]

        [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)]   
        public string Jwt_token { get; set; }

        private System.Collections.Generic.IDictionary<string, object> _additionalProperties;

        [System.Text.Json.Serialization.JsonExtensionData]
        public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")]
    public partial class Login
    {

        [System.Text.Json.Serialization.JsonPropertyName("username")]

        [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)]   
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Username { get; set; }

        [System.Text.Json.Serialization.JsonPropertyName("password")]

        [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)]   
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Password { get; set; }

        private System.Collections.Generic.IDictionary<string, object> _additionalProperties;

        [System.Text.Json.Serialization.JsonExtensionData]
        public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }


}

#pragma warning restore  108
#pragma warning restore  114
#pragma warning restore  472
#pragma warning restore  612
#pragma warning restore 1573
#pragma warning restore 1591
#pragma warning restore 8073
#pragma warning restore 3016
#pragma warning restore 8603

The original behaviour seems to be a bug, I have no idea however if my "fix" has any impact on existing projects but I suspect that ActualResponse wil link be a reference to itself when not being used by a ref. But that is a question you might be able to answer.

Add SourceGenerator support to the standalone tool

Is your feature request related to a problem? Please describe.
To enable the generated client to have the serializers source generated, the tool could generate an <ApiTypeName>JsonSerializerContext class which inherits from JsonSerializerContext and add all the type names with JsonSerializable to the class, so the serializer source generator could kick in and do its job.

This feature can only be added to the standalone tool because source generators cannot interact with each other currently.

Describe the solution you'd like
An <ApiTypeName>JsonSerializerContext class would be generated as well.

Describe alternatives you've considered
Manually write this class by hand which is tedious.

Additional context
More info on it: https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/

Model definition with property named System results in class that does not compile

Describe the bug
When generating a client for an OpenApi (swagger version 2.0) with a model definition with a property named System, the generated code no longer compiles. Reason being that the same class also uses references to other classes from the System namespace.

Support Key: mmt1dt0

OpenAPI Specifications
(not the full specs, but this is the gist of it)

{
  "swagger": "2.0",
  "info": {
    "title": "XX",
    "version": "0.0.0"
  },
  "host": "x.io",
  "basePath": "/",
  "schemes": [
    "https"
  ],
  "definitions": {
    "Dummy": {
      "type": "object",
      "properties": {
        "system": {
          "type": "string",
          "description": "XX"
        }
      },
      "description": "XXX",
      "example": {
        "system": "Dummy"
      }
    }
  }
},

Additional context
May also be true for some reserved words. System is not really a reserved word, but just not very practical.

Generated code doesn't build if operationId contains spaces

The generated code for OpenAPI specs where the operationId contains spaces, doesn't build

OpenAPI Specifications

openapi: '3.0.0'
paths:
  /jobs/{job-id}:
    get:
      tags:
      - 'Jobs'
      operationId: 'Get job details'
      description: 'Get the details of the specified job.'
      parameters:
        - in: 'path'
          name: 'job-id'
          description: 'Job ID'
          required: true
          schema:
            type: 'string'
      responses:
        '200':
          description: 'successful operation'

Generate Refit interfaces as `partial` by default

I propose ~~ a --partial Flag for the CLI.~~ refit interfaces to be generated as partial interfaces by default, which would comply with most other code-generator tools out there and the way the models are generated currently.

Reasoning being, for certain classes we need to add attributes, interfaces or utility methods which is currently not possible without modifying the generated code.

By having partial classes for Models and Refit-Interfaces, we can make use of a generated.additions.cs (example name) which contains the needed modifications.

// a.cs
internal partial interface ISomething
{

}

// a.additions.cs
[FancyAttribute("With Value")]
[Headers("X-App: MyApp")]
partial interface ISomething {}

OperationHeaders generation problem with headers containing a -

When generating Refit Interfaces for operations with header parameters, if the header key contains a - , the resulting code contains an AliasAs, which is ignored by Refit when serializing the header parameters.

Example

parameters:
  - name: api-key
    in: header
    description: ''
    required: false
    schema:
      type: string
   [Header("api_key"), AliasAs("api-key")] string api_key

Missing "Accept" Request Header in generated files based on OAS 3.0

When Refitter generates Refit interfaces from a OAS 3.0 spec file, it doesn't add the "Accept" header , which necesssary for ASP.NET Core's ProblemDetails Middleware to function properly.

According to OAS 3.0 specification, The content property in responses contains the MIME type expected in the response.

I'm suggesting to add an optional parameter addAcceptHeaders to add the accept header.
Examples

  • without addAcceptHeaders:
        /// Multiple status values can be provided with comma separated strings
        /// </summary>
        [Get("/pet/findByStatus")]
        Task<ICollection<Pet>> FindPetsByStatus([Query(CollectionFormat.Multi)] IEnumerable<Anonymous> status);
  • with addAcceptHeaders:
        /// Multiple status values can be provided with comma separated strings
        /// </summary>
        [Headers("Accept: application/json")]
        [Get("/pet/findByStatus")]
        Task<ICollection<Pet>> FindPetsByStatus([Query(CollectionFormat.Multi)] IEnumerable<Anonymous> status);```


IServiceCollectionExtensions extra closing parenthesis with httpMessageHandlers

Describe the bug
Version 0.8.2 generates a new dependency injection IServiceCollectionExtensions, and seems to introduce a syntax error as shown below:

            services
                .AddRefitClient<IBackendApi>()
                .ConfigureHttpClient(c => c.BaseAddress = baseUrl)
                .AddHttpMessageHandler<AuthorizationMessageHandler>()
                .AddHttpMessageHandler<TelemetryMessageHandler>()); // extra ) here

This seems to originate from this line

I wasn't sure if there were use cases that this worked, but this is the config that does not work, and probably has something to do with the httpMessageHandlers in the dependency injection section.

{
    "openApiPath": "../../Services/BackendApi/wwwroot/swaggerfile.json",
    "namespace": "App.Services.Clients.BackendApiClient",
    "naming": {
      "useOpenApiTitle": false,
      "interfaceName": "BackendApiClient"
    },
    "generateContracts": true,
    "generateXmlDocCodeComments": true,
    "addAutoGeneratedHeader": false,
    "addAcceptHeaders": false,
    "returnIApiResponse": true,
    "generateOperationHeaders": true,
    "typeAccessibility": 0,
    "useCancellationTokens": false,
    "useIsoDateFormat": true,
    "multipleInterfaces": 2,
    "generateDeprecatedOperations": false,
    "operationNameTemplate": "{operationName}Async",
    "optionalParameters": true,
    "outputFolder": "./Clients",
    "includePathMatches": [
      "^(?!.*[Ii]ntegration[Ee]vent).*\/api.*"
     ],
    "dependencyInjectionSettings": {
      "httpMessageHandlers": [
          "AuthorizationMessageHandler",
          "TelemetryMessageHandler"
      ]
    }
  }

I cannot include the API spec for privacy reasons.

Allow for naming of methods when generating interfaces by endpoint

Is your feature request related to a problem?
I think it would be nice if it were possible to choose naming for methods when interfaces are generated ByEndpoint. Right now it is only possible to customize method names when all endpoints are in a single interface. When doing it for a single interface the {operationName} is a requirement, but I do not think this should be a requirement since there will only be one method in each interface.

Describe the solution you'd like
Example of how this could work:

{
    "openApiPath": "./openapi.json",
    "namespace": "Your.Namespace.Of.Choice.GeneratedCode",
    "multipleInterfaces": "ByEndpoint",
    "operationNameTemplate": "ExecuteAsync" // Default is Execute
}

The .refitter file above results in the following code:

/// <summary>
/// Update an existing pet
/// </summary>
public partial 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 partial interface IAddPetEndpoint
{
    /// <summary>
    /// Add a new pet to the store
    /// </summary>
    [Post("/pet")]
    Task<Pet> Execute([Body] Pet body);
}

...

Refitter fails to generate FormData parameter for file upload

Describe the bug

Refitter ignores the IFormFile parameter decorated with [FromForm] which is used for receiving file upload.
The controller parameter is completely missed in the generated refit interface method.

If it worked, the generated method would have [Multipart] as method annotation and StreamPart as the type for IFormFile parameter
https://github.com/reactiveui/refit#multipart-uploads

Support Key: 8hz0dgn

OpenAPI Specifications
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"formFile": {
"type": "string",
"format": "binary"
}
}
},
"encoding": {
"formFile": {
"style": "form"
}
}
}
}
}

Additional context

Adding security schemes to the api interface generator

I'm currently looking at adding security schemes to the Api generator, but am unsure how to handle it. I suspect that it have to be separate functions, or maybe seperate interfaces, for each securityscheme defined one? But I'm unsure what would be the better way forward and wanted to discuss it.

Add support for generating IApiResponse<T> as return types

Add support for generating interfaces that returns Task<IApiResponse<T>> as the return type

Like this:

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

So that if the call to GetPetById() fails then we can handle the negative response ourselves instead of catching an ApiException

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

if (!response. IsSuccessStatusCode)
{
    // Do something about the failed response
}

var pet = response.Content;

The IApiResponse<T> interface comes from Refit and is defined as

/// <inheritdoc/>
public interface IApiResponse<out T> : IApiResponse
{
    /// <summary>
    /// Deserialized request content as <typeparamref name="T"/>.
    /// </summary>
    T? Content { get; }
}

/// <summary>
/// Base interface used to represent an API response.
/// </summary>
public interface IApiResponse : IDisposable
{
    /// <summary>
    /// HTTP response headers.
    /// </summary>
    HttpResponseHeaders Headers { get; }

    /// <summary>
    /// HTTP response content headers as defined in RFC 2616.
    /// </summary>
    HttpContentHeaders? ContentHeaders { get; }

    /// <summary>
    /// Indicates whether the request was successful.
    /// </summary>
    bool IsSuccessStatusCode { get; }

    /// <summary>
    /// HTTP response status code.
    /// </summary>
    HttpStatusCode StatusCode { get; }

    /// <summary>
    /// The reason phrase which typically is sent by the server together with the status code.
    /// </summary>
    string? ReasonPhrase { get; }

    /// <summary>
    /// The HTTP Request message which led to this response.
    /// </summary>
    HttpRequestMessage? RequestMessage { get; }

    /// <summary>
    /// HTTP Message version.
    /// </summary>
    Version Version { get; }

    /// <summary>
    /// The <see cref="ApiException"/> object in case of unsuccessful response.
    /// </summary>
    ApiException? Error { get; }
}

Please add support for kebab string casing parameters

I found Refitter didn't generate valid C# output with kebab string casing parameters.
See job-id in this sample OpenAPI 3.0 file:

openapi: "3.0.0"
info:
  version: "v1"
  title: "Test API"
servers:
  - url: "https://test.host.com/api/v1"
paths:
  /jobs/{job-id}:
    get:
      tags:
      - "Jobs"
      summary: "Get job details"
      description: "Get the details of the specified job."
      parameters:
        - in: "path"
          name: "job-id"
          description: "Job ID"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "successful operation"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobResponse"
components:
  schemas:
    JobResponse:
      type: "object"
      properties:
        job:
          type: "object"
          properties:
            start-date:
              type: "string"
              format: "date-time"
            details:
              type: "string"

Invalid C# parameter output snippet:

        [Get("/jobs/{job-id}")]
        Task<JobResponse> Jobs(string job-id);

Multipart endpoint [FromForm] decorated argument is missing from signature

Describe the bug
I have a receiving API endpoint with following signature:

[HttpPost("[action]/{bucketName}/{directory}", Name = nameof(UploadFileAsync))]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> UploadFileAsync(string bucketName,
	[FromRoute] string directory,
	[FromForm] UploadFileArgs uploadFileArgs,
	IFormFile file,
	CancellationToken cancellationToken)

using refitter CLI i get the following contract generated for the controller:

 /// <summary>
 /// Returns information about file
 /// </summary>
 [System.CodeDom.Compiler.GeneratedCode("Refitter", "0.7.5.0")]
 public interface IFileApi
 {
     [Headers("Accept: application/json")]
     [Get("/v1/File/GetFileInfo/{bucketName}/{objectName}")]
     Task<FileInfo> GetFileInfoAsync(string bucketName, string objectName, CancellationToken cancellationToken = default);

     [Headers("Accept: application/octet-stream")]
     [Get("/v1/File/DownloadFile/{bucketName}/{objectName}")]
     Task DownloadFileAsync(string bucketName, string objectName, [Query] string versionId, CancellationToken cancellationToken = default);

     [Multipart]
     [Headers("Accept: application/json")]
     [Post("/v1/File/UploadFile/{bucketName}/{directory}")]
     Task UploadFileAsync(string bucketName, string directory, StreamPart file, CancellationToken cancellationToken = default);

     [Headers("Accept: application/json")]
     [Post("/v1/File/CopyFile/{fromBucketName}/{filePath}/{toDirectory}")]
     Task CopyFileAsync(string fromBucketName, string filePath, string toDirectory, [Query] string toBucketName, CancellationToken cancellationToken = default);

     [Headers("Accept: application/json")]
     [Post("/v1/File/MoveFile/{fromBucketName}/{fromFilePath}/{toFilePath}")]
     Task MoveFileAsync(string fromBucketName, string fromFilePath, string toFilePath, [Query] string toBucketName, CancellationToken cancellationToken = default);

     [Delete("/v1/File/DeleteFile/{bucketName}/{filePath}")]
     Task DeleteFileAsync(string bucketName, string filePath, CancellationToken cancellationToken = default);
 }

The endpoint in question is missing the long[] OrganizationalUnitIds which is a single property of the [FromForm] UploadFileArgs uploadFileArgs argument in the receiving endpoint. In line 545 of openApi spec the request body is defined as follows:

"requestBody": {
                    "content": {
                        "multipart/form-data": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "OrganizationalUnitIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer",
                                            "format": "int64"
                                        }
                                    },
                                    "file": {
                                        "type": "string",
                                        "format": "binary"
                                    }
                                }
                            },
                            "encoding": {
                                "OrganizationalUnitIds": {
                                    "style": "form"
                                },
                                "file": {
                                    "style": "form"
                                }
                            }
                        }
                    }
                }

Support Key: [h/tnek2]
The support key is a unique identifier generated on the machine which is used when logging telemetry data. This feature is introduced in v0.5.4 and is shown in the output when running Refitter.

openapispec.json

String parameters with format 'date' get no Format in the QueryAttribute

Describe the bug
When generating a client for an OpenApi (swagger version 2.0) with url (query) parameters of type string with a format of 'date' are generated as a regular DateTimeOffset without a 'Date' format.

Support Key: mmt1dt0

OpenAPI Specifications
(not the full specs, but this is the gist of it)

{
  "swagger": "2.0",
  "info": {
    "title": "XX",
    "version": "0.0.0"
  },
  "host": "x.io",
  "basePath": "/",
  "schemes": [
    "https"
  ],
  "paths": {
    "/t/dummy/{employee_id}": {
      "get": {
        "summary": "X",
        "description": "X",
        "operationId": "dummy",
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "description": "the specific employee",
            "required": true,
            "format": "int64",
            "type": "integer"
          },
          {
            "name": "valid_from",
            "in": "query",
            "description": "the start of the period",
            "required": true,
            "format": "date",
            "type": "string"
          },
          {
            "name": "valid_to",
            "in": "query",
            "description": "the end of the period",
            "required": true,
            "format": "date",
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "No response was specified",
            "schema": {
              "$ref": "#/definitions/DummyList"
            }
          }
        },
        "produces": [
          "application/json",
          "application/xml"
        ]
      }
    },
  }
},

Additional context
Resulting code is:

        /// <summary>
        /// X
        /// </summary>
        [Get("/t/dummy/{employee_id}")]
        Task<DummyList> Dummy(long employee_id, [Query(CollectionFormat.Multi)] System.DateTimeOffset valid_from, [Query(CollectionFormat.Multi)] System.DateTimeOffset valid_to);

When executing, this results in errors.

Expected code:

        /// <summary>
        /// X
        /// </summary>
        [Get("/t/dummy/{employee_id}")]
        Task<DummyList> Dummy(long employee_id, [Query(CollectionFormat.Multi, Format = "yyyy-MM-dd")] System.DateTimeOffset valid_from, [Query(CollectionFormat.Multi, Format = "yyyy-MM-dd")] System.DateTimeOffset valid_to);

The same may be true for other date / time related formats, like 'time'. The same may also be true for entity classes where properties are generated as regular 'DateTimeOffset'.

Ideally, for 'time' and 'date', both the arguments of functions and the properties of entity classes would result in 'TimeOnly' and 'DateOnly', respectively, but I'm not sure if those are fully supported in Refit? I think they are for properties, not sure about function arguments.

Edit: apparently 'time' was no format in OpenApi (Swagger) v2, but 'date' apparently is.

DirectoryNotFoundException

Describe the bug
Code generation fails if the output folder doesn't exist

Support Key: ptbddgt

OpenAPI Specifications
https://petstore3.swagger.io/api/v3/openapi.json

Additional context:

$ refitter https://petstore3.swagger.io/api/v3/openapi.json --output ../Boom/Kaboom/GeneratedCode.cs

Exception message
Could not find a part of the path 'C:\Boom\Kaboom\GeneratedCode.cs'.

Stack trace

System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\Boom\Kaboom\GeneratedCode.cs'.
at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(System.String fullPath, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.IO.FileOptions options) at offset 161
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(System.String fullPath, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.IO.FileOptions options, System.Int64 preallocationSize) at offset 6
at System.IO.Strategies.OSFileStreamStrategy..ctor(System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.IO.FileOptions options, System.Int64 preallocationSize) at offset 49
at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.IO.FileOptions options, System.Int64 preallocationSize) at offset 24
at System.IO.Strategies.FileStreamHelpers.ChooseStrategy(System.IO.FileStream fileStream, System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.IO.FileOptions options, System.Int64 preallocationSize)
at System.IO.File.AsyncStreamWriter(System.String path, System.Text.Encoding encoding, System.Boolean append)
at System.IO.File.WriteAllTextAsync(System.String path, System.String contents, System.Text.Encoding encoding, System.Threading.CancellationToken cancellationToken) at offset 85
at Refitter.GenerateCommand.<ExecuteAsync>d__1.MoveNext() at offset 397 in /home/runner/work/refitter/refitter/src/Refitter/GenerateCommand.cs:line 45:col 13

Downloading OpenAPI specification from URI using `content-encoding: gzip` fails

Describe the bug
Attempting to download https://developers.intellihr.io/docs/v1/swagger.json results in garbled/binary JSON file being saved.
The file is publicly accessible and does not require auth, so it should be reproducible. I have a similar bug reported to another of my projects

Support Key: ptbddgt

OpenAPI Specifications
https://developers.intellihr.io/docs/v1/swagger.json

Additional context
After making a cURL request to the URI, I found out that that Refitter doesn't support URI's returning content-encoding: gzip

The following command:

curl https://developers.intellihr.io/docs/v1/swagger.json -v

returns:

*   Trying 18.173.5.96:443...
* Connected to developers.intellihr.io (18.173.5.96) port 443 (#0)
* schannel: disabled automatic use of client certificate
* ALPN: offers http/1.1
* ALPN: server accepted http/1.1
* using HTTP/1.1
> GET /docs/v1/swagger.json HTTP/1.1
> Host: developers.intellihr.io
> User-Agent: curl/8.0.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 70281
< Connection: keep-alive
< x-amz-id-2: KbRD1cgNtvruOBsX/Xyxmvcnca3PLvJnb/o2tjFQV9jEBFB8rGareRNv8mj6ub1jd5AX6to/3Y4=
< x-amz-request-id: 898QGBVBFHSY8SNM
< Date: Mon, 04 Sep 2023 09:05:13 GMT
< Last-Modified: Mon, 04 Sep 2023 00:29:47 GMT
< ETag: "614dd1541549b50f0b008444459f94da"
< x-amz-server-side-encryption: AES256
< Content-Encoding: gzip
< x-amz-version-id: SDHILeEo0afGzS0a0a5ljWAToLzlnjWQ
< Accept-Ranges: bytes
< Server: AmazonS3
< Strict-Transport-Security: max-age=15552000; includeSubDomains; preload
< X-Cache: Miss from cloudfront
< Via: 1.1 78a128491ada170a2d9b82ba12d23d7a.cloudfront.net (CloudFront)
< X-Amz-Cf-Pop: CPH50-P1
< X-Amz-Cf-Id: pxEry-q7wwDbWme5LptPbbbMHcKPttbcKbFp4V5LmAIQBOtKNkvMIw==
<
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
* Failure writing output to destination
* Closing connection 0
* schannel: shutting down SSL/TLS connection with developers.intellihr.io port 443

Generated files have inconsistent lined endings

Describe the bug
The generated files seem to have a mixture different lines endings. This dialog appears everytime a file is opened in VS.

image

Perhaps consider normalizing the line endings to whatever the OS environment the tools is run in :)

Support Key: kedfgny

OpenAPI Specifications
N/A

Additional context
Add any other context about the problem here.

Improving documentation for --settings-file cli tool parameter

Per documentation file for --settings-file: .. Specifying this option will ignore all other settings.

This does not seem to be precisely correct as --output parameter is still taken into account and allows to specify a name for a generated output file. This is a welcome feature as output file name doesn't seem to be configurable via a `.refitter' file.

Maybe it would help to reflect the option in the documentation.

Generated nullable query method params are not set to a default value of null

I have a swagger doc with nullable query params and the generated Refit method signature has the param marked as nullable but not set with null by default so in order to call the method all of the nullable params need to be set as null vs just setting the params you want to set.

"/v1/accounts/{accountId}/addresses": { "get": { "tags": [ "AccountAddresses" ], "operationId": "AccountAddresses_GetAccountAddresses", "parameters": [ { "name": "accountId", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" }, "x-position": 1 }, { "name": "IsPrimary", "in": "query", "schema": { "type": "boolean", "nullable": true, "default": null } }, { "name": "IsActive", "in": "query", "schema": { "type": "boolean", "nullable": true } }, { "name": "IncludeSpouse", "in": "query", "schema": { "type": "boolean" } }, { "name": "SortBy", "in": "query", "style": "form", "explode": true, "schema": { "type": "array", "nullable": true, "xml": { "name": "ArrayOfstring", "wrapped": true }, "items": { "type": "string", "xml": { "name": "string" } } } }, { "name": "Search", "in": "query", "schema": { "type": "string", "nullable": true } }, { "name": "PageNumber", "in": "query", "schema": { "type": "integer", "format": "int32" } }, { "name": "PageSize", "in": "query", "schema": { "type": "integer", "format": "int32" } }, { "name": "ExportFileName", "in": "query", "schema": { "type": "string", "nullable": true } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PagedApiResponseOfIEnumerableOfAccountAddressSummary" } } } } } }, "post": { "tags": [ "AccountAddresses" ], "operationId": "AccountAddresses_CreateAccountAddress", "parameters": [ { "name": "accountId", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" }, "x-position": 1 } ], "requestBody": { "x-name": "command", "content": { "application/json": { "schema": { "nullable": true, "$ref": "#/components/schemas/CreateAccountAddress" } } }, "x-position": 2 }, "responses": { "201": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountAddressSummary" } } } } } } },

The client parameter type's names occur wrong when multipart is include.

image

sample openapi:
animal.json

sample refitter config file:
Animal.refitter
{
"openApiPath": "./animal.json",
"namespace": "Animal",
"naming": {
"useOpenApiTitle": false
},
"multipleInterfaces": "ByTag",
"operationNameTemplate": "{operationName}Async",
"outputFolder": "../Clients",
"outputFilename": "Animal.cs",
"codeGeneratorSettings": {
"requiredPropertiesMustBeDefined": true,
"generateDataAnnotations": true,
"anyType": "object",
"dateType": "System.DateTimeOffset",
"dateTimeType": "System.DateTimeOffset",
"timeType": "System.TimeSpan",
"timeSpanType": "System.TimeSpan",
"arrayType": "System.Collections.Generic.ICollection",
"dictionaryType": "System.Collections.Generic.IDictionary",
"arrayInstanceType": "System.Collections.ObjectModel.Collection",
"dictionaryInstanceType": "System.Collections.Generic.Dictionary",
"arrayBaseType": "System.Collections.ObjectModel.Collection",
"dictionaryBaseType": "System.Collections.Generic.Dictionary",
"propertySetterAccessModifier": "",
"generateImmutableArrayProperties": false,
"generateImmutableDictionaryProperties": false,
"handleReferences": false,
"jsonSerializerSettingsTransformationMethod": null,
"generateJsonMethods": false,
"enforceFlagEnums": false,
"inlineNamedDictionaries": false,
"inlineNamedTuples": true,
"inlineNamedArrays": false,
"generateOptionalPropertiesAsNullable": false,
"generateNullableReferenceTypes": false,
"generateNativeRecords": false,
"generateDefaultValues": true,
"inlineNamedAny": false,
"excludedTypeNames": []
}
}

Generated Method names contains invalid characters.

Describe the bug
Hello there, interesting project so gave it a go :)

The following command refitter https://api.hubspot.com/api-catalog-public/v1/apis/events/v3/send --namespace HubSpot --output ./GeneratedCode.cs generated this code

namespace HubSpot
{
    public interface ICustomBehavioralEventsAPI
    {

        /// <summary>
        /// Endpoint to send an instance of a behavioral event
        /// </summary>
        [Post("/events/v3/send")]
        Task Post/events/v3/send([Body] BehavioralEventHttpCompletionRequest body);

    }


}

As you can see the method name is not valid C#.

Support Key: kedfgny

OpenAPI Specifications
https://api.hubspot.com/api-catalog-public/v1/apis/events/v3/send

Additional context
Add any other context about the problem here.

Missing path parameters in parent

This was pointed out to me by @kgamecarter

Example YAML:

swagger: '2.0'
info:
  title: Reference parameters
  version: v0.0.1
paths:
  '/orders/{orderId}/order-items/{orderItemId}':
    parameters:
      - $ref: '#/parameters/OrderId'
      - $ref: '#/parameters/OrderItemId'
    delete:
      summary: Delete an order item
      description: >-
        This method allows to remove an order item from an order, by specifying
        their ids.
      responses:
        "204":
          description: No Content.
        default:
          description: Default response
          schema:
            $ref: '#/definitions/Error'
definitions:
  Error:
    type: object
    properties:
      message:
          type: string
parameters:
  OrderId:
    name: orderId
    in: path
    description: Identifier of the order.
    required: true
    type: string
    format: uuid
  OrderItemId:
    name: orderItemId
    in: path
    description: Identifier of the order item.
    required: true
    type: string
    format: uuid

Generated code:

public interface IReferenceparameters
{
	/// <summary>
	/// This method allows to remove an order item from an order, by specifying their ids.
	/// </summary>
	[Delete("/orders/{orderId}/order-items/{orderItemId}")]
	Task OrderItems();
}

missing orderId and orderItemId path parameters

Support $ref references to separate files in OpenAPI specifications.

Is your feature request related to a problem? Please describe.
I'd like to generate the refit internfaces for an OpenAPI specification that consists of multiple files that are linked to each other using $ref (see https://swagger.io/docs/specification/using-ref/).

Describe the solution you'd like
It would be great if refitter would support $ref.

Describe alternatives you've considered
The alternative would be to merge the files first using a different tool. But it would be much easier, if this extra step could be avoided.

Additional context
Thank you for your help.

Support for .net6.0 / Reasoning why .net7 is required

i don't quite get the reasoning why the LTS net6.0 is not supported.

i can't install this tool into our build, because we develop on "LTS"s only.
Current workaround is to install the tool globally if net7.0 is installed and use the global executable.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.