diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d64d2753f..40da8e3d6 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -92,21 +92,21 @@ All new code must include unit tests following the pattern used in `Refitter.Tes public class MyFeatureTests { private const string OpenApiSpec = @"..."; // OpenAPI specification - + [Fact] public async Task Can_Generate_Code() { string generatedCode = await GenerateCode(); generatedCode.Should().NotBeNullOrWhiteSpace(); } - + [Fact] public async Task Generated_Code_Contains_Expected_Pattern() { string generatedCode = await GenerateCode(); generatedCode.Should().Contain("ExpectedPattern"); } - + [Fact] public async Task Can_Build_Generated_Code() { @@ -146,12 +146,13 @@ dotnet run --project src/Refitter --configuration Release --framework net9.0 -- ### Recent CLI Options Added - `--use-apizr`: Integration with Apizr library for request options -- `--use-dynamic-querystring-parameters`: Enable dynamic query string parameter wrapping +- `--use-dynamic-querystring-parameters`: Enable dynamic query string parameter wrapping - `--use-polymorphic-serialization`: Use System.Text.Json polymorphic serialization - `--disposable`: Generate IDisposable clients - `--collection-format`: Control query parameter collection formatting (Multi/Csv/Ssv/Tsv/Pipes) - `--no-banner`: Hide donation banner in CLI output - `--integer-type`: Set the .NET type for OpenAPI integers without a format specifier (Int32/Int64) +- `--custom-template-directory`: Custom directory with NSwag fluid templates for code generation. Default is null which uses the default NSwag templates. See ### Working with OpenAPI Specifications - Test resources are located in `src/Refitter.Tests/Resources/V2/` and `src/Refitter.Tests/Resources/V3/` @@ -187,7 +188,7 @@ dotnet run --project src/Refitter --configuration Release --framework net9.0 -- ### Expected Build Times - Package restore: ~65 seconds -- Debug build: ~15 seconds +- Debug build: ~15 seconds - Release build: ~22 seconds - Full test suite: ~284 seconds (4 minutes 44 seconds) - Code formatting: ~19-23 seconds @@ -211,4 +212,4 @@ dotnet run --project src/Refitter --configuration Release --framework net9.0 -- - CLI code generation: 1-10 seconds depending on OpenAPI spec complexity - Source generator: Runs during build time, adds minimal overhead - Test execution: Allow full test suite to complete (~5 minutes) -- Build process: Complete clean build takes ~2-3 minutes including restore \ No newline at end of file +- Build process: Complete clean build takes ~2-3 minutes including restore diff --git a/README.md b/README.md index 8a826206d..5c1d3f1b7 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ OPTIONS: --disposable Generate refit clients that implement IDisposable --no-inline-json-converters Don't inline JsonConverter attributes for enum properties. When disabled, enum properties will not have [JsonConverter(typeof(JsonStringEnumConverter))] attributes --integer-type int The .NET type to use for OpenAPI integer types without a format specifier. Common values: 'int' (default), 'long' + --custom-template-directory Custom directory with NSwag fluid templates for code generation. Default is null which uses the default NSwag templates. See ``` To generate code from an OpenAPI specifications file, run the following: @@ -296,7 +297,8 @@ The following is an example `.refitter` file "excludedTypeNames": [ "ExcludedTypeFoo", "ExcludedTypeBar" - ] + ], + "customTemplateDirectory": "./path/to/directory/" // Optional. See } } ``` diff --git a/docs/docfx_project/articles/cli-tool.md b/docs/docfx_project/articles/cli-tool.md index ec530f342..0999fa085 100644 --- a/docs/docfx_project/articles/cli-tool.md +++ b/docs/docfx_project/articles/cli-tool.md @@ -48,78 +48,79 @@ 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 - --contracts-namespace Default namespace to use for generated contracts - -o, --output Output.cs Path to Output file or folder (if multiple files are generated) - --contracts-output Output path for generated contracts. Enabling this automatically enables generating multiple files - --no-auto-generated-header Don't add header to output file - --no-accept-headers Don't add header to output file - --interface-only Don't generate contract types - --contract-only Don't generate clients - --use-api-response Return Task> instead of Task - --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 - --exclude-namespace Exclude namespace on 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 - --multiple-files Generate multiple files instead of a single large file. - The output files can be the following: - - RefitInterfaces.cs - - DependencyInjection.cs - - Contracts.cs - --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 + 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 + --contracts-namespace Default namespace to use for generated contracts + -o, --output Output.cs Path to Output file or folder (if multiple files are generated) + --contracts-output Output path for generated contracts. Enabling this automatically enables generating multiple files + --no-auto-generated-header Don't add header to output file + --no-accept-headers Don't add header to output file + --interface-only Don't generate contract types + --contract-only Don't generate clients + --use-api-response Return Task> instead of Task + --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 + --exclude-namespace Exclude namespace on 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 + --multiple-files Generate multiple files instead of a single large file. + The output files can be the following: + - RefitInterfaces.cs + - DependencyInjection.cs + - Contracts.cs + --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 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 - --include-inheritance-hierarchy Keep all possible inherited types/union types even if they are not directly used - --no-banner Don't show donation banner - --skip-default-additional-properties Set to true to skip default additional properties - --simple-output Generate output with no color, no formatting, no emojis, and no banners suitable for terminal or IDE output + --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 + --include-inheritance-hierarchy Keep all possible inherited types/union types even if they are not directly used + --no-banner Don't show donation banner + --skip-default-additional-properties Set to true to skip default additional properties + --simple-output Generate output with no color, no formatting, no emojis, and no banners suitable for terminal or IDE output --collection-format Multi Determines the format of collection parameters. May be one of: - Multi (separate parameter instances for each array item) - Csv (comma separated values) - Ssv (space separated values) - Tsv (tab separated values) - Pipes (pipe separated values) - --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 - --immutable-records Generate contracts as immutable records instead of classes - --use-apizr Use Apizr by: - - Adding a final IApizrRequestOptions options parameter to all generated methods - - Providing cancellation tokens by Apizr request options instead of a dedicated parameter - - Using method overloads instead of optional parameters - See https://refitter.github.io for more information and https://www.apizr.net to get started with Apizr - --use-dynamic-querystring-parameters Enable wrapping multiple query parameters into a single complex one. Default is no wrapping. - See https://github.com/reactiveui/refit?tab=readme-ov-file#dynamic-querystring-parameters for more information - --use-polymorphic-serialization Use System.Text.Json polymorphic serialization. - Replaces NSwag JsonInheritanceConverter attributes with System.Text.Json JsonPolymorphicAttributes. - To have the native support of inheritance (de)serialization and fallback to base types when - payloads with (yet) unknown types are offered by newer versions of an API + --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 + --immutable-records Generate contracts as immutable records instead of classes + --use-apizr Use Apizr by: + - Adding a final IApizrRequestOptions options parameter to all generated methods + - Providing cancellation tokens by Apizr request options instead of a dedicated parameter + - Using method overloads instead of optional parameters + See https://refitter.github.io for more information and https://www.apizr.net to get started with Apizr + --use-dynamic-querystring-parameters Enable wrapping multiple query parameters into a single complex one. Default is no wrapping. + See https://github.com/reactiveui/refit?tab=readme-ov-file#dynamic-querystring-parameters for more information + --use-polymorphic-serialization Use System.Text.Json polymorphic serialization. + Replaces NSwag JsonInheritanceConverter attributes with System.Text.Json JsonPolymorphicAttributes. + To have the native support of inheritance (de)serialization and fallback to base types when + payloads with (yet) unknown types are offered by newer versions of an API See https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism for more information --disposable Generate refit clients that implement IDisposable --no-inline-json-converters Don't inline JsonConverter attributes for enum properties. When disabled, enum properties will not have [JsonConverter(typeof(JsonStringEnumConverter))] attributes --integer-type int The .NET type to use for OpenAPI integer types without a format specifier. Common values: 'int' (default), 'long' + --custom-template-directory Custom directory with NSwag fluid templates for code generation. Default is null which uses the default NSwag templates. See ``` ## CLI Tool Output Example diff --git a/docs/docfx_project/articles/refitter-file-format.md b/docs/docfx_project/articles/refitter-file-format.md index d0c1dbeec..eb8054797 100644 --- a/docs/docfx_project/articles/refitter-file-format.md +++ b/docs/docfx_project/articles/refitter-file-format.md @@ -122,7 +122,8 @@ The following is an example `.refitter` file "excludedTypeNames": [ "ExcludedTypeFoo", "ExcludedTypeBar" - ] + ], + "customTemplateDirectory": "./path/to/directory/" //Optional. See } } ``` diff --git a/docs/json-schema.json b/docs/json-schema.json index 8a856348e..8ac5db536 100644 --- a/docs/json-schema.json +++ b/docs/json-schema.json @@ -192,6 +192,10 @@ }, "propertyNameGenerator": { "$ref": "#/definitions/IPropertyNameGenerator" + }, + "customTemplateDirectory": { + "description": "Custom directory with NSwag fluid templates for code generation. Default is null which uses the default NSwag templates. See https://github.com/RicoSuter/NSwag/wiki/Templates", + "type": "string" } } }, diff --git a/src/Refitter.Core/CSharpClientGeneratorFactory.cs b/src/Refitter.Core/CSharpClientGeneratorFactory.cs index 03cbd33fd..cd86d2d5d 100644 --- a/src/Refitter.Core/CSharpClientGeneratorFactory.cs +++ b/src/Refitter.Core/CSharpClientGeneratorFactory.cs @@ -42,6 +42,7 @@ public CustomCSharpClientGenerator Create() GenerateNativeRecords = settings.ImmutableRecords || settings.CodeGeneratorSettings?.GenerateNativeRecords is true, + TemplateDirectory = settings.CustomTemplateDirectory, } }; diff --git a/src/Refitter.Core/Settings/CodeGeneratorSettings.cs b/src/Refitter.Core/Settings/CodeGeneratorSettings.cs index 412a18634..ce4579c33 100644 --- a/src/Refitter.Core/Settings/CodeGeneratorSettings.cs +++ b/src/Refitter.Core/Settings/CodeGeneratorSettings.cs @@ -272,4 +272,11 @@ Gets or sets a value indicating whether named/referenced any schemas should be i "Gets or sets a value indicating whether to inline JsonConverter attributes for enum properties (default: true). When set to false, enum properties will not have [JsonConverter(typeof(JsonStringEnumConverter))] attributes." )] public bool InlineJsonConverters { get; set; } = true; + + /// + /// Gets or sets a directory path which contains liquid templates for NSwag. If null or empty, uses default + /// templates. + /// + [Description("Custom directory with NSwag fluid templates for code generation. Default is null which uses the default NSwag templates. See https://github.com/RicoSuter/NSwag/wiki/Templates")] + public string? CustomTemplateDirectory { get; set; } } diff --git a/src/Refitter.Core/Settings/RefitGeneratorSettings.cs b/src/Refitter.Core/Settings/RefitGeneratorSettings.cs index c2ce8b698..48d5e6522 100644 --- a/src/Refitter.Core/Settings/RefitGeneratorSettings.cs +++ b/src/Refitter.Core/Settings/RefitGeneratorSettings.cs @@ -387,4 +387,11 @@ payloads with (yet) unknown types are offered by newer versions of an API [Description("The collection format to use for array query parameters. Default is CollectionFormat.Multi.")] [JsonConverter(typeof(JsonStringEnumConverter))] public CollectionFormat CollectionFormat { get; set; } = CollectionFormat.Multi; + + /// + /// Gets or sets a directory path which contains liquid templates for NSwag. If null or empty, uses default + /// templates. + /// + [Description("Custom directory with NSwag fluid templates for code generation. Default is null which uses the default NSwag templates. See https://github.com/RicoSuter/NSwag/wiki/Templates")] + public string? CustomTemplateDirectory { get; set; } } diff --git a/src/Refitter.MSBuild/README.md b/src/Refitter.MSBuild/README.md index 49e27fbc6..16b173465 100644 --- a/src/Refitter.MSBuild/README.md +++ b/src/Refitter.MSBuild/README.md @@ -165,7 +165,8 @@ The following is an example `.refitter` file "excludedTypeNames": [ "ExcludedTypeFoo", "ExcludedTypeBar" - ] + ], + "customTemplateDirectory": "./path/to/directory/" // Optional. See } } ``` @@ -241,3 +242,4 @@ The following is an example `.refitter` file - `generateDefaultValues` - Default is true - `inlineNamedAny` - Default is false - `excludedTypeNames` - Default is empty + - `customTemplateDirectory` - Custom directory with NSwag fluid templates for code generation. Default is null which uses the default NSwag templates. See diff --git a/src/Refitter.SourceGenerator/README.md b/src/Refitter.SourceGenerator/README.md index 9eb64001e..9885079ff 100644 --- a/src/Refitter.SourceGenerator/README.md +++ b/src/Refitter.SourceGenerator/README.md @@ -126,7 +126,8 @@ The following is an example `.refitter` file "excludedTypeNames": [ "ExcludedTypeFoo", "ExcludedTypeBar" - ] + ], + "customTemplateDirectory": "./path/to/directory/" // Optional. See } } ``` @@ -204,3 +205,4 @@ The following is an example `.refitter` file - `dateFormat` - Default is null - `dateTimeFormat` - Default is null - `excludedTypeNames` - Default is empty + - `customTemplateDirectory` - Custom directory with NSwag fluid templates for code generation. Default is null which uses the default NSwag templates. See diff --git a/src/Refitter.Tests/CustomCSharpGeneratorSettingsTests.cs b/src/Refitter.Tests/CustomCSharpGeneratorSettingsTests.cs index 012939819..b747125b7 100644 --- a/src/Refitter.Tests/CustomCSharpGeneratorSettingsTests.cs +++ b/src/Refitter.Tests/CustomCSharpGeneratorSettingsTests.cs @@ -143,6 +143,22 @@ public async Task Can_Generate_With_Immutable_Records(SampleOpenSpecifications v generatedCode.Should().Contain("[JsonConstructor]"); } + [Test] + [Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV3, "SwaggerPetstore.json")] + [Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV3, "SwaggerPetstore.yaml")] + [Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV2, "SwaggerPetstore.json")] + [Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV2, "SwaggerPetstore.yaml")] + public async Task Can_Generate_With_CustomTemplates(SampleOpenSpecifications version, string filename) + { + var settings = new RefitGeneratorSettings(); + settings.ReturnIApiResponse = true; + settings.CustomTemplateDirectory = "./Templates/"; + var generatedCode = await GenerateCode(version, filename, settings); + generatedCode.Should().Contain("/* Example Custom Template Text */"); + generatedCode.Should().Contain("public partial class Pet"); + } + + private static async Task GenerateCode( SampleOpenSpecifications version, string filename, diff --git a/src/Refitter.Tests/Examples/UsePolymorphicSerializationAndCustomTemplatesTests.cs b/src/Refitter.Tests/Examples/UsePolymorphicSerializationAndCustomTemplatesTests.cs new file mode 100644 index 000000000..d343bb959 --- /dev/null +++ b/src/Refitter.Tests/Examples/UsePolymorphicSerializationAndCustomTemplatesTests.cs @@ -0,0 +1,245 @@ +using FluentAssertions; +using Refitter.Core; +using Refitter.Tests.Build; +using Refitter.Tests.TestUtilities; + +namespace Refitter.Tests.Examples; + +public class UsePolymorphicSerializationAndCustomTemplatesTests +{ + private const string OpenApiSpec = @" +openapi: 3.0.1 +paths: + /v1/Warehouses: + post: + tags: + - Warehouses + operationId: CreateWarehouse + parameters: + - name: 'token' + in: 'query' + description: 'Some Token' + required: false + type: 'string' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Warehouse' + responses: + '201': + description: Created + headers: + X-Rate-Limit: + type: 'integer' + format: 'int32' + description: 'calls per hour allowed by the user' + content: + application/json: + schema: + type: 'array' + items: + $ref: '#/components/schemas/WarehouseResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Server Error +components: + schemas: + Metadata: + type: object + properties: + createdAt: + type: string + format: date-time + createdBy: + type: string + nullable: true + lastModifiedAt: + type: string + format: date-time + lastModifiedBy: + type: string + nullable: true + additionalProperties: false + SomeComponent: + required: + - $type + type: object + allOf: + - $ref: '#/components/schemas/Component' + properties: + $type: + type: string + typeId: + type: integer + format: int64 + additionalProperties: false + discriminator: + propertyName: $type + SomeComponentState: + enum: + - Active + - Inactive + - Blocked + - Deleted + type: string + SomeComponentType: + type: object + allOf: + - $ref: '#/components/schemas/Component' + properties: + state: + $ref: '#/components/schemas/SomeComponentState' + isBaseRole: + type: boolean + name: + type: string + nullable: true + numberingId: + type: string + nullable: true + additionalProperties: false + Component: + type: object + properties: + id: + type: integer + format: int64 + metadata: + $ref: '#/components/schemas/Metadata' + additionalProperties: false + LoadingAddress: + type: object + allOf: + - $ref: '#/components/schemas/SomeComponent' + properties: + info: + type: string + nullable: true + additionalProperties: false + Warehouse: + type: object + allOf: + - $ref: '#/components/schemas/SomeComponent' + properties: + info: + type: string + nullable: true + additionalProperties: false + WarehouseResponse: + type: object + allOf: + - $ref: '#/components/schemas/SomeComponent' + properties: + info: + type: string + nullable: true + additionalProperties: false + UserComponent: + type: object + allOf: + - $ref: '#/components/schemas/SomeComponent' + properties: + info: + type: string + nullable: true + additionalProperties: false + UserComponent2: + type: object + allOf: + - $ref: '#/components/schemas/UserComponent' + properties: + info2: + type: string + nullable: true + additionalProperties: false + ProblemDetails: + required: + - $type + type: object + properties: + $type: + type: string + type: + type: string + nullable: true + title: + type: string + nullable: true + status: + type: integer + format: int32 + nullable: true + detail: + type: string + nullable: true + instance: + type: string + nullable: true + additionalProperties: { } + discriminator: + propertyName: $type +"; + + [Test] + public async Task Can_Generate_Code() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().NotBeNullOrWhiteSpace(); + } + + [Test] + public async Task Use_Polymorphic_Serialization() + { + string generatedCode = await GenerateCode(); + + generatedCode.Should().NotContain("JsonInheritanceConverter"); + generatedCode.Should().NotContain("JsonInheritanceAttribute"); + + generatedCode.Should().Contain("[JsonPolymorphic(TypeDiscriminatorPropertyName = \"$type\", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType, IgnoreUnrecognizedTypeDiscriminators = true)]"); + generatedCode.Should().Contain("[JsonDerivedType(typeof(Warehouse), typeDiscriminator: \"Warehouse\")]"); + generatedCode.Should().Contain("[JsonDerivedType(typeof(WarehouseResponse), typeDiscriminator: \"WarehouseResponse\")]"); + generatedCode.Should().Contain("[JsonDerivedType(typeof(LoadingAddress), typeDiscriminator: \"LoadingAddress\")]"); + generatedCode.Should().Contain("[JsonDerivedType(typeof(UserComponent), typeDiscriminator: \"UserComponent\")]"); + generatedCode.Should().Contain("[JsonDerivedType(typeof(UserComponent2), typeDiscriminator: \"UserComponent2\")]"); + } + + [Test] + public async Task Use_CustomTemplates() + { + string generatedCode = await GenerateCode(); + + generatedCode.Should().Contain("/* Example Custom Template Text */"); + } + + [Test] + public async Task Can_Build_Generated_Code() + { + string generatedCode = await GenerateCode(); + BuildHelper + .BuildCSharp(generatedCode) + .Should() + .BeTrue(); + } + + private static async Task GenerateCode() + { + var swaggerFile = await SwaggerFileHelper.CreateSwaggerFile(OpenApiSpec); + var settings = new RefitGeneratorSettings + { + OpenApiPath = swaggerFile, + UsePolymorphicSerialization = true, + CustomTemplateDirectory = "./Templates/", + }; + + var sut = await RefitGenerator.CreateAsync(settings); + var generatedCode = sut.Generate(); + return generatedCode; + } + +} diff --git a/src/Refitter.Tests/Refitter.Tests.csproj b/src/Refitter.Tests/Refitter.Tests.csproj index ac210d03d..9dcd70902 100644 --- a/src/Refitter.Tests/Refitter.Tests.csproj +++ b/src/Refitter.Tests/Refitter.Tests.csproj @@ -15,6 +15,9 @@ + + PreserveNewest + diff --git a/src/Refitter.Tests/Templates/Class.Annotations.liquid b/src/Refitter.Tests/Templates/Class.Annotations.liquid new file mode 100644 index 000000000..b308027ed --- /dev/null +++ b/src/Refitter.Tests/Templates/Class.Annotations.liquid @@ -0,0 +1 @@ +/* Example Custom Template Text */ diff --git a/src/Refitter/GenerateCommand.cs b/src/Refitter/GenerateCommand.cs index f13a02bfc..61674b2c5 100644 --- a/src/Refitter/GenerateCommand.cs +++ b/src/Refitter/GenerateCommand.cs @@ -314,7 +314,8 @@ private static RefitGeneratorSettings CreateRefitGeneratorSettings(Settings sett { InlineJsonConverters = !settings.NoInlineJsonConverters, IntegerType = settings.IntegerType - } + }, + CustomTemplateDirectory = settings.CustomTemplateDirectory, }; } private static async Task WriteSingleFile( diff --git a/src/Refitter/README.md b/src/Refitter/README.md index 618e6fe2b..fc85ee27a 100644 --- a/src/Refitter/README.md +++ b/src/Refitter/README.md @@ -115,6 +115,7 @@ OPTIONS: --disposable Generate refit clients that implement IDisposable --no-inline-json-converters Don't inline JsonConverter attributes for enum properties. When disabled, enum properties will not have [JsonConverter(typeof(JsonStringEnumConverter))] attributes --integer-type int The .NET type to use for OpenAPI integer types without a format specifier. Common values: 'int' (default), 'long' + --custom-template-directory Custom directory with NSwag fluid templates for code generation. Default is null which uses the default NSwag templates. See ``` ## CLI Tool Output Example @@ -239,7 +240,8 @@ The following is an example `.refitter` file "excludedTypeNames": [ "ExcludedTypeFoo", "ExcludedTypeBar" - ] + ], + "customTemplateDirectory": "./path/to/directory/" // Optional. See } } ``` @@ -323,6 +325,7 @@ The following is an example `.refitter` file - `dateFormat` - Default is null - `dateTimeFormat` - Default is null - `excludedTypeNames` - Default is empty + - `customTemplateDirectory` - Custom directory with NSwag fluid templates for code generation. Default is null which uses the default NSwag templates. See To generate code from an OpenAPI specifications file, run the following: diff --git a/src/Refitter/Settings.cs b/src/Refitter/Settings.cs index 08126a945..7eca58e6d 100644 --- a/src/Refitter/Settings.cs +++ b/src/Refitter/Settings.cs @@ -268,4 +268,9 @@ payloads with (yet) unknown types are offered by newer versions of an API [CommandOption("--integer-type")] [DefaultValue(IntegerType.Int32)] public IntegerType IntegerType { get; set; } = IntegerType.Int32; + + [Description("Custom directory with NSwag fluid templates for code generation. Default is null which uses the default NSwag templates. See https://github.com/RicoSuter/NSwag/wiki/Templates")] + [CommandOption("--custom-template-directory")] + [DefaultValue(null)] + public string? CustomTemplateDirectory { get; set; } = null; }