Skip to content

Commit

Permalink
Merge pull request #172 from Noblix/main
Browse files Browse the repository at this point in the history
Allow for custom relative output path in .refitter
  • Loading branch information
christianhelle authored Oct 3, 2023
2 parents fac28a4 + 65e742f commit 54a9b90
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 3 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ This will generate a file called `Output.cs` which contains the Refit interface

Refitter is available as a C# Source Generator that uses the [Refitter.Core](https://github.com/christianhelle/refitter.core) library for generating a REST API Client using the [Refit](https://github.com/reactiveui/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. 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
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)***

Expand Down Expand Up @@ -136,6 +136,7 @@ The following is an example `.refitter` file
"generateDeprecatedOperations": false, // Optional. Default=true
"operationNameTemplate": "{operationName}Async", // Optional. Must contain {operationName}
"optionalParameters": false, // Optional. Default=false
"outputFolder": "../CustomOutput" // Optional. Default=./Generated
"additionalNamespaces": [ // Optional
"Namespace1",
"Namespace2"
Expand Down Expand Up @@ -166,6 +167,7 @@ The following is an example `.refitter` file
- `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`
- `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.
Expand Down
7 changes: 7 additions & 0 deletions src/Refitter.Core/RefitGeneratorSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ public class RefitGeneratorSettings
[JsonPropertyName("optionalParameters")]
[JsonProperty("optionalParameters")]
public bool OptionalParameters { get; set; }

/// <summary>
/// Gets or sets the relative path to a folder in which the output files are generated. (default: ./Generated)
/// </summary>
[JsonPropertyName("outputFolder")]
[JsonProperty("outputFolder")]
public string OutputFolder { get; set; } = "./Generated";
}

public enum MultipleInterfaces
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"openApiPath": "../Resources/V3/SwaggerPetstore.json",
"namespace": "Refitter.Tests.CustomGenerated",
"output": "../CustomGenerated",
"generateContracts": false,
"additionalNamespaces": ["Refitter.Tests.AdditionalFiles.SingeInterface"],
"naming": {
"useOpenApiTitle": false,
"interfaceName": "ApiInCustomGeneratedFolder"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// <auto-generated>
// This code was generated by Refitter.
// </auto-generated>

using Refit;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

using Refitter.Tests.AdditionalFiles.SingeInterface;

namespace Refitter.Tests.CustomGenerated
{
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IApiInCustomGeneratedFolder
{
/// <summary>
/// Update an existing pet by Id
/// </summary>
[Headers("Accept: application/xml, application/json")]
[Put("/pet")]
Task<Pet> UpdatePet([Body] Pet body);

/// <summary>
/// Add a new pet to the store
/// </summary>
[Headers("Accept: application/xml, application/json")]
[Post("/pet")]
Task<Pet> AddPet([Body] Pet body);

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

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

/// <summary>
/// Returns a single pet
/// </summary>
[Headers("Accept: application/xml, application/json")]
[Get("/pet/{petId}")]
Task<Pet> GetPetById(long petId);

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

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

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

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

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

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

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

/// <summary>
/// This can only be done by the logged in user.
/// </summary>
[Headers("Accept: application/json, application/xml")]
[Post("/user")]
Task CreateUser([Body] User body);

/// <summary>
/// Creates list of users with given input array
/// </summary>
[Headers("Accept: application/xml, application/json")]
[Post("/user/createWithList")]
Task<User> CreateUsersWithListInput([Body] IEnumerable<User> body);

[Headers("Accept: application/json")]
[Get("/user/login")]
Task<string> LoginUser([Query] string username, [Query] string password);

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

[Headers("Accept: application/json")]
[Get("/user/{username}")]
Task<User> GetUserByName(string username);

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

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


}
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using FluentAssertions;

using Refit;

using Refitter.Tests.CustomGenerated;

using Xunit;

namespace Refitter.SourceGenerators.Tests;

public class CustomOutputFolderGeneratorTests
{
[Fact]
public void Can_Create_File_In_Custom_Path() =>
File.Exists("../../../CustomGenerated/SwaggerPetstoreCustomOutputFolder.g.cs").Should().BeTrue();

[Fact]
public void Can_Resolve_Refit_Interface() =>
RestService.For<IApiInCustomGeneratedFolder>("https://petstore3.swagger.io/api/v3")
.Should()
.NotBeNull();
}
4 changes: 3 additions & 1 deletion src/Refitter.SourceGenerator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Refitter is available as a C# Source Generator that uses the [Refitter.Core](https://github.com/christianhelle/refitter.core) library for generating a REST API Client using the [Refit](https://github.com/reactiveui/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. 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
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)***

Expand Down Expand Up @@ -45,6 +45,7 @@ The following is an example `.refitter` file
"generateDeprecatedOperations": false, // Optional. Default=true
"operationNameTemplate": "{operationName}Async", // Optional. Must contain {operationName}
"optionalParameters": false, // Optional. Default=false
"outputFolder": "../CustomOutput" // Optional. Default=./Generated
"additionalNamespaces": [ // Optional
"Namespace1",
"Namespace2"
Expand Down Expand Up @@ -75,6 +76,7 @@ The following is an example `.refitter` file
- `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`
- `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.
Expand Down
2 changes: 1 addition & 1 deletion src/Refitter.SourceGenerator/RefitterSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private static List<Diagnostic> GenerateCode(
cancellationToken.ThrowIfCancellationRequested();
try
{
var folder = Path.Combine(Path.GetDirectoryName(file.Path), "Generated");
var folder = Path.Combine(Path.GetDirectoryName(file.Path), settings.OutputFolder);
var output = Path.Combine(folder, filename);
if (!Directory.Exists(folder))
{
Expand Down

0 comments on commit 54a9b90

Please sign in to comment.