diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 24261ab18..5bab9a24c 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -16,8 +16,6 @@ on: - "!.github/workflows/template-url.yml" - "!.github/workflows/smoke-tests.yml" - "!test/smoke-tests.ps1" - branches: - - "main" pull_request: paths-ignore: - "**/*" @@ -32,7 +30,7 @@ on: - "!.github/workflows/smoke-tests.yml" - "!test/**/*" branches: - - "*" + - "main" jobs: diff --git a/README.md b/README.md index 13bff2576..be7195333 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ OPTIONS: --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 + --operation-name-template Generate operation names using pattern. When using --multiple-interfaces ByEndpoint, this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' --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 @@ -223,7 +223,7 @@ The following is an example `.refitter` file "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 + "operationNameTemplate": "{operationName}Async", // Optional. Must contain {operationName}. When multipleInterfaces == "ByEndpoint", this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' "optionalParameters": false, // Optional. Default=false "outputFolder": "../CustomOutput" // Optional. Default=./Generated "outputFilename": "RefitInterface.cs", // Optional. Default=Output.cs for CLI tool @@ -337,7 +337,7 @@ The following is an example `.refitter` file - `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 +- `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. When using multiple interfaces with `ByEndpoint`, this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' - `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` diff --git a/docs/docfx_project/articles/cli-tool.md b/docs/docfx_project/articles/cli-tool.md index e3e893a80..c12a5b843 100644 --- a/docs/docfx_project/articles/cli-tool.md +++ b/docs/docfx_project/articles/cli-tool.md @@ -77,7 +77,7 @@ OPTIONS: --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 + --operation-name-template Generate operation names using pattern. When using --multiple-interfaces ByEndpoint, this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' --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 diff --git a/docs/docfx_project/articles/refitter-file-format.md b/docs/docfx_project/articles/refitter-file-format.md index c5adce0f4..dd812415a 100644 --- a/docs/docfx_project/articles/refitter-file-format.md +++ b/docs/docfx_project/articles/refitter-file-format.md @@ -29,7 +29,7 @@ The following is an example `.refitter` file "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 + "operationNameTemplate": "{operationName}Async", // Optional. Must contain {operationName}. When multipleInterfaces == "ByEndpoint", this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' "optionalParameters": false, // Optional. Default=false "outputFolder": "../CustomOutput", // Optional. Default=./Generated "outputFilename": "RefitInterface.cs", // Optional. Default=Output.cs for CLI tool @@ -150,7 +150,7 @@ The following is an example `.refitter` file - `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 +- `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. When using multiple interfaces with `ByEndpoint`, this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' - `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` diff --git a/docs/json-schema.json b/docs/json-schema.json index ac65f90a2..22fd00a73 100644 --- a/docs/json-schema.json +++ b/docs/json-schema.json @@ -384,7 +384,7 @@ "type": "boolean" }, "operationNameTemplate": { - "description": "Generate operation names using pattern. When using --multiple-interfaces ByEndpoint,\r\nthis is name of the Execute() method in the interface.", + "description": "Generate operation names using pattern. When using multiple interfaces ByEndpoint,\r\nthis is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute'.", "type": "string" }, "optionalParameters": { diff --git a/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs b/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs index 28d4bef1a..891b95f39 100644 --- a/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs +++ b/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs @@ -31,7 +31,9 @@ public override IEnumerable GenerateCode() var returnType = GetTypeName(operation); var verb = operations.Key.CapitalizeFirstCharacter(); - var methodName = settings.OperationNameTemplate ?? "Execute"; + var methodName = !string.IsNullOrWhiteSpace(settings.OperationNameTemplate) + ? settings.OperationNameTemplate!.Replace("{operationName}", "Execute") + : "Execute"; var code = new StringBuilder(); this.docGenerator.AppendInterfaceDocumentation(operation, code); diff --git a/src/Refitter.Core/Settings/MultipleInterfaces.cs b/src/Refitter.Core/Settings/MultipleInterfaces.cs index f2ea74f51..8fa254c04 100644 --- a/src/Refitter.Core/Settings/MultipleInterfaces.cs +++ b/src/Refitter.Core/Settings/MultipleInterfaces.cs @@ -16,12 +16,12 @@ public enum MultipleInterfaces /// /// Generate a Refit interface for each endpoint with a single Execute() method. - /// The method name can be customized using the setting. + /// The method name can be customized using , where this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute'. /// [Description( """ Generate a Refit interface for each endpoint with a single Execute() method. - The method name can be customized using the OperationNameTemplate setting. + The method name can be customized using OperationNameTemplate, where this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute'. """ )] ByEndpoint, diff --git a/src/Refitter.MSBuild/README.md b/src/Refitter.MSBuild/README.md index 9ce71ebf9..5b10bc6dc 100644 --- a/src/Refitter.MSBuild/README.md +++ b/src/Refitter.MSBuild/README.md @@ -51,7 +51,7 @@ The following is an example `.refitter` file "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 + "operationNameTemplate": "{operationName}Async", // Optional. Must contain {operationName}. When multipleInterfaces == "ByEndpoint", this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' "optionalParameters": false, // Optional. Default=false "outputFolder": "../CustomOutput" // Optional. Default=./Generated "outputFilename": "RefitInterface.cs", // Optional. Default=Output.cs for CLI tool @@ -148,7 +148,7 @@ The following is an example `.refitter` file - `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. When using `"multipleIinterfaces": "ByEndpoint"`, This is name of the Execute() method in the interface +- `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. When using multiple interfaces with `ByEndpoint`, this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' - `optionalParameters` - Generate non-required parameters as nullable optional parameters - `useDynamicQuerystringParameters`: Set to `true` to wrap multiple query parameters into a single complex one. Default is `false` (no wrapping). - `dependencyInjectionSettings` - Setting this will generated extension methods to `IServiceCollection` for configuring Refit clients. See https://github.com/reactiveui/refit?tab=readme-ov-file#dynamic-querystring-parameters for more information. diff --git a/src/Refitter.SourceGenerator/README.md b/src/Refitter.SourceGenerator/README.md index 65a9f8cde..e0e6d5337 100644 --- a/src/Refitter.SourceGenerator/README.md +++ b/src/Refitter.SourceGenerator/README.md @@ -48,7 +48,7 @@ The following is an example `.refitter` file "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 + "operationNameTemplate": "{operationName}Async", // Optional. Must contain {operationName}. When multipleInterfaces == "ByEndpoint", this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' "optionalParameters": false, // Optional. Default=false "outputFolder": "../CustomOutput" // Optional. Default=./Generated "outputFilename": "RefitInterface.cs", // Optional. Default=Output.cs for CLI tool @@ -147,7 +147,7 @@ The following is an example `.refitter` file - `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. When using `"multipleIinterfaces": "ByEndpoint"`, This is name of the Execute() method in the interface +- `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. When using multiple interfaces with `ByEndpoint`, this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' - `optionalParameters` - Generate non-required parameters as nullable optional parameters - `useDynamicQuerystringParameters`: Set to `true` to wrap multiple query parameters into a single complex one. Default is `false` (no wrapping). - `dependencyInjectionSettings` - Setting this will generated extension methods to `IServiceCollection` for configuring Refit clients. See for more information. diff --git a/src/Refitter.Tests/Examples/MultipleInterfacesByEndpointWithOperationNameTests.cs b/src/Refitter.Tests/Examples/MultipleInterfacesByEndpointWithOperationNameTests.cs new file mode 100644 index 000000000..73a0f6aec --- /dev/null +++ b/src/Refitter.Tests/Examples/MultipleInterfacesByEndpointWithOperationNameTests.cs @@ -0,0 +1,185 @@ +using FluentAssertions; +using Refitter.Tests.TestUtilities; +using Refitter.Core; +using Refitter.Tests.Build; +using Xunit; + +namespace Refitter.Tests.Examples; + +public class MultipleInterfacesByEndpointWithOperationNameTests +{ + private const string OpenApiSpec = @" +openapi: '3.0.0' +paths: + /foo/{id}: + get: + tags: + - 'Foo' + operationId: 'Get foo details' + description: 'Get the details of the specified foo' + parameters: + - in: 'path' + name: 'id' + description: 'Foo ID' + required: true + schema: + type: 'string' + - in: 'query' + name: 'Title' + description: 'Job title' + nullable: true + schema: + type: 'string' + - in: 'query' + name: 'Description' + description: 'Job description' + optional: true + schema: + type: 'string' + - in: 'query' + name: 'Contact' + description: 'Contact Person' + schema: + type: 'string' + responses: + '200': + description: 'successful operation' + /foo: + get: + tags: + - 'Foo' + operationId: 'Get all foos' + description: 'Get all foos' + parameters: + - in: 'query' + name: 'Title' + description: 'Foo title' + nullable: true + schema: + type: 'string' + responses: + '200': + description: 'successful operation' + /bar: + get: + tags: + - 'Bar' + operationId: 'Get all bars' + description: 'Get all bars' + responses: + '200': + description: 'successful operation' + /bar/{id}: + get: + tags: + - 'Bar' + operationId: 'Get bar details' + description: 'Get the details of the specified bar' + parameters: + - in: 'path' + name: 'id' + description: 'Bar ID' + required: true + schema: + type: 'string' + - in: 'query' + name: 'Title' + description: 'Bar title' + nullable: true + schema: + type: 'string' + - in: 'query' + name: 'Description' + description: 'Bar description' + optional: true + schema: + type: 'string' + - in: 'query' + name: 'Contact' + description: 'Contact Person' + schema: + type: 'string' + responses: + '200': + description: 'successful operation' +"; + + [Fact] + public async Task Can_Generate_Code() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().NotBeNullOrWhiteSpace(); + } + + [Fact] + public async Task Generates_IGetAllFooEndpoint() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().Contain("partial interface IGetAllFoosEndpoint"); + } + + [Fact] + public async Task Generates_IGetFooDetailsEndpoint() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().Contain("partial interface IGetFooDetailsEndpoint"); + } + + [Fact] + public async Task Generates_IGetAllBarEndpoint() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().Contain("partial interface IGetAllBarsEndpoint"); + } + + [Fact] + public async Task Generates_IGetBarDetailsEndpoint() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().Contain("partial interface IGetBarDetailsEndpoint"); + } + + [Fact] + public async Task Generates_Dynamic_Querystring_Parameters() + { + string generatedCode = await GenerateCode(true); + generatedCode.Should().Contain("GetFooDetailsQueryParams"); + generatedCode.Should().NotContain("GetAllFoosQueryParams"); + generatedCode.Should().Contain("GetBarDetailsQueryParams"); + } + + [Fact] + public async Task Methods_Name_ExecuteAsync() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().Contain("ExecuteAsync"); + } + + [Fact] + public async Task Can_Build_Generated_Code() + { + string generatedCode = await GenerateCode(); + BuildHelper + .BuildCSharp(generatedCode) + .Should() + .BeTrue(); + } + + private static async Task GenerateCode(bool useDynamicQuerystringParameters = false) + { + var swaggerFile = await SwaggerFileHelper.CreateSwaggerFile(OpenApiSpec); + var settings = new RefitGeneratorSettings + { + OpenApiPath = swaggerFile, + MultipleInterfaces = MultipleInterfaces.ByEndpoint, + UseDynamicQuerystringParameters = useDynamicQuerystringParameters, + OperationNameTemplate = "{operationName}Async", + ImmutableRecords = true + }; + + var sut = await RefitGenerator.CreateAsync(settings); + var generatedCode = sut.Generate(); + return generatedCode; + } + +} diff --git a/src/Refitter.Tests/Examples/OpenApiUrlTests.cs b/src/Refitter.Tests/Examples/OpenApiUrlTests.cs index 6e030987c..10713f1e0 100644 --- a/src/Refitter.Tests/Examples/OpenApiUrlTests.cs +++ b/src/Refitter.Tests/Examples/OpenApiUrlTests.cs @@ -45,5 +45,5 @@ public async Task Can_Build_Generated_Code_From_OpenApi_v3_Url_Yaml(string url) .Should() .BeTrue(url); } -#endif } +#endif diff --git a/src/Refitter/README.md b/src/Refitter/README.md index a1f60e0c7..da6556d3d 100644 --- a/src/Refitter/README.md +++ b/src/Refitter/README.md @@ -79,7 +79,7 @@ OPTIONS: --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 + --operation-name-template Generate operation names using pattern. When using --multiple-interfaces ByEndpoint, this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' --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 @@ -147,7 +147,7 @@ The following is an example `.refitter` file "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 + "operationNameTemplate": "{operationName}Async", // Optional. Must contain {operationName}. When multipleInterfaces == "ByEndpoint", this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' "optionalParameters": false, // Optional. Default=false "outputFolder": "../CustomOutput" // Optional. Default=./Generated "outputFilename": "RefitInterface.cs", // Optional. Default=Output.cs for CLI tool @@ -259,7 +259,7 @@ The following is an example `.refitter` file - `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 +- `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. When using multiple interfaces with `ByEndpoint`, this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute' - `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` diff --git a/src/Refitter/Settings.cs b/src/Refitter/Settings.cs index c809d170d..291a00947 100644 --- a/src/Refitter/Settings.cs +++ b/src/Refitter/Settings.cs @@ -143,7 +143,7 @@ Generate multiple files instead of a single large file. [DefaultValue(false)] public bool NoDeprecatedOperations { get; set; } - [Description("Generate operation names using pattern. When using --multiple-interfaces ByEndpoint, this is name of the Execute() method in the interface.")] + [Description("Generate operation names using pattern. When using --multiple-interfaces ByEndpoint, this is name of the Execute() method in the interface where all instances of the string '{operationName}' is replaced with 'Execute'.")] [CommandOption("--operation-name-template")] [DefaultValue(null)] public string? OperationNameTemplate { get; internal set; }