From d9dcffc3701cd18ba9ec652d0010c2b6b10319f1 Mon Sep 17 00:00:00 2001 From: Kwok He <105217051+Kwok-he-Chu@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:32:20 +0100 Subject: [PATCH 1/4] Added /Core classes and tests in preparations for the v7-generator-upgrade --- Adyen/Adyen.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Adyen/Adyen.csproj b/Adyen/Adyen.csproj index deeff46a8..3e50f1b5f 100644 --- a/Adyen/Adyen.csproj +++ b/Adyen/Adyen.csproj @@ -57,4 +57,8 @@ + + + + From 74b88ecc5960f4498cf9715ebd516d6d3ed4aa5d Mon Sep 17 00:00:00 2001 From: Kwok He <105217051+Kwok-he-Chu@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:24:26 +0100 Subject: [PATCH 2/4] Include updated core classes for review --- Adyen/Adyen.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Adyen/Adyen.csproj b/Adyen/Adyen.csproj index 3e50f1b5f..deeff46a8 100644 --- a/Adyen/Adyen.csproj +++ b/Adyen/Adyen.csproj @@ -57,8 +57,4 @@ - - - - From d49b160bb86dc219ae659312ffb8ba40604eb597 Mon Sep 17 00:00:00 2001 From: Kwok He <105217051+Kwok-he-Chu@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:47:14 +0100 Subject: [PATCH 3/4] Add new mustache templates for v7 Remove old mustache templates --- templates-v7/csharp/AssemblyInfo.mustache | 40 ++ .../csharp/NullConditionalParameter.mustache | 1 + .../csharp/NullConditionalProperty.mustache | 1 + .../csharp/OpenAPIDateConverter.mustache | 21 + templates-v7/csharp/ValidateRegex.mustache | 6 + .../csharp/auth/OAuthAuthenticator.mustache | 136 ++++ templates-v7/csharp/auth/OAuthFlow.mustache | 19 + .../csharp/auth/TokenResponse.mustache | 24 + .../generichost/ApiException.mustache | 47 ++ .../libraries/generichost/ApiFactory.mustache | 48 ++ .../generichost/ApiKeyToken.mustache | 53 ++ .../generichost/ApiResponseEventArgs.mustache | 24 + .../generichost/ApiResponse`1.mustache | 202 ++++++ .../generichost/ApiTestsBase.mustache | 66 ++ .../libraries/generichost/AsModel.mustache | 3 + .../generichost/BearerToken.mustache | 41 ++ .../generichost/ClientUtils.mustache | 385 ++++++++++ .../generichost/CookieContainer.mustache | 22 + .../generichost/DateFormats.mustache | 2 + .../DateOnlyJsonConverter.mustache | 52 ++ .../DateOnlyNullableJsonConverter.mustache | 57 ++ .../generichost/DateTimeFormats.mustache | 22 + .../DateTimeJsonConverter.mustache | 52 ++ .../DateTimeNullableJsonConverter.mustache | 57 ++ .../DependencyInjectionTests.mustache | 211 ++++++ .../generichost/EnumValueDataType.mustache | 1 + .../generichost/ExceptionEventArgs.mustache | 24 + .../generichost/HmacKeyToken.mustache | 36 + .../generichost/HostConfiguration.mustache | 178 +++++ .../HttpSigningConfiguration.mustache | 679 ++++++++++++++++++ .../generichost/HttpSigningToken.mustache | 45 ++ .../libraries/generichost/IApi.mustache | 13 + .../IHostBuilderExtensions.mustache | 41 ++ .../IHttpClientBuilderExtensions.mustache | 75 ++ .../IServiceCollectionExtensions.mustache | 33 + .../generichost/ImplementsIEquatable.mustache | 1 + .../ImplementsValidatable.mustache | 1 + .../generichost/JsonConverter.mustache | 661 +++++++++++++++++ .../JsonSerializerOptionsProvider.mustache | 29 + .../generichost/ModelBaseSignature.mustache | 1 + .../generichost/ModelSignature.mustache | 1 + .../libraries/generichost/OAuthToken.mustache | 41 ++ .../OnDeserializationError.mustache | 2 + .../generichost/OperationSignature.mustache | 1 + .../libraries/generichost/Option.mustache | 48 ++ .../generichost/OptionProperty.mustache | 1 + .../generichost/README.client.mustache | 140 ++++ .../SourceGenerationContext.mustache | 9 + .../libraries/generichost/TokenBase.mustache | 73 ++ .../generichost/TokenContainer.mustache | 39 + .../generichost/TokenProvider.mustache | 39 + .../generichost/ValidateRegex.mustache | 7 + .../generichost/WebhookHandler.mustache | 88 +++ .../generichost/WriteProperty.mustache | 10 + .../generichost/WritePropertyHelper.mustache | 10 + .../csharp/libraries/generichost/api.mustache | 653 +++++++++++++++++ .../libraries/generichost/api_doc.mustache | 91 +++ .../libraries/generichost/api_test.mustache | 71 ++ .../libraries/generichost/model.mustache | 56 ++ .../generichost/modelGeneric.mustache | 413 +++++++++++ .../generichost/modelInnerEnum.mustache | 116 +++ templates-v7/csharp/partial_header.mustache | 18 + templates-v7/csharp/validatable.mustache | 141 ++++ templates-v7/csharp/visibility.mustache | 1 + .../csharp/AbstractOpenAPISchema.mustache | 71 -- templates/csharp/api-single.mustache | 87 --- templates/csharp/api.mustache | 86 --- templates/csharp/api_invoke.mustache | 1 - .../csharp/api_parameter_ordering.mustache | 1 - .../api_parameter_ordering_required.mustache | 1 - templates/csharp/api_parameters.mustache | 2 - .../csharp/api_parameters_async.mustache | 2 - templates/csharp/config.yaml | 6 - .../csharp/method_documentation.mustache | 13 - templates/csharp/model.mustache | 49 -- templates/csharp/modelGeneric.mustache | 384 ---------- templates/csharp/modelOneOf.mustache | 286 -------- templates/csharp/partial_header.mustache | 12 - 78 files changed, 5479 insertions(+), 1001 deletions(-) create mode 100644 templates-v7/csharp/AssemblyInfo.mustache create mode 100644 templates-v7/csharp/NullConditionalParameter.mustache create mode 100644 templates-v7/csharp/NullConditionalProperty.mustache create mode 100644 templates-v7/csharp/OpenAPIDateConverter.mustache create mode 100644 templates-v7/csharp/ValidateRegex.mustache create mode 100644 templates-v7/csharp/auth/OAuthAuthenticator.mustache create mode 100644 templates-v7/csharp/auth/OAuthFlow.mustache create mode 100644 templates-v7/csharp/auth/TokenResponse.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ApiException.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ApiFactory.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ApiKeyToken.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ApiResponseEventArgs.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ApiResponse`1.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ApiTestsBase.mustache create mode 100644 templates-v7/csharp/libraries/generichost/AsModel.mustache create mode 100644 templates-v7/csharp/libraries/generichost/BearerToken.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ClientUtils.mustache create mode 100644 templates-v7/csharp/libraries/generichost/CookieContainer.mustache create mode 100644 templates-v7/csharp/libraries/generichost/DateFormats.mustache create mode 100644 templates-v7/csharp/libraries/generichost/DateOnlyJsonConverter.mustache create mode 100644 templates-v7/csharp/libraries/generichost/DateOnlyNullableJsonConverter.mustache create mode 100644 templates-v7/csharp/libraries/generichost/DateTimeFormats.mustache create mode 100644 templates-v7/csharp/libraries/generichost/DateTimeJsonConverter.mustache create mode 100644 templates-v7/csharp/libraries/generichost/DateTimeNullableJsonConverter.mustache create mode 100644 templates-v7/csharp/libraries/generichost/DependencyInjectionTests.mustache create mode 100644 templates-v7/csharp/libraries/generichost/EnumValueDataType.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ExceptionEventArgs.mustache create mode 100644 templates-v7/csharp/libraries/generichost/HmacKeyToken.mustache create mode 100644 templates-v7/csharp/libraries/generichost/HostConfiguration.mustache create mode 100644 templates-v7/csharp/libraries/generichost/HttpSigningConfiguration.mustache create mode 100644 templates-v7/csharp/libraries/generichost/HttpSigningToken.mustache create mode 100644 templates-v7/csharp/libraries/generichost/IApi.mustache create mode 100644 templates-v7/csharp/libraries/generichost/IHostBuilderExtensions.mustache create mode 100644 templates-v7/csharp/libraries/generichost/IHttpClientBuilderExtensions.mustache create mode 100644 templates-v7/csharp/libraries/generichost/IServiceCollectionExtensions.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ImplementsIEquatable.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ImplementsValidatable.mustache create mode 100644 templates-v7/csharp/libraries/generichost/JsonConverter.mustache create mode 100644 templates-v7/csharp/libraries/generichost/JsonSerializerOptionsProvider.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ModelBaseSignature.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ModelSignature.mustache create mode 100644 templates-v7/csharp/libraries/generichost/OAuthToken.mustache create mode 100644 templates-v7/csharp/libraries/generichost/OnDeserializationError.mustache create mode 100644 templates-v7/csharp/libraries/generichost/OperationSignature.mustache create mode 100644 templates-v7/csharp/libraries/generichost/Option.mustache create mode 100644 templates-v7/csharp/libraries/generichost/OptionProperty.mustache create mode 100644 templates-v7/csharp/libraries/generichost/README.client.mustache create mode 100644 templates-v7/csharp/libraries/generichost/SourceGenerationContext.mustache create mode 100644 templates-v7/csharp/libraries/generichost/TokenBase.mustache create mode 100644 templates-v7/csharp/libraries/generichost/TokenContainer.mustache create mode 100644 templates-v7/csharp/libraries/generichost/TokenProvider.mustache create mode 100644 templates-v7/csharp/libraries/generichost/ValidateRegex.mustache create mode 100644 templates-v7/csharp/libraries/generichost/WebhookHandler.mustache create mode 100644 templates-v7/csharp/libraries/generichost/WriteProperty.mustache create mode 100644 templates-v7/csharp/libraries/generichost/WritePropertyHelper.mustache create mode 100644 templates-v7/csharp/libraries/generichost/api.mustache create mode 100644 templates-v7/csharp/libraries/generichost/api_doc.mustache create mode 100644 templates-v7/csharp/libraries/generichost/api_test.mustache create mode 100644 templates-v7/csharp/libraries/generichost/model.mustache create mode 100644 templates-v7/csharp/libraries/generichost/modelGeneric.mustache create mode 100644 templates-v7/csharp/libraries/generichost/modelInnerEnum.mustache create mode 100644 templates-v7/csharp/partial_header.mustache create mode 100644 templates-v7/csharp/validatable.mustache create mode 100644 templates-v7/csharp/visibility.mustache delete mode 100644 templates/csharp/AbstractOpenAPISchema.mustache delete mode 100644 templates/csharp/api-single.mustache delete mode 100644 templates/csharp/api.mustache delete mode 100644 templates/csharp/api_invoke.mustache delete mode 100644 templates/csharp/api_parameter_ordering.mustache delete mode 100644 templates/csharp/api_parameter_ordering_required.mustache delete mode 100644 templates/csharp/api_parameters.mustache delete mode 100644 templates/csharp/api_parameters_async.mustache delete mode 100644 templates/csharp/config.yaml delete mode 100644 templates/csharp/method_documentation.mustache delete mode 100644 templates/csharp/model.mustache delete mode 100644 templates/csharp/modelGeneric.mustache delete mode 100644 templates/csharp/modelOneOf.mustache delete mode 100644 templates/csharp/partial_header.mustache diff --git a/templates-v7/csharp/AssemblyInfo.mustache b/templates-v7/csharp/AssemblyInfo.mustache new file mode 100644 index 000000000..d5d937dc1 --- /dev/null +++ b/templates-v7/csharp/AssemblyInfo.mustache @@ -0,0 +1,40 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("{{packageTitle}}")] +[assembly: AssemblyDescription("{{packageDescription}}")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("{{packageCompany}}")] +[assembly: AssemblyProduct("{{packageProductName}}")] +[assembly: AssemblyCopyright("{{packageCopyright}}")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("{{packageVersion}}")] +[assembly: AssemblyFileVersion("{{packageVersion}}")] +{{^supportsAsync}} +// Settings which don't support asynchronous operations rely on non-public constructors +// This is due to how RestSharp requires the type constraint `where T : new()` in places it probably shouldn't. +[assembly: InternalsVisibleTo("RestSharp")] +[assembly: InternalsVisibleTo("NewtonSoft.Json")] +[assembly: InternalsVisibleTo("JsonSubTypes")] +{{/supportsAsync}} diff --git a/templates-v7/csharp/NullConditionalParameter.mustache b/templates-v7/csharp/NullConditionalParameter.mustache new file mode 100644 index 000000000..d8ad92666 --- /dev/null +++ b/templates-v7/csharp/NullConditionalParameter.mustache @@ -0,0 +1 @@ +{{#isNullable}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}}{{/isNullable}} \ No newline at end of file diff --git a/templates-v7/csharp/NullConditionalProperty.mustache b/templates-v7/csharp/NullConditionalProperty.mustache new file mode 100644 index 000000000..7dcafa803 --- /dev/null +++ b/templates-v7/csharp/NullConditionalProperty.mustache @@ -0,0 +1 @@ +{{#lambda.first}}{{#isNullable}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/isNullable}}{{^required}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/required}}{{/lambda.first}} \ No newline at end of file diff --git a/templates-v7/csharp/OpenAPIDateConverter.mustache b/templates-v7/csharp/OpenAPIDateConverter.mustache new file mode 100644 index 000000000..d79051025 --- /dev/null +++ b/templates-v7/csharp/OpenAPIDateConverter.mustache @@ -0,0 +1,21 @@ +{{>partial_header}} +using Newtonsoft.Json.Converters; + +namespace {{packageName}}.Client +{ + /// + /// Formatter for 'date' openapi formats ss defined by full-date - RFC3339 + /// see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#data-types + /// + public class OpenAPIDateConverter : IsoDateTimeConverter + { + /// + /// Initializes a new instance of the class. + /// + public OpenAPIDateConverter() + { + // full-date = date-fullyear "-" date-month "-" date-mday + DateTimeFormat = "yyyy-MM-dd"; + } + } +} diff --git a/templates-v7/csharp/ValidateRegex.mustache b/templates-v7/csharp/ValidateRegex.mustache new file mode 100644 index 000000000..d05be8a97 --- /dev/null +++ b/templates-v7/csharp/ValidateRegex.mustache @@ -0,0 +1,6 @@ +// {{{name}}} ({{{dataType}}}) pattern. +Regex regex{{{name}}} = new Regex(@"{{{vendorExtensions.x-regex}}}"{{#vendorExtensions.x-modifiers}}{{#-first}}, {{/-first}}RegexOptions.{{{.}}}{{^-last}} | {{/-last}}{{/vendorExtensions.x-modifiers}}); +if (!regex{{{name}}}.Match(this.{{{name}}}{{#isUuid}}.ToString(){{/isUuid}}).Success) +{ + yield return new System.ComponentModel.DataAnnotations.ValidationResult("Invalid value for {{{name}}}, must match a pattern of " + regex{{{name}}}, new [] { "{{{name}}}" }); +} \ No newline at end of file diff --git a/templates-v7/csharp/auth/OAuthAuthenticator.mustache b/templates-v7/csharp/auth/OAuthAuthenticator.mustache new file mode 100644 index 000000000..4a2a3fa85 --- /dev/null +++ b/templates-v7/csharp/auth/OAuthAuthenticator.mustache @@ -0,0 +1,136 @@ +{{>partial_header}} + +using System; +using System.Threading.Tasks; +using Newtonsoft.Json; +using RestSharp; +using RestSharp.Authenticators; + +namespace {{packageName}}.{{clientPackage}}.Auth +{ + /// + /// An authenticator for OAuth2 authentication flows + /// + public class OAuthAuthenticator : IAuthenticator + { + private TokenResponse{{nrt?}} _token; + + /// + /// Returns the current authentication token. Can return null if there is no authentication token, or it has expired. + /// + public string{{nrt?}} Token + { + get + { + if (_token == null) return null; + if (_token.ExpiresIn == null) return _token.AccessToken; + if (_token.ExpiresAt < DateTime.Now) return null; + + return _token.AccessToken; + } + } + + readonly string _tokenUrl; + readonly string _clientId; + readonly string _clientSecret; + readonly string{{nrt?}} _scope; + readonly string _grantType; + readonly JsonSerializerSettings _serializerSettings; + readonly IReadableConfiguration _configuration; + + /// + /// Initialize the OAuth2 Authenticator + /// + public OAuthAuthenticator( + string tokenUrl, + string clientId, + string clientSecret, + string{{nrt?}} scope, + OAuthFlow? flow, + JsonSerializerSettings serializerSettings, + IReadableConfiguration configuration) + { + _tokenUrl = tokenUrl; + _clientId = clientId; + _clientSecret = clientSecret; + _scope = scope; + _serializerSettings = serializerSettings; + _configuration = configuration; + + switch (flow) + { + /*case OAuthFlow.ACCESS_CODE: + _grantType = "authorization_code"; + break; + case OAuthFlow.IMPLICIT: + _grantType = "implicit"; + break; + case OAuthFlow.PASSWORD: + _grantType = "password"; + break;*/ + case OAuthFlow.APPLICATION: + _grantType = "client_credentials"; + break; + default: + break; + } + } + + /// + /// Creates an authentication parameter from an access token. + /// + /// An authentication parameter. + protected async ValueTask GetAuthenticationParameter() + { + var token = string.IsNullOrEmpty(Token) ? await GetToken().ConfigureAwait(false) : Token; + return new HeaderParameter(KnownHeaders.Authorization, token); + } + + /// + /// Gets the token from the OAuth2 server. + /// + /// An authentication token. + async Task GetToken() + { + var client = new RestClient(_tokenUrl, configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(_serializerSettings, _configuration))); + + var request = new RestRequest(); + if (!string.IsNullOrWhiteSpace(_token?.RefreshToken)) + { + request.AddParameter("grant_type", "refresh_token") + .AddParameter("refresh_token", _token.RefreshToken); + } + else + { + request + .AddParameter("grant_type", _grantType) + .AddParameter("client_id", _clientId) + .AddParameter("client_secret", _clientSecret); + } + if (!string.IsNullOrEmpty(_scope)) + { + request.AddParameter("scope", _scope); + } + _token = await client.PostAsync(request).ConfigureAwait(false); + // RFC6749 - token_type is case insensitive. + // RFC6750 - In Authorization header Bearer should be capitalized. + // Fix the capitalization irrespective of token_type casing. + switch (_token?.TokenType?.ToLower()) + { + case "bearer": + return $"Bearer {_token.AccessToken}"; + default: + return $"{_token?.TokenType} {_token?.AccessToken}"; + } + } + + /// + /// Retrieves the authentication token (creating a new one if necessary) and adds it to the current request + /// + /// + /// + /// + public async ValueTask Authenticate(IRestClient client, RestRequest request) + => request.AddOrUpdateParameter(await GetAuthenticationParameter().ConfigureAwait(false)); + } +} diff --git a/templates-v7/csharp/auth/OAuthFlow.mustache b/templates-v7/csharp/auth/OAuthFlow.mustache new file mode 100644 index 000000000..0578a2d16 --- /dev/null +++ b/templates-v7/csharp/auth/OAuthFlow.mustache @@ -0,0 +1,19 @@ +{{>partial_header}} + +namespace {{packageName}}.{{clientPackage}}.Auth +{ + /// + /// Available flows for OAuth2 authentication + /// + public enum OAuthFlow + { + /// Authorization code flow + ACCESS_CODE, + /// Implicit flow + IMPLICIT, + /// Password flow + PASSWORD, + /// Client credentials flow + APPLICATION + } +} \ No newline at end of file diff --git a/templates-v7/csharp/auth/TokenResponse.mustache b/templates-v7/csharp/auth/TokenResponse.mustache new file mode 100644 index 000000000..fc90d0c51 --- /dev/null +++ b/templates-v7/csharp/auth/TokenResponse.mustache @@ -0,0 +1,24 @@ +{{>partial_header}} + +using System; +using Newtonsoft.Json; + +namespace {{packageName}}.{{clientPackage}}.Auth +{ + class TokenResponse + { + [JsonProperty("token_type")] + public string TokenType { get; set; } + [JsonProperty("access_token")] + public string AccessToken { get; set; } + [JsonProperty("expires_in")] + public int? ExpiresIn { get; set; } + [JsonProperty("created")] + public DateTime? Created { get; set; } + + [JsonProperty("refresh_token")] + public string{{nrt?}} RefreshToken { get; set; } + + public DateTime? ExpiresAt => ExpiresIn == null ? null : Created?.AddSeconds(ExpiresIn.Value); + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/ApiException.mustache b/templates-v7/csharp/libraries/generichost/ApiException.mustache new file mode 100644 index 000000000..df9992b69 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ApiException.mustache @@ -0,0 +1,47 @@ +// +{{>partial_header}} + +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; + +namespace {{packageName}}.{{corePackageName}}.{{clientPackage}} +{ + /// + /// API Exception + /// + {{>visibility}} class ApiException : Exception + { + /// + /// The reason the api request failed + /// + public string{{nrt?}} ReasonPhrase { get; } + + /// + /// The HttpStatusCode + /// + public System.Net.HttpStatusCode StatusCode { get; } + + /// + /// The raw data returned by the api + /// + public string RawContent { get; } + + /// + /// Construct the ApiException from parts of the response + /// + /// + /// + /// + public ApiException(string{{nrt?}} reasonPhrase, System.Net.HttpStatusCode statusCode, string rawContent) : base(reasonPhrase ?? rawContent) + { + ReasonPhrase = reasonPhrase; + + StatusCode = statusCode; + + RawContent = rawContent; + } + } +} diff --git a/templates-v7/csharp/libraries/generichost/ApiFactory.mustache b/templates-v7/csharp/libraries/generichost/ApiFactory.mustache new file mode 100644 index 000000000..873c8b355 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ApiFactory.mustache @@ -0,0 +1,48 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace {{packageName}}.{{corePackageName}}.{{clientPackage}} +{ + /// + /// The factory interface for creating the services that can communicate with the {{packageName}} APIs. + /// + {{>visibility}} interface {{interfacePrefix}}ApiFactory + { + /// + /// A method to create an IApi of type IResult + /// + /// + /// + IResult Create() where IResult : {{interfacePrefix}}{{packageName}}ApiService; + } + + /// + /// The implementation of . + /// + {{>visibility}} class ApiFactory : {{interfacePrefix}}ApiFactory + { + /// + /// The service provider + /// + public IServiceProvider Services { get; } + + /// + /// Initializes a new instance of the class. + /// + /// + public ApiFactory(IServiceProvider services) + { + Services = services; + } + + /// + /// A method to create an IApi of type IResult + /// + /// + /// + public IResult Create() where IResult : {{interfacePrefix}}{{packageName}}ApiService + { + return Services.GetRequiredService(); + } + } +} diff --git a/templates-v7/csharp/libraries/generichost/ApiKeyToken.mustache b/templates-v7/csharp/libraries/generichost/ApiKeyToken.mustache new file mode 100644 index 000000000..efc32707b --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ApiKeyToken.mustache @@ -0,0 +1,53 @@ +// +{{partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using {{packageName}}.{{corePackageName}}.Auth; +using {{packageName}}.{{corePackageName}}.{{clientPackage}}; + +namespace {{packageName}}.{{apiName}}.{{clientPackage}} +{ + /// + /// The `ADYEN_API_KEY` is an Adyen API authentication token that must be included in the HTTP request header and allows your application to securely communicate with the Adyen APIs. + /// Guide on how to obtain the `ADYEN_API_KEY` + /// 1. For Digital/ECOM & In-Person Payments, visit: https://docs.adyen.com/development-resources/api-credentials/#generate-api-key to get your API Key. + /// 2. For Platforms & Financial Services, visit: https://docs.adyen.com/adyen-for-platforms-model to get started. + /// + {{>visibility}} class ApiKeyToken : TokenBase + { + /// + /// The `ADYEN_API_KEY`. + /// + private readonly string _apiKeyValue; + + /// + /// The name of the header. + /// + public ClientUtils.ApiKeyHeader Header { get; } + + /// + /// Constructs the ApiKeyToken object with the API key value provided. + /// This can then be accessed using . + /// + /// Your Adyen API Key value. + /// The header name, retrieved from + /// Your prefix, default: empty. + public ApiKeyToken(string value, ClientUtils.ApiKeyHeader header, string prefix = "") : base() + { + Header = header; + _apiKeyValue = $"{prefix}{value}"; + } + + /// + /// Adds the token the HttpRequestMessage headers. + /// + /// . + public virtual void AddTokenToHttpRequestMessageHeader(global::System.Net.Http.HttpRequestMessage request) + { + request.Headers.Add(ClientUtils.ApiKeyHeaderToString(Header), _apiKeyValue); + } + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/ApiResponseEventArgs.mustache b/templates-v7/csharp/libraries/generichost/ApiResponseEventArgs.mustache new file mode 100644 index 000000000..a3de3f0d0 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ApiResponseEventArgs.mustache @@ -0,0 +1,24 @@ +using System; + +namespace {{packageName}}.{{corePackageName}}.{{clientPackage}} +{ + /// + /// Useful for tracking server health + /// + {{>visibility}} class ApiResponseEventArgs : EventArgs + { + /// + /// The ApiResponse. + /// + public ApiResponse ApiResponse { get; } + + /// + /// The ApiResponseEventArgs. + /// + /// + public ApiResponseEventArgs(ApiResponse apiResponse) + { + ApiResponse = apiResponse; + } + } +} diff --git a/templates-v7/csharp/libraries/generichost/ApiResponse`1.mustache b/templates-v7/csharp/libraries/generichost/ApiResponse`1.mustache new file mode 100644 index 000000000..b78cbf1f4 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ApiResponse`1.mustache @@ -0,0 +1,202 @@ +// +{{#nrt}} +#nullable enable +{{/nrt}} +using System; +{{^netStandard}} +using System.Diagnostics.CodeAnalysis; +{{/netStandard}} +using System.Net; + +namespace {{packageName}}.{{corePackageName}}.{{clientPackage}} +{ + /// + /// Provides a non-generic contract for the ApiResponse wrapper. + /// + {{>visibility}} partial interface IApiResponse + { + /// + /// The IsSuccessStatusCode from the api response + /// + bool IsSuccessStatusCode { get; } + + /// + /// Gets the status code (HTTP status code) + /// + /// The status code. + HttpStatusCode StatusCode { get; } + + /// + /// The raw content of this response. + /// + string RawContent { get; } + + /// + /// The raw binary stream (only set for binary responses) + /// + System.IO.Stream{{nrt?}} ContentStream { get; } + + /// + /// The DateTime when the request was retrieved. + /// + DateTime DownloadedAt { get; } + + /// + /// The headers contained in the api response + /// + System.Net.Http.Headers.HttpResponseHeaders Headers { get; } + + /// + /// The path used when making the request. + /// + string Path { get; } + + /// + /// The reason phrase contained in the api response + /// + string{{nrt?}} ReasonPhrase { get; } + + /// + /// The DateTime when the request was sent. + /// + DateTime RequestedAt { get; } + + /// + /// The Uri used when making the request. + /// + Uri{{nrt?}} RequestUri { get; } + } + + /// + /// API Response + /// + {{>visibility}} partial class ApiResponse : IApiResponse + { + /// + /// Gets the status code (HTTP status code). + /// + /// The status code. + public HttpStatusCode StatusCode { get; } + + /// + /// The raw data. + /// + public string RawContent { get; protected set; } + + /// + /// The raw binary stream (only set for binary responses). + /// + public System.IO.Stream{{nrt?}} ContentStream { get; protected set; } + + /// + /// The IsSuccessStatusCode from the api response. + /// + public bool IsSuccessStatusCode { get; } + + /// + /// The reason phrase contained in the api response. + /// + public string{{nrt?}} ReasonPhrase { get; } + + /// + /// The headers contained in the api response. + /// + public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } + + /// + /// The DateTime when the request was retrieved. + /// + public DateTime DownloadedAt { get; } = DateTime.UtcNow; + + /// + /// The DateTime when the request was sent. + /// + public DateTime RequestedAt { get; } + + /// + /// The path used when making the request. + /// + public string Path { get; } + + /// + /// The Uri used when making the request. + /// + public Uri{{nrt?}} RequestUri { get; } + + /// + /// The + /// + protected System.Text.Json.JsonSerializerOptions _jsonSerializerOptions; + + /// + /// Construct the response using an HttpResponseMessage + /// + /// . + /// . + /// The raw data. + /// The path used when making the request. + /// The DateTime.UtcNow when the request was retrieved. + /// + public ApiResponse(global::System.Net.Http.HttpRequestMessage httpRequestMessage, System.Net.Http.HttpResponseMessage httpResponseMessage, string rawContent, string path, DateTime requestedAt, System.Text.Json.JsonSerializerOptions jsonSerializerOptions) + { + StatusCode = httpResponseMessage.StatusCode; + Headers = httpResponseMessage.Headers; + IsSuccessStatusCode = httpResponseMessage.IsSuccessStatusCode; + ReasonPhrase = httpResponseMessage.ReasonPhrase; + RawContent = rawContent; + Path = path; + RequestUri = httpRequestMessage.RequestUri; + RequestedAt = requestedAt; + _jsonSerializerOptions = jsonSerializerOptions; + OnCreated(httpRequestMessage, httpResponseMessage); + } + + /// + /// Construct the response using an HttpResponseMessage. + /// + /// . + /// . + /// The raw binary stream (only set for binary responses). + /// The path used when making the request. + /// The DateTime.UtcNow when the request was retrieved. + /// + public ApiResponse(global::System.Net.Http.HttpRequestMessage httpRequestMessage, System.Net.Http.HttpResponseMessage httpResponseMessage, System.IO.Stream contentStream, string path, DateTime requestedAt, System.Text.Json.JsonSerializerOptions jsonSerializerOptions) + { + StatusCode = httpResponseMessage.StatusCode; + Headers = httpResponseMessage.Headers; + IsSuccessStatusCode = httpResponseMessage.IsSuccessStatusCode; + ReasonPhrase = httpResponseMessage.ReasonPhrase; + ContentStream = contentStream; + RawContent = string.Empty; + Path = path; + RequestUri = httpRequestMessage.RequestUri; + RequestedAt = requestedAt; + _jsonSerializerOptions = jsonSerializerOptions; + OnCreated(httpRequestMessage, httpResponseMessage); + } + + partial void OnCreated(global::System.Net.Http.HttpRequestMessage httpRequestMessage, System.Net.Http.HttpResponseMessage httpResponseMessage); + } + {{#x-http-statuses-with-return}} + + /// + /// An interface for responses of type {{TType}}. + /// + /// + {{>visibility}} interface I{{.}} : IApiResponse + { + /// + /// Deserializes the response if the response is {{.}}. + /// + /// + TType {{.}}(); + + /// + /// Returns true if the response is {{.}} and the deserialized response is not null. + /// + /// + /// + bool TryDeserialize{{.}}Response({{#net60OrLater}}[NotNullWhen(true)]{{/net60OrLater}}out TType{{nrt?}} result); + } + {{/x-http-statuses-with-return}} +} diff --git a/templates-v7/csharp/libraries/generichost/ApiTestsBase.mustache b/templates-v7/csharp/libraries/generichost/ApiTestsBase.mustache new file mode 100644 index 000000000..789de490e --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ApiTestsBase.mustache @@ -0,0 +1,66 @@ +{{>partial_header}} +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using Microsoft.Extensions.Hosting; +using {{packageName}}.{{clientPackage}};{{#hasImport}} +using {{packageName}}.{{modelPackage}};{{/hasImport}} +using {{packageName}}.Extensions; + + +{{>testInstructions}} + + + +namespace {{packageName}}.Test.{{apiPackage}} +{ + /// + /// Base class for API tests + /// + public class ApiTestsBase + { + protected readonly IHost _host; + + public ApiTestsBase(string[] args) + { + _host = CreateHostBuilder(args).Build(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) + .Configure{{apiName}}((context, services, options) => + { + {{#lambda.trimTrailingWithNewLine}} + {{#apiKeyMethods}} + string apiKeyTokenValue{{-index}} = context.Configuration[""] ?? throw new Exception("Token not found."); + ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}(apiKeyTokenValue{{-index}}, ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(apiKeyToken{{-index}}); + + {{/apiKeyMethods}} + {{#httpBearerMethods}} + string bearerTokenValue{{-index}} = context.Configuration[""] ?? throw new Exception("Token not found."); + BearerToken bearerToken{{-index}} = new{{^net70OrLater}} BearerToken{{/net70OrLater}}(bearerTokenValue{{-index}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(bearerToken{{-index}}); + + {{/httpBearerMethods}} + {{#httpBasicMethods}} + string basicTokenUsername{{-index}} = context.Configuration[""] ?? throw new Exception("Username not found."); + string basicTokenPassword{{-index}} = context.Configuration[""] ?? throw new Exception("Password not found."); + BasicToken basicToken{{-index}} = new{{^net70OrLater}} BasicToken{{/net70OrLater}}(basicTokenUsername{{-index}}, basicTokenPassword{{-index}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(basicToken{{-index}}); + + {{/httpBasicMethods}} + {{#httpSignatureMethods}} + HttpSigningConfiguration config{{-index}} = new{{^net70OrLater}} HttpSigningConfiguration{{/net70OrLater}}("", "", null, new List(), HashAlgorithmName.SHA256, "", 0); + HttpSignatureToken httpSignatureToken{{-index}} = new{{^net70OrLater}} HttpSignatureToken{{/net70OrLater}}(config{{-index}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(httpSignatureToken{{-index}}); + + {{/httpSignatureMethods}} + {{#oauthMethods}} + string oauthTokenValue{{-index}} = context.Configuration[""] ?? throw new Exception("Token not found."); + OAuthToken oauthToken{{-index}} = new{{^net70OrLater}} OAuthToken{{/net70OrLater}}(oauthTokenValue{{-index}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(oauthToken{{-index}}); + {{/oauthMethods}} + {{/lambda.trimTrailingWithNewLine}} + }); + } +} diff --git a/templates-v7/csharp/libraries/generichost/AsModel.mustache b/templates-v7/csharp/libraries/generichost/AsModel.mustache new file mode 100644 index 000000000..f98d3ad57 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/AsModel.mustache @@ -0,0 +1,3 @@ +return Is{{vendorExtensions.x-http-status}} + ? {{#isBinary}}ContentStream{{/isBinary}}{{^isBinary}}System.Text.Json.JsonSerializer.Deserialize<{{#isModel}}{{^containerType}}{{packageName}}.{{modelPackage}}.{{/containerType}}{{/isModel}}{{{dataType}}}>(RawContent, _jsonSerializerOptions){{/isBinary}} + : {{#net60OrLater}}null{{/net60OrLater}}{{^net60OrLater}}default{{/net60OrLater}}; diff --git a/templates-v7/csharp/libraries/generichost/BearerToken.mustache b/templates-v7/csharp/libraries/generichost/BearerToken.mustache new file mode 100644 index 000000000..1dd463bcd --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/BearerToken.mustache @@ -0,0 +1,41 @@ +\// +{{partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// A token constructed from a token from a bearer token. + /// + {{>visibility}} class BearerToken : TokenBase + { + private string _raw; + + /// + /// Constructs a BearerToken object. + /// + /// + /// + public BearerToken(string value, TimeSpan? timeout = null) : base(timeout) + { + _raw = value; + } + + /// + /// Places the token in the header. + /// + /// + /// + public virtual void UseInHeader(global::System.Net.Http.HttpRequestMessage request, string headerName) + { + request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _raw); + } + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/ClientUtils.mustache b/templates-v7/csharp/libraries/generichost/ClientUtils.mustache new file mode 100644 index 000000000..c11ae8da7 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ClientUtils.mustache @@ -0,0 +1,385 @@ +{{>partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using System.IO; +using System.Linq; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions;{{#useCompareNetObjects}} +using KellermanSoftware.CompareNetObjects;{{/useCompareNetObjects}} +using {{packageName}}; +using {{packageName}}.{{corePackageName}}.Options; +{{#models}} +{{#-first}} +using {{packageName}}.{{modelPackage}}; +{{/-first}} +{{/models}} +using Models = {{packageName}}.{{modelPackage}}; +using System.Runtime.CompilerServices; + +namespace {{packageName}}.{{apiName}}.{{clientPackage}} +{ + /// + /// Utility functions providing some benefit to API client consumers. + /// + {{>visibility}} static {{#net70OrLater}}partial {{/net70OrLater}}class ClientUtils + { + {{#useCompareNetObjects}} + /// + /// An instance of CompareLogic. + /// + public static CompareLogic compareLogic; + + /// + /// Static constructor to initialise compareLogic. + /// + static ClientUtils() + { + {{#equatable}} + ComparisonConfig comparisonConfig = new{{^net70OrLater}} ComparisonConfig{{/net70OrLater}}(); + comparisonConfig.UseHashCodeIdentifier = true; + {{/equatable}} + compareLogic = new{{^net70OrLater}} CompareLogic{{/net70OrLater}}({{#equatable}}comparisonConfig{{/equatable}}); + } + {{/useCompareNetObjects}} + /// + /// A delegate for events. + /// + /// + /// + /// + /// + public delegate void EventHandler(object sender, T e) where T : EventArgs; + + /// + /// Returns true when deserialization succeeds. + /// + /// + /// + /// + /// + /// + public static bool TryDeserialize(string json, JsonSerializerOptions options, {{#net60OrLater}}[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] {{/net60OrLater}}out T{{#nrt}}{{#net60OrLater}}?{{/net60OrLater}}{{/nrt}} result) + { + try + { + result = JsonSerializer.Deserialize(json, options); + return result != null; + } + catch (Exception) + { + result = default; + return false; + } + } + + /// + /// Returns true when deserialization succeeds. + /// + /// + /// + /// + /// + /// + public static bool TryDeserialize(ref Utf8JsonReader reader, JsonSerializerOptions options, {{#net60OrLater}}[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] {{/net60OrLater}}out T{{#nrt}}{{#net60OrLater}}?{{/net60OrLater}}{{/nrt}} result) + { + try + { + result = JsonSerializer.Deserialize(ref reader, options); + return result != null; + } + catch (Exception) + { + result = default; + return false; + } + } + + /// + /// The format to use for DateTime serialization. + /// + public const string ISO8601_DATETIME_FORMAT = "o"; + + /// + /// If parameter is DateTime, output in a formatted string (default ISO 8601), customizable with Configuration.DateTime. + /// If parameter is a list, join the list with ",". + /// Otherwise just return the string. + /// + /// The parameter (header, path, query, form). + /// The DateTime serialization format. + /// Formatted string. + public static string{{nrt?}} ParameterToString(object{{nrt?}} obj, string{{nrt?}} format = ISO8601_DATETIME_FORMAT) + { + if (obj is DateTime dateTime) + // Return a formatted date string - Can be customized with Configuration.DateTimeFormat + // Defaults to an ISO 8601, using the known as a Round-trip date/time pattern ("o") + // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8 + // For example: 2009-06-15T13:45:30.0000000 + return dateTime.ToString(format); + if (obj is DateTimeOffset dateTimeOffset) + // Return a formatted date string - Can be customized with Configuration.DateTimeFormat + // Defaults to an ISO 8601, using the known as a Round-trip date/time pattern ("o") + // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8 + // For example: 2009-06-15T13:45:30.0000000 + return dateTimeOffset.ToString(format); + {{#net60OrLater}} + if (obj is DateOnly dateOnly) + return dateOnly.ToString(format); + {{/net60OrLater}} + if (obj is bool boolean) + return boolean ? "true" : "false"; + {{#models}} + {{#model}} + {{#isEnum}} + if (obj is Models.{{classname}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}) + {{! below has #isNumeric as a work around but should probably have ^isString instead https://github.com/OpenAPITools/openapi-generator/issues/15038}} + return {{classname}}ValueConverter{{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}}.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}){{#isNumeric}}.ToString(){{/isNumeric}}; + {{/isEnum}} + {{^isEnum}} + {{#vars}} + {{#items.isEnum}} + {{#items}} + {{^complexType}} + if (obj is Models.{{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{{datatypeWithEnum}}}{{/lambda.camelcase_sanitize_param}}) + {{! below has #isNumeric as a work around but should probably have ^isString instead https://github.com/OpenAPITools/openapi-generator/issues/15038}} + return {{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}}.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{{datatypeWithEnum}}}{{/lambda.camelcase_sanitize_param}}){{#isNumeric}}.ToString(){{/isNumeric}}; + {{/complexType}} + {{/items}} + {{/items.isEnum}} + {{#isEnum}} + {{^complexType}} + if (obj is Models.{{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{{datatypeWithEnum}}}{{/lambda.camelcase_sanitize_param}}) + {{! below has #isNumeric as a work around but should probably have ^isString instead https://github.com/OpenAPITools/openapi-generator/issues/15038}} + return Models.{{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}}.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{{datatypeWithEnum}}}{{/lambda.camelcase_sanitize_param}}){{#isNumeric}}.ToString(){{/isNumeric}}; + {{/complexType}} + {{/isEnum}} + {{/vars}} + {{/isEnum}} + {{/model}} + {{/models}} + if (obj is ICollection collection) + { + List entries = new{{^net70OrLater}} List{{/net70OrLater}}(); + foreach (var entry in collection) + entries.Add(ParameterToString(entry)); + return string.Join(",", entries); + } + + return Convert.ToString(obj, System.Globalization.CultureInfo.InvariantCulture); + } + + /// + /// URL encode a string + /// Credit/Ref: https://github.com/restsharp/RestSharp/blob/master/RestSharp/Extensions/StringExtensions.cs#L50 + /// + /// string to be URL encoded + /// Byte array + public static string UrlEncode(string input) + { + const int maxLength = 32766; + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (input.Length <= maxLength) + { + return Uri.EscapeDataString(input); + } + + StringBuilder sb = new StringBuilder(input.Length * 2); + int index = 0; + + while (index < input.Length) + { + int length = Math.Min(input.Length - index, maxLength); + string subString = input.Substring(index, length); + + sb.Append(Uri.EscapeDataString(subString)); + index += subString.Length; + } + + return sb.ToString(); + } + + /// + /// Encode string in base64 format. + /// + /// string to be encoded. + /// Encoded string. + public static string Base64Encode(string text) + { + return Convert.ToBase64String(global::System.Text.Encoding.UTF8.GetBytes(text)); + } + + /// + /// Convert stream to byte array + /// + /// Input stream to be converted + /// Byte array + public static byte[] ReadAsBytes(Stream inputStream) + { + using (var ms = new MemoryStream()) + { + inputStream.CopyTo(ms); + return ms.ToArray(); + } + } + + /// + /// Select the Content-Type header's value from the given content-type array: + /// if JSON type exists in the given array, use it; + /// otherwise use the first one defined in 'consumes' + /// + /// The Content-Type array to select from. + /// The Content-Type header to use. + public static string{{nrt?}} SelectHeaderContentType(string[] contentTypes) + { + if (contentTypes.Length == 0) + return null; + + foreach (var contentType in contentTypes) + { + if (IsJsonMime(contentType)) + return contentType; + } + + return contentTypes[0]; // use the first content type specified in 'consumes' + } + + /// + /// Select the Accept header's value from the given accepts array: + /// if JSON exists in the given array, use it; + /// otherwise use all of them (joining into a string) + /// + /// The accepts array to select from. + /// The Accept header to use. + public static string{{nrt?}} SelectHeaderAccept(string[] accepts) + { + if (accepts.Length == 0) + return null; + + if (accepts.Contains("application/json", StringComparer.OrdinalIgnoreCase)) + return "application/json"; + + return string.Join(",", accepts); + } + + /// + /// Provides a case-insensitive check that a provided content type is a known JSON-like content type. + /// + {{#net70OrLater}} + [GeneratedRegex("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$")] + private static partial Regex JsonRegex(); + {{/net70OrLater}} + {{^net70OrLater}} + private static readonly Regex JsonRegex = new Regex("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"); + {{/net70OrLater}} + + /// + /// Check if the given MIME is a JSON MIME. + /// JSON MIME examples: + /// application/json + /// application/json; charset=UTF8 + /// APPLICATION/JSON + /// application/vnd.company+json + /// + /// MIME + /// Returns True if MIME type is json. + public static bool IsJsonMime(string mime) + { + if (string.IsNullOrWhiteSpace(mime)) return false; + + return {{#net70OrLater}}JsonRegex(){{/net70OrLater}}{{^net70OrLater}}JsonRegex{{/net70OrLater}}.IsMatch(mime) || mime.Equals("application/json-patch+json"); + } + + /// + /// Get the discriminator + /// + /// + /// + /// + /// + public static string{{nrt?}} GetDiscriminator(Utf8JsonReader utf8JsonReader, string discriminator) + { + int currentDepth = utf8JsonReader.CurrentDepth; + + if (utf8JsonReader.TokenType != JsonTokenType.StartObject && utf8JsonReader.TokenType != JsonTokenType.StartArray) + throw new JsonException(); + + JsonTokenType startingTokenType = utf8JsonReader.TokenType; + + while (utf8JsonReader.Read()) + { + if (startingTokenType == JsonTokenType.StartObject && utf8JsonReader.TokenType == JsonTokenType.EndObject && currentDepth == utf8JsonReader.CurrentDepth) + break; + + if (startingTokenType == JsonTokenType.StartArray && utf8JsonReader.TokenType == JsonTokenType.EndArray && currentDepth == utf8JsonReader.CurrentDepth) + break; + + if (utf8JsonReader.TokenType == JsonTokenType.PropertyName && currentDepth == utf8JsonReader.CurrentDepth - 1) + { + string{{nrt?}} jsonPropertyName = utf8JsonReader.GetString(); + utf8JsonReader.Read(); + + if (jsonPropertyName != null && jsonPropertyName.Equals(discriminator)) + return utf8JsonReader.GetString(); + } + } + + throw new JsonException("The specified discriminator was not found."); + } + + {{#hasApiKeyMethods}} + /// + /// An enum of headers. + /// + public enum ApiKeyHeader + { + {{#apiKeyMethods}} + /// + /// The {{keyParamName}} header. + /// + {{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}{{^-last}},{{/-last}} + {{/apiKeyMethods}} + } + + /// + /// Converts an ApiKeyHeader to a string. + /// + /// + /// as a string value. + /// + {{>visibility}} static string ApiKeyHeaderToString(ApiKeyHeader value) + { + {{#net80OrLater}} + return value switch + { + {{#apiKeyMethods}} + ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}} => "{{keyParamName}}", + {{/apiKeyMethods}} + _ => throw new System.ComponentModel.InvalidEnumArgumentException(nameof(value), (int)value, typeof(ApiKeyHeader)), + }; + {{/net80OrLater}} + {{^net80OrLater}} + switch(value) + { + {{#apiKeyMethods}} + case ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}: + return "{{keyParamName}}"; + {{/apiKeyMethods}} + default: + throw new System.ComponentModel.InvalidEnumArgumentException(nameof(value), (int)value, typeof(ApiKeyHeader)); + } + {{/net80OrLater}} + } + + {{/hasApiKeyMethods}} + } +} diff --git a/templates-v7/csharp/libraries/generichost/CookieContainer.mustache b/templates-v7/csharp/libraries/generichost/CookieContainer.mustache new file mode 100644 index 000000000..f96d4fb41 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/CookieContainer.mustache @@ -0,0 +1,22 @@ +// +{{partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System.Linq; +using System.Collections.Generic; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// A class containing a CookieContainer + /// + {{>visibility}} sealed class CookieContainer + { + /// + /// The collection of tokens + /// + public System.Net.CookieContainer Value { get; } = new System.Net.CookieContainer(); + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/DateFormats.mustache b/templates-v7/csharp/libraries/generichost/DateFormats.mustache new file mode 100644 index 000000000..920ecda88 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/DateFormats.mustache @@ -0,0 +1,2 @@ + "yyyy'-'MM'-'dd", + "yyyyMMdd" diff --git a/templates-v7/csharp/libraries/generichost/DateOnlyJsonConverter.mustache b/templates-v7/csharp/libraries/generichost/DateOnlyJsonConverter.mustache new file mode 100644 index 000000000..1441421aa --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/DateOnlyJsonConverter.mustache @@ -0,0 +1,52 @@ +{{>partial_header}} +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// Formatter for 'date' openapi formats ss defined by full-date - RFC3339 + /// see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#data-types + /// + {{>visibility}} class DateOnlyJsonConverter : JsonConverter + { + /// + /// The formats used to deserialize the date + /// + public static string[] Formats { get; } = { +{{>DateFormats}} + + }; + + /// + /// Returns a DateOnly from the Json object + /// + /// + /// + /// + /// + public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (reader.TokenType == JsonTokenType.Null) + throw new NotSupportedException(); + + string value = reader.GetString(){{nrt!}}; + + foreach(string format in Formats) + if (DateOnly.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out DateOnly result)) + return result; + + throw new NotSupportedException(); + } + + /// + /// Writes the DateOnly to the json writer + /// + /// + /// + /// + public override void Write(Utf8JsonWriter writer, DateOnly dateOnlyValue, JsonSerializerOptions options) => + writer.WriteStringValue(dateOnlyValue.ToString("{{{dateFormat}}}", CultureInfo.InvariantCulture)); + } +} diff --git a/templates-v7/csharp/libraries/generichost/DateOnlyNullableJsonConverter.mustache b/templates-v7/csharp/libraries/generichost/DateOnlyNullableJsonConverter.mustache new file mode 100644 index 000000000..53fdfbee7 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/DateOnlyNullableJsonConverter.mustache @@ -0,0 +1,57 @@ +{{>partial_header}} +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// Formatter for 'date' openapi formats ss defined by full-date - RFC3339 + /// see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#data-types + /// + {{>visibility}} class DateOnlyNullableJsonConverter : JsonConverter + { + /// + /// The formats used to deserialize the date + /// + public static string[] Formats { get; } = { +{{>DateFormats}} + + }; + + /// + /// Returns a DateOnly from the Json object + /// + /// + /// + /// + /// + public override DateOnly? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (reader.TokenType == JsonTokenType.Null) + return null; + + string value = reader.GetString(){{nrt!}}; + + foreach(string format in Formats) + if (DateOnly.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out DateOnly result)) + return result; + + throw new NotSupportedException(); + } + + /// + /// Writes the DateOnly to the json writer + /// + /// + /// + /// + public override void Write(Utf8JsonWriter writer, DateOnly? dateOnlyValue, JsonSerializerOptions options) + { + if (dateOnlyValue == null) + writer.WriteNullValue(); + else + writer.WriteStringValue(dateOnlyValue.Value.ToString("{{{dateFormat}}}", CultureInfo.InvariantCulture)); + } + } +} diff --git a/templates-v7/csharp/libraries/generichost/DateTimeFormats.mustache b/templates-v7/csharp/libraries/generichost/DateTimeFormats.mustache new file mode 100644 index 000000000..85ed99a2c --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/DateTimeFormats.mustache @@ -0,0 +1,22 @@ + "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffK", + "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ffffffK", + "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffK", + "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ffffK", + "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffK", + "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ffK", + "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fK", + "yyyy'-'MM'-'dd'T'HH':'mm':'ssK", + {{^supportsDateOnly}} + "yyyy'-'MM'-'dd", + {{/supportsDateOnly}} + "yyyyMMddTHHmmss.fffffffK", + "yyyyMMddTHHmmss.ffffffK", + "yyyyMMddTHHmmss.fffffK", + "yyyyMMddTHHmmss.ffffK", + "yyyyMMddTHHmmss.fffK", + "yyyyMMddTHHmmss.ffK", + "yyyyMMddTHHmmss.fK", + "yyyyMMddTHHmmssK", + {{^supportsDateOnly}} + "yyyyMMdd" + {{/supportsDateOnly}} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/DateTimeJsonConverter.mustache b/templates-v7/csharp/libraries/generichost/DateTimeJsonConverter.mustache new file mode 100644 index 000000000..bf5059fca --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/DateTimeJsonConverter.mustache @@ -0,0 +1,52 @@ +{{>partial_header}} +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// Formatter for {{#supportsDateOnly}}'date-time'{{/supportsDateOnly}}{{^supportsDateOnly}}'date' and 'date-time'{{/supportsDateOnly}} openapi formats ss defined by full-date - RFC3339 + /// see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#data-types + /// + {{>visibility}} class DateTimeJsonConverter : JsonConverter + { + /// + /// The formats used to deserialize the date + /// + public static string[] Formats { get; } = { +{{>DateTimeFormats}} + + }; + + /// + /// Returns a DateTime from the Json object + /// + /// + /// + /// + /// + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (reader.TokenType == JsonTokenType.Null) + throw new NotSupportedException(); + + string value = reader.GetString(){{nrt!}}; + + foreach(string format in Formats) + if (DateTime.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out DateTime result)) + return result; + + throw new NotSupportedException(); + } + + /// + /// Writes the DateTime to the json writer + /// + /// + /// + /// + public override void Write(Utf8JsonWriter writer, DateTime dateTimeValue, JsonSerializerOptions options) => + writer.WriteStringValue(dateTimeValue.ToString("{{{dateTimeFormat}}}", CultureInfo.InvariantCulture)); + } +} diff --git a/templates-v7/csharp/libraries/generichost/DateTimeNullableJsonConverter.mustache b/templates-v7/csharp/libraries/generichost/DateTimeNullableJsonConverter.mustache new file mode 100644 index 000000000..bbc036490 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/DateTimeNullableJsonConverter.mustache @@ -0,0 +1,57 @@ +{{>partial_header}} +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// Formatter for {{#supportsDateOnly}}'date-time'{{/supportsDateOnly}}{{^supportsDateOnly}}'date' and 'date-time'{{/supportsDateOnly}} openapi formats ss defined by full-date - RFC3339 + /// see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#data-types + /// + {{>visibility}} class DateTimeNullableJsonConverter : JsonConverter + { + /// + /// The formats used to deserialize the date + /// + public static string[] Formats { get; } = { +{{>DateTimeFormats}} + + }; + + /// + /// Returns a DateTime from the Json object + /// + /// + /// + /// + /// + public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (reader.TokenType == JsonTokenType.Null) + return null; + + string value = reader.GetString(){{nrt!}}; + + foreach(string format in Formats) + if (DateTime.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out DateTime result)) + return result; + + return null; + } + + /// + /// Writes the DateTime to the json writer + /// + /// + /// + /// + public override void Write(Utf8JsonWriter writer, DateTime? dateTimeValue, JsonSerializerOptions options) + { + if (dateTimeValue == null) + writer.WriteNullValue(); + else + writer.WriteStringValue(dateTimeValue.Value.ToString("{{{dateTimeFormat}}}", CultureInfo.InvariantCulture)); + } + } +} diff --git a/templates-v7/csharp/libraries/generichost/DependencyInjectionTests.mustache b/templates-v7/csharp/libraries/generichost/DependencyInjectionTests.mustache new file mode 100644 index 000000000..6085b51e5 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/DependencyInjectionTests.mustache @@ -0,0 +1,211 @@ +{{>partial_header}} +using System; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using System.Security.Cryptography; +using {{packageName}}.{{clientPackage}}; +using {{packageName}}.{{apiPackage}}; +using {{packageName}}.Extensions; +using Xunit; + +namespace {{packageName}}.Test.{{apiPackage}} +{ + /// + /// Tests the dependency injection. + /// + public class DependencyInjectionTest + { + private readonly IHost _hostUsingConfigureWithoutAClient = + Host.CreateDefaultBuilder({{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}Array.Empty(){{/net80OrLater}}).Configure{{apiName}}((context, services, options) => + { + {{#lambda.trimTrailingWithNewLine}} + {{#apiKeyMethods}} + ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(apiKeyToken{{-index}}); + + {{/apiKeyMethods}} + {{#httpBearerMethods}} + BearerToken bearerToken{{-index}} = new{{^net70OrLater}} BearerToken{{/net70OrLater}}("", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(bearerToken{{-index}}); + + {{/httpBearerMethods}} + {{#httpBasicMethods}} + BasicToken basicToken{{-index}} = new{{^net70OrLater}} BasicToken{{/net70OrLater}}("", "", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(basicToken{{-index}}); + + {{/httpBasicMethods}} + {{#httpSignatureMethods}} + HttpSigningConfiguration config{{-index}} = new{{^net70OrLater}} HttpSigningConfiguration{{/net70OrLater}}("", "", null, {{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}new List(){{/net80OrLater}}, HashAlgorithmName.SHA256, "", 0); + HttpSignatureToken httpSignatureToken{{-index}} = new{{^net70OrLater}} HttpSignatureToken{{/net70OrLater}}(config{{-index}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(httpSignatureToken{{-index}}); + + {{/httpSignatureMethods}} + {{#oauthMethods}} + OAuthToken oauthToken{{-index}} = new{{^net70OrLater}} OAuthToken{{/net70OrLater}}("token", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(oauthToken{{-index}}); + + {{/oauthMethods}} + {{/lambda.trimTrailingWithNewLine}} + }) + .Build(); + + private readonly IHost _hostUsingConfigureWithAClient = + Host.CreateDefaultBuilder({{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}Array.Empty(){{/net80OrLater}}).Configure{{apiName}}((context, services, options) => + { + {{#lambda.trimTrailingWithNewLine}} + {{#apiKeyMethods}} + ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(apiKeyToken{{-index}}); + + {{/apiKeyMethods}} + {{#httpBearerMethods}} + BearerToken bearerToken{{-index}} = new{{^net70OrLater}} BearerToken{{/net70OrLater}}("", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(bearerToken{{-index}}); + + {{/httpBearerMethods}} + {{#httpBasicMethods}} + BasicToken basicToken{{-index}} = new{{^net70OrLater}} BasicToken{{/net70OrLater}}("", "", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(basicToken{{-index}}); + + {{/httpBasicMethods}} + {{#httpSignatureMethods}} + HttpSigningConfiguration config{{-index}} = new{{^net70OrLater}} HttpSigningConfiguration{{/net70OrLater}}("", "", null, {{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}new List(){{/net80OrLater}}, HashAlgorithmName.SHA256, "", 0); + HttpSignatureToken httpSignatureToken{{-index}} = new{{^net70OrLater}} HttpSignatureToken{{/net70OrLater}}(config{{-index}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(httpSignatureToken{{-index}}); + + {{/httpSignatureMethods}} + {{#oauthMethods}} + OAuthToken oauthToken = new{{^net70OrLater}} OAuthToken{{/net70OrLater}}("token", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(oauthToken); + + {{/oauthMethods}} + {{/lambda.trimTrailingWithNewLine}} + options.Add{{apiName}}HttpClients(client => client.BaseAddress = new Uri(ClientUtils.BASE_ADDRESS)); + }) + .Build(); + + private readonly IHost _hostUsingAddWithoutAClient = + Host.CreateDefaultBuilder({{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}Array.Empty(){{/net80OrLater}}).ConfigureServices((host, services) => + { + services.Add{{apiName}}(options => + { + {{#lambda.trimTrailingWithNewLine}} + {{#apiKeyMethods}} + ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(apiKeyToken{{-index}}); + + {{/apiKeyMethods}} + {{#httpBearerMethods}} + BearerToken bearerToken{{-index}} = new{{^net70OrLater}} BearerToken{{/net70OrLater}}("", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(bearerToken{{-index}}); + + {{/httpBearerMethods}} + {{#httpBasicMethods}} + BasicToken basicToken{{-index}} = new{{^net70OrLater}} BasicToken{{/net70OrLater}}("", "", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(basicToken{{-index}}); + + {{/httpBasicMethods}} + {{#httpSignatureMethods}} + HttpSigningConfiguration config{{-index}} = new{{^net70OrLater}} HttpSigningConfiguration{{/net70OrLater}}("", "", null, {{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}new List(){{/net80OrLater}}, HashAlgorithmName.SHA256, "", 0); + HttpSignatureToken httpSignatureToken{{-index}} = new{{^net70OrLater}} HttpSignatureToken{{/net70OrLater}}(config{{-index}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(httpSignatureToken{{-index}}); + + {{/httpSignatureMethods}} + {{#oauthMethods}} + OAuthToken oauthToken{{-index}} = new{{^net70OrLater}} OAuthToken{{/net70OrLater}}("token", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(oauthToken{{-index}}); + + {{/oauthMethods}} + {{/lambda.trimTrailingWithNewLine}} + }); + }) + .Build(); + + private readonly IHost _hostUsingAddWithAClient = + Host.CreateDefaultBuilder({{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}Array.Empty(){{/net80OrLater}}).ConfigureServices((host, services) => + { + services.Add{{apiName}}(options => + { + {{#lambda.trimTrailingWithNewLine}} + {{#apiKeyMethods}} + ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(apiKeyToken{{-index}}); + + {{/apiKeyMethods}} + {{#httpBearerMethods}} + BearerToken bearerToken{{-index}} = new{{^net70OrLater}} BearerToken{{/net70OrLater}}("", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(bearerToken{{-index}}); + + {{/httpBearerMethods}} + {{#httpBasicMethods}} + BasicToken basicToken{{-index}} = new{{^net70OrLater}} BasicToken{{/net70OrLater}}("", "", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(basicToken{{-index}}); + + {{/httpBasicMethods}} + {{#httpSignatureMethods}} + HttpSigningConfiguration config{{-index}} = new{{^net70OrLater}} HttpSigningConfiguration{{/net70OrLater}}("", "", null, {{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}new List(){{/net80OrLater}}, HashAlgorithmName.SHA256, "", 0); + HttpSignatureToken httpSignatureToken{{-index}} = new{{^net70OrLater}} HttpSignatureToken{{/net70OrLater}}(config{{-index}}, timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(httpSignatureToken{{-index}}); + + {{/httpSignatureMethods}} + {{#oauthMethods}} + OAuthToken oauthToken{{-index}} = new{{^net70OrLater}} OAuthToken{{/net70OrLater}}("token", timeout: TimeSpan.FromSeconds(1)); + options.AddTokens(oauthToken{{-index}}); + + {{/oauthMethods}} + {{/lambda.trimTrailingWithNewLine}} + options.Add{{apiName}}HttpClients(client => client.BaseAddress = new Uri(ClientUtils.BASE_ADDRESS)); + }); + }) + .Build(); + + /// + /// Test dependency injection when using the configure method + /// + [Fact] + public void ConfigureApiWithAClientTest() + { + {{#apiInfo}}{{#apis}}var {{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}} = _hostUsingConfigureWithAClient.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); + Assert.True({{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}}.HttpClient.BaseAddress != null);{{^-last}} + + {{/-last}}{{/apis}}{{/apiInfo}} + } + + /// + /// Test dependency injection when using the configure method + /// + [Fact] + public void ConfigureApiWithoutAClientTest() + { + {{#apiInfo}}{{#apis}}var {{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}} = _hostUsingConfigureWithoutAClient.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); + Assert.True({{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}}.HttpClient.BaseAddress != null);{{^-last}} + + {{/-last}}{{/apis}}{{/apiInfo}} + } + + /// + /// Test dependency injection when using the add method + /// + [Fact] + public void AddApiWithAClientTest() + { + {{#apiInfo}}{{#apis}}var {{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}} = _hostUsingAddWithAClient.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); + Assert.True({{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}}.HttpClient.BaseAddress != null);{{^-last}} + + {{/-last}}{{/apis}}{{/apiInfo}} + } + + /// + /// Test dependency injection when using the add method + /// + [Fact] + public void AddApiWithoutAClientTest() + { + {{#apiInfo}}{{#apis}}var {{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}} = _hostUsingAddWithoutAClient.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); + Assert.True({{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}}.HttpClient.BaseAddress != null);{{^-last}} + + {{/-last}}{{/apis}}{{/apiInfo}} + } + } +} diff --git a/templates-v7/csharp/libraries/generichost/EnumValueDataType.mustache b/templates-v7/csharp/libraries/generichost/EnumValueDataType.mustache new file mode 100644 index 000000000..e92e67b36 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/EnumValueDataType.mustache @@ -0,0 +1 @@ +{{#allowableValues}}{{#enumVars}}{{#-first}}{{#isString}}{{^isNumeric}}string{{/isNumeric}}{{/isString}}{{#isNumeric}}{{#isLong}}long{{/isLong}}{{#isFloat}}float{{/isFloat}}{{#isDouble}}double{{/isDouble}}{{#isDecimal}}decimal{{/isDecimal}}{{^isLong}}{{^isFloat}}{{^isDouble}}{{^isDecimal}}int{{/isDecimal}}{{/isDouble}}{{/isFloat}}{{/isLong}}{{/isNumeric}}{{/-first}}{{/enumVars}}{{/allowableValues}} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/ExceptionEventArgs.mustache b/templates-v7/csharp/libraries/generichost/ExceptionEventArgs.mustache new file mode 100644 index 000000000..b74fcfa0a --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ExceptionEventArgs.mustache @@ -0,0 +1,24 @@ +using System; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// Useful for tracking server health + /// + {{>visibility}} class ExceptionEventArgs : EventArgs + { + /// + /// The ApiResponse + /// + public Exception Exception { get; } + + /// + /// The ExceptionEventArgs + /// + /// + public ExceptionEventArgs(Exception exception) + { + Exception = exception; + } + } +} diff --git a/templates-v7/csharp/libraries/generichost/HmacKeyToken.mustache b/templates-v7/csharp/libraries/generichost/HmacKeyToken.mustache new file mode 100644 index 000000000..8a24aecef --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/HmacKeyToken.mustache @@ -0,0 +1,36 @@ +// +{{partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using {{packageName}}.{{corePackageName}}.Auth; +using {{packageName}}.{{corePackageName}}.{{clientPackage}}; + +namespace {{packageName}}.{{apiName}}.{{clientPackage}} +{ + /// + /// The `ADYEN_HMAC_KEY` is used to verify the incoming HMAC signature from incoming webhooks. + /// To protect your server from unauthorised webhook events, Adyen recommends that you use Hash-based message authentication code HMAC signatures for our webhooks. + /// Each webhook event will include a signature calculated using a secret `ADYEN_HMAC_KEY` key and the payload from the webhook. + /// By verifying this signature, you confirm that the webhook was sent by Adyen, and was not modified during transmission. + /// + {{>visibility}} class HmacKeyToken : TokenBase + { + /// + /// The `ADYEN_HMAC_KEY`, can be configured in . + /// + public string? AdyenHmacKey { get; } + + /// + /// Constructs the HmacKeyToken object with the ADYEN_HMAC_KEY value provided. + /// This can then be accessed using . + /// + /// Your `ADYEN_HMAC_KEY`, configured in . + public HmacKeyToken(string hmacKey) : base() + { + AdyenHmacKey = hmacKey; + } + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/HostConfiguration.mustache b/templates-v7/csharp/libraries/generichost/HostConfiguration.mustache new file mode 100644 index 000000000..b1387180c --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/HostConfiguration.mustache @@ -0,0 +1,178 @@ +{{>partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; +using {{packageName}}.{{apiPackage}}; +using {{packageName}}.{{apiName}}.{{clientPackage}}; +{{#models}} +{{#-first}} +using {{packageName}}.{{modelPackage}}; +{{/-first}} +{{/models}} +using {{packageName}}.{{corePackageName}}; +using {{packageName}}.{{corePackageName}}.Auth; +using {{packageName}}.{{corePackageName}}.Client; +using {{packageName}}.{{corePackageName}}.Options; +using {{packageName}}.{{corePackageName}}.Converters; + +namespace {{packageName}}.{{apiName}}.{{clientPackage}} +{ + /// + /// Provides hosting configuration for {{apiName}} + /// + {{>visibility}} class HostConfiguration + { + private readonly IServiceCollection _services; + private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions(); + private readonly AdyenOptions _adyenOptions = new AdyenOptions(); + + /// + /// The base path of the API, it includes the http(s)-scheme, the host domain name, and the base path. + /// This value can change when `ConfigureAdyenOptions` is called in ). The new value will be based on the .. + /// + public static string BASE_URL = "{{{basePath}}}"; + + /// + /// Instantiates the HostConfiguration (custom JsonConverters, Events, HttpClient) with the necessary dependencies to communicate with the API. + /// + /// + public HostConfiguration(IServiceCollection services) + { + _services = services; + if (_jsonOptions.Converters.FirstOrDefault(x => x.GetType() == typeof(JsonStringEnumConverter)) == null) + _jsonOptions.Converters.Add(new JsonStringEnumConverter()); + if (_jsonOptions.Converters.FirstOrDefault(x => x.GetType() == typeof(DateTimeJsonConverter)) == null) + _jsonOptions.Converters.Add(new DateTimeJsonConverter()); + if (_jsonOptions.Converters.FirstOrDefault(x => x.GetType() == typeof(DateTimeNullableJsonConverter)) == null) + _jsonOptions.Converters.Add(new DateTimeNullableJsonConverter()); + if (_jsonOptions.Converters.FirstOrDefault(x => x.GetType() == typeof(ByteArrayConverter)) == null) + _jsonOptions.Converters.Add(new ByteArrayConverter()); + {{#supportsDateOnly}} + if (_jsonOptions.Converters.FirstOrDefault(x => x.GetType() == typeof(DateOnlyJsonConverter)) == null) + _jsonOptions.Converters.Add(new DateOnlyJsonConverter()); + if (_jsonOptions.Converters.FirstOrDefault(x => x.GetType() == typeof(DateOnlyNullableJsonConverter)) == null) + _jsonOptions.Converters.Add(new DateOnlyNullableJsonConverter()); + {{/supportsDateOnly}} + {{#models}} + {{#model}} + {{#isEnum}} + _jsonOptions.Converters.Add(new {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}JsonConverter()); + _jsonOptions.Converters.Add(new {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}NullableJsonConverter()); + {{/isEnum}} + {{^isEnum}} + _jsonOptions.Converters.Add(new {{classname}}JsonConverter()); + {{/isEnum}} + {{/model}} + {{/models}} + JsonSerializerOptionsProvider jsonSerializerOptionsProvider = new{{^net60OrLater}} JsonSerializerOptionsProvider{{/net60OrLater}}(_jsonOptions); + _services.AddSingleton(jsonSerializerOptionsProvider); + {{#useSourceGeneration}} + + {{#models}} + {{#-first}} + _jsonOptions.TypeInfoResolver = System.Text.Json.Serialization.Metadata.JsonTypeInfoResolver.Combine( + {{/-first}} + {{/models}} + {{#lambda.joinLinesWithComma}} + {{#models}} + {{#model}} + new {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}SerializationContext(){{#-last}},{{/-last}} + {{/model}} + {{/models}} + {{/lambda.joinLinesWithComma}} + {{#models}} + {{#-last}} + + new System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver() + ); + {{/-last}} + {{/models}} + + {{/useSourceGeneration}} + _services.AddSingleton<{{interfacePrefix}}ApiFactory, ApiFactory>();{{#apiInfo}}{{#apis}} + _services.AddSingleton<{{classname}}Events>();{{/apis}}{{/apiInfo}} + } + + /// + /// Configures the and . + /// + /// Configures the . + /// Configures the . + /// . + public HostConfiguration Add{{apiName}}HttpClients(Action{{nrt?}} httpClientOptions = null, Action{{nrt?}} httpClientBuilderOptions = null) + { + Action httpClientAction = httpClient => + { + // Configure HttpClient set by the user. + httpClientOptions?.Invoke(httpClient); + + // Set BaseAddress if it's not set. + if (httpClient.BaseAddress == null) + httpClient.BaseAddress = new Uri(UrlBuilderExtensions.ConstructHostUrl(_adyenOptions, BASE_URL)); + }; + + List builders = new List(); + + {{#models}} + {{#model.vendorExtensions.x-has-at-least-one-webhook-root}} + _services.AddSingleton<{{packageName}}.{{apiName}}.Handlers.{{interfacePrefix}}{{apiName}}Handler, {{packageName}}.{{apiName}}.Handlers.{{apiName}}Handler>(); + {{/model.vendorExtensions.x-has-at-least-one-webhook-root}} + {{/models}} + {{#apiInfo}}{{#apis}}builders.Add(_services.AddHttpClient<{{interfacePrefix}}{{classname}}, {{classname}}>(httpClientAction)); + {{/apis}}{{/apiInfo}} + if (httpClientBuilderOptions != null) + foreach (IHttpClientBuilder builder in builders) + httpClientBuilderOptions(builder); + + return this; + } + + /// + /// Configures the . + /// + /// Configures the . + /// . + public HostConfiguration ConfigureJsonOptions(Action jsonSerializerOptions) + { + jsonSerializerOptions(_jsonOptions); + + return this; + } + + /// + /// Configures the (e.g. Environment, LiveEndpointPrefix). + /// + /// Configures the . + /// . + public HostConfiguration ConfigureAdyenOptions(Action adyenOptions) + { + adyenOptions(_adyenOptions); + {{#hasApiKeyMethods}} + _services.AddSingleton>( + new TokenProvider( + new ApiKeyToken(_adyenOptions.AdyenApiKey, ClientUtils.ApiKeyHeader.X_API_Key, "") + ) + ); + {{/hasApiKeyMethods}} + + {{#models}} + {{#model.vendorExtensions.x-has-at-least-one-webhook-root}} + _services.AddSingleton>( + new TokenProvider( + new HmacKeyToken(_adyenOptions.AdyenHmacKey) + ) + ); + {{/model.vendorExtensions.x-has-at-least-one-webhook-root}} + {{/models}} + return this; + } + } +} diff --git a/templates-v7/csharp/libraries/generichost/HttpSigningConfiguration.mustache b/templates-v7/csharp/libraries/generichost/HttpSigningConfiguration.mustache new file mode 100644 index 000000000..8b69a8c0b --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/HttpSigningConfiguration.mustache @@ -0,0 +1,679 @@ +// +{{>partial_header}} + +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Cryptography; +using System.Text; +using System.Web; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// Class for HttpSigning auth related parameter and methods + /// + {{>visibility}} class HttpSigningConfiguration + { + /// + /// Create an instance + /// + public HttpSigningConfiguration(string keyId, string keyFilePath, SecureString{{nrt?}} keyPassPhrase, List httpSigningHeader, HashAlgorithmName hashAlgorithm, string signingAlgorithm, int signatureValidityPeriod) + { + KeyId = keyId; + KeyFilePath = keyFilePath; + KeyPassPhrase = keyPassPhrase; + HttpSigningHeader = httpSigningHeader; + HashAlgorithm = hashAlgorithm; + SigningAlgorithm = signingAlgorithm; + SignatureValidityPeriod = signatureValidityPeriod; + } + + /// + ///Gets the Api keyId + /// + public string KeyId { get; set; } + + /// + /// Gets the Key file path + /// + public string KeyFilePath { get; set; } + + /// + /// Gets the key pass phrase for password protected key + /// + public SecureString{{nrt?}} KeyPassPhrase { get; set; } + + /// + /// Gets the HTTP signing header + /// + public List HttpSigningHeader { get; set; } + + /// + /// Gets the hash algorithm sha256 or sha512 + /// + public HashAlgorithmName HashAlgorithm { get; set; } = HashAlgorithmName.SHA256; + + /// + /// Gets the signing algorithm + /// + public string SigningAlgorithm { get; set; } + + /// + /// Gets the Signature validity period in seconds + /// + public int SignatureValidityPeriod { get; set; } + + private enum PrivateKeyType + { + None = 0, + RSA = 1, + ECDSA = 2, + } + + /// + /// Gets the Headers for HttpSigning + /// + /// + /// + /// + internal Dictionary GetHttpSignedHeader(global::System.Net.Http.HttpRequestMessage request, string requestBody, System.Threading.CancellationToken cancellationToken = default{{^netstandard20OrLater}}(global::System.Threading.CancellationToken){{/netstandard20OrLater}}) + { + if (request.RequestUri == null) + throw new NullReferenceException("The request URI was null"); + + const string HEADER_REQUEST_TARGET = "(request-target)"; + + // The time when the HTTP signature expires. The API server should reject HTTP requests that have expired. + const string HEADER_EXPIRES = "(expires)"; + + //The 'Date' header. + const string HEADER_DATE = "Date"; + + //The 'Host' header. + const string HEADER_HOST = "Host"; + + //The time when the HTTP signature was generated. + const string HEADER_CREATED = "(created)"; + + //When the 'Digest' header is included in the HTTP signature, the client automatically + //computes the digest of the HTTP request body, per RFC 3230. + const string HEADER_DIGEST = "Digest"; + + //The 'Authorization' header is automatically generated by the client. It includes + //the list of signed headers and a base64-encoded signature. + const string HEADER_AUTHORIZATION = "Authorization"; + + //Hash table to store singed headers + var HttpSignedRequestHeader = new Dictionary(); + + var httpSignatureHeader = new Dictionary(); + + if (HttpSigningHeader.Count == 0) + HttpSigningHeader.Add("(created)"); + + var dateTime = DateTime.Now; + string digest = String.Empty; + + if (HashAlgorithm == HashAlgorithmName.SHA256) + { + var bodyDigest = GetStringHash(HashAlgorithm, requestBody); + digest = string.Format("SHA-256={0}", Convert.ToBase64String(bodyDigest)); + } + else if (HashAlgorithm == HashAlgorithmName.SHA512) + { + var bodyDigest = GetStringHash(HashAlgorithm, requestBody); + digest = string.Format("SHA-512={0}", Convert.ToBase64String(bodyDigest)); + } + else + throw new Exception(string.Format("{0} not supported", HashAlgorithm)); + + foreach (var header in HttpSigningHeader) + if (header.Equals(HEADER_REQUEST_TARGET)) + httpSignatureHeader.Add(header.ToLower(), request.RequestUri.ToString()); + else if (header.Equals(HEADER_EXPIRES)) + { + var expireDateTime = dateTime.AddSeconds(SignatureValidityPeriod); + httpSignatureHeader.Add(header.ToLower(), GetUnixTime(expireDateTime).ToString()); + } + else if (header.Equals(HEADER_DATE)) + { + var utcDateTime = dateTime.ToString("r").ToString(); + httpSignatureHeader.Add(header.ToLower(), utcDateTime); + HttpSignedRequestHeader.Add(HEADER_DATE, utcDateTime); + } + else if (header.Equals(HEADER_HOST)) + { + httpSignatureHeader.Add(header.ToLower(), request.RequestUri.ToString()); + HttpSignedRequestHeader.Add(HEADER_HOST, request.RequestUri.ToString()); + } + else if (header.Equals(HEADER_CREATED)) + httpSignatureHeader.Add(header.ToLower(), GetUnixTime(dateTime).ToString()); + else if (header.Equals(HEADER_DIGEST)) + { + HttpSignedRequestHeader.Add(HEADER_DIGEST, digest); + httpSignatureHeader.Add(header.ToLower(), digest); + } + else + { + bool isHeaderFound = false; + foreach (var item in request.Headers) + { + if (string.Equals(item.Key, header, StringComparison.OrdinalIgnoreCase)) + { + httpSignatureHeader.Add(header.ToLower(), item.Value.ToString()); + isHeaderFound = true; + break; + } + } + + if (!isHeaderFound) + throw new Exception(string.Format("Cannot sign HTTP request.Request does not contain the {0} header.",header)); + } + + var headersKeysString = String.Join(" ", httpSignatureHeader.Keys); + var headerValuesList = new List(); + + foreach (var keyVal in httpSignatureHeader) + headerValuesList.Add(string.Format("{0}: {1}", keyVal.Key, keyVal.Value)); + + //Concatenate headers value separated by new line + var headerValuesString = string.Join("\n", headerValuesList); + var signatureStringHash = GetStringHash(HashAlgorithm, headerValuesString); + string{{nrt?}} headerSignatureStr = null; + var keyType = GetKeyType(KeyFilePath); + + if (keyType == PrivateKeyType.RSA) + headerSignatureStr = GetRSASignature(signatureStringHash); + + else if (keyType == PrivateKeyType.ECDSA) + headerSignatureStr = GetECDSASignature(signatureStringHash); + + var cryptographicScheme = "hs2019"; + var authorizationHeaderValue = string.Format("Signature keyId=\"{0}\",algorithm=\"{1}\"", + KeyId, cryptographicScheme); + + if (httpSignatureHeader.ContainsKey(HEADER_CREATED)) + authorizationHeaderValue += string.Format(",created={0}", httpSignatureHeader[HEADER_CREATED]); + + if (httpSignatureHeader.ContainsKey(HEADER_EXPIRES)) + authorizationHeaderValue += string.Format(",expires={0}", httpSignatureHeader[HEADER_EXPIRES]); + + authorizationHeaderValue += string.Format(",headers=\"{0}\",signature=\"{1}\"", headersKeysString, headerSignatureStr); + + HttpSignedRequestHeader.Add(HEADER_AUTHORIZATION, authorizationHeaderValue); + + return HttpSignedRequestHeader; + } + + private byte[] GetStringHash(HashAlgorithmName hashAlgorithmName, string stringToBeHashed) + { + HashAlgorithm{{nrt?}} hashAlgorithm = null; + + if (hashAlgorithmName == HashAlgorithmName.SHA1) + hashAlgorithm = SHA1.Create(); + + if (hashAlgorithmName == HashAlgorithmName.SHA256) + hashAlgorithm = SHA256.Create(); + + if (hashAlgorithmName == HashAlgorithmName.SHA512) + hashAlgorithm = SHA512.Create(); + + if (hashAlgorithmName == HashAlgorithmName.MD5) + hashAlgorithm = MD5.Create(); + + if (hashAlgorithm == null) + throw new NullReferenceException($"{ nameof(hashAlgorithm) } was null."); + + byte[] bytes = Encoding.UTF8.GetBytes(stringToBeHashed); + byte[] stringHash = hashAlgorithm.ComputeHash(bytes); + return stringHash; + } + + private int GetUnixTime(DateTime date2) + { + DateTime date1 = new DateTime(1970, 01, 01); + TimeSpan timeSpan = date2 - date1; + return (int)timeSpan.TotalSeconds; + } + + private string GetRSASignature(byte[] stringToSign) + { + if (KeyPassPhrase == null) + throw new NullReferenceException($"{ nameof(KeyPassPhrase) } was null."); + + RSA{{nrt?}} rsa = GetRSAProviderFromPemFile(KeyFilePath, KeyPassPhrase); + + if (rsa == null) + return string.Empty; + else if (SigningAlgorithm == "RSASSA-PSS") + { + var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pss); + return Convert.ToBase64String(signedbytes); + } + else if (SigningAlgorithm == "PKCS1-v15") + { + var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pkcs1); + return Convert.ToBase64String(signedbytes); + } + + return string.Empty; + } + + /// + /// Gets the ECDSA signature + /// + /// + /// + private string GetECDSASignature(byte[] dataToSign) + { + {{#net60OrLater}} + if (!File.Exists(KeyFilePath)) + throw new Exception("key file path does not exist."); + + var ecKeyHeader = "-----BEGIN EC PRIVATE KEY-----"; + var ecKeyFooter = "-----END EC PRIVATE KEY-----"; + var keyStr = File.ReadAllText(KeyFilePath); + var ecKeyBase64String = keyStr.Replace(ecKeyHeader, "").Replace(ecKeyFooter, "").Trim(); + var keyBytes = System.Convert.FromBase64String(ecKeyBase64String); + var ecdsa = ECDsa.Create(); + + var byteCount = 0; + if (KeyPassPhrase != null) + { + IntPtr unmanagedString = IntPtr.Zero; + try + { + // convert secure string to byte array + unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(KeyPassPhrase); + + string ptrToStringUni = Marshal.PtrToStringUni(unmanagedString) ?? throw new NullReferenceException(); + + ecdsa.ImportEncryptedPkcs8PrivateKey(Encoding.UTF8.GetBytes(ptrToStringUni), keyBytes, out byteCount); + } + finally + { + if (unmanagedString != IntPtr.Zero) + Marshal.ZeroFreeBSTR(unmanagedString); + } + } + else + ecdsa.ImportPkcs8PrivateKey(keyBytes, out byteCount); + + var signedBytes = ecdsa.SignHash(dataToSign); + var derBytes = ConvertToECDSAANS1Format(signedBytes); + var signedString = System.Convert.ToBase64String(derBytes); + + return signedString; + {{/net60OrLater}} + {{^net60OrLater}} + throw new Exception("ECDSA signing is supported only on NETCOREAPP3_0 and above"); + {{/net60OrLater}} + } + + private byte[] ConvertToECDSAANS1Format(byte[] signedBytes) + { + var derBytes = new List(); + byte derLength = 68; //default length for ECDSA code signing bit 0x44 + byte rbytesLength = 32; //R length 0x20 + byte sbytesLength = 32; //S length 0x20 + var rBytes = new List(); + var sBytes = new List(); + for (int i = 0; i < 32; i++) + rBytes.Add(signedBytes[i]); + + for (int i = 32; i < 64; i++) + sBytes.Add(signedBytes[i]); + + if (rBytes[0] > 0x7F) + { + derLength++; + rbytesLength++; + var tempBytes = new List(); + tempBytes.AddRange(rBytes); + rBytes.Clear(); + rBytes.Add(0x00); + rBytes.AddRange(tempBytes); + } + + if (sBytes[0] > 0x7F) + { + derLength++; + sbytesLength++; + var tempBytes = new List(); + tempBytes.AddRange(sBytes); + sBytes.Clear(); + sBytes.Add(0x00); + sBytes.AddRange(tempBytes); + + } + + derBytes.Add(48); //start of the sequence 0x30 + derBytes.Add(derLength); //total length r length, type and r bytes + + derBytes.Add(2); //tag for integer + derBytes.Add(rbytesLength); //length of r + derBytes.AddRange(rBytes); + + derBytes.Add(2); //tag for integer + derBytes.Add(sbytesLength); //length of s + derBytes.AddRange(sBytes); + return derBytes.ToArray(); + } + + private RSACryptoServiceProvider{{nrt?}} GetRSAProviderFromPemFile(String pemfile, SecureString{{nrt?}} keyPassPhrase = null) + { + const String pempubheader = "-----BEGIN PUBLIC KEY-----"; + const String pempubfooter = "-----END PUBLIC KEY-----"; + bool isPrivateKeyFile = true; + byte[]{{nrt?}} pemkey = null; + + if (!File.Exists(pemfile)) + throw new Exception("private key file does not exist."); + + string pemstr = File.ReadAllText(pemfile).Trim(); + + if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter)) + isPrivateKeyFile = false; + + if (isPrivateKeyFile) + { + pemkey = ConvertPrivateKeyToBytes(pemstr, keyPassPhrase); + + if (pemkey == null) + return null; + + return DecodeRSAPrivateKey(pemkey); + } + return null; + } + + private byte[]{{nrt?}} ConvertPrivateKeyToBytes(String instr, SecureString{{nrt?}} keyPassPhrase = null) + { + const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----"; + const String pemprivfooter = "-----END RSA PRIVATE KEY-----"; + String pemstr = instr.Trim(); + byte[] binkey; + + if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter)) + return null; + + StringBuilder sb = new StringBuilder(pemstr); + sb.Replace(pemprivheader, ""); + sb.Replace(pemprivfooter, ""); + String pvkstr = sb.ToString().Trim(); + + try + { // if there are no PEM encryption info lines, this is an UNencrypted PEM private key + binkey = Convert.FromBase64String(pvkstr); + return binkey; + } + catch (global::System.FormatException) + { + StringReader str = new StringReader(pvkstr); + + //-------- read PEM encryption info. lines and extract salt ----- + if (!str.ReadLine(){{nrt!}}.StartsWith("Proc-Type: 4,ENCRYPTED")) // TODO: what do we do here if ReadLine is null? + return null; + + String saltline = str.ReadLine(){{nrt!}}; // TODO: what do we do here if ReadLine is null? + if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,")) + return null; + + String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim(); + byte[] salt = new byte[saltstr.Length / 2]; + for (int i = 0; i < salt.Length; i++) + salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16); + + if (!(str.ReadLine() == "")) + return null; + + //------ remaining b64 data is encrypted RSA key ---- + String encryptedstr = str.ReadToEnd(); + + try + { //should have b64 encrypted RSA key now + binkey = Convert.FromBase64String(encryptedstr); + } + catch (global::System.FormatException) + { //data is not in base64 format + return null; + } + + // TODO: what do we do here if keyPassPhrase is null? + byte[] deskey = GetEncryptedKey(salt, keyPassPhrase{{nrt!}}, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes + if (deskey == null) + return null; + + //------ Decrypt the encrypted 3des-encrypted RSA private key ------ + byte[]{{nrt?}} rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV + + return rsakey; + } + } + + private RSACryptoServiceProvider{{nrt?}} DecodeRSAPrivateKey(byte[] privkey) + { + byte[] MODULUS, E, D, P, Q, DP, DQ, IQ; + + // --------- Set up stream to decode the asn.1 encoded RSA private key ------ + MemoryStream mem = new MemoryStream(privkey); + BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading + byte bt = 0; + ushort twobytes = 0; + int elems = 0; + try + { + twobytes = binr.ReadUInt16(); + if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) + binr.ReadByte(); //advance 1 byte + else if (twobytes == 0x8230) + binr.ReadInt16(); //advance 2 bytes + else + return null; + + twobytes = binr.ReadUInt16(); + if (twobytes != 0x0102) //version number + return null; + + bt = binr.ReadByte(); + if (bt != 0x00) + return null; + + //------ all private key components are Integer sequences ---- + elems = GetIntegerSize(binr); + MODULUS = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + E = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + D = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + P = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + Q = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + DP = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + DQ = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + IQ = binr.ReadBytes(elems); + + // ------- create RSACryptoServiceProvider instance and initialize with public key ----- + RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); + RSAParameters RSAparams = new RSAParameters(); + RSAparams.Modulus = MODULUS; + RSAparams.Exponent = E; + RSAparams.D = D; + RSAparams.P = P; + RSAparams.Q = Q; + RSAparams.DP = DP; + RSAparams.DQ = DQ; + RSAparams.InverseQ = IQ; + RSA.ImportParameters(RSAparams); + return RSA; + } + catch (Exception) + { + return null; + } + finally + { + binr.Close(); + } + } + + private int GetIntegerSize(BinaryReader binr) + { + byte bt = 0; + byte lowbyte = 0x00; + byte highbyte = 0x00; + int count = 0; + bt = binr.ReadByte(); + if (bt != 0x02) //expect integer + return 0; + + bt = binr.ReadByte(); + + if (bt == 0x81) + count = binr.ReadByte(); // data size in next byte + else if (bt == 0x82) + { + highbyte = binr.ReadByte(); // data size in next 2 bytes + lowbyte = binr.ReadByte(); + byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; + count = BitConverter.ToInt32(modint, 0); + } + else + count = bt; // we already have the data size + + while (binr.ReadByte() == 0x00) + //remove high order zeros in data + count -= 1; + + binr.BaseStream.Seek(-1, SeekOrigin.Current); + + //last ReadByte wasn't a removed zero, so back up a byte + return count; + } + + private byte[] GetEncryptedKey(byte[] salt, SecureString secpswd, int count, int miter) + { + IntPtr unmanagedPswd = IntPtr.Zero; + int HASHLENGTH = 16; //MD5 bytes + byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store concatenated Mi hashed results + + byte[] psbytes = new byte[secpswd.Length]; + unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd); + Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length); + Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd); + + // --- concatenate salt and pswd bytes into fixed data array --- + byte[] data00 = new byte[psbytes.Length + salt.Length]; + Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes + Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes + + // ---- do multi-hashing and concatenate results D1, D2 ... into keymaterial bytes ---- + MD5 md5 = MD5.Create(); + byte[]{{nrt?}} result = null; + byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget + + for (int j = 0; j < miter; j++) + { + // ---- Now hash consecutively for count times ------ + if (j == 0) + result = data00; //initialize + else + { + Array.Copy(result{{nrt!}}, hashtarget, result{{nrt!}}.Length); // TODO: what do we do if result is null here? + Array.Copy(data00, 0, hashtarget, result.Length, data00.Length); + result = hashtarget; + } + + for (int i = 0; i < count; i++) + result = md5.ComputeHash(result); + + Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //concatenate to keymaterial + } + byte[] deskey = new byte[24]; + Array.Copy(keymaterial, deskey, deskey.Length); + + Array.Clear(psbytes, 0, psbytes.Length); + Array.Clear(data00, 0, data00.Length); + Array.Clear(result{{nrt!}}, 0, result{{nrt!}}.Length); // TODO: what do we do if result is null here? + Array.Clear(hashtarget, 0, hashtarget.Length); + Array.Clear(keymaterial, 0, keymaterial.Length); + return deskey; + } + + private byte[]{{nrt?}} DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV) + { + MemoryStream memst = new MemoryStream(); + TripleDES alg = TripleDES.Create(); + alg.Key = desKey; + alg.IV = IV; + try + { + CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write); + cs.Write(cipherData, 0, cipherData.Length); + cs.Close(); + } + catch (Exception) + { + return null; + } + byte[] decryptedData = memst.ToArray(); + return decryptedData; + } + + /// + /// Detect the key type from the pem file. + /// + /// key file path in pem format + /// + private PrivateKeyType GetKeyType(string keyFilePath) + { + if (!File.Exists(keyFilePath)) + throw new Exception("Key file path does not exist."); + + var ecPrivateKeyHeader = "BEGIN EC PRIVATE KEY"; + var ecPrivateKeyFooter = "END EC PRIVATE KEY"; + var rsaPrivateKeyHeader = "BEGIN RSA PRIVATE KEY"; + var rsaPrivateFooter = "END RSA PRIVATE KEY"; + //var pkcs8Header = "BEGIN PRIVATE KEY"; + //var pkcs8Footer = "END PRIVATE KEY"; + var keyType = PrivateKeyType.None; + var key = File.ReadAllLines(keyFilePath); + + if (key[0].ToString().Contains(rsaPrivateKeyHeader) && key[key.Length - 1].ToString().Contains(rsaPrivateFooter)) + keyType = PrivateKeyType.RSA; + else if (key[0].ToString().Contains(ecPrivateKeyHeader) && key[key.Length - 1].ToString().Contains(ecPrivateKeyFooter)) + keyType = PrivateKeyType.ECDSA; + + else if (key[0].ToString().Contains(ecPrivateKeyHeader) && key[key.Length - 1].ToString().Contains(ecPrivateKeyFooter)) + { + /* this type of key can hold many type different types of private key, but here due lack of pem header + Considering this as EC key + */ + //TODO :- update the key based on oid + keyType = PrivateKeyType.ECDSA; + } + else + throw new Exception("Either the key is invalid or key is not supported"); + + return keyType; + } + } +} diff --git a/templates-v7/csharp/libraries/generichost/HttpSigningToken.mustache b/templates-v7/csharp/libraries/generichost/HttpSigningToken.mustache new file mode 100644 index 000000000..881682e89 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/HttpSigningToken.mustache @@ -0,0 +1,45 @@ +// +{{partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// A token constructed from an HttpSigningConfiguration + /// + {{>visibility}} class HttpSignatureToken : TokenBase + { + private HttpSigningConfiguration _configuration; + + /// + /// Constructs an HttpSignatureToken object. + /// + /// + /// + public HttpSignatureToken(HttpSigningConfiguration configuration, TimeSpan? timeout = null) : base(timeout) + { + _configuration = configuration; + } + + /// + /// Places the token in the header. + /// + /// + /// + /// + public void UseInHeader(global::System.Net.Http.HttpRequestMessage request, string requestBody, CancellationToken cancellationToken = default{{^netstandard20OrLater}}(global::System.Threading.CancellationToken){{/netstandard20OrLater}}) + { + var signedHeaders = _configuration.GetHttpSignedHeader(request, requestBody, cancellationToken); + + foreach (var signedHeader in signedHeaders) + request.Headers.Add(signedHeader.Key, signedHeader.Value); + } + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/IApi.mustache b/templates-v7/csharp/libraries/generichost/IApi.mustache new file mode 100644 index 000000000..a2fc6d1ea --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/IApi.mustache @@ -0,0 +1,13 @@ +namespace {{packageName}}.{{apiPackage}} +{ + /// + /// Interface for interacting with any {{packageName}} API using . + /// + {{>visibility}} interface {{interfacePrefix}}{{packageName}}ApiService + { + /// + /// The object, best practice: instantiate and manage object using the . + /// + System.Net.Http.HttpClient HttpClient { get; } + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/IHostBuilderExtensions.mustache b/templates-v7/csharp/libraries/generichost/IHostBuilderExtensions.mustache new file mode 100644 index 000000000..b3df192b6 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/IHostBuilderExtensions.mustache @@ -0,0 +1,41 @@ +{{>partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using {{packageName}}.{{apiName}}; +using {{packageName}}.{{apiName}}.{{clientPackage}}; + +namespace {{packageName}}.{{apiName}}.Extensions +{ + /// + /// Extension methods for IHostBuilder. + /// + {{>visibility}} static class HostBuilderExtensions + { + /// + /// Add the {{apiName}} API services to the . + /// You can optionally configure the and . + /// + /// . + /// Configures the , , and . + /// Configures the . + /// Configures the . + public static IHostBuilder Configure{{apiName}}(this IHostBuilder hostBuilder, Action hostConfigurationOptions, Action? httpClientOptions = null, Action? httpClientBuilderOptions = null) + { + hostBuilder.ConfigureServices((context, services) => + { + HostConfiguration hostConfiguration = new HostConfiguration(services); + + hostConfigurationOptions(context, services, hostConfiguration); + + hostConfiguration.Add{{apiName}}HttpClients(httpClientOptions, httpClientBuilderOptions); + }); + + return hostBuilder; + } + } +} diff --git a/templates-v7/csharp/libraries/generichost/IHttpClientBuilderExtensions.mustache b/templates-v7/csharp/libraries/generichost/IHttpClientBuilderExtensions.mustache new file mode 100644 index 000000000..053c0226a --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/IHttpClientBuilderExtensions.mustache @@ -0,0 +1,75 @@ +{{>partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using System.Net.Http; +using Microsoft.Extensions.DependencyInjection;{{#supportsRetry}} +using Polly.Timeout; +using Polly.Extensions.Http; +using Polly;{{/supportsRetry}} + +namespace {{packageName}}.Extensions +{ + /// + /// Extension methods for IHttpClientBuilder + /// + {{>visibility}} static class IHttpClientBuilderExtensions + { + {{#supportsRetry}} + /// + /// Adds a Polly retry policy to your clients. + /// + /// + /// + /// + public static IHttpClientBuilder AddRetryPolicy(this IHttpClientBuilder client, int retries) + { + client.AddPolicyHandler(RetryPolicy(retries)); + + return client; + } + + /// + /// Adds a Polly timeout policy to your clients. + /// + /// + /// + /// + public static IHttpClientBuilder AddTimeoutPolicy(this IHttpClientBuilder client, TimeSpan timeout) + { + client.AddPolicyHandler(TimeoutPolicy(timeout)); + + return client; + } + + /// + /// Adds a Polly circuit breaker to your clients. + /// + /// + /// + /// + /// + public static IHttpClientBuilder AddCircuitBreakerPolicy(this IHttpClientBuilder client, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) + { + client.AddTransientHttpErrorPolicy(builder => CircuitBreakerPolicy(builder, handledEventsAllowedBeforeBreaking, durationOfBreak)); + + return client; + } + + private static Polly.Retry.AsyncRetryPolicy RetryPolicy(int retries) + => HttpPolicyExtensions + .HandleTransientHttpError() + .Or() + .RetryAsync(retries); + + private static AsyncTimeoutPolicy TimeoutPolicy(TimeSpan timeout) + => Policy.TimeoutAsync(timeout); + + private static Polly.CircuitBreaker.AsyncCircuitBreakerPolicy CircuitBreakerPolicy( + PolicyBuilder builder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) + => builder.CircuitBreakerAsync(handledEventsAllowedBeforeBreaking, durationOfBreak); + {{/supportsRetry}} + } +} diff --git a/templates-v7/csharp/libraries/generichost/IServiceCollectionExtensions.mustache b/templates-v7/csharp/libraries/generichost/IServiceCollectionExtensions.mustache new file mode 100644 index 000000000..fb32d770a --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/IServiceCollectionExtensions.mustache @@ -0,0 +1,33 @@ +{{>partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using {{packageName}}.{{corePackageName}}.Auth; +using {{packageName}}.{{apiName}}.{{clientPackage}}; + +namespace {{packageName}}.{{apiName}}.Extensions +{ + /// + /// Extension methods for . + /// + {{>visibility}} static class ServiceCollectionExtensions + { + /// + /// Add the {{packageName}} {{apiName}} API services to your . + /// + /// . + /// Configures the . + /// Configures the . + /// Configures the . + public static void Add{{apiName}}Services(this IServiceCollection services, Action hostConfigurationOptions, Action? httpClientOptions = null, Action? httpClientBuilderOptions = null) + { + HostConfiguration hostConfiguration = new{{^net70OrLater}} HostConfiguration{{/net70OrLater}}(services); + hostConfigurationOptions(hostConfiguration); + hostConfiguration.Add{{apiName}}HttpClients(httpClientOptions, httpClientBuilderOptions); + } + } +} diff --git a/templates-v7/csharp/libraries/generichost/ImplementsIEquatable.mustache b/templates-v7/csharp/libraries/generichost/ImplementsIEquatable.mustache new file mode 100644 index 000000000..dd576dd0f --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ImplementsIEquatable.mustache @@ -0,0 +1 @@ +{{#equatable}}{{#readOnlyVars}}{{#-first}}IEquatable<{{classname}}{{nrt?}}> {{/-first}}{{/readOnlyVars}}{{/equatable}} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/ImplementsValidatable.mustache b/templates-v7/csharp/libraries/generichost/ImplementsValidatable.mustache new file mode 100644 index 000000000..7c3f0e02a --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ImplementsValidatable.mustache @@ -0,0 +1 @@ +{{#validatable}}IValidatableObject {{/validatable}} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/JsonConverter.mustache b/templates-v7/csharp/libraries/generichost/JsonConverter.mustache new file mode 100644 index 000000000..391e00e66 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/JsonConverter.mustache @@ -0,0 +1,661 @@ + /// + /// A Json converter for type + /// + {{>visibility}} class {{classname}}JsonConverter : JsonConverter<{{classname}}> + { + {{#allVars}} + {{#isDateTime}} + /// + /// The format to use to serialize {{name}}. + /// + public static string {{name}}Format { get; set; } = "{{{dateTimeFormat}}}"; + + {{/isDateTime}} + {{#isDate}} + /// + /// The format to use to serialize {{name}}. + /// + public static string {{name}}Format { get; set; } = "{{{dateFormat}}}"; + + {{/isDate}} + {{/allVars}} + /// + /// Deserializes json to . + /// + /// . + /// . + /// The , initialized from . + /// . + /// + public override {{classname}} Read(ref Utf8JsonReader utf8JsonReader, Type typeToConvert, JsonSerializerOptions jsonSerializerOptions) + { + {{#lambda.trimTrailingWithNewLine}} + {{#lambda.trimLineBreaks}} + int currentDepth = utf8JsonReader.CurrentDepth; + + if (utf8JsonReader.TokenType != JsonTokenType.StartObject && utf8JsonReader.TokenType != JsonTokenType.StartArray) + throw new JsonException(); + + JsonTokenType startingTokenType = utf8JsonReader.TokenType; + + {{#allVars}} + Option<{{#isInnerEnum}}{{^isMap}}{{classname}}.{{/isMap}}{{/isInnerEnum}}{{{datatypeWithEnum}}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}}> {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = default; + {{#-last}} + + {{/-last}} + {{/allVars}} + {{#discriminator}} + {{#children}} + {{#-first}} + string{{nrt?}} discriminator = ClientUtils.GetDiscriminator(utf8JsonReader, "{{discriminator.propertyBaseName}}"); + + {{/-first}} + if (discriminator != null && discriminator.Equals("{{name}}")) + return JsonSerializer.Deserialize<{{{classname}}}>(ref utf8JsonReader, jsonSerializerOptions) ?? throw new JsonException("The result was an unexpected value."); + + {{/children}} + {{/discriminator}} + {{#model.discriminator}} + {{#model.hasDiscriminatorWithNonEmptyMapping}} + {{#mappedModels}} + {{#model}} + {{^vendorExtensions.x-duplicated-data-type}} + {{classname}}{{nrt?}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}} = null; + {{#-last}} + + {{/-last}} + {{/vendorExtensions.x-duplicated-data-type}} + {{/model}} + {{/mappedModels}} + Utf8JsonReader utf8JsonReaderDiscriminator = utf8JsonReader; + while (utf8JsonReaderDiscriminator.Read()) + { + if (startingTokenType == JsonTokenType.StartObject && utf8JsonReaderDiscriminator.TokenType == JsonTokenType.EndObject && currentDepth == utf8JsonReaderDiscriminator.CurrentDepth) + break; + + if (startingTokenType == JsonTokenType.StartArray && utf8JsonReaderDiscriminator.TokenType == JsonTokenType.EndArray && currentDepth == utf8JsonReaderDiscriminator.CurrentDepth) + break; + + if (utf8JsonReaderDiscriminator.TokenType == JsonTokenType.PropertyName && currentDepth == utf8JsonReaderDiscriminator.CurrentDepth - 1) + { + string{{nrt?}} jsonPropertyName = utf8JsonReaderDiscriminator.GetString(); + utf8JsonReaderDiscriminator.Read(); + if (jsonPropertyName{{nrt?}}.Equals("{{propertyBaseName}}"){{#nrt}} ?? false{{/nrt}}) + { + string{{nrt?}} discriminator = utf8JsonReaderDiscriminator.GetString(); + {{#mappedModels}} + if (discriminator{{nrt?}}.Equals("{{mappingName}}"){{#nrt}} ?? false{{/nrt}}) + { + Utf8JsonReader utf8JsonReader{{model.classname}} = utf8JsonReader; + {{#lambda.camelcase_sanitize_param}}{{model.classname}}{{/lambda.camelcase_sanitize_param}} = JsonSerializer.Deserialize<{{{model.classname}}}>(ref utf8JsonReader{{model.classname}}, jsonSerializerOptions); + } + {{/mappedModels}} + } + } + } + + {{/model.hasDiscriminatorWithNonEmptyMapping}} + {{/model.discriminator}} + {{^model.discriminator}} + {{#composedSchemas}} + {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + {{{datatypeWithEnum}}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = default; + {{#-last}} + + Utf8JsonReader utf8JsonReaderOneOf = utf8JsonReader; + while (utf8JsonReaderOneOf.Read()) + { + if (startingTokenType == JsonTokenType.StartObject && utf8JsonReaderOneOf.TokenType == JsonTokenType.EndObject && currentDepth == utf8JsonReaderOneOf.CurrentDepth) + break; + + if (startingTokenType == JsonTokenType.StartArray && utf8JsonReaderOneOf.TokenType == JsonTokenType.EndArray && currentDepth == utf8JsonReaderOneOf.CurrentDepth) + break; + + if (utf8JsonReaderOneOf.TokenType == JsonTokenType.PropertyName && currentDepth == utf8JsonReaderOneOf.CurrentDepth - 1) + { + {{#oneOf}} + Utf8JsonReader utf8JsonReader{{name}} = utf8JsonReader; + ClientUtils.TryDeserialize<{{{datatypeWithEnum}}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}}>(ref utf8JsonReader{{name}}, jsonSerializerOptions, out {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}); + {{^-last}} + + {{/-last}} + {{/oneOf}} + } + } + {{/-last}} + {{/vendorExtensions.x-duplicated-data-type}} + {{/oneOf}} + + {{#anyOf}} + {{^vendorExtensions.x-duplicated-data-type}} + {{{datatypeWithEnum}}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = default; + {{#-last}} + + Utf8JsonReader utf8JsonReaderAnyOf = utf8JsonReader; + while (utf8JsonReaderAnyOf.Read()) + { + if (startingTokenType == JsonTokenType.StartObject && utf8JsonReaderAnyOf.TokenType == JsonTokenType.EndObject && currentDepth == utf8JsonReaderAnyOf.CurrentDepth) + break; + + if (startingTokenType == JsonTokenType.StartArray && utf8JsonReaderAnyOf.TokenType == JsonTokenType.EndArray && currentDepth == utf8JsonReaderAnyOf.CurrentDepth) + break; + + if (utf8JsonReaderAnyOf.TokenType == JsonTokenType.PropertyName && currentDepth == utf8JsonReaderAnyOf.CurrentDepth - 1) + { + {{#anyOf}} + Utf8JsonReader utf8JsonReader{{name}} = utf8JsonReader; + ClientUtils.TryDeserialize<{{{datatypeWithEnum}}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}}>(ref utf8JsonReader{{name}}, jsonSerializerOptions, out {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}); + {{^-last}} + + {{/-last}} + {{/anyOf}} + } + } + {{/-last}} + {{/vendorExtensions.x-duplicated-data-type}} + {{/anyOf}} + + {{/composedSchemas}} + {{/model.discriminator}} + while (utf8JsonReader.Read()) + { + if (startingTokenType == JsonTokenType.StartObject && utf8JsonReader.TokenType == JsonTokenType.EndObject && currentDepth == utf8JsonReader.CurrentDepth) + break; + + if (startingTokenType == JsonTokenType.StartArray && utf8JsonReader.TokenType == JsonTokenType.EndArray && currentDepth == utf8JsonReader.CurrentDepth) + break; + + if (utf8JsonReader.TokenType == JsonTokenType.PropertyName && currentDepth == utf8JsonReader.CurrentDepth - 1) + { + string{{nrt?}} jsonPropertyName = utf8JsonReader.GetString(); + utf8JsonReader.Read(); + + switch (jsonPropertyName) + { + {{#allVars}} + case "{{baseName}}": + {{#isString}} + {{^isMap}} + {{^isEnum}} + {{^isUuid}} + {{#isDecimal}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.GetDecimal()); + {{/isDecimal}} + {{^isDecimal}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.GetString(){{^isNullable}}{{nrt!}}{{/isNullable}}); + {{/isDecimal}} + {{/isUuid}} + {{/isEnum}} + {{/isMap}} + {{/isString}} + {{#isBoolean}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? (bool?)null : utf8JsonReader.GetBoolean()); + {{/isBoolean}} + {{#isNumeric}} + {{^isEnum}} + {{#isDouble}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? (double?)null : utf8JsonReader.GetDouble()); + {{/isDouble}} + {{#isDecimal}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? (decimal?)null : utf8JsonReader.GetDecimal()); + {{/isDecimal}} + {{#isFloat}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? (float?)null : (float)utf8JsonReader.GetDouble()); + {{/isFloat}} + {{#isLong}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? ({{#vendorExtensions.x-unsigned}}u{{/vendorExtensions.x-unsigned}}long?)null : utf8JsonReader.Get{{#vendorExtensions.x-unsigned}}U{{/vendorExtensions.x-unsigned}}Int64()); + {{/isLong}} + {{^isLong}} + {{^isFloat}} + {{^isDecimal}} + {{^isDouble}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? ({{#vendorExtensions.x-unsigned}}u{{/vendorExtensions.x-unsigned}}int?)null : utf8JsonReader.Get{{#vendorExtensions.x-unsigned}}U{{/vendorExtensions.x-unsigned}}Int32()); + {{/isDouble}} + {{/isDecimal}} + {{/isFloat}} + {{/isLong}} + {{/isEnum}} + {{/isNumeric}} + {{#isDate}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}JsonSerializer.Deserialize<{{#supportsDateOnly}}DateOnly{{/supportsDateOnly}}{{^supportsDateOnly}}DateTime{{/supportsDateOnly}}{{#isNullable}}?{{/isNullable}}>(ref utf8JsonReader, jsonSerializerOptions)); + {{/isDate}} + {{#isDateTime}} + {{! Fix: Added support for DateTimeOffset, when `useDateTimeOffset=true` }} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}JsonSerializer.Deserialize(ref utf8JsonReader, jsonSerializerOptions)); + {{/isDateTime}} + {{#isEnum}} + {{^isMap}} + {{#isNumeric}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? ({{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}}?)null : ({{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}})utf8JsonReader.Get{{#vendorExtensions.x-unsigned}}U{{/vendorExtensions.x-unsigned}}Int32()); + {{/isNumeric}} + {{^isNumeric}} + string{{nrt?}} {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue = utf8JsonReader.GetString(); + {{^isInnerEnum}} + if ({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue != null) + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}{{{datatypeWithEnum}}}ValueConverter.FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue)); + {{/isInnerEnum}} + {{#isInnerEnum}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}{{classname}}.{{{datatypeWithEnum}}}.FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue)); + {{/isInnerEnum}} + {{/isNumeric}} + {{/isMap}} + {{/isEnum}} + {{#isUuid}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? (Guid?)null : utf8JsonReader.GetGuid()); + {{/isUuid}} + {{^isUuid}} + {{^isEnum}} + {{^isString}} + {{^isBoolean}} + {{^isNumeric}} + {{^isDate}} + {{^isDateTime}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}JsonSerializer.Deserialize<{{{datatypeWithEnum}}}>(ref utf8JsonReader, jsonSerializerOptions){{^isNullable}}{{nrt!}}{{/isNullable}}); + {{/isDateTime}} + {{/isDate}} + {{/isNumeric}} + {{/isBoolean}} + {{/isString}} + {{/isEnum}} + {{/isUuid}} + break; + {{/allVars}} + default: + break; + } + } + } + + {{! Required fields }} + {{#allVars}} + {{#required}} + if (!{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}.IsSet) + throw new ArgumentException("Property is required for class {{classname}}.", nameof({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}})); + + {{/required}} + {{/allVars}} + {{^vendorExtensions.x-duplicated-data-type}} + {{#model.discriminator}} + {{#model.hasDiscriminatorWithNonEmptyMapping}} + {{^model.composedSchemas.anyOf}} + {{#mappedModels}} + if ({{#lambda.camelcase_sanitize_param}}{{model.classname}}{{/lambda.camelcase_sanitize_param}} != null) + return new {{classname}}({{#lambda.joinWithComma}}{{#lambda.camelcase_sanitize_param}}{{model.classname}}{{/lambda.camelcase_sanitize_param}}{{#vendorExtensions.x-is-value-type}}{{^isNullable}}.Value{{/isNullable}}{{/vendorExtensions.x-is-value-type}} {{#model.composedSchemas.anyOf}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}{{#vendorExtensions.x-is-value-type}}{{^isNullable}}.Value{{/isNullable}}{{/vendorExtensions.x-is-value-type}} {{/model.composedSchemas.anyOf}}{{#allVars}}{{^isDiscriminator}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}{{#required}}.Value{{nrt!}}{{^isNullable}}{{#vendorExtensions.x-is-value-type}}.Value{{/vendorExtensions.x-is-value-type}}{{/isNullable}}{{/required}} {{/isDiscriminator}}{{/allVars}}{{/lambda.joinWithComma}}); + + {{#-last}} + throw new JsonException(); + {{/-last}} + {{/mappedModels}} + {{/model.composedSchemas.anyOf}} + {{/model.hasDiscriminatorWithNonEmptyMapping}} + {{/model.discriminator}} + {{^composedSchemas.oneOf}} + {{^required}} + {{#model.composedSchemas.anyOf}} + Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}> {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ParsedValue = {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} == null + ? default + : new Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}>({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}); + {{/model.composedSchemas.anyOf}} + {{#-last}} + + {{/-last}} + {{/required}} + return new {{classname}}({{#lambda.joinWithComma}}{{#model.composedSchemas.anyOf}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ParsedValue{{#required}}.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{nrt!}}{{/vendorExtensions.x-is-value-type}}{{/required}} {{/model.composedSchemas.anyOf}}{{#allVars}}{{^isDiscriminator}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}{{#required}}.Value{{nrt!}}{{^isNullable}}{{#vendorExtensions.x-is-value-type}}.Value{{nrt!}}{{/vendorExtensions.x-is-value-type}}{{/isNullable}}{{/required}} {{/isDiscriminator}}{{/allVars}}{{/lambda.joinWithComma}}); + {{/composedSchemas.oneOf}} + {{^model.discriminator}} + {{#composedSchemas}} + {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + if ({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}?.Type != null) + return new {{classname}}({{#lambda.joinWithComma}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}{{#vendorExtensions.x-is-value-type}}.Value{{/vendorExtensions.x-is-value-type}} {{#model.composedSchemas.anyOf}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}{{#vendorExtensions.x-is-value-type}}{{^isNullable}}.Value{{/isNullable}}{{/vendorExtensions.x-is-value-type}} {{/model.composedSchemas.anyOf}}{{#allVars}}{{^isDiscriminator}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}{{#required}}ParsedValue{{/required}} {{/isDiscriminator}}{{/allVars}}{{/lambda.joinWithComma}}); + + {{/vendorExtensions.x-duplicated-data-type}} + {{#-last}} + throw new JsonException(); + {{/-last}} + {{/oneOf}} + {{/composedSchemas}} + {{/model.discriminator}} + {{/vendorExtensions.x-duplicated-data-type}} + {{/lambda.trimLineBreaks}} + {{/lambda.trimTrailingWithNewLine}} + } + + /// + /// Serializes a . + /// + /// + /// + /// + /// + public override void Write(Utf8JsonWriter writer, {{classname}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}, JsonSerializerOptions jsonSerializerOptions) + { + {{#lambda.trimLineBreaks}} + {{#lambda.copyText}} + {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}} + {{/lambda.copyText}} + {{#discriminator}} + {{#children}} + if ({{#lambda.paste}}{{/lambda.paste}} is {{classname}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}){ + JsonSerializer.Serialize<{{{classname}}}>(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}, jsonSerializerOptions); + return; + } + + {{/children}} + {{/discriminator}} + {{! start | support oneOf without discriminator.mapping property - WriteProperties }} + {{^model.discriminator}} + {{#composedSchemas.oneOf}} + if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null) + JsonSerializer.Serialize(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); + {{/composedSchemas.oneOf}} + {{/model.discriminator}} + {{! end | oneOf support without discriminator.mapping property - WriteProperties }} + {{! start | support oneOf without discriminator.mapping property- WriteStartObject }} + {{^model.discriminator}}{{#oneOf}}{{#-first}}/* {{/-first}}{{/oneOf}}{{/model.discriminator}} + writer.WriteStartObject(); + {{^model.discriminator}}{{#oneOf}}{{#-first}} */{{/-first}}{{/oneOf}}{{/model.discriminator}} + {{! end | support oneOf without discriminator.mapping property - WriteStartObject }} + {{#model.discriminator}} + {{#model.hasDiscriminatorWithNonEmptyMapping}} + {{#composedSchemas.oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null) + {{#isPrimitiveType}} + {{#isString}} + writer.WriteString("{{vendorExtensions.x-base-name}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option.Value); + {{/isString}} + {{#isBoolean}} + writer.WriteBoolean("{{vendorExtensions.x-base-name}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option.Value.Value); + {{/isBoolean}} + {{#isNumeric}} + writer.WriteNumber("{{vendorExtensions.x-base-name}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option.Value.Value); + {{/isNumeric}} + {{/isPrimitiveType}} + {{^isPrimitiveType}} + { + {{baseType}}JsonConverter {{#lambda.camelcase_sanitize_param}}{{baseType}}JsonConverter{{/lambda.camelcase_sanitize_param}} = ({{baseType}}JsonConverter) jsonSerializerOptions.Converters.First(c => c.CanConvert({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}.GetType())); + {{#lambda.camelcase_sanitize_param}}{{baseType}}JsonConverter{{/lambda.camelcase_sanitize_param}}.WriteProperties(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); + } + {{/isPrimitiveType}} + + {{/vendorExtensions.x-duplicated-data-type}} + {{/composedSchemas.oneOf}} + {{/model.hasDiscriminatorWithNonEmptyMapping}} + {{/model.discriminator}} + {{^model.discriminator}} + {{#composedSchemas}} + {{#anyOf}} + if ({{#lambda.joinWithAmpersand}}{{^required}}{{#lambda.camelcase_sanitize_param}}{{model.classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option.IsSet {{/required}}{{#lambda.camelcase_sanitize_param}}{{model.classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{/required}} != null{{/lambda.joinWithAmpersand}}) + {{#isPrimitiveType}} + {{#isString}} + writer.WriteString("{{vendorExtensions.x-base-name}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option.Value); + {{/isString}} + {{#isBoolean}} + writer.WriteBoolean("{{vendorExtensions.x-base-name}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option.Value.Value); + {{/isBoolean}} + {{#isNumeric}} + writer.WriteNumber("{{vendorExtensions.x-base-name}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option.Value.Value); + {{/isNumeric}} + {{#isEnum}} + { + {{datatypeWithEnum}}JsonConverter {{#lambda.camelcase}}{{datatypeWithEnum}}{{/lambda.camelcase}}JsonConverter = ({{datatypeWithEnum}}JsonConverter) jsonSerializerOptions.Converters.First(c => c.CanConvert({{#lambda.camelcase_sanitize_param}}{{model.classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{/required}}.GetType())); + {{#lambda.camelcase}}{{datatypeWithEnum}}{{/lambda.camelcase}}JsonConverter.Write{{^isEnumRef}}Properties{{/isEnumRef}}(writer, {{#lambda.camelcase_sanitize_param}}{{model.classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}, jsonSerializerOptions); + } + {{/isEnum}} + {{/isPrimitiveType}} + {{^isPrimitiveType}} + { + {{datatypeWithEnum}}JsonConverter {{#lambda.camelcase}}{{datatypeWithEnum}}{{/lambda.camelcase}}JsonConverter = ({{datatypeWithEnum}}JsonConverter) jsonSerializerOptions.Converters.First(c => c.CanConvert({{#lambda.camelcase_sanitize_param}}{{model.classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{/required}}.GetType())); + {{#lambda.camelcase}}{{datatypeWithEnum}}{{/lambda.camelcase}}JsonConverter.Write{{^isEnumRef}}Properties{{/isEnumRef}}(writer, {{#lambda.camelcase_sanitize_param}}{{model.classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}, jsonSerializerOptions); + } + {{/isPrimitiveType}} + + {{/anyOf}} + {{/composedSchemas}} + {{/model.discriminator}} + WriteProperties(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}, jsonSerializerOptions); + {{! start | support oneOf without discriminator.mapping property- WriteEndObject }} + {{^model.discriminator}}{{#oneOf}}{{#-first}}/* {{/-first}}{{/oneOf}}{{/model.discriminator}} + writer.WriteEndObject(); + {{^model.discriminator}}{{#oneOf}}{{#-first}} */{{/-first}}{{/oneOf}}{{/model.discriminator}} + {{! end | support oneOf without discriminator.mapping property - WriteEndObject }} + {{/lambda.trimLineBreaks}} + } + + /// + /// Serializes the properties of . + /// + /// + /// + /// + /// + public void WriteProperties(Utf8JsonWriter writer, {{classname}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}, JsonSerializerOptions jsonSerializerOptions) + { + {{#lambda.trimTrailingWithNewLine}} + {{#lambda.trimLineBreaks}} + + {{#allVars}} + {{#isDiscriminator}} + {{^model.composedSchemas.anyOf}} + {{^model.composedSchemas.oneOf}} + if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null) + writer.WriteString("{{baseName}}", {{^isEnum}}{{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{/isEnum}}{{#isEnum}}{{#isInnerEnum}}{{classname}}.{{{datatypeWithEnum}}}.ToJsonValue{{/isInnerEnum}}{{^isInnerEnum}}{{{datatypeWithEnum}}}ValueConverter.ToJsonValue{{/isInnerEnum}}({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}{{/required}}){{/isEnum}}); + + {{/model.composedSchemas.oneOf}} + {{/model.composedSchemas.anyOf}} + {{/isDiscriminator}} + {{^isDiscriminator}} + {{#isString}} + {{^isMap}} + {{^isEnum}} + {{^isUuid}} + {{#lambda.copyText}} + {{#isDecimal}} + if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null) + writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}.ToString()); + {{/isDecimal}} + {{^isDecimal}} + if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null) + writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}); + {{/isDecimal}} + {{/lambda.copyText}} + {{#lambda.indent3}} + {{>WriteProperty}}{{! prevent indent}} + {{/lambda.indent3}} + {{/isUuid}} + {{/isEnum}} + {{/isMap}} + {{/isString}} + {{#isBoolean}} + {{#lambda.copyText}} + writer.WriteBoolean("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{^required}}_{{/required}}{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}}); + {{/lambda.copyText}} + {{#lambda.indent3}} + {{>WriteProperty}}{{! prevent indent}} + {{/lambda.indent3}} + {{/isBoolean}} + {{^isEnum}} + {{#isNumeric}} + {{#lambda.copyText}} + writer.WriteNumber("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{^required}}_{{/required}}{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}}); + {{/lambda.copyText}} + {{#lambda.indent3}} + {{>WriteProperty}}{{! prevent indent}} + {{/lambda.indent3}} + {{/isNumeric}} + {{/isEnum}} + {{#isDate}} + {{#lambda.copyText}} + writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{^required}}_{{/required}}{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}}.ToString({{name}}Format)); + {{/lambda.copyText}} + {{#lambda.indent3}} + {{>WriteProperty}}{{! prevent indent}} + {{/lambda.indent3}} + {{/isDate}} + {{#isDateTime}} + {{#lambda.copyText}} + writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{^required}}_{{/required}}{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}}.ToString({{name}}Format)); + {{/lambda.copyText}} + {{#lambda.indent3}} + {{>WriteProperty}}{{! prevent indent}} + {{/lambda.indent3}} + {{/isDateTime}} + {{#isEnum}} + {{#isNumeric}} + {{#lambda.copyText}} + writer.WriteNumber("{{baseName}}", {{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}}.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{^required}}_{{/required}}{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}})); + {{/lambda.copyText}} + {{#lambda.indent3}} + {{>WriteProperty}}{{! prevent indent}} + {{/lambda.indent3}} + {{/isNumeric}} + {{^isMap}} + {{^isNumeric}} + {{#isInnerEnum}} + {{#isNullable}} + var {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue = {{classname}}.{{{datatypeWithEnum}}}.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{^required}}_{{/required}}{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}{{nrt!}}.Value{{/isNullable}}{{/required}}); + if ({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue != null) + writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue); + else + writer.WriteNull("{{baseName}}"); + + {{/isNullable}} + {{^isNullable}} + if ({{^required}}{{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}._{{name}}Option.IsSet && {{/required}}{{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null) {{! - }} + { + string? {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue = {{classname}}.{{{datatypeWithEnum}}}.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{^required}}_{{/required}}{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}{{nrt!}}.Value{{/isNullable}}{{/required}}); + writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue); + } + + {{/isNullable}} + {{/isInnerEnum}} + {{^isInnerEnum}} + {{#lambda.copyText}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} + {{/lambda.copyText}} + {{#required}} + {{#isNullable}} + if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} == null) + writer.WriteNull("{{baseName}}"); + else + { + var {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue = {{{datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}.Value); + {{#allowableValues}} + {{#enumVars}} + {{#-first}} + {{#isString}} + if ({{#lambda.paste}}{{/lambda.paste}}RawValue != null){{! we cant use name here because enumVar also has a name property, so use the paste lambda instead }} + writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{nameInPascalCase}}{{/lambda.camelcase_sanitize_param}}RawValue); + else + writer.WriteNull("{{baseName}}"); + {{/isString}} + {{^isString}} + writer.WriteNumber("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{nameInPascalCase}}{{/lambda.camelcase_sanitize_param}}RawValue); + {{/isString}} + {{/-first}} + {{/enumVars}} + {{/allowableValues}} + } + {{/isNullable}} + {{^isNullable}} + var {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue = {{{datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}); + {{#allowableValues}} + {{#enumVars}} + {{#-first}} + {{^isNumeric}} + writer.WriteString("{{baseName}}", {{#lambda.paste}}{{/lambda.paste}}RawValue); + {{/isNumeric}} + {{#isNumeric}} + writer.WriteNumber("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{#lambda.paste}}{{/lambda.paste}}{{/lambda.camelcase_sanitize_param}}RawValue); + {{/isNumeric}} + {{/-first}} + {{/enumVars}} + {{/allowableValues}} + {{/isNullable}} + + {{/required}} + {{^required}} + if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}._{{name}}Option.IsSet) + {{#isNullable}} + if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}._{{name}}Option{{nrt!}}.Value != null) + { + var {{#lambda.paste}}{{/lambda.paste}}RawValue = {{{datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}._{{name}}Option.Value{{nrt!}}.Value); + writer.{{#lambda.first}}{{#allowableValues}}{{#enumVars}}{{^isNumeric}}WriteString {{/isNumeric}}{{#isNumeric}}WriteNumber {{/isNumeric}}{{/enumVars}}{{/allowableValues}}{{/lambda.first}}("{{baseName}}", {{#lambda.paste}}{{/lambda.paste}}RawValue); + } + else + writer.WriteNull("{{baseName}}"); + {{/isNullable}} + {{^isNullable}} + { + var {{#lambda.paste}}{{/lambda.paste}}RawValue = {{{datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{nrt!}}.Value); + writer.{{#lambda.first}}{{#allowableValues}}{{#enumVars}}{{^isNumeric}}WriteString {{/isNumeric}}{{#isNumeric}}WriteNumber {{/isNumeric}}{{/enumVars}}{{/allowableValues}}{{/lambda.first}}("{{baseName}}", {{#lambda.paste}}{{/lambda.paste}}RawValue); + } + {{/isNullable}} + {{/required}} + {{/isInnerEnum}} + {{/isNumeric}} + {{/isMap}} + {{/isEnum}} + {{#isUuid}} + {{#lambda.copyText}} + writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{^required}}_{{/required}}{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}}); + {{/lambda.copyText}} + {{#lambda.indent3}} + {{>WriteProperty}}{{! prevent indent}} + {{/lambda.indent3}} + {{/isUuid}} + {{^isUuid}} + {{^isEnum}} + {{^isString}} + {{^isBoolean}} + {{^isNumeric}} + {{^isDate}} + {{^isDateTime}} + {{#required}} + {{#isNullable}} + if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null) + { + writer.WritePropertyName("{{baseName}}"); + JsonSerializer.Serialize(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); + } + else + writer.WriteNull("{{baseName}}"); + {{/isNullable}} + {{^isNullable}} + writer.WritePropertyName("{{baseName}}"); + JsonSerializer.Serialize(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); + {{/isNullable}} + {{/required}} + {{^required}} + if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}._{{name}}Option.IsSet) + {{#isNullable}} + if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}._{{name}}Option.Value != null) + { + writer.WritePropertyName("{{baseName}}"); + JsonSerializer.Serialize(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); + } + else + writer.WriteNull("{{baseName}}"); + {{/isNullable}} + {{^isNullable}} + { + writer.WritePropertyName("{{baseName}}"); + JsonSerializer.Serialize(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); + } + {{/isNullable}} + {{/required}} + {{/isDateTime}} + {{/isDate}} + {{/isNumeric}} + {{/isBoolean}} + {{/isString}} + {{/isEnum}} + {{/isUuid}} + {{/isDiscriminator}} + {{/allVars}} + {{/lambda.trimLineBreaks}} + {{/lambda.trimTrailingWithNewLine}} + } + } \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/JsonSerializerOptionsProvider.mustache b/templates-v7/csharp/libraries/generichost/JsonSerializerOptionsProvider.mustache new file mode 100644 index 000000000..7e5cf9cb8 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/JsonSerializerOptionsProvider.mustache @@ -0,0 +1,29 @@ +// +{{partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System.Text.Json; + +namespace {{packageName}}.{{apiName}}.{{clientPackage}} +{ + /// + /// Provides the JsonSerializerOptions. + /// + {{>visibility}} class JsonSerializerOptionsProvider + { + /// + /// The JsonSerializerOptions. + /// + public JsonSerializerOptions Options { get; } + + /// + /// Instantiates a JsonSerializerOptionsProvider to access the JsonSerializerOptions. + /// + public JsonSerializerOptionsProvider(JsonSerializerOptions options) + { + Options = options; + } + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/ModelBaseSignature.mustache b/templates-v7/csharp/libraries/generichost/ModelBaseSignature.mustache new file mode 100644 index 000000000..909a68e35 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ModelBaseSignature.mustache @@ -0,0 +1 @@ +{{#parentModel.composedSchemas.anyOf}}{{#lambda.camelcase_sanitize_param}}{{parent}}{{/lambda.camelcase_sanitize_param}}.{{#lambda.titlecase}}{{baseType}}{{#isArray}}{{{dataFormat}}}{{/isArray}}{{/lambda.titlecase}} {{/parentModel.composedSchemas.anyOf}}{{#allVars}}{{^isDiscriminator}}{{#isInherited}}{{^isNew}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}{{/isNew}}{{#isNew}}{{#isEnum}}{{#isInnerEnum}}{{classname}}.{{{datatypeWithEnum}}}ToJsonValue{{/isInnerEnum}}{{^isInnerEnum}}{{{datatypeWithEnum}}}ValueConverter.ToJsonValue{{/isInnerEnum}}({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}{{^required}}.Value{{/required}}){{/isEnum}}{{^isEnum}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}.ToString(){{/isEnum}}{{/isNew}} {{/isInherited}}{{/isDiscriminator}}{{/allVars}} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/ModelSignature.mustache b/templates-v7/csharp/libraries/generichost/ModelSignature.mustache new file mode 100644 index 000000000..09a41b278 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ModelSignature.mustache @@ -0,0 +1 @@ +{{#model.allVars}}{{^isDiscriminator}}{{^required}}Option<{{/required}}{{{datatypeWithEnum}}}{{>NullConditionalProperty}}{{^required}}>{{/required}} {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}{{#defaultValue}}{{^isEnum}} = {{^required}}default{{/required}}{{#required}}{{^isDateTime}}{{#isString}}{{^isEnum}}@{{/isEnum}}{{/isString}}{{{.}}}{{/isDateTime}}{{#isDateTime}}default{{/isDateTime}}{{/required}}{{/isEnum}}{{#isEnum}} = default{{/isEnum}}{{/defaultValue}}{{^defaultValue}}{{#lambda.first}}{{#isNullable}} = default {{/isNullable}}{{^required}} = default {{/required}}{{/lambda.first}}{{/defaultValue}} {{/isDiscriminator}}{{/model.allVars}} diff --git a/templates-v7/csharp/libraries/generichost/OAuthToken.mustache b/templates-v7/csharp/libraries/generichost/OAuthToken.mustache new file mode 100644 index 000000000..23b3cab91 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/OAuthToken.mustache @@ -0,0 +1,41 @@ +// +{{partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// A token constructed with OAuth. + /// + {{>visibility}} class OAuthToken : TokenBase + { + private string _raw; + + /// + /// Consturcts an OAuthToken object. + /// + /// + /// + public OAuthToken(string value, TimeSpan? timeout = null) : base(timeout) + { + _raw = value; + } + + /// + /// Places the token in the header. + /// + /// + /// + public virtual void UseInHeader(global::System.Net.Http.HttpRequestMessage request, string headerName) + { + request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _raw); + } + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/OnDeserializationError.mustache b/templates-v7/csharp/libraries/generichost/OnDeserializationError.mustache new file mode 100644 index 000000000..ff83a5076 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/OnDeserializationError.mustache @@ -0,0 +1,2 @@ +if (!suppressDefaultLog) + Logger.LogError(exception, "An error occurred while deserializing the {code} response.", httpStatusCode); \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/OperationSignature.mustache b/templates-v7/csharp/libraries/generichost/OperationSignature.mustache new file mode 100644 index 000000000..049717717 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/OperationSignature.mustache @@ -0,0 +1 @@ +{{#pathParams}}{{{dataType}}}{{>NullConditionalParameter}} {{paramName}}, {{/pathParams}}{{#queryParams}}{{#required}}{{{dataType}}}{{>NullConditionalParameter}} {{paramName}}, {{/required}}{{/queryParams}}{{#bodyParams}}{{{dataType}}}{{>NullConditionalParameter}} {{paramName}}, {{/bodyParams}}{{#queryParams}}{{^required}}Option<{{{dataType}}}{{>NullConditionalParameter}}> {{paramName}}{{#notRequiredOrIsNullable}} = default{{/notRequiredOrIsNullable}}, {{/required}}{{/queryParams}} RequestOptions? requestOptions = default, System.Threading.CancellationToken cancellationToken = default{{^netstandard20OrLater}}(global::System.Threading.CancellationToken){{/netstandard20OrLater}} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/Option.mustache b/templates-v7/csharp/libraries/generichost/Option.mustache new file mode 100644 index 000000000..8b33bdb51 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/Option.mustache @@ -0,0 +1,48 @@ +// +{{>partial_header}} + +{{#nrt}} +#nullable enable + +{{/nrt}} + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// A wrapper for operation parameters which are not required + /// + public struct Option + { + /// + /// The value to send to the server + /// + public TType Value { get; } + + /// + /// When true the value will be sent to the server + /// + internal bool IsSet { get; } + + /// + /// A wrapper for operation parameters which are not required + /// + /// + public Option(TType value) + { + IsSet = true; + Value = value; + } + + /// + /// Implicitly converts this option to the contained type + /// + /// + public static implicit operator TType(Option option) => option.Value; + + /// + /// Implicitly converts the provided value to an Option + /// + /// + public static implicit operator Option(TType value) => new Option(value); + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/OptionProperty.mustache b/templates-v7/csharp/libraries/generichost/OptionProperty.mustache new file mode 100644 index 000000000..d75041887 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/OptionProperty.mustache @@ -0,0 +1 @@ +new Option<{{#isInnerEnum}}{{^isMap}}{{classname}}.{{/isMap}}{{/isInnerEnum}}{{{datatypeWithEnum}}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}}>( \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/README.client.mustache b/templates-v7/csharp/libraries/generichost/README.client.mustache new file mode 100644 index 000000000..247cbe399 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/README.client.mustache @@ -0,0 +1,140 @@ +# Created with Openapi Generator + + +## Creating the library +Create a config.yaml file similar to what is below, then run the following powershell command to generate the library `java -jar "/openapi-generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar" generate -c config.yaml` + +```yaml +generatorName: csharp +inputSpec: {{inputSpec}} +outputDir: out + +# https://openapi-generator.tech/docs/generators/csharp +additionalProperties: + packageGuid: '{{packageGuid}}' + +# https://openapi-generator.tech/docs/integrations/#github-integration +# gitHost: +# gitUserId: +# gitRepoId: + +# https://openapi-generator.tech/docs/globals +# globalProperties: + +# https://openapi-generator.tech/docs/customization/#inline-schema-naming +# inlineSchemaOptions: + +# https://openapi-generator.tech/docs/customization/#name-mapping +# modelNameMappings: +# nameMappings: + +# https://openapi-generator.tech/docs/customization/#openapi-normalizer +# openapiNormalizer: + +# templateDir: https://openapi-generator.tech/docs/templating/#modifying-templates + +# releaseNote: +``` + + +## Using the library in your project + +```cs +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using {{packageName}}.Api; +using {{packageName}}.Client; +using {{packageName}}.Model; +using Org.OpenAPITools.Extensions; + +namespace YourProject +{ + public class Program + { + public static async Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + {{#apiInfo}} + {{#apis}} + {{#-first}} + var api = host.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); + {{#operations}} + {{#-first}} + {{#operation}} + {{#-first}} + {{interfacePrefix}}{{operationId}}ApiResponse apiResponse = await api.{{operationId}}Async("todo"); + {{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}object{{/returnType}}{{nrt?}} model = apiResponse.Ok(); + {{/-first}} + {{/operation}} + {{/-first}} + {{/operations}} + {{/-first}} + {{/apis}} + {{/apiInfo}} + } + + public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) + .Configure{{apiName}}((context, services, options) => + { + {{#authMethods}} + {{#-first}} + // The type of token here depends on the api security specifications + // Available token types are ApiKeyToken, BearerToken, HttpSigningToken, and OAuthToken. + BearerToken token = new(""); + options.AddTokens(token); + + // optionally choose the method the tokens will be provided with, default is RateLimitProvider + options.UseProvider, BearerToken>(); + + {{/-first}} + {{/authMethods}} + options.ConfigureJsonOptions((jsonOptions) => + { + // your custom converters if any + }); + + options.Add{{apiName}}HttpClients(client => + { + // client configuration + }, builder => + { + builder + .AddRetryPolicy(2) + .AddTimeoutPolicy(TimeSpan.FromSeconds(5)) + .AddCircuitBreakerPolicy(10, TimeSpan.FromSeconds(30)); + // add whatever middleware you prefer + } + ); + }); + } +} +``` + +## Questions + +- What about HttpRequest failures and retries? + Configure Polly in the IHttpClientBuilder +- How are tokens used? + Tokens are provided by a TokenProvider class. The default is RateLimitProvider which will perform client side rate limiting. + Other providers can be used with the UseProvider method. +- Does an HttpRequest throw an error when the server response is not Ok? + It depends how you made the request. If the return type is ApiResponse no error will be thrown, though the Content property will be null. + StatusCode and ReasonPhrase will contain information about the error. + If the return type is T, then it will throw. If the return type is TOrDefault, it will return null. +- How do I validate requests and process responses? + Use the provided On and After partial methods in the api classes. + +## Api Information +- appName: {{appName}} +- appVersion: {{appVersion}} +- appDescription: {{appDescription}} + +## Build +This C# SDK is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project. + +- SDK version: {{packageVersion}} +{{^hideGenerationTimestamp}} +- Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} +- Generator version: {{generatorVersion}} +- Build package: {{generatorClass}} diff --git a/templates-v7/csharp/libraries/generichost/SourceGenerationContext.mustache b/templates-v7/csharp/libraries/generichost/SourceGenerationContext.mustache new file mode 100644 index 000000000..b1798c98e --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/SourceGenerationContext.mustache @@ -0,0 +1,9 @@ +{{#useSourceGeneration}} + + /// + /// The {{classname}}SerializationContext + /// + [JsonSourceGenerationOptions(WriteIndented = true, GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof({{classname}}))] + {{>visibility}} partial class {{classname}}SerializationContext : JsonSerializerContext { } +{{/useSourceGeneration}} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/TokenBase.mustache b/templates-v7/csharp/libraries/generichost/TokenBase.mustache new file mode 100644 index 000000000..05f06b4a7 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/TokenBase.mustache @@ -0,0 +1,73 @@ +// +{{partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// The base for all tokens. + /// + {{>visibility}} abstract class TokenBase + { + private DateTime _nextAvailable = DateTime.UtcNow; + private object _nextAvailableLock = new object(); + private readonly System.Timers.Timer _timer = new System.Timers.Timer(); + + + internal TimeSpan? Timeout { get; set; } + internal delegate void TokenBecameAvailableEventHandler(object sender); + internal event TokenBecameAvailableEventHandler{{nrt?}} TokenBecameAvailable; + + + /// + /// Initialize a TokenBase object. + /// + /// + internal TokenBase(TimeSpan? timeout = null) + { + Timeout = timeout; + + if (Timeout != null) + StartTimer(Timeout.Value); + } + + + /// + /// Starts the token's timer + /// + /// + internal void StartTimer(TimeSpan timeout) + { + Timeout = timeout; + _timer.Interval = Timeout.Value.TotalMilliseconds; + _timer.Elapsed += OnTimer; + _timer.AutoReset = true; + _timer.Start(); + } + + /// + /// Returns true while the token is rate limited. + /// + public bool IsRateLimited => _nextAvailable > DateTime.UtcNow; + + /// + /// Triggered when the server returns status code TooManyRequests + /// Once triggered the local timeout will be extended an arbitrary length of time. + /// + public void BeginRateLimit() + { + lock(_nextAvailableLock) + _nextAvailable = DateTime.UtcNow.AddSeconds(5); + } + + private void OnTimer(object{{nrt?}} sender, System.Timers.ElapsedEventArgs e) + { + if (TokenBecameAvailable != null && !IsRateLimited) + TokenBecameAvailable.Invoke(this); + } + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/TokenContainer.mustache b/templates-v7/csharp/libraries/generichost/TokenContainer.mustache new file mode 100644 index 000000000..24c84a551 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/TokenContainer.mustache @@ -0,0 +1,39 @@ +// +{{partial_header}} +{{#nrt}} +#nullable enable + +{{/nrt}} +using System.Linq; +using System.Collections.Generic; + +namespace {{packageName}}.{{clientPackage}} +{ + /// + /// A container for a collection of tokens. + /// + /// + {{>visibility}} sealed class TokenContainer where TTokenBase : TokenBase + { + /// + /// The collection of tokens + /// + public List Tokens { get; } = new List(); + + /// + /// Instantiates a TokenContainer + /// + public TokenContainer() + { + } + + /// + /// Instantiates a TokenContainer + /// + /// + public TokenContainer(global::System.Collections.Generic.IEnumerable tokens) + { + Tokens = tokens.ToList(); + } + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/TokenProvider.mustache b/templates-v7/csharp/libraries/generichost/TokenProvider.mustache new file mode 100644 index 000000000..9fc5f3ea0 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/TokenProvider.mustache @@ -0,0 +1,39 @@ +// +{{>partial_header}} + +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using System.Linq; +using System.Collections.Generic; +using {{packageName}}.{{clientPackage}}; + +namespace {{packageName}} +{ + /// + /// A class which will provide tokens. + /// + {{>visibility}} abstract class TokenProvider where TTokenBase : TokenBase + { + /// + /// The array of tokens. + /// + protected TTokenBase[] _tokens; + + internal abstract System.Threading.Tasks.ValueTask GetAsync(string header = "", System.Threading.CancellationToken cancellation = default{{^netstandard20OrLater}}(global::System.Threading.CancellationToken){{/netstandard20OrLater}}); + + /// + /// Instantiates a TokenProvider. + /// + /// + public TokenProvider(IEnumerable tokens) + { + _tokens = tokens.ToArray(); + + if (_tokens.Length == 0) + throw new ArgumentException("You did not provide any tokens."); + } + } +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/ValidateRegex.mustache b/templates-v7/csharp/libraries/generichost/ValidateRegex.mustache new file mode 100644 index 000000000..8918b8cf4 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/ValidateRegex.mustache @@ -0,0 +1,7 @@ +// {{{name}}} ({{{dataType}}}) pattern +Regex regex{{{name}}} = new Regex(@"{{{vendorExtensions.x-regex}}}"{{#vendorExtensions.x-modifiers}}{{#-first}}, {{/-first}}RegexOptions.{{{.}}}{{^-last}} | {{/-last}}{{/vendorExtensions.x-modifiers}}); +{{#lambda.copy}}this.{{{name}}}{{#useGenericHost}}{{^required}}Option.Value{{/required}}{{/useGenericHost}} != null && {{/lambda.copy}} +if ({{#lambda.first}}{{^required}}{{#lambda.paste}}{{/lambda.paste}} {{/required}}{{#isNullable}}{{#lambda.paste}}{{/lambda.paste}}{{/isNullable}}{{/lambda.first}}!regex{{{name}}}.Match(this.{{{name}}}{{#useGenericHost}}{{^required}}Option.Value{{/required}}{{/useGenericHost}}{{#isUuid}}.ToString(){{#lambda.first}}{{^required}}{{nrt!}} {{/required}}{{#isNullable}}! {{/isNullable}}{{/lambda.first}}{{/isUuid}}).Success) +{ + yield return new System.ComponentModel.DataAnnotations.ValidationResult("Invalid value for {{{name}}}, must match a pattern of " + regex{{{name}}}, new [] { "{{{name}}}" }); +} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/WebhookHandler.mustache b/templates-v7/csharp/libraries/generichost/WebhookHandler.mustache new file mode 100644 index 000000000..3a35bdd49 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/WebhookHandler.mustache @@ -0,0 +1,88 @@ +{{#lambda.trimLineBreaks}} +// +{{>partial_header}} + +{{#nrt}} +#nullable enable + +{{/nrt}} +using Microsoft.Extensions.Logging; +using System.Text.Json; +using {{packageName}}.{{corePackageName}}.Auth; +using {{packageName}}.{{apiName}}.Client; +using {{packageName}}.{{modelPackage}}; + +namespace {{packageName}}.{{apiName}}.Handlers +{ + /// + /// Interface for deserializing {{apiName}} webhooks or verify its HMAC signature. + /// + {{>visibility}} interface {{interfacePrefix}}{{apiName}}Handler + { + /// + /// Returns the to deserialize the json payload from the webhook. This is initialized in the . + /// + {{packageName}}.{{apiName}}.{{clientPackage}}.JsonSerializerOptionsProvider JsonSerializerOptionsProvider { get; } + {{#models}} + {{#model.vendorExtensions.x-webhook-root}} + + /// + /// Uses to attempt to deserialize . + /// + /// The full webhook payload. + /// + {{model.name}}? Deserialize{{model.name}}(string json); + {{/model.vendorExtensions.x-webhook-root}} + {{/models}} + + /// + /// Verifies the HMAC signature of the webhook json payload. + /// + /// The full webhook payload. + /// The signature from the webhook. + /// True if the HMAC signature is valid + bool IsValidHmacSignature(string json, string hmacSignature); + } + + /// + /// Handler utility function used to deserialize {{apiName}} or verify the HMAC signature of the webhook. + /// + {{>visibility}} partial class {{apiName}}Handler : {{interfacePrefix}}{{apiName}}Handler + { + /// + /// The `ADYEN_HMAC_KEY` configured in . + /// + private readonly string? _adyenHmacKey; + + /// + public {{packageName}}.{{apiName}}.{{clientPackage}}.JsonSerializerOptionsProvider JsonSerializerOptionsProvider { get; } + + /// + /// Initializes the handler utility for deserializing {{apiName}} or verify its HMAC signature. + /// + /// . + /// which contains the HMACKey configured in . + public {{apiName}}Handler({{packageName}}.{{apiName}}.{{clientPackage}}.JsonSerializerOptionsProvider jsonSerializerOptionsProvider, ITokenProvider hmacKeyProvider = null) + { + JsonSerializerOptionsProvider = jsonSerializerOptionsProvider; + } + + {{#models}} + {{#model.vendorExtensions.x-webhook-root}} + /// + public {{model.name}}? Deserialize{{model.name}}(string json) + { + return JsonSerializer.Deserialize<{{model.name}}>(json, JsonSerializerOptionsProvider.Options); + } + + {{/model.vendorExtensions.x-webhook-root}} + {{/models}} + + /// + public bool IsValidHmacSignature(string json, string hmacSignature) + { + return {{packageName}}.{{corePackageName}}.Utilities.HmacValidatorUtility.IsHmacSignatureValid(hmacSignature, _adyenHmacKey, json); + } + } +} +{{/lambda.trimLineBreaks}} diff --git a/templates-v7/csharp/libraries/generichost/WriteProperty.mustache b/templates-v7/csharp/libraries/generichost/WriteProperty.mustache new file mode 100644 index 000000000..8fe669906 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/WriteProperty.mustache @@ -0,0 +1,10 @@ +{{#required}} +{{>WritePropertyHelper}} + +{{/required}} +{{^required}} +if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}._{{name}}Option.IsSet) + {{#lambda.indent1}} + {{>WritePropertyHelper}}{{! prevent indent}} + {{/lambda.indent1}} +{{/required}} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/WritePropertyHelper.mustache b/templates-v7/csharp/libraries/generichost/WritePropertyHelper.mustache new file mode 100644 index 000000000..04270093a --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/WritePropertyHelper.mustache @@ -0,0 +1,10 @@ +{{#isNullable}} +if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{^required}}_{{/required}}{{name}}{{^required}}Option.Value{{/required}} != null) + {{#lambda.paste}}{{/lambda.paste}} +else + writer.WriteNull("{{baseName}}"); +{{/isNullable}} +{{^isNullable}} +{{#lambda.paste}} +{{/lambda.paste}} +{{/isNullable}} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/api.mustache b/templates-v7/csharp/libraries/generichost/api.mustache new file mode 100644 index 000000000..67264bf5a --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/api.mustache @@ -0,0 +1,653 @@ +{{#lambda.trimLineBreaks}} +// +{{>partial_header}} + +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using System.Collections.Generic; +{{#net80OrLater}} +{{#lambda.uniqueLines}} +{{#operations}} +{{#operation}} +{{#vendorExtensions.x-set-cookie}} +using System.Linq; +{{/vendorExtensions.x-set-cookie}} +{{/operation}} +{{/operations}} +{{/lambda.uniqueLines}} +{{/net80OrLater}} +using System.Net; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.Json; +using {{packageName}}.{{corePackageName}}; +using {{packageName}}.{{corePackageName}}.Auth; +using {{packageName}}.{{corePackageName}}.Client; +using {{packageName}}.{{corePackageName}}.Client.Extensions; +using {{packageName}}.{{apiName}}.{{clientPackage}}; +{{#hasImport}} +using {{packageName}}.{{modelPackage}}; +{{/hasImport}} +{{^netStandard}} +using System.Diagnostics.CodeAnalysis; +{{/netStandard}} + +namespace {{packageName}}.{{apiPackage}} +{ + {{#operations}} + /// + /// Represents a collection of functions to interact with the API endpoints. + /// This class is registered as transient. + /// + {{>visibility}} interface {{interfacePrefix}}{{classname}} : {{interfacePrefix}}{{packageName}}ApiService + { + /// + /// The class containing the events. + /// + {{classname}}Events Events { get; } + + {{#operation}} + /// + /// {{summary}} + /// + /// + /// {{notes}} + /// + /// Thrown when fails to make API call. + {{#allParams}} + /// {{{description}}}{{#isHeaderParam}} - Pass this header parameter using .{{/isHeaderParam}}{{#isDeprecated}} (deprecated){{/isDeprecated}} + {{/allParams}} + /// . + /// . + /// of . + {{#isDeprecated}} + [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] + {{/isDeprecated}} + Task<{{interfacePrefix}}{{operationId}}ApiResponse> {{operationId}}Async({{>OperationSignature}}); + + {{/operation}} + } + {{#operation}} + {{^vendorExtensions.x-duplicates}} + {{#responses}} + {{#-first}} + + /// + /// The , wraps . + /// + {{>visibility}} interface {{interfacePrefix}}{{operationId}}ApiResponse : {{#lambda.joinWithComma}}{{packageName}}.{{corePackageName}}.{{clientPackage}}.{{interfacePrefix}}ApiResponse {{#responses}}{{#dataType}}{{interfacePrefix}}{{vendorExtensions.x-http-status}}<{{#isModel}}{{^containerType}}{{packageName}}.{{modelPackage}}.{{/containerType}}{{/isModel}}{{{dataType}}}{{#nrt}}?{{/nrt}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}}> {{/dataType}}{{/responses}}{{/lambda.joinWithComma}} + { + {{#responses}} + {{#vendorExtensions.x-http-status-is-default}} + /// + /// Returns true if the response is the default response type. + /// + /// + bool Is{{vendorExtensions.x-http-status}} { get; } + {{/vendorExtensions.x-http-status-is-default}} + {{^vendorExtensions.x-http-status-is-default}} + /// + /// Returns true if the response is {{code}} {{vendorExtensions.x-http-status}}. + /// + /// + bool Is{{vendorExtensions.x-http-status}} { get; } + {{/vendorExtensions.x-http-status-is-default}} + {{^-last}} + + {{/-last}} + {{/responses}} + } + {{/-first}} + {{/responses}} + {{/vendorExtensions.x-duplicates}} + {{/operation}} + + /// + /// Represents a collection of functions to interact with the API endpoints. + /// + {{>visibility}} class {{classname}}Events + { + {{#lambda.trimTrailingWithNewLine}} + {{#operation}} + /// + /// The event raised after the server response. + /// + public event EventHandler{{nrt?}} On{{operationId}}; + + /// + /// The event raised after an error querying the server. + /// + public event EventHandler{{nrt?}} OnError{{operationId}}; + + internal void ExecuteOn{{operationId}}({{#vendorExtensions.x-duplicates}}{{.}}{{/vendorExtensions.x-duplicates}}{{^vendorExtensions.x-duplicates}}{{classname}}{{/vendorExtensions.x-duplicates}}.{{operationId}}ApiResponse apiResponse) + { + On{{operationId}}?.Invoke(this, new ApiResponseEventArgs(apiResponse)); + } + + internal void ExecuteOnError{{operationId}}(Exception exception) + { + OnError{{operationId}}?.Invoke(this, new ExceptionEventArgs(exception)); + } + + {{/operation}} + {{/lambda.trimTrailingWithNewLine}} + } + + /// + /// Represents a collection of functions to interact with the API endpoints. + /// + {{>visibility}} sealed partial class {{classname}} : {{interfacePrefix}}{{classname}} + { + private JsonSerializerOptions _jsonSerializerOptions; + + /// + /// The logger factory. + /// + public ILoggerFactory LoggerFactory { get; } + + /// + /// The logger. + /// + public ILogger<{{classname}}> Logger { get; } + + /// + /// The HttpClient. + /// + public System.Net.Http.HttpClient HttpClient { get; } + + /// + /// The class containing the events. + /// + public {{classname}}Events Events { get; }{{#hasApiKeyMethods}} + + /// + /// A token provider of type . + /// + public ITokenProvider ApiKeyProvider { get; }{{/hasApiKeyMethods}}{{#hasHttpBearerMethods}} + + /// + /// A token provider of type . + /// + public ITokenProvider BearerTokenProvider { get; }{{/hasHttpBearerMethods}}{{#hasHttpSignatureMethods}} + + /// + /// A token provider of type . + /// + public ITokenProvider HttpSignatureTokenProvider { get; }{{/hasHttpSignatureMethods}}{{#hasOAuthMethods}} + + /// + /// A token provider of type . + /// + public ITokenProvider OauthTokenProvider { get; }{{/hasOAuthMethods}} + + {{#net80OrLater}} + {{#lambda.unique}} + {{#operation}} + {{#vendorExtensions.x-set-cookie}} + /// + /// The token cookie container. + /// + public {{packageName}}.{{corePackageName}}.Auth.CookieContainer CookieContainer { get; } + + {{/vendorExtensions.x-set-cookie}} + {{/operation}} + {{/lambda.unique}} + {{/net80OrLater}} + /// + /// Initializes a new instance of the class. + /// + public {{classname}}(ILogger<{{classname}}> logger, ILoggerFactory loggerFactory, System.Net.Http.HttpClient httpClient, JsonSerializerOptionsProvider jsonSerializerOptionsProvider, {{classname}}Events {{#lambda.camelcase_sanitize_param}}{{classname}}Events{{/lambda.camelcase_sanitize_param}}{{#hasApiKeyMethods}}, + ITokenProvider apiKeyProvider{{/hasApiKeyMethods}}{{#hasHttpBearerMethods}}, + ITokenProvider bearerTokenProvider{{/hasHttpBearerMethods}}{{#hasHttpSignatureMethods}}, + ITokenProvider httpSignatureTokenProvider{{/hasHttpSignatureMethods}}{{#hasOAuthMethods}}, + ITokenProvider oauthTokenProvider{{/hasOAuthMethods}}{{#net80OrLater}}{{#operation}}{{#lambda.uniqueLines}}{{#vendorExtensions.x-set-cookie}}, + {{packageName}}.{{clientPackage}}.CookieContainer cookieContainer{{/vendorExtensions.x-set-cookie}}{{/lambda.uniqueLines}}{{/operation}}{{/net80OrLater}}) + { + _jsonSerializerOptions = jsonSerializerOptionsProvider.Options; + LoggerFactory = loggerFactory; + Logger = logger == null ? LoggerFactory.CreateLogger<{{classname}}>() : logger; + HttpClient = httpClient; + Events = {{#lambda.camelcase_sanitize_param}}{{classname}}Events{{/lambda.camelcase_sanitize_param}};{{#hasApiKeyMethods}} + ApiKeyProvider = apiKeyProvider;{{/hasApiKeyMethods}}{{#hasHttpBearerMethods}} + BearerTokenProvider = bearerTokenProvider;{{/hasHttpBearerMethods}}{{#hasHttpSignatureMethods}} + HttpSignatureTokenProvider = httpSignatureTokenProvider;{{/hasHttpSignatureMethods}}{{#hasOAuthMethods}} + OauthTokenProvider = oauthTokenProvider;{{/hasOAuthMethods}}{{#net80OrLater}}{{#operation}}{{#lambda.uniqueLines}}{{#vendorExtensions.x-set-cookie}} + CookieContainer = cookieContainer;{{/vendorExtensions.x-set-cookie}}{{/lambda.uniqueLines}}{{/operation}}{{/net80OrLater}} + } + + {{#operation}} + /// + /// {{summary}} {{notes}} + /// + /// Thrown when fails to make API call. + {{#allParams}} + /// {{{description}}}{{#isBodyParam}}{{/isBodyParam}}{{^required}} ({{#defaultValue}}default to {{.}}{{/defaultValue}}){{/required}}{{#isHeaderParam}} Pass this header parameter in .{{/isHeaderParam}} + {{/allParams}} + /// . + /// . + /// of - If 200 OK response, wraps the when `TryDeserializeOk(...)` is called. + public async Task<{{interfacePrefix}}{{operationId}}ApiResponse> {{operationId}}Async({{>OperationSignature}}) + { + {{#lambda.trimLineBreaks}} + UriBuilder uriBuilder = new UriBuilder(); + + try + { + using (HttpRequestMessage httpRequestMessage = new HttpRequestMessage()) + { + {{^servers}} + uriBuilder.Host = HttpClient.BaseAddress{{nrt!}}.Host; + uriBuilder.Port = HttpClient.BaseAddress.Port; + uriBuilder.Scheme = HttpClient.BaseAddress.Scheme; + uriBuilder.Path = HttpClient.BaseAddress.AbsolutePath == "/" + ? "{{{path}}}" + : string.Concat(HttpClient.BaseAddress.AbsolutePath, "{{{path}}}"); + {{/servers}} + {{#servers}} + {{#-first}} + Uri url = httpRequestMessage.RequestUri = new Uri("{{url}}"); + uriBuilder.Host = url.Authority; + uriBuilder.Scheme = url.Scheme; + uriBuilder.Path = url.AbsolutePath; + {{/-first}} + {{/servers}} + {{#constantParams}} + {{#isPathParam}} + // Set client side default value of Path Param "{{baseName}}". + uriBuilder.Path = uriBuilder.Path.Replace("%7B{{baseName}}%7D", Uri.EscapeDataString(ClientUtils.ParameterToString({{#_enum}}"{{{.}}}"{{/_enum}}))); // Constant path parameter + {{/isPathParam}} + {{/constantParams}} + {{#pathParams}} + uriBuilder.Path = uriBuilder.Path.Replace("%7B{{baseName}}%7D", Uri.EscapeDataString({{paramName}}.ToString())); + {{#-last}} + + {{/-last}} + {{/pathParams}} + {{#queryParams}} + {{#-first}} + + System.Collections.Specialized.NameValueCollection parseQueryString = System.Web.HttpUtility.ParseQueryString(string.Empty); + {{/-first}} + {{/queryParams}} + {{^queryParams}} + {{#authMethods}} + {{#isApiKey}} + {{#isKeyInQuery}} + + System.Collections.Specialized.NameValueCollection parseQueryString = System.Web.HttpUtility.ParseQueryString(string.Empty); + {{/isKeyInQuery}} + {{/isApiKey}} + {{/authMethods}} + {{/queryParams}} + {{#queryParams}} + {{#required}} + {{#-first}} + + {{/-first}} + parseQueryString["{{baseName}}"] = ClientUtils.ParameterToString({{paramName}}); + {{/required}} + {{/queryParams}} + + {{#constantParams}} + {{#isQueryParam}} + // Set client side default value of Query Param "{{baseName}}". + parseQueryString["{{baseName}}"] = ClientUtils.ParameterToString({{#_enum}}"{{{.}}}"{{/_enum}}); // Constant query parameter + {{/isQueryParam}} + {{/constantParams}} + {{#queryParams}} + {{^required}} + if ({{paramName}}.IsSet) + parseQueryString["{{baseName}}"] = ClientUtils.ParameterToString({{paramName}}.Value); + + {{/required}} + {{#-last}} + uriBuilder.Query = parseQueryString.ToString(); + + {{/-last}} + {{/queryParams}} + // Adds headers to the HttpRequestMessage header, these can be set in the RequestOptions (Idempotency-Key etc.) + requestOptions?.AddHeadersToHttpRequestMessage(httpRequestMessage); + {{#formParams}} + {{#-first}} + MultipartContent multipartContent = new MultipartContent(); + + httpRequestMessage.Content = multipartContent; + + List> formParameters = new List>(); + + multipartContent.Add(new FormUrlEncodedContent(formParameters));{{/-first}}{{^isFile}}{{#required}} + + formParameters.Add(new KeyValuePair("{{baseName}}", ClientUtils.ParameterToString({{paramName}}))); + + {{/required}} + {{^required}} + if ({{paramName}}.IsSet) + formParameters.Add(new KeyValuePair("{{baseName}}", ClientUtils.ParameterToString({{paramName}}.Value))); + + {{/required}} + {{/isFile}} + {{#isFile}} + {{#required}} + multipartContent.Add(new StreamContent({{paramName}})); + + {{/required}} + {{^required}} + if ({{paramName}}.IsSet) + multipartContent.Add(new StreamContent({{paramName}}.Value)); + + {{/required}} + {{/isFile}} + {{/formParams}} + {{#bodyParam}} + httpRequestMessage.Content = ({{paramName}} as object) is System.IO.Stream stream + ? httpRequestMessage.Content = new StreamContent(stream) + : httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize({{paramName}}, _jsonSerializerOptions)); + {{/bodyParam}} + {{#authMethods}} + + {{#isApiKey}} + {{! Only use API Keys that are appended to the HTTP-headers }} + {{^isKeyInCookie}} + {{#isKeyInHeader}} + // Add authorization token to the HttpRequestMessage header + ApiKeyProvider.Get().AddTokenToHttpRequestMessageHeader(httpRequestMessage); + + {{/isKeyInHeader}} + {{/isKeyInCookie}} + {{/isApiKey}} + {{/authMethods}} + httpRequestMessage.RequestUri = uriBuilder.Uri; + {{#authMethods}} + {{#isBasicBearer}} + // Not supported, you'll have to add the BearerToken class and fetch is similar to the ApiKeyProvider. + BearerToken bearerToken{{-index}} = (BearerToken) await BearerTokenProvider.GetAsync(cancellation: cancellationToken).ConfigureAwait(false); + bearerToken{{-index}}.UseInHeader(httpRequestMessage, "{{keyParamName}}"); + {{/isBasicBearer}} + {{#isOAuth}} + // Not supported (yet), you'll have to add the BearerToken class and fetch is similar to the ApiKeyProvider. + OAuthToken oauthToken{{-index}} = (OAuthToken) await OauthTokenProvider.GetAsync(cancellation: cancellationToken).ConfigureAwait(false); + oauthToken{{-index}}.UseInHeader(httpRequestMessage, "{{keyParamName}}"); + {{/isOAuth}} + {{#isHttpSignature}} + + HttpSignatureToken httpSignatureToken{{-index}} = (HttpSignatureToken) await HttpSignatureTokenProvider.GetAsync(cancellation: cancellationToken).ConfigureAwait(false); + + if (httpRequestMessage.Content != null) { + string requestBody = await httpRequestMessage.Content.ReadAsStringAsync({{#net60OrLater}}cancellationToken{{/net60OrLater}}).ConfigureAwait(false); + + httpSignatureToken{{-index}}.UseInHeader(httpRequestMessage, requestBody, cancellationToken); + } + {{/isHttpSignature}} + {{/authMethods}} + {{#consumes}} + {{#-first}} + + {{=<% %>=}} + string[] contentTypes = new string[] {<%/-first%> + <%={{ }}=%> + "{{{mediaType}}}"{{^-last}},{{/-last}}{{#-last}} + }; + {{/-last}} + {{/consumes}} + {{#consumes}} + {{#-first}} + + httpRequestMessage.AddUserAgentToHeaders(); + httpRequestMessage.AddLibraryNameToHeader(); + httpRequestMessage.AddLibraryVersionToHeader(); + + string{{nrt?}} contentType = ClientUtils.SelectHeaderContentType(contentTypes); + + if (contentType != null && httpRequestMessage.Content != null) + httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType); + + {{/-first}} + {{/consumes}} + {{#produces}} + {{#-first}} + + {{=<% %>=}} + string[] accepts = new string[] {<%/-first%> + <%={{ }}=%> + "{{{mediaType}}}"{{^-last}},{{/-last}}{{#-last}} + }; + {{/-last}} + {{/produces}} + {{#produces}} + {{#-first}} + + string{{nrt?}} accept = ClientUtils.SelectHeaderAccept(accepts); + + if (accept != null) + httpRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(accept)); + {{/-first}} + {{/produces}} + {{#net60OrLater}} + + httpRequestMessage.Method = HttpMethod.{{#lambda.titlecase}}{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}{{/lambda.titlecase}}; + {{/net60OrLater}} + {{^net60OrLater}} + httpRequestMessage.Method = new HttpMethod("{{#lambda.uppercase}}{{httpMethod}}{{/lambda.uppercase}}"); + {{/net60OrLater}} + + DateTime requestedAt = DateTime.UtcNow; + + using (HttpResponseMessage httpResponseMessage = await HttpClient.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false)) + { + ILogger<{{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse> apiResponseLogger = LoggerFactory.CreateLogger<{{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse>(); + {{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse apiResponse; + + switch ((int)httpResponseMessage.StatusCode) { + {{#responses}} + {{#isBinary}} + case ({{code}}): + {{/isBinary}} + {{/responses}} + {{#responses}} + {{#isBinary}} + {{#-first}} + { + byte[] responseBytesArray = await httpResponseMessage.Content.ReadAsByteArrayAsync({{#net60OrLater}}cancellationToken{{/net60OrLater}}).ConfigureAwait(false); + System.IO.Stream responseContentStream = new System.IO.MemoryStream(responseBytesArray); + apiResponse = new{{^net60OrLater}} {{operationId}}ApiResponse{{/net60OrLater}}(apiResponseLogger, httpRequestMessage, httpResponseMessage, responseContentStream, "{{{path}}}", requestedAt, _jsonSerializerOptions); + + break; + } + {{/-first}} + {{/isBinary}} + {{/responses}} + default: { + string responseContent = await httpResponseMessage.Content.ReadAsStringAsync({{#net60OrLater}}cancellationToken{{/net60OrLater}}).ConfigureAwait(false); + apiResponse = new{{^net60OrLater}} {{operationId}}ApiResponse{{/net60OrLater}}(apiResponseLogger, httpRequestMessage, httpResponseMessage, responseContent, "{{{path}}}", requestedAt, _jsonSerializerOptions); + + break; + } + } + + Events.ExecuteOn{{operationId}}(apiResponse); + {{#net80OrLater}} + {{#responses}} + {{#vendorExtensions.x-set-cookie}} + if (httpResponseMessage.StatusCode == (HttpStatusCode) {{code}} && httpResponseMessage.Headers.TryGetValues("Set-Cookie", out var cookieHeaders)) + { + foreach(string cookieHeader in cookieHeaders) + { + IList setCookieHeaderValues = Microsoft.Net.Http.Headers.SetCookieHeaderValue.ParseList(cookieHeaders.ToArray()); + + foreach(Microsoft.Net.Http.Headers.SetCookieHeaderValue setCookieHeaderValue in setCookieHeaderValues) + { + Cookie cookie = new Cookie(setCookieHeaderValue.Name.ToString(), setCookieHeaderValue.Value.ToString()) + { + HttpOnly = setCookieHeaderValue.HttpOnly + }; + + if (setCookieHeaderValue.Expires.HasValue) + cookie.Expires = setCookieHeaderValue.Expires.Value.UtcDateTime; + + if (setCookieHeaderValue.Path.HasValue) + cookie.Path = setCookieHeaderValue.Path.Value; + + if (setCookieHeaderValue.Domain.HasValue) + cookie.Domain = setCookieHeaderValue.Domain.Value; + + CookieContainer.Value.Add(new Uri($"{uriBuilder.Scheme}://{uriBuilder.Host}"), cookie); + } + } + } + + {{/vendorExtensions.x-set-cookie}} + {{/responses}} + {{/net80OrLater}} + return apiResponse; + } + } + } + catch(Exception exception) + { + Events.ExecuteOnError{{operationId}}(exception); + throw; + } + {{/lambda.trimLineBreaks}} + } + {{^vendorExtensions.x-duplicates}} + {{#responses}} + {{#-first}} + + /// + /// The . + /// + {{>visibility}} partial class {{operationId}}ApiResponse : {{packageName}}.{{corePackageName}}.{{clientPackage}}.ApiResponse, {{interfacePrefix}}{{operationId}}ApiResponse + { + /// + /// The logger for . + /// + public ILogger<{{operationId}}ApiResponse> Logger { get; } + + /// + /// The . + /// + /// . + /// . + /// . + /// The raw data. + /// The path used when making the request. + /// The DateTime.UtcNow when the request was retrieved. + /// + public {{operationId}}ApiResponse(ILogger<{{operationId}}ApiResponse> logger, System.Net.Http.HttpRequestMessage httpRequestMessage, System.Net.Http.HttpResponseMessage httpResponseMessage, string rawContent, string path, DateTime requestedAt, System.Text.Json.JsonSerializerOptions jsonSerializerOptions) : base(httpRequestMessage, httpResponseMessage, rawContent, path, requestedAt, jsonSerializerOptions) + { + Logger = logger; + OnCreated(httpRequestMessage, httpResponseMessage); + } + + /// + /// The . + /// + /// . + /// . + /// . + /// The raw binary stream (only set for binary responses). + /// The path used when making the request. + /// The DateTime.UtcNow when the request was retrieved. + /// + public {{operationId}}ApiResponse(ILogger<{{operationId}}ApiResponse> logger, System.Net.Http.HttpRequestMessage httpRequestMessage, System.Net.Http.HttpResponseMessage httpResponseMessage, System.IO.Stream contentStream, string path, DateTime requestedAt, System.Text.Json.JsonSerializerOptions jsonSerializerOptions) : base(httpRequestMessage, httpResponseMessage, contentStream, path, requestedAt, jsonSerializerOptions) + { + Logger = logger; + OnCreated(httpRequestMessage, httpResponseMessage); + } + + partial void OnCreated(global::System.Net.Http.HttpRequestMessage httpRequestMessage, System.Net.Http.HttpResponseMessage httpResponseMessage); + {{#responses}} + + {{#vendorExtensions.x-http-status-is-default}} + /// + /// Returns true if the response is the default response type. + /// + /// + public bool Is{{vendorExtensions.x-http-status}} => {{#vendorExtensions.x-only-default}}true{{/vendorExtensions.x-only-default}}{{^vendorExtensions.x-only-default}}{{#lambda.joinConditions}}{{#responses}}{{^vendorExtensions.x-http-status-is-default}}!Is{{vendorExtensions.x-http-status}} {{/vendorExtensions.x-http-status-is-default}}{{/responses}}{{/lambda.joinConditions}}{{/vendorExtensions.x-only-default}}; + {{/vendorExtensions.x-http-status-is-default}} + {{^vendorExtensions.x-http-status-is-default}} + /// + /// Returns true if the response is {{code}} {{vendorExtensions.x-http-status}}. + /// + /// + {{#vendorExtensions.x-http-status-range}} + public bool Is{{vendorExtensions.x-http-status}} + { + get + { + int statusCode = (int)StatusCode; + return {{vendorExtensions.x-http-status-range}}00 >= statusCode && {{vendorExtensions.x-http-status-range}}99 <= statusCode; + } + } + {{/vendorExtensions.x-http-status-range}} + {{^vendorExtensions.x-http-status-range}} + public bool Is{{vendorExtensions.x-http-status}} => {{code}} == (int)StatusCode; + {{/vendorExtensions.x-http-status-range}} + {{/vendorExtensions.x-http-status-is-default}} + {{#dataType}} + + /// + /// Deserializes the response if the response is {{code}} {{vendorExtensions.x-http-status}}. + /// + /// + public {{#isModel}}{{^containerType}}{{packageName}}.{{modelPackage}}.{{/containerType}}{{/isModel}}{{{dataType}}}{{#nrt}}?{{/nrt}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{vendorExtensions.x-http-status}}() + { + {{#lambda.trimTrailingWithNewLine}} + {{#lambda.indent4}} + {{>AsModel}}{{! prevent indent}} + {{/lambda.indent4}} + {{/lambda.trimTrailingWithNewLine}} + } + + /// + /// Returns true if the response is {{code}} {{vendorExtensions.x-http-status}} and the deserialized response is not null. + /// + /// + /// + public bool TryDeserialize{{vendorExtensions.x-http-status}}Response({{#net60OrLater}}[NotNullWhen(true)]{{/net60OrLater}}out {{#isModel}}{{^containerType}}{{packageName}}.{{modelPackage}}.{{/containerType}}{{/isModel}}{{{dataType}}}{{#nrt}}?{{/nrt}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} result) + { + result = null; + + try + { + result = {{vendorExtensions.x-http-status}}(); + } + catch (Exception exception) + { + OnDeserializationError(exception, (HttpStatusCode){{#vendorExtensions.x-http-status-range}}{{.}}{{/vendorExtensions.x-http-status-range}}{{^vendorExtensions.x-http-status-range}}{{code}}{{/vendorExtensions.x-http-status-range}}); + } + + return result != null; + } + {{/dataType}} + {{#-last}} + + private void OnDeserializationError(Exception exception, HttpStatusCode httpStatusCode) + { + bool suppressDefaultLog = false; + OnDeserializationError(ref suppressDefaultLog, exception, httpStatusCode); + {{#lambda.trimTrailingWithNewLine}} + {{#lambda.indent4}} + {{>OnDeserializationError}}{{! prevent indent}} + {{/lambda.indent4}} + {{/lambda.trimTrailingWithNewLine}} + } + + partial void OnDeserializationError(ref bool suppressDefaultLog, Exception exception, HttpStatusCode httpStatusCode); + {{/-last}} + {{/responses}} + } + + {{/-first}} + {{/responses}} + {{/vendorExtensions.x-duplicates}} + {{/operation}} + } + {{/operations}} +} +{{/lambda.trimLineBreaks}} diff --git a/templates-v7/csharp/libraries/generichost/api_doc.mustache b/templates-v7/csharp/libraries/generichost/api_doc.mustache new file mode 100644 index 000000000..feb36ebc0 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/api_doc.mustache @@ -0,0 +1,91 @@ +## {{packageName}}.{{apiPackage}}.{{classname}}{{#description}} +{{.}}{{/description}} + +#### API Base-Path: **{{{basePath}}}** + + +#### Authorization: {{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}{{#isApiKey}}{{^isKeyInCookie}}{{#isKeyInHeader}}{{{name}}}{{/isKeyInHeader}}{{/isKeyInCookie}}{{/isApiKey}}{{/authMethods}} + + +#### Initialization + + +```csharp +using {{packageName}}.{{corePackageName}}.Options; +using {{packageName}}.{{apiName}}.Extensions; +using {{packageName}}.{{apiName}}.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +IHost host = Host.CreateDefaultBuilder() + .Configure{{apiName}}((context, services, config) => + { + config.ConfigureAdyenOptions(options => + { + // Set your ADYEN_API_KEY or use get it from your environment using context.Configuration["ADYEN_API_KEY"]. + options.AdyenApiKey = context.Configuration["ADYEN_API_KEY"]; + + // Set your environment, e.g. `Test` or `Live`. + options.Environment = AdyenEnvironment.Test; + + // For the Live environment, additionally include your LiveEndpointUrlPrefix. + options.LiveEndpointUrlPrefix = "your-live-endpoint-url-prefix"; + }); + }) + .Build(); + + // You can inject this service in your constructor. + {{interfacePrefix}}{{classname}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}} = host.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); +``` + +| Method | HTTP request | Description | +|--------|--------------|-------------| +{{#operations}} +{{#operation}} +| [**{{operationId}}**]({{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{summary}} | +{{/operation}} +{{/operations}} + +{{#operations}} +{{#operation}} + +### **{{httpMethod}}** **{{{path}}}** + +{{{summary}}} + +#### Parameters +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +| Name | Type | Description | +|------|------|-------------| +{{/-last}} +{{/allParams}} +{{#allParams}} +| **{{paramName}}** | {{#isFile}}**{{dataType}}**{{/isFile}}{{#isPrimitiveType}}[{{dataType}}]{{/isPrimitiveType}}{{^isPrimitiveType}}{{^isFile}}**{{dataType}}**{{/isFile}}{{/isPrimitiveType}} | {{description}} | +{{/allParams}} + +#### Example usage + +```csharp +using {{packageName}}.{{modelPackage}}; +using {{packageName}}.{{apiName}}.Services; + +// Example `{{classname}}.{{operationId}}` usage: +// Provide the following values: {{#allParams}}{{^isHeaderParam}}{{paramName}}{{^-last}}, {{/-last}}{{/isHeaderParam}}{{#isHeaderParam}}[HeaderParameter] {{paramName}}{{^-last}}, {{/-last}}{{/isHeaderParam}}{{/allParams}} +{{#returnType}}{{^isVoid}}{{interfacePrefix}}{{returnType}} response = {{/isVoid}}{{/returnType}}await {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{operationId}}Async( + {{#pathParams}}{{{dataType}}}{{>NullConditionalParameter}} {{paramName}}, {{/pathParams}}{{#queryParams}}{{#required}}{{{dataType}}}{{>NullConditionalParameter}} {{paramName}}, {{/required}}{{/queryParams}}{{#bodyParams}}{{{dataType}}}{{>NullConditionalParameter}} {{paramName}}, {{/bodyParams}}{{#queryParams}}{{^required}}Option<{{{dataType}}}{{>NullConditionalParameter}}> {{paramName}}{{#notRequiredOrIsNullable}} = default{{/notRequiredOrIsNullable}}, {{/required}}{{/queryParams}} + RequestOptions requestOptions = default, + CancellationToken cancellationToken = default); + +if (response.TryDeserializeOkResponse(out {{returnType}} result)) +{ + // Handle result, if 200 OK response +} + +``` + +#### Return type +{{#returnType}}{{#returnTypeIsPrimitive}}[{{{returnType}}}]{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[{{returnType}}](){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}void (empty response body){{/returnType}} + + +{{/operation}} +{{/operations}} diff --git a/templates-v7/csharp/libraries/generichost/api_test.mustache b/templates-v7/csharp/libraries/generichost/api_test.mustache new file mode 100644 index 000000000..d54244957 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/api_test.mustache @@ -0,0 +1,71 @@ +{{>partial_header}} +using {{packageName}}.Core.Options;{{#hasImport}} +using {{packageName}}.{{modelPackage}};{{/hasImport}} +using {{packageName}}.{{apiName}}.{{clientPackageName}}; +using {{packageName}}.{{apiName}}.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Text.Json; + +{{>testInstructions}} + + +namespace {{packageName}}.Test.{{apiPackage}} +{ + /// + /// Class for testing {{classname}} + /// + [TestClass] + public sealed class {{classname}}Test + { + private readonly JsonSerializerOptionsProvider _jsonSerializerOptionsProvider; + + public {{classname}}Test() + { + IHost host = Host.CreateDefaultBuilder() + .Configure{{classname}}((context, services, config) => + { + config.ConfigureAdyenOptions(options => + { + options.Environment = AdyenEnvironment.Test; + }); + }) + .Build(); + + _jsonSerializerOptionsProvider = host.Services.GetRequiredService(); + } + {{#operations}} + {{#operation}} + + /// + /// Test {{operationId}}. + /// + public async Task Given_Deserialize_When_{{operationId}}_Successfully_Deserializes() + { + // Arrange + // TODO: Insert your example code from open-api here + var client = TestUtilities.GetTestFileContent("mocks/posmobile/create-session.json"); + + {{#allParams}} + {{^required}}Client.Option<{{/required}}{{{dataType}}}{{>NullConditionalParameter}}{{^required}}>{{/required}} {{paramName}} = default{{nrt!}}; + {{/allParams}} + {{#responses}} + {{#-first}} + {{#dataType}} + + // Act + // TODO: Prefill the ResponseType + // IDEALLY: We insert the SERVICE and then we call it using the right parameters (this gets tricky with query params* but very doable!) + var response = JsonSerializer.Deserialize<{{name}}Response}>(json, _jsonSerializerOptionsProvider); + + // Assert + Assert.IsNotNull(response); + {{/dataType}} + {{/-first}} + {{/responses}} + } + {{/operation}} + {{/operations}} + } +} diff --git a/templates-v7/csharp/libraries/generichost/model.mustache b/templates-v7/csharp/libraries/generichost/model.mustache new file mode 100644 index 000000000..fa07a7053 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/model.mustache @@ -0,0 +1,56 @@ +// +{{>partial_header}} + +{{#nrt}} +#nullable enable + +{{/nrt}} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +{{^useGenericHost}} +using System.Runtime.Serialization; +{{/useGenericHost}} +using System.Text; +using System.Text.RegularExpressions; +using System.Text.Json; +using System.Text.Json.Serialization; +{{#validatable}} +using System.ComponentModel.DataAnnotations; +{{/validatable}} +{{#useCompareNetObjects}} +using OpenAPIClientUtils = {{packageName}}.Client.ClientUtils; +{{/useCompareNetObjects}} +{{#useGenericHost}} +{{#useSourceGeneration}} +using System.Text.Json.Serialization.Metadata; +{{/useSourceGeneration}} +using {{packageName}}.{{corePackageName}}; +using {{packageName}}.{{apiName}}.{{clientPackage}}; +{{/useGenericHost}} +{{#models}} +{{#lambda.trimTrailingWithNewLine}} +{{#model}} + +namespace {{packageName}}.{{modelPackage}} +{ +{{#isEnum}} +{{>modelEnum}} + +{{/isEnum}} +{{^isEnum}} +{{>modelGeneric}} + + +{{>JsonConverter}} + +{{/isEnum}} +{{>SourceGenerationContext}} + +{{/model}} +{{/lambda.trimTrailingWithNewLine}} +{{/models}} +} diff --git a/templates-v7/csharp/libraries/generichost/modelGeneric.mustache b/templates-v7/csharp/libraries/generichost/modelGeneric.mustache new file mode 100644 index 000000000..00f340413 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/modelGeneric.mustache @@ -0,0 +1,413 @@ + /// + /// {{{description}}}{{^description}}{{classname}}{{/description}}. + /// + {{>visibility}} partial class {{classname}}{{#lambda.firstDot}}{{#parent}} : .{{/parent}}{{#validatable}} : .{{/validatable}}{{#equatable}}{{#readOnlyVars}}{{#-first}} : .{{/-first}}{{/readOnlyVars}}{{/equatable}}{{/lambda.firstDot}}{{#lambda.joinWithComma}}{{#parent}}{{{.}}} {{/parent}}{{>ImplementsIEquatable}}{{#validatable}}IValidatableObject {{/validatable}}{{/lambda.joinWithComma}} + { + {{#composedSchemas.oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + /// + /// Initializes a new instance of the class. + /// + /// + {{#composedSchemas.anyOf}} + /// + {{/composedSchemas.anyOf}} + {{#allVars}} + {{^isDiscriminator}} + /// {{description}}{{^description}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/description}}{{#defaultValue}} (default to {{.}}){{/defaultValue}} + {{/isDiscriminator}} + {{/allVars}} + {{#model.vendorExtensions.x-model-is-mutable}}{{>visibility}}{{/model.vendorExtensions.x-model-is-mutable}}{{^model.vendorExtensions.x-model-is-mutable}}internal{{/model.vendorExtensions.x-model-is-mutable}} {{classname}}({{#lambda.joinWithComma}}{{{dataType}}} {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}} {{#model.composedSchemas.anyOf}}{{^required}}Option<{{/required}}{{{dataType}}}{{>NullConditionalProperty}}{{^required}}>{{/required}} {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{baseType}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}} {{/model.composedSchemas.anyOf}}{{>ModelSignature}}{{/lambda.joinWithComma}}){{#parent}} : base({{#lambda.joinWithComma}}{{#parentModel.composedSchemas.oneOf}}{{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{parent}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}.{{#lambda.titlecase}}{{baseType}}{{/lambda.titlecase}} {{/parentModel.composedSchemas.oneOf}}{{>ModelBaseSignature}}{{/lambda.joinWithComma}}){{/parent}} + { + {{#composedSchemas.anyOf}} + {{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}{{^required}}Option{{/required}} = {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}; + {{/composedSchemas.anyOf}} + {{name}} = {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}; + {{#allVars}} + {{^isDiscriminator}} + {{^isInherited}} + {{name}}{{^required}}Option{{/required}} = {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}; + {{/isInherited}} + {{#isInherited}} + {{#isNew}} + {{name}}{{^required}}Option{{/required}} = {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}; + {{/isNew}} + {{/isInherited}} + {{/isDiscriminator}} + {{#vendorExtensions.x-is-base-or-new-discriminator}} + {{^model.composedSchemas.anyOf}} + {{^model.composedSchemas.oneOf}} + {{name}} = {{^isEnum}}this.GetType().Name{{/isEnum}}{{#isEnum}}({{datatypeWithEnum}})Enum.Parse(typeof({{datatypeWithEnum}}), this.GetType().Name){{/isEnum}}; + {{/model.composedSchemas.oneOf}} + {{/model.composedSchemas.anyOf}} + {{/vendorExtensions.x-is-base-or-new-discriminator}} + {{/allVars}} + OnCreated(); + } + + {{/vendorExtensions.x-duplicated-data-type}} + {{/composedSchemas.oneOf}} + {{^composedSchemas.oneOf}} + /// + /// Initializes a new instance of the class. + /// + {{#composedSchemas.anyOf}} + /// + {{/composedSchemas.anyOf}} + {{#allVars}} + {{^isDiscriminator}} + /// {{description}}{{^description}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/description}}{{#defaultValue}} (default to {{.}}){{/defaultValue}} + {{/isDiscriminator}} + {{/allVars}} + {{^composedSchemas.anyOf}} + [JsonConstructor] + {{/composedSchemas.anyOf}} + {{#model.vendorExtensions.x-model-is-mutable}}{{>visibility}}{{/model.vendorExtensions.x-model-is-mutable}}{{^model.vendorExtensions.x-model-is-mutable}}internal{{/model.vendorExtensions.x-model-is-mutable}} {{classname}}({{#lambda.joinWithComma}}{{#composedSchemas.anyOf}}{{^required}}Option<{{/required}}{{{datatypeWithEnum}}}{{>NullConditionalProperty}}{{^required}}>{{/required}} {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}} {{/composedSchemas.anyOf}}{{>ModelSignature}}{{/lambda.joinWithComma}}){{#parent}} : base({{#lambda.joinWithComma}}{{>ModelBaseSignature}}{{/lambda.joinWithComma}}){{/parent}} + { + {{#composedSchemas.anyOf}} + {{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}{{^required}}Option{{/required}} = {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}; + {{/composedSchemas.anyOf}} + {{#allVars}} + {{^isDiscriminator}} + {{^isInherited}} + {{^required}}_{{/required}}{{name}}{{^required}}Option{{/required}} = {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}; + {{/isInherited}} + {{#isInherited}} + {{#isNew}} + {{^required}}_{{/required}}{{name}}{{^required}}Option{{/required}} = {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}; + {{/isNew}} + {{/isInherited}} + {{/isDiscriminator}} + {{#vendorExtensions.x-is-base-or-new-discriminator}} + {{^model.composedSchemas.anyOf}} + {{^model.composedSchemas.oneOf}} + {{name}} = {{^isEnum}}this.GetType().Name{{/isEnum}}{{#isEnum}}({{datatypeWithEnum}})Enum.Parse(typeof({{datatypeWithEnum}}), this.GetType().Name){{/isEnum}}; + {{/model.composedSchemas.oneOf}} + {{/model.composedSchemas.anyOf}} + {{/vendorExtensions.x-is-base-or-new-discriminator}} + {{/allVars}} + OnCreated(); + } + + {{#allVars}} + {{^isDiscriminator}} + {{#-first}} + /// + /// Best practice: Use the constructor to initialize your objects to understand which parameters are required/optional. + /// + {{#model.vendorExtensions.x-model-is-mutable}}{{>visibility}}{{/model.vendorExtensions.x-model-is-mutable}}{{^model.vendorExtensions.x-model-is-mutable}}internal{{/model.vendorExtensions.x-model-is-mutable}} {{classname}}() + { + } + {{/-first}} + {{/isDiscriminator}} + {{/allVars}} + + {{/composedSchemas.oneOf}} + partial void OnCreated(); + + {{#vars}} + {{#items.isEnum}} + {{#items}} + {{^complexType}} +{{>modelInnerEnum}} + + {{/complexType}} + {{/items}} + {{/items.isEnum}} + {{#isEnum}} + {{^complexType}} +{{>modelInnerEnum}} + + {{/complexType}} + {{/isEnum}} + {{^isDiscriminator}} + {{#isEnum}} + {{^required}} + /// + /// This is used to track if an optional field is set. If set, will be populated. + /// + [JsonIgnore] + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public {{#isNew}}new {{/isNew}}Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}> _{{name}}Option { get; {{^isReadOnly}}private set; {{/isReadOnly}}} + + {{/required}} + /// + /// {{{description}}}{{^description}}.{{/description}} + /// + {{#description}} + /// {{.}} + {{/description}} + {{#example}} + /* {{.}} */ + {{/example}} + [JsonPropertyName("{{baseName}}")] + {{#deprecated}} + [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] + {{/deprecated}} + public {{#isNew}}new {{/isNew}}{{{datatypeWithEnum}}}{{#lambda.first}}{{#isNullable}}{{>NullConditionalProperty}} {{/isNullable}}{{^required}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/required}}{{/lambda.first}} {{name}} {{#required}}{ get; {{^isReadOnly}}set; {{/isReadOnly}}}{{/required}}{{^required}}{ get { return this._{{name}}Option; } {{^isReadOnly}}set { this._{{name}}Option = new{{^net70OrLater}} Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}>{{/net70OrLater}}(value); } {{/isReadOnly}}}{{/required}} + + {{/isEnum}} + {{/isDiscriminator}} + {{/vars}} + {{#composedSchemas.anyOf}} + {{^vendorExtensions.x-duplicated-data-type}} + {{^required}} + /// + /// This is used to track if an optional field is set. If set, will be populated. + /// + [JsonIgnore] + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public {{#isNew}}new {{/isNew}}Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}> _{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Option { get; {{^isReadOnly}}private set; {{/isReadOnly}}} + + {{/required}} + /// + /// {{{description}}}{{^description}}.{{/description}} + /// {{#description}} + /// {{.}}{{/description}} + {{#example}} + /* {{.}} */ + {{/example}} + {{#deprecated}} + [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] + {{/deprecated}} + public {{{datatypeWithEnum}}}{{#lambda.first}}{{#isNullable}}{{>NullConditionalProperty}} {{/isNullable}}{{^required}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/required}}{{/lambda.first}} {{#lambda.titlecase}}{{baseType}}{{/lambda.titlecase}} {{#required}}{ get; {{^isReadOnly}}set; {{/isReadOnly}}}{{/required}}{{^required}}{ get { return this._{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Option; } {{^isReadOnly}}set { this._{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Option = new{{^net70OrLater}} Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}>{{/net70OrLater}}(value); } {{/isReadOnly}}}{{/required}} + + {{/vendorExtensions.x-duplicated-data-type}} + {{/composedSchemas.anyOf}} + {{#composedSchemas.oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + /// + /// {{{description}}}{{^description}}.{{/description}}. + /// {{#description}} + /// {{.}}{{/description}} + {{#example}} + /* {{.}} */ + {{/example}} + {{#deprecated}} + [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] + {{/deprecated}} + public {{{dataType}}}{{>NullConditionalProperty}} {{#lambda.titlecase}}{{name}}{{/lambda.titlecase}} { get; {{^isReadOnly}}set; {{/isReadOnly}}} + + {{/vendorExtensions.x-duplicated-data-type}} + {{/composedSchemas.oneOf}} + {{#allVars}} + {{#vendorExtensions.x-is-base-or-new-discriminator}} + {{^model.composedSchemas.anyOf}} + {{^model.composedSchemas.oneOf}} + /// + /// The discriminator. + /// + [JsonIgnore] + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public {{#isNew}}new {{/isNew}}{{datatypeWithEnum}} {{name}} { get; } + + {{/model.composedSchemas.oneOf}} + {{/model.composedSchemas.anyOf}} + {{/vendorExtensions.x-is-base-or-new-discriminator}} + {{^vendorExtensions.x-is-base-or-new-discriminator}} + {{^isEnum}} + {{#isInherited}} + {{#isNew}} + {{^required}} + /// + /// This is used to track if an optional field is set. If set, will be populated. + /// + [JsonIgnore] + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public new Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}> _{{name}}Option { get; {{^isReadOnly}}private set; {{/isReadOnly}}} + + {{/required}} + /// + /// {{{description}}}{{^description}}.{{/description}} + /// {{#description}} + /// {{.}}{{/description}} + {{#example}} + /* {{.}} */ + {{/example}} + [JsonPropertyName("{{baseName}}")] + {{#deprecated}} + [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] + {{/deprecated}} + public new {{{datatypeWithEnum}}}{{#lambda.first}}{{#isNullable}}{{>NullConditionalProperty}} {{/isNullable}}{{^required}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/required}}{{/lambda.first}} {{name}} {{#required}}{ get; {{^isReadOnly}}set; {{/isReadOnly}}}{{/required}}{{^required}}{ get { return this._{{name}}Option } {{^isReadOnly}}set { this._{{name}}Option = new{{^net70OrLater}} Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}>{{/net70OrLater}}(value); } {{/isReadOnly}}}{{/required}} + + {{/isNew}} + {{/isInherited}} + {{^isInherited}} + {{^required}} + /// + /// This is used to track if an optional field is set. If set, will be populated. + /// + [JsonIgnore] + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}> _{{name}}Option { get; {{^isReadOnly}}private set; {{/isReadOnly}}} + + {{/required}} + /// + /// {{description}}{{^description}}.{{/description}} + /// {{#description}} + /// {{{description}}}{{/description}} + {{#example}} + /* {{.}} */ + {{/example}} + [JsonPropertyName("{{baseName}}")] + {{#deprecated}} + [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] + {{/deprecated}} + public {{{datatypeWithEnum}}}{{#lambda.first}}{{#isNullable}}{{>NullConditionalProperty}} {{/isNullable}}{{^required}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/required}}{{/lambda.first}} {{name}} {{#required}}{ get; {{^isReadOnly}}set; {{/isReadOnly}}}{{/required}}{{^required}}{ get { return this._{{name}}Option; } {{^isReadOnly}}set { this._{{name}}Option = new{{^net70OrLater}} Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}>{{/net70OrLater}}(value); } {{/isReadOnly}}}{{/required}} + + {{/isInherited}} + {{/isEnum}} + {{/vendorExtensions.x-is-base-or-new-discriminator}} + {{/allVars}} + {{#isAdditionalPropertiesTrue}} + {{^parentModel}} + /// + /// Gets or sets additional properties + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; } = new Dictionary(); + + {{/parentModel}} + {{/isAdditionalPropertiesTrue}} + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class {{classname}} {\n"); + {{#composedSchemas.oneOf}} + if (this.{{name}} != null) + sb.Append({{name}}.ToString().Replace("\n", "\n ")); + {{/composedSchemas.oneOf}} + {{#parent}} + sb.Append(" ").Append(base.ToString()?.Replace("\n", "\n ")).Append("\n"); + {{/parent}} + {{#vars}} + {{^isDiscriminator}} + sb.Append(" {{name}}: ").Append({{name}}).Append("\n"); + {{/isDiscriminator}} + {{/vars}} + {{#isAdditionalPropertiesTrue}} + {{^parentModel}} + sb.Append(" AdditionalProperties: ").Append(AdditionalProperties).Append("\n"); + {{/parentModel}} + {{/isAdditionalPropertiesTrue}} + sb.Append("}\n"); + return sb.ToString(); + } + {{#equatable}} + {{#readOnlyVars}} + {{#-first}} + + /// + /// Returns true if objects are equal. + /// + /// Object to be compared + /// Boolean + public override bool Equals(object{{nrt?}} input) + { + {{#useCompareNetObjects}} + return OpenAPIClientUtils.compareLogic.Compare(this, input as {{classname}}).AreEqual; + {{/useCompareNetObjects}} + {{^useCompareNetObjects}} + return this.Equals(input as {{classname}}); + {{/useCompareNetObjects}} + } + + /// + /// Returns true if {{classname}} instances are equal. + /// + /// Instance of {{classname}} to be compared + /// Boolean + public bool Equals({{classname}}{{nrt?}} input) + { + {{#useCompareNetObjects}} + return OpenAPIClientUtils.compareLogic.Compare(this, input).AreEqual; + {{/useCompareNetObjects}} + {{^useCompareNetObjects}} + if (input == null) + return false; + + return {{#parent}}base.Equals(input) && {{/parent}}{{#readOnlyVars}}{{^isInherited}}{{^isContainer}} + ( + {{name}} == input.{{name}} || + {{^vendorExtensions.x-is-value-type}} + ({{name}} != null && + {{name}}.Equals(input.{{name}})) + {{/vendorExtensions.x-is-value-type}} + {{#vendorExtensions.x-is-value-type}} + {{name}}.Equals(input.{{name}}) + {{/vendorExtensions.x-is-value-type}} + ){{^-last}} && {{/-last}}{{/isContainer}}{{#isContainer}} + ( + {{name}} == input.{{name}} || + {{^vendorExtensions.x-is-value-type}}{{name}} != null && + input.{{name}} != null && + {{/vendorExtensions.x-is-value-type}}{{name}}.SequenceEqual(input.{{name}}) + ){{^-last}} && {{/-last}}{{/isContainer}}{{/isInherited}}{{/readOnlyVars}}{{^readOnlyVars}}{{#parent}}base.Equals(input){{/parent}}{{^parent}}false{{/parent}}{{/readOnlyVars}}{{^isAdditionalPropertiesTrue}};{{/isAdditionalPropertiesTrue}} + {{#isAdditionalPropertiesTrue}} + {{^parentModel}} + && (AdditionalProperties.Count == input.AdditionalProperties.Count && !AdditionalProperties.Except(input.AdditionalProperties).Any()); + {{/parentModel}} + {{/isAdditionalPropertiesTrue}} + {{/useCompareNetObjects}} + } + + /// + /// Gets the hash code. + /// + /// Hash code + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + {{#parent}} + int hashCode = base.GetHashCode(); + {{/parent}} + {{^parent}} + int hashCode = 41; + {{/parent}} + {{#readOnlyVars}} + {{#required}} + {{^isNullable}} + hashCode = (hashCode * 59) + {{name}}.GetHashCode(); + {{/isNullable}} + {{/required}} + {{/readOnlyVars}} + {{#readOnlyVars}} + {{#lambda.copy}} + if ({{name}} != null) + hashCode = (hashCode * 59) + {{name}}.GetHashCode(); + + {{/lambda.copy}} + {{#isNullable}} + {{#lambda.pasteOnce}} + {{/lambda.pasteOnce}} + {{/isNullable}} + {{^required}} + {{#lambda.pasteOnce}} + {{/lambda.pasteOnce}} + {{/required}} + {{/readOnlyVars}} + {{#isAdditionalPropertiesTrue}} + {{^parentModel}} + hashCode = (hashCode * 59) + AdditionalProperties.GetHashCode(); + {{/parentModel}} + {{/isAdditionalPropertiesTrue}} + + return hashCode; + } + } + {{/-first}} + {{/readOnlyVars}} + {{/equatable}} +{{#validatable}} +{{^parentModel}} + +{{>validatable}} + +{{/parentModel}} +{{/validatable}} + } \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/modelInnerEnum.mustache b/templates-v7/csharp/libraries/generichost/modelInnerEnum.mustache new file mode 100644 index 000000000..572d0f136 --- /dev/null +++ b/templates-v7/csharp/libraries/generichost/modelInnerEnum.mustache @@ -0,0 +1,116 @@ + {{^isContainer}} + /// + /// {{{description}}}{{^description}}Defines {{{name}}}.{{/description}} + /// + {{#description}} + /// {{.}} + {{/description}} + {{#isString}} + {{#useGenericHost}} + [JsonConverter(typeof({{datatypeWithEnum}}JsonConverter))] + {{/useGenericHost}} + {{/isString}} + {{>visibility}} class {{datatypeWithEnum}} : IEnum + { + /// + /// Returns the value of the {{datatypeWithEnum}}. + /// + public string? Value { get; set; } + + {{#allowableValues}} + {{#enumVars}} + /// + /// {{datatypeWithEnum}}.{{name}} - {{value}} + /// + public static readonly {{datatypeWithEnum}} {{name}} = new("{{value}}"); + {{^-last}} + + {{/-last}} + {{/enumVars}} + {{/allowableValues}} + + private {{datatypeWithEnum}}(string? value) + { + Value = value; + } + + /// + /// Converts a string to a implicitly. + /// + /// The string value to convert. Defaults to null. + /// A new instance initialized with the string value. + public static implicit operator {{datatypeWithEnum}}?(string? value) => value == null ? null : new {{datatypeWithEnum}}(value); + + /// + /// Converts a instance to a string implicitly. + /// + /// The instance. Default to null. + /// String value of the instance./// + public static implicit operator string?({{datatypeWithEnum}}? option) => option?.Value; + + public static bool operator ==({{datatypeWithEnum}}? left, {{datatypeWithEnum}}? right) => string.Equals(left?.Value, right?.Value, StringComparison.OrdinalIgnoreCase); + + public static bool operator !=({{datatypeWithEnum}}? left, {{datatypeWithEnum}}? right) => !string.Equals(left?.Value, right?.Value, StringComparison.OrdinalIgnoreCase); + + public override bool Equals(object? obj) => obj is {{datatypeWithEnum}} other && string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + public override int GetHashCode() => Value?.GetHashCode() ?? 0; + + public override string ToString() => Value ?? string.Empty; + + {{#useGenericHost}} + /// + /// Returns a . + /// + /// + /// or null. + public static {{datatypeWithEnum}}? FromStringOrDefault(string value) + { + return value switch { + {{#allowableValues}}{{#enumVars}}{{#isString}}"{{{value}}}"{{/isString}} => {{datatypeWithEnum}}.{{name}}, + {{/enumVars}}{{/allowableValues}}_ => null, + }; + } + + /// + /// Converts the to the json value. + /// + /// + /// String value of the enum. + {{#isString}} + /// + {{/isString}} + public static {{>EnumValueDataType}}?{{#lambda.first}}{{#nrt}}{{/nrt}}{{/lambda.first}} ToJsonValue({{datatypeWithEnum}}? value) + { + if (value == null) + return null; + + {{#allowableValues}} + {{#enumVars}} + if (value == {{datatypeWithEnum}}.{{name}}) + return {{#isString}}"{{{value}}}"{{/isString}}; + + {{/enumVars}} + {{/allowableValues}} + return null; + } + + /// + /// JsonConverter for writing {{datatypeWithEnum}}. + /// + public class {{datatypeWithEnum}}JsonConverter : JsonConverter<{{datatypeWithEnum}}> + { + public override {{datatypeWithEnum}}? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions jsonOptions) + { + string value = reader.GetString(); + return value == null ? null : {{datatypeWithEnum}}.FromStringOrDefault(value) ?? new {{datatypeWithEnum}}(value); + } + + public override void Write(Utf8JsonWriter writer, {{datatypeWithEnum}} value, JsonSerializerOptions jsonOptions) + { + writer.WriteStringValue({{datatypeWithEnum}}.ToJsonValue(value)); + } + } + {{/useGenericHost}} + } + {{/isContainer}} \ No newline at end of file diff --git a/templates-v7/csharp/partial_header.mustache b/templates-v7/csharp/partial_header.mustache new file mode 100644 index 000000000..377ccfabb --- /dev/null +++ b/templates-v7/csharp/partial_header.mustache @@ -0,0 +1,18 @@ +/* + {{#appName}} + * {{{.}}} + * + {{/appName}} + {{#appDescription}} + * {{{.}}} + * + {{/appDescription}} + {{#version}} + * The version of the OpenAPI document: {{{.}}} + {{/version}} + {{#infoEmail}} + * Contact: {{{.}}} + {{/infoEmail}} + * NOTE: This class is auto-generated using a modified version (https://github.com/Adyen/adyen-sdk-automation) of the OpenAPI Generator: https://github.com/openapitools/openapi-generator.git. + * Do not edit this class manually. + */ diff --git a/templates-v7/csharp/validatable.mustache b/templates-v7/csharp/validatable.mustache new file mode 100644 index 000000000..14fbc18c4 --- /dev/null +++ b/templates-v7/csharp/validatable.mustache @@ -0,0 +1,141 @@ +{{#discriminator}} + /// + /// To validate all properties of the instance. + /// + /// . + /// . + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + return this.BaseValidate(validationContext); + } + + /// + /// To validate all properties of the instance. + /// + /// . + /// . + protected IEnumerable BaseValidate(ValidationContext validationContext) + { +{{/discriminator}} +{{^discriminator}} + {{#parent}} + /// + /// To validate all properties of the instance. + /// + /// . + /// . + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + return this.BaseValidate(validationContext); + } + + /// + /// To validate all properties of the instance. + /// + /// . + /// . + protected IEnumerable BaseValidate(ValidationContext validationContext) + { + {{/parent}} + {{^parent}} + /// + /// To validate all properties of the instance. + /// + /// . + /// . + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + {{/parent}} +{{/discriminator}} + {{#parent}} + {{^isArray}} + {{^isMap}} + foreach (var x in {{#discriminator}}base.{{/discriminator}}BaseValidate(validationContext)) + { + yield return x; + } + {{/isMap}} + {{/isArray}} + {{/parent}} + {{#vars}} + {{#hasValidation}} + {{^isEnum}} + {{^isDateTime}} + {{^isDate}} + {{^isTime}} + {{#maxLength}} + // {{{name}}} ({{{dataType}}}) maxLength + if (this.{{{name}}} != null && this.{{{name}}}.Length > {{maxLength}}) + { + yield return new ValidationResult("Invalid value for {{{name}}}, length must be less than {{maxLength}}.", new [] { "{{{name}}}" }); + } + + {{/maxLength}} + {{#minLength}} + // {{{name}}} ({{{dataType}}}) minLength + if (this.{{{name}}} != null && this.{{{name}}}.Length < {{minLength}}) + { + yield return new ValidationResult("Invalid value for {{{name}}}, length must be greater than {{minLength}}.", new [] { "{{{name}}}" }); + } + + {{/minLength}} + {{/isTime}} + {{/isDate}} + {{/isDateTime}} + {{#maximum}} + // {{{name}}} ({{{dataType}}}) maximum + if ({{#useGenericHost}}{{^required}}this.{{{name}}}Option.IsSet && {{/required}}{{/useGenericHost}}this.{{{name}}}{{#useGenericHost}}{{^required}}Option.Value{{/required}}{{/useGenericHost}} {{#exclusiveMaximum}}<={{/exclusiveMaximum}}{{^exclusiveMaximum}}>{{/exclusiveMaximum}} ({{{dataType}}}){{maximum}}) + { + yield return new ValidationResult("Invalid value for {{{name}}}, must be a value less than {{^exclusiveMaximum}}or equal to {{/exclusiveMaximum}}{{maximum}}.", new [] { "{{{name}}}" }); + } + + {{/maximum}} + {{#minimum}} + // {{{name}}} ({{{dataType}}}) minimum + if ({{#useGenericHost}}{{^required}}this.{{{name}}}Option.IsSet && {{/required}}{{/useGenericHost}}this.{{{name}}}{{#useGenericHost}}{{^required}}Option.Value{{/required}}{{/useGenericHost}} {{#exclusiveMaximum}}>={{/exclusiveMaximum}}{{^exclusiveMaximum}}<{{/exclusiveMaximum}} ({{{dataType}}}){{minimum}}) + { + yield return new ValidationResult("Invalid value for {{{name}}}, must be a value greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}{{minimum}}.", new [] { "{{{name}}}" }); + } + + {{/minimum}} + {{#pattern}} + {{^isByteArray}} + {{#vendorExtensions.x-is-value-type}} + {{#isNullable}} + if (this.{{{name}}}{{#useGenericHost}}{{^required}}Option.Value{{/required}}{{/useGenericHost}} != null){ + {{#lambda.trimTrailingWithNewLine}} + {{#lambda.indent4}} + {{>ValidateRegex}}{{! prevent indent}} + {{/lambda.indent4}} + + {{/lambda.trimTrailingWithNewLine}} + } + + {{/isNullable}} + {{^isNullable}} + {{#lambda.trimTrailingWithNewLine}} + {{#lambda.indent3}} + {{>ValidateRegex}}{{! prevent indent}} + {{/lambda.indent3}} + + {{/lambda.trimTrailingWithNewLine}} + {{/isNullable}} + {{/vendorExtensions.x-is-value-type}} + {{^vendorExtensions.x-is-value-type}} + if (this.{{{name}}}{{#useGenericHost}}{{^required}}Option.Value{{/required}}{{/useGenericHost}} != null) { + {{#lambda.trimTrailingWithNewLine}} + {{#lambda.indent4}} + {{>ValidateRegex}}{{! prevent indent}} + {{/lambda.indent4}} + + {{/lambda.trimTrailingWithNewLine}} + } + + {{/vendorExtensions.x-is-value-type}} + {{/isByteArray}} + {{/pattern}} + {{/isEnum}} + {{/hasValidation}} + {{/vars}} + yield break; + } \ No newline at end of file diff --git a/templates-v7/csharp/visibility.mustache b/templates-v7/csharp/visibility.mustache new file mode 100644 index 000000000..a1d1f4163 --- /dev/null +++ b/templates-v7/csharp/visibility.mustache @@ -0,0 +1 @@ +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} \ No newline at end of file diff --git a/templates/csharp/AbstractOpenAPISchema.mustache b/templates/csharp/AbstractOpenAPISchema.mustache deleted file mode 100644 index ab90f174c..000000000 --- a/templates/csharp/AbstractOpenAPISchema.mustache +++ /dev/null @@ -1,71 +0,0 @@ -{{>partial_header}} -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace {{packageName}}.{{modelPackage}} -{ - /// - /// Abstract base class for oneOf, anyOf schemas in the OpenAPI specification - /// - {{>visibility}} abstract partial class AbstractOpenAPISchema - { - /// - /// Custom JSON serializer - /// - static public readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings - { - // OpenAPI generated types generally hide default constructors. - ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, - MissingMemberHandling = MissingMemberHandling.Ignore, - ContractResolver = new DefaultContractResolver - { - NamingStrategy = new CamelCaseNamingStrategy - { - OverrideSpecifiedNames = false - } - } - }; - - /// - /// Gets or Sets the actual instance - /// - public abstract Object ActualInstance { get; set; } - - /// - /// Gets or Sets IsNullable to indicate whether the instance is nullable - /// - public bool IsNullable { get; protected set; } - - /// - /// Gets or Sets the schema type, which can be either `oneOf` or `anyOf` - /// - public string SchemaType { get; protected set; } - - /// - /// Converts the instance into JSON string. - /// - public abstract string ToJson(); - - // Check if the contains TypeEnum value - protected static bool ContainsValue(string type) where T : struct, IConvertible - { - // Search for type in .TypeEnum - List list = new List(); - var members = typeof(T).GetTypeInfo().DeclaredMembers; - foreach (var member in members) - { - var val = member?.GetCustomAttribute(false)?.Value; - if (!string.IsNullOrEmpty(val)) - { - list.Add(val); - } - } - - return list.Contains(type); - } - } -} diff --git a/templates/csharp/api-single.mustache b/templates/csharp/api-single.mustache deleted file mode 100644 index da60553d0..000000000 --- a/templates/csharp/api-single.mustache +++ /dev/null @@ -1,87 +0,0 @@ -{{>partial_header}} -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Adyen.Constants; -using Adyen.Model; -{{#hasImport}}using Adyen.{{modelPackage}}; -{{/hasImport}} - -namespace {{packageName}}.Service -{ -{{#operations}} - /// - /// {{classname}} Interface - /// - public interface I{{customApi}}Service - { - {{#operation}} -{{>method_documentation}} - {{#returnType}} - /// . - {{/returnType}} - {{#isDeprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/isDeprecated}} - {{#returnType}}{{modelPackage}}.{{{.}}}{{/returnType}}{{^returnType}}void{{/returnType}} {{#lambda.pascalcase}}{{vendorExtensions.x-methodName}}{{/lambda.pascalcase}}({{>api_parameters}}); - - {{#supportsAsync}} -{{>method_documentation}} - /// A CancellationToken enables cooperative cancellation between threads, thread pool work items, or Task objects.{{#returnType}} - /// Task of .{{/returnType}} - {{#isDeprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/isDeprecated}} - {{#returnType}}Task<{{modelPackage}}.{{{.}}}>{{/returnType}}{{^returnType}}Task{{/returnType}} {{#lambda.pascalcase}}{{vendorExtensions.x-methodName}}{{/lambda.pascalcase}}Async({{>api_parameters_async}}); - - {{/supportsAsync}} - {{/operation}} - } - {{/operations}} - - {{#operations}} - /// - /// Represents a collection of functions to interact with the {{customApi}}Service API endpoints - /// - {{>visibility}} class {{customApi}}Service : AbstractService, I{{customApi}}Service - { - private readonly string _baseUrl; - - public {{customApi}}Service(Client client) : base(client) - { - _baseUrl = CreateBaseUrl("{{{basePath}}}"); - } - {{#operation}} - - {{#isDeprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/isDeprecated}} - public {{#returnType}}{{modelPackage}}.{{{.}}}{{/returnType}}{{^returnType}}void{{/returnType}} {{#lambda.pascalcase}}{{vendorExtensions.x-methodName}}{{/lambda.pascalcase}}({{>api_parameters}}) - { - {{#returnType}}return {{/returnType}}{{#lambda.pascalcase}}{{vendorExtensions.x-methodName}}{{/lambda.pascalcase}}Async({{>api_invoke}}).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - {{#supportsAsync}} - {{#isDeprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/isDeprecated}} - {{#returnType}}public async Task<{{modelPackage}}.{{{.}}}>{{/returnType}}{{^returnType}}public async Task{{/returnType}} {{#lambda.pascalcase}}{{vendorExtensions.x-methodName}}{{/lambda.pascalcase}}Async({{>api_parameters_async}}) - { - {{#hasQueryParams}} - // Build the query string - var queryParams = new Dictionary(); - {{#queryParams}} - {{^required}}if ({{paramName}} != null) {{/required}}queryParams.Add("{{baseName}}", {{paramName}}{{^isString}}{{^isDateTime}}.Value.ToString(){{/isDateTime}}{{#isDateTime}}.ToString("yyyy-MM-ddTHH:mm:ssZ"){{/isDateTime}}{{/isString}}); - {{/queryParams}} - {{/hasQueryParams}} - var endpoint = _baseUrl + {{#hasPathParams}}${{/hasPathParams}}"{{{path}}}"{{#hasQueryParams}} + ToQueryString(queryParams){{/hasQueryParams}}; - var resource = new ServiceResource(this, endpoint); - {{#returnType}}return {{/returnType}}await resource.RequestAsync{{#returnType}}<{{modelPackage}}.{{returnType}}>{{/returnType}}({{#bodyParam}}{{paramName}}.ToJson(){{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}, requestOptions, new HttpMethod("{{httpMethod}}"), cancellationToken).ConfigureAwait(false); - } - {{/supportsAsync}} - {{/operation}} - } - {{/operations}} -} \ No newline at end of file diff --git a/templates/csharp/api.mustache b/templates/csharp/api.mustache deleted file mode 100644 index 27ff0eb4d..000000000 --- a/templates/csharp/api.mustache +++ /dev/null @@ -1,86 +0,0 @@ -{{>partial_header}} -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Adyen.Model; -{{#hasImport}}using Adyen.{{modelPackage}}; -{{/hasImport}} - -namespace {{packageName}}.{{apiPackage}} -{ -{{#operations}} - /// - /// {{classname}} Interface - /// - public interface I{{classname}} - { - {{#operation}} -{{>method_documentation}} - {{#returnType}} - /// . - {{/returnType}} - {{#isDeprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/isDeprecated}} - {{#returnType}}{{modelPackage}}.{{{.}}}{{/returnType}}{{^returnType}}void{{/returnType}} {{#lambda.pascalcase}}{{vendorExtensions.x-methodName}}{{/lambda.pascalcase}}({{>api_parameters}}); - - {{#supportsAsync}} -{{>method_documentation}} - /// A CancellationToken enables cooperative cancellation between threads, thread pool work items, or Task objects.{{#returnType}} - /// Task of .{{/returnType}} - {{#isDeprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/isDeprecated}} - {{#returnType}}Task<{{modelPackage}}.{{{.}}}>{{/returnType}}{{^returnType}}Task{{/returnType}} {{#lambda.pascalcase}}{{vendorExtensions.x-methodName}}{{/lambda.pascalcase}}Async({{>api_parameters_async}}); - - {{/supportsAsync}} - {{/operation}} - } - {{/operations}} - - {{#operations}} - /// - /// Represents a collection of functions to interact with the {{classname}} API endpoints - /// - {{>visibility}} class {{classname}} : AbstractService, I{{classname}} - { - private readonly string _baseUrl; - - public {{classname}}(Client client) : base(client) - { - _baseUrl = CreateBaseUrl("{{{basePath}}}"); - } - {{#operation}} - - {{#isDeprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/isDeprecated}} - public {{#returnType}}{{modelPackage}}.{{{.}}}{{/returnType}}{{^returnType}}void{{/returnType}} {{#lambda.pascalcase}}{{vendorExtensions.x-methodName}}{{/lambda.pascalcase}}({{>api_parameters}}) - { - {{#returnType}}return {{/returnType}}{{#lambda.pascalcase}}{{vendorExtensions.x-methodName}}{{/lambda.pascalcase}}Async({{>api_invoke}}).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - {{#supportsAsync}} - {{#isDeprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/isDeprecated}} - {{#returnType}}public async Task<{{modelPackage}}.{{{.}}}>{{/returnType}}{{^returnType}}public async Task{{/returnType}} {{#lambda.pascalcase}}{{vendorExtensions.x-methodName}}{{/lambda.pascalcase}}Async({{>api_parameters_async}}) - { - {{#hasQueryParams}} - // Build the query string - var queryParams = new Dictionary(); - {{#queryParams}} - {{^required}}if ({{paramName}} != null) {{/required}}queryParams.Add("{{baseName}}", {{paramName}}{{^isString}}{{^isDateTime}}.ToString(){{/isDateTime}}{{#isDateTime}}{{^required}}.Value{{/required}}.ToString("yyyy-MM-ddTHH:mm:ssZ"){{/isDateTime}}{{/isString}}); - {{/queryParams}} - {{/hasQueryParams}} - var endpoint = _baseUrl + {{#hasPathParams}}${{/hasPathParams}}"{{{path}}}"{{#hasQueryParams}} + ToQueryString(queryParams){{/hasQueryParams}}; - var resource = new ServiceResource(this, endpoint); - {{#returnType}}return {{/returnType}}await resource.RequestAsync{{#returnType}}<{{modelPackage}}.{{returnType}}>{{/returnType}}({{#bodyParam}}{{paramName}}.ToJson(){{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}, requestOptions, new HttpMethod("{{httpMethod}}"), cancellationToken).ConfigureAwait(false); - } - {{/supportsAsync}} - {{/operation}} - } - {{/operations}} -} \ No newline at end of file diff --git a/templates/csharp/api_invoke.mustache b/templates/csharp/api_invoke.mustache deleted file mode 100644 index 45815fc53..000000000 --- a/templates/csharp/api_invoke.mustache +++ /dev/null @@ -1 +0,0 @@ -{{#requiredParams}}{{#isPathParam}}{{paramName}}, {{/isPathParam}}{{#isBodyParam}}{{paramName}}, {{/isBodyParam}}{{#isQueryParam}}{{paramName}}, {{/isQueryParam}}{{/requiredParams}}{{#optionalParams}}{{#isPathParam}}{{paramName}}, {{/isPathParam}}{{#isBodyParam}}{{paramName}}, {{/isBodyParam}}{{#isQueryParam}}{{paramName}}, {{/isQueryParam}}{{/optionalParams}}requestOptions \ No newline at end of file diff --git a/templates/csharp/api_parameter_ordering.mustache b/templates/csharp/api_parameter_ordering.mustache deleted file mode 100644 index 593846f25..000000000 --- a/templates/csharp/api_parameter_ordering.mustache +++ /dev/null @@ -1 +0,0 @@ -{{#isPathParam}}{{{dataType}}} {{paramName}} = default, {{/isPathParam}}{{#isBodyParam}}{{{dataType}}} {{paramName}} = default, {{/isBodyParam}}{{#isQueryParam}}{{{dataType}}} {{paramName}} = default, {{/isQueryParam}} \ No newline at end of file diff --git a/templates/csharp/api_parameter_ordering_required.mustache b/templates/csharp/api_parameter_ordering_required.mustache deleted file mode 100644 index 0c1ecfea1..000000000 --- a/templates/csharp/api_parameter_ordering_required.mustache +++ /dev/null @@ -1 +0,0 @@ -{{#isPathParam}}{{{dataType}}} {{paramName}}, {{/isPathParam}}{{#isBodyParam}}{{{dataType}}} {{paramName}}, {{/isBodyParam}}{{#isQueryParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}} \ No newline at end of file diff --git a/templates/csharp/api_parameters.mustache b/templates/csharp/api_parameters.mustache deleted file mode 100644 index 3ca742884..000000000 --- a/templates/csharp/api_parameters.mustache +++ /dev/null @@ -1,2 +0,0 @@ -{{! Path and body are required, followed by optional query string and request options }} -{{#requiredParams}}{{>api_parameter_ordering_required}}{{/requiredParams}}{{#optionalParams}}{{>api_parameter_ordering}}{{/optionalParams}}RequestOptions requestOptions = default \ No newline at end of file diff --git a/templates/csharp/api_parameters_async.mustache b/templates/csharp/api_parameters_async.mustache deleted file mode 100644 index 4287dc8b4..000000000 --- a/templates/csharp/api_parameters_async.mustache +++ /dev/null @@ -1,2 +0,0 @@ -{{! Path and body are required, followed by optional query string and request options }} -{{#requiredParams}}{{>api_parameter_ordering_required}}{{/requiredParams}}{{#optionalParams}}{{>api_parameter_ordering}}{{/optionalParams}}RequestOptions requestOptions = default, CancellationToken cancellationToken = default \ No newline at end of file diff --git a/templates/csharp/config.yaml b/templates/csharp/config.yaml deleted file mode 100644 index 829b2714b..000000000 --- a/templates/csharp/config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -templateDir: ./templates/csharp -files: - api-single.mustache: - folder: api - templateType: API - destinationFilename: Single.cs \ No newline at end of file diff --git a/templates/csharp/method_documentation.mustache b/templates/csharp/method_documentation.mustache deleted file mode 100644 index 537b3924e..000000000 --- a/templates/csharp/method_documentation.mustache +++ /dev/null @@ -1,13 +0,0 @@ - /// - /// {{{summary}}} - /// - {{#pathParams}} - /// - {{description}}{{#isDeprecated}} (deprecated){{/isDeprecated}} - {{/pathParams}} - {{#bodyParams}} - /// - {{description}}{{#isDeprecated}} (deprecated){{/isDeprecated}} - {{/bodyParams}} - {{#queryParams}} - /// - {{description}}{{#isDeprecated}} (deprecated){{/isDeprecated}} - {{/queryParams}} - /// - Additional request options. \ No newline at end of file diff --git a/templates/csharp/model.mustache b/templates/csharp/model.mustache deleted file mode 100644 index 51a2ad036..000000000 --- a/templates/csharp/model.mustache +++ /dev/null @@ -1,49 +0,0 @@ -{{>partial_header}} -{{#models}} -{{#model}} -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.IO; -{{#vendorExtensions.x-com-visible}} -using System.Runtime.InteropServices; -{{/vendorExtensions.x-com-visible}} -using System.Runtime.Serialization; -using System.Text; -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Linq; -{{#discriminator}} -using JsonSubTypes; -{{/discriminator}} -{{/model}} -{{/models}} -{{#validatable}} -using System.ComponentModel.DataAnnotations; -{{/validatable}} -using OpenAPIDateConverter = Adyen.ApiSerialization.OpenAPIDateConverter; -{{#useCompareNetObjects}} -using OpenAPIClientUtils = {{packageName}}.Client.ClientUtils; -{{/useCompareNetObjects}} -{{#models}} -{{#model}} -{{#oneOf}} -{{#-first}} -using System.Reflection; -{{/-first}} -{{/oneOf}} -{{#anyOf}} -{{#-first}} -using System.Reflection; -{{/-first}} -{{/anyOf}} - -namespace {{packageName}}.{{modelPackage}} -{ -{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{#anyOf}}{{#-first}}{{>modelAnyOf}}{{/-first}}{{/anyOf}}{{^oneOf}}{{^anyOf}}{{>modelGeneric}}{{/anyOf}}{{/oneOf}}{{/isEnum}} -{{/model}} -{{/models}} -} diff --git a/templates/csharp/modelGeneric.mustache b/templates/csharp/modelGeneric.mustache deleted file mode 100644 index ffeedcb6e..000000000 --- a/templates/csharp/modelGeneric.mustache +++ /dev/null @@ -1,384 +0,0 @@ - /// - /// {{description}}{{^description}}{{classname}}{{/description}} - /// - {{#vendorExtensions.x-cls-compliant}} - [CLSCompliant({{{vendorExtensions.x-cls-compliant}}})] - {{/vendorExtensions.x-cls-compliant}} - {{#vendorExtensions.x-com-visible}} - [ComVisible({{{vendorExtensions.x-com-visible}}})] - {{/vendorExtensions.x-com-visible}} - [DataContract(Name = "{{{name}}}")] - {{#discriminator}} - [JsonConverter(typeof(JsonSubtypes), "{{{discriminatorName}}}")] - {{#mappedModels}} - [JsonSubtypes.KnownSubType(typeof({{{modelName}}}), "{{^vendorExtensions.x-discriminator-value}}{{{mappingName}}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{.}}}{{/vendorExtensions.x-discriminator-value}}")] - {{/mappedModels}} - {{/discriminator}} - {{>visibility}} partial class {{classname}} : {{#parent}}{{{.}}}, {{/parent}}IEquatable<{{classname}}>{{#validatable}}, IValidatableObject{{/validatable}} - { - {{#vars}} - {{#items.isEnum}} - {{#items}} - {{^complexType}} -{{>modelInnerEnum}} - {{/complexType}} - {{/items}} - {{/items.isEnum}} - {{#isEnum}} - {{^complexType}} -{{>modelInnerEnum}} - {{/complexType}} - {{/isEnum}} - {{#isEnum}} - - /// - /// {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} - /// - {{#description}} - /// {{.}} - {{/description}} - {{^conditionalSerialization}} - [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = false{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#required}}false{{/required}}{{^required}}{{#isBoolean}}false{{/isBoolean}}{{^isBoolean}}{{#isNullable}}false{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/required}}{{/vendorExtensions.x-emit-default-value}})] - {{#deprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/deprecated}} - public {{{complexType}}}{{^complexType}}{{{datatypeWithEnum}}}{{/complexType}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}} {{name}} { get; set; } - {{#isReadOnly}} - - /// - /// Returns false as {{name}} should not be serialized given that it's read-only. - /// - /// false (boolean) - public bool ShouldSerialize{{name}}() - { - return false; - } - {{/isReadOnly}} - {{/conditionalSerialization}} - {{#conditionalSerialization}} - {{#isReadOnly}} - [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = false{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#required}}false{{/required}}{{^required}}{{#isBoolean}}false{{/isBoolean}}{{^isBoolean}}{{#isNullable}}false{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/required}}{{/vendorExtensions.x-emit-default-value}})] - {{#deprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/deprecated}} - public {{{complexType}}}{{^complexType}}{{{datatypeWithEnum}}}{{/complexType}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}} {{name}} { get; set; } - - /// - /// Returns false as {{name}} should not be serialized given that it's read-only. - /// - /// false (boolean) - public bool ShouldSerialize{{name}}() - { - return false; - } - {{/isReadOnly}} - - {{^isReadOnly}} - [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = false{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#required}}true{{/required}}{{^required}}{{#isBoolean}}false{{/isBoolean}}{{^isBoolean}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/required}}{{/vendorExtensions.x-emit-default-value}})] - {{#deprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/deprecated}} - public {{{complexType}}}{{^complexType}}{{{datatypeWithEnum}}}{{/complexType}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}} {{name}} - { - get{ return _{{name}};} - set - { - _{{name}} = value; - _flag{{name}} = true; - } - } - private {{{complexType}}}{{^complexType}}{{{datatypeWithEnum}}}{{/complexType}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}} _{{name}}; - private bool _flag{{name}}; - - /// - /// Returns false as {{name}} should not be serialized given that it's read-only. - /// - /// false (boolean) - public bool ShouldSerialize{{name}}() - { - return _flag{{name}}; - } - {{/isReadOnly}} - {{/conditionalSerialization}} - {{/isEnum}} - {{/vars}} - {{#hasRequired}} - {{^hasOnlyReadOnly}} - /// - /// Initializes a new instance of the class. - /// - [JsonConstructorAttribute] - {{^isAdditionalPropertiesTrue}} - protected {{classname}}() { } - {{/isAdditionalPropertiesTrue}} - {{#isAdditionalPropertiesTrue}} - protected {{classname}}() - { - this.AdditionalProperties = new Dictionary(); - } - {{/isAdditionalPropertiesTrue}} - {{/hasOnlyReadOnly}} - {{/hasRequired}} - /// - /// Initializes a new instance of the class. - /// - {{#readWriteVars}} - /// {{description}}{{^description}}{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}{{/description}}{{#required}} (required){{/required}}{{#defaultValue}} (default to {{.}}){{/defaultValue}}. - {{/readWriteVars}} - {{#hasOnlyReadOnly}} - [JsonConstructorAttribute] - {{/hasOnlyReadOnly}} - public {{classname}}({{#readWriteVars}}{{{datatypeWithEnum}}}{{#isNumeric}}?{{/isNumeric}}{{#isBoolean}}{{^isNullable}}?{{/isNullable}}{{/isBoolean}}{{#isEnum}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}}{{/isEnum}} {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} = {{#defaultValue}}{{^isDateTime}}{{{defaultValue}}}{{/isDateTime}}{{#isDateTime}}default({{{datatypeWithEnum}}}){{/isDateTime}}{{/defaultValue}}{{^defaultValue}}default({{{datatypeWithEnum}}}{{#isNumeric}}?{{/isNumeric}}{{#isBoolean}}{{^isNullable}}?{{/isNullable}}{{/isBoolean}}{{#isEnum}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}}{{/isEnum}}){{/defaultValue}}{{^-last}}, {{/-last}}{{/readWriteVars}}){{#parent}} : base({{#parentVars}}{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}{{^-last}}, {{/-last}}{{/parentVars}}){{/parent}} - { - {{#vars}} - {{^isInherited}} - {{^isReadOnly}} - {{#required}} - {{^conditionalSerialization}} - {{^vendorExtensions.x-csharp-value-type}} - this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}; - {{/vendorExtensions.x-csharp-value-type}} - {{#vendorExtensions.x-csharp-value-type}} - this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}; - {{/vendorExtensions.x-csharp-value-type}} - {{/conditionalSerialization}} - {{#conditionalSerialization}} - {{^vendorExtensions.x-csharp-value-type}} - this._{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}; - {{/vendorExtensions.x-csharp-value-type}} - {{#vendorExtensions.x-csharp-value-type}} - this._{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}; - {{/vendorExtensions.x-csharp-value-type}} - {{/conditionalSerialization}} - {{/required}} - {{/isReadOnly}} - {{/isInherited}} - {{/vars}} - {{#vars}} - {{^isInherited}} - {{^isReadOnly}} - {{^required}} - {{#defaultValue}} - {{^conditionalSerialization}} - {{^vendorExtensions.x-csharp-value-type}} - // use default value if no "{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}" provided - this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} ?? {{{defaultValue}}}; - {{/vendorExtensions.x-csharp-value-type}} - {{#vendorExtensions.x-csharp-value-type}} - this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}; - {{/vendorExtensions.x-csharp-value-type}} - {{/conditionalSerialization}} - {{/defaultValue}} - {{^defaultValue}} - {{^conditionalSerialization}} - this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}; - {{/conditionalSerialization}} - {{#conditionalSerialization}} - this._{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}; - if (this.{{name}} != null) - { - this._flag{{name}} = true; - } - {{/conditionalSerialization}} - {{/defaultValue}} - {{/required}} - {{/isReadOnly}} - {{/isInherited}} - {{/vars}} - {{#isAdditionalPropertiesTrue}} - this.AdditionalProperties = new Dictionary(); - {{/isAdditionalPropertiesTrue}} - } - - {{#vars}} - {{^isInherited}} - {{^isEnum}} - /// - /// {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} - /// {{#description}} - /// {{.}}{{/description}} - {{^conditionalSerialization}} - [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = false{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#required}}false{{/required}}{{^required}}{{#isBoolean}}false{{/isBoolean}}{{^isBoolean}}{{#isNullable}}false{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/required}}{{/vendorExtensions.x-emit-default-value}})] - {{#isDate}} - [JsonConverter(typeof(OpenAPIDateConverter))] - {{/isDate}} - {{#deprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/deprecated}} - public {{{dataType}}}{{#isNumeric}}?{{/isNumeric}}{{#isBoolean}}{{^isNullable}}?{{/isNullable}}{{/isBoolean}} {{name}} { get; {{#isReadOnly}}private {{/isReadOnly}}set; } - - {{/conditionalSerialization}} - {{#conditionalSerialization}} - {{#isReadOnly}} - [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = false{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#required}}true{{/required}}{{^required}}{{#isBoolean}}false{{/isBoolean}}{{^isBoolean}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/required}}{{/vendorExtensions.x-emit-default-value}})] - {{#isDate}} - [JsonConverter(typeof(OpenAPIDateConverter))] - {{/isDate}} - {{#deprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/deprecated}} - public {{{dataType}}} {{name}} { get; private set; } - - {{/isReadOnly}} - {{^isReadOnly}} - {{#isDate}} - [JsonConverter(typeof(OpenAPIDateConverter))] - {{/isDate}} - [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = false{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#required}}false{{/required}}{{^required}}{{#isBoolean}}false{{/isBoolean}}{{^isBoolean}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/required}}{{/vendorExtensions.x-emit-default-value}})] - {{#deprecated}} - [Obsolete("{{#vendorExtensions.x-deprecatedInVersion}}Deprecated since {{#appName}}{{{.}}}{{/appName}} v{{#vendorExtensions.x-deprecatedInVersion}}{{.}}{{/vendorExtensions.x-deprecatedInVersion}}.{{/vendorExtensions.x-deprecatedInVersion}}{{#vendorExtensions.x-deprecatedMessage}} {{{.}}}{{/vendorExtensions.x-deprecatedMessage}}")] - {{/deprecated}} - public {{{dataType}}} {{name}} - { - get{ return _{{name}};} - set - { - _{{name}} = value; - _flag{{name}} = true; - } - } - private {{{dataType}}} _{{name}}; - private bool _flag{{name}}; - - /// - /// Returns false as {{name}} should not be serialized given that it's read-only. - /// - /// false (boolean) - public bool ShouldSerialize{{name}}() - { - return _flag{{name}}; - } - {{/isReadOnly}} - {{/conditionalSerialization}} - {{/isEnum}} - {{/isInherited}} - {{/vars}} - {{#isAdditionalPropertiesTrue}} - /// - /// Gets or Sets additional properties - /// - [JsonExtensionData] - public IDictionary AdditionalProperties { get; set; } - - {{/isAdditionalPropertiesTrue}} - /// - /// Returns the string presentation of the object - /// - /// String presentation of the object - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.Append("class {{classname}} {\n"); - {{#parent}} - sb.Append(" ").Append(base.ToString().Replace("\n", "\n ")).Append("\n"); - {{/parent}} - {{#vars}} - sb.Append(" {{name}}: ").Append({{name}}).Append("\n"); - {{/vars}} - {{#isAdditionalPropertiesTrue}} - sb.Append(" AdditionalProperties: ").Append(AdditionalProperties).Append("\n"); - {{/isAdditionalPropertiesTrue}} - sb.Append("}\n"); - return sb.ToString(); - } - - /// - /// Returns the JSON string presentation of the object - /// - /// JSON string presentation of the object - public {{#parent}}{{^isArray}}{{^isMap}}override {{/isMap}}{{/isArray}}{{/parent}}{{^parent}}virtual {{/parent}}string ToJson() - { - return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); - } - - /// - /// Returns true if objects are equal - /// - /// Object to be compared - /// Boolean - public override bool Equals(object input) - { - {{#useCompareNetObjects}} - return OpenAPIClientUtils.compareLogic.Compare(this, input as {{classname}}).AreEqual; - {{/useCompareNetObjects}} - {{^useCompareNetObjects}} - return this.Equals(input as {{classname}}); - {{/useCompareNetObjects}} - } - - /// - /// Returns true if {{classname}} instances are equal - /// - /// Instance of {{classname}} to be compared - /// Boolean - public bool Equals({{classname}} input) - { - {{#useCompareNetObjects}} - return OpenAPIClientUtils.compareLogic.Compare(this, input).AreEqual; - {{/useCompareNetObjects}} - {{^useCompareNetObjects}} - if (input == null) - { - return false; - } - return {{#vars}}{{#parent}}base.Equals(input) && {{/parent}}{{^isContainer}} - ( - this.{{name}} == input.{{name}} || - {{^vendorExtensions.x-is-value-type}} - (this.{{name}} != null && - this.{{name}}.Equals(input.{{name}})) - {{/vendorExtensions.x-is-value-type}} - {{#vendorExtensions.x-is-value-type}} - this.{{name}}.Equals(input.{{name}}) - {{/vendorExtensions.x-is-value-type}} - ){{^-last}} && {{/-last}}{{/isContainer}}{{#isContainer}} - ( - this.{{name}} == input.{{name}} || - {{^vendorExtensions.x-is-value-type}}this.{{name}} != null && - input.{{name}} != null && - {{/vendorExtensions.x-is-value-type}}this.{{name}}.SequenceEqual(input.{{name}}) - ){{^-last}} && {{/-last}}{{/isContainer}}{{/vars}}{{^vars}}{{#parent}}base.Equals(input){{/parent}}{{^parent}}false{{/parent}}{{/vars}}{{^isAdditionalPropertiesTrue}};{{/isAdditionalPropertiesTrue}} - {{#isAdditionalPropertiesTrue}} - && (this.AdditionalProperties.Count == input.AdditionalProperties.Count && !this.AdditionalProperties.Except(input.AdditionalProperties).Any()); - {{/isAdditionalPropertiesTrue}} - {{/useCompareNetObjects}} - } - - /// - /// Gets the hash code - /// - /// Hash code - public override int GetHashCode() - { - unchecked // Overflow is fine, just wrap - { - {{#parent}} - int hashCode = base.GetHashCode(); - {{/parent}} - {{^parent}} - int hashCode = 41; - {{/parent}} - {{#vars}} - {{^vendorExtensions.x-is-value-type}} - if (this.{{name}} != null) - { - hashCode = (hashCode * 59) + this.{{name}}.GetHashCode(); - } - {{/vendorExtensions.x-is-value-type}} - {{#vendorExtensions.x-is-value-type}} - hashCode = (hashCode * 59) + this.{{name}}.GetHashCode(); - {{/vendorExtensions.x-is-value-type}} - {{/vars}} - {{#isAdditionalPropertiesTrue}} - if (this.AdditionalProperties != null) - { - hashCode = (hashCode * 59) + this.AdditionalProperties.GetHashCode(); - } - {{/isAdditionalPropertiesTrue}} - return hashCode; - } - } -{{#validatable}} -{{>validatable}} -{{/validatable}} - } diff --git a/templates/csharp/modelOneOf.mustache b/templates/csharp/modelOneOf.mustache deleted file mode 100644 index 66721caf6..000000000 --- a/templates/csharp/modelOneOf.mustache +++ /dev/null @@ -1,286 +0,0 @@ -{{#model}} - /// - /// {{description}}{{^description}}{{classname}}{{/description}} - /// - {{#vendorExtensions.x-cls-compliant}} - [CLSCompliant({{{.}}})] - {{/vendorExtensions.x-cls-compliant}} - {{#vendorExtensions.x-com-visible}} - [ComVisible({{{.}}})] - {{/vendorExtensions.x-com-visible}} - [JsonConverter(typeof({{classname}}JsonConverter))] - [DataContract(Name = "{{{name}}}")] - {{>visibility}} partial class {{classname}} : AbstractOpenAPISchema, {{#parent}}{{{.}}}, {{/parent}}IEquatable<{{classname}}>{{#validatable}}, IValidatableObject{{/validatable}} - { - {{#isNullable}} - /// - /// Initializes a new instance of the class. - /// - public {{classname}}() - { - this.IsNullable = true; - this.SchemaType= "oneOf"; - } - - {{/isNullable}} - {{#composedSchemas.oneOf}} - {{^isNull}} - /// - /// Initializes a new instance of the class - /// with the class - /// - /// An instance of {{dataType}}. - public {{classname}}({{{dataType}}} actualInstance) - { - this.IsNullable = {{#model.isNullable}}true{{/model.isNullable}}{{^model.isNullable}}false{{/model.isNullable}}; - this.SchemaType= "oneOf"; - this.ActualInstance = actualInstance{{^model.isNullable}}{{^isPrimitiveType}} ?? throw new ArgumentException("Invalid instance found. Must not be null."){{/isPrimitiveType}}{{#isPrimitiveType}}{{#isArray}} ?? throw new ArgumentException("Invalid instance found. Must not be null."){{/isArray}}{{/isPrimitiveType}}{{#isPrimitiveType}}{{#isFreeFormObject}} ?? throw new ArgumentException("Invalid instance found. Must not be null."){{/isFreeFormObject}}{{/isPrimitiveType}}{{#isPrimitiveType}}{{#isString}} ?? throw new ArgumentException("Invalid instance found. Must not be null."){{/isString}}{{/isPrimitiveType}}{{/model.isNullable}}; - } - - {{/isNull}} - {{/composedSchemas.oneOf}} - - private Object _actualInstance; - - /// - /// Gets or Sets ActualInstance - /// - public override Object ActualInstance - { - get - { - return _actualInstance; - } - set - { - {{#oneOf}} - {{^-first}}else {{/-first}}if (value.GetType() == typeof({{{.}}})) - { - this._actualInstance = value; - } - {{/oneOf}} - else - { - throw new ArgumentException("Invalid instance found. Must be the following types:{{#oneOf}} {{{.}}}{{^-last}},{{/-last}}{{/oneOf}}"); - } - } - } - {{#composedSchemas.oneOf}} - {{^isNull}} - - /// - /// Get the actual instance of `{{dataType}}`. If the actual instance is not `{{dataType}}`, - /// the InvalidClassException will be thrown - /// - /// An instance of {{dataType}} - public {{{dataType}}} Get{{#lambda.titlecase}}{{baseType}}{{/lambda.titlecase}}{{#isArray}}{{#lambda.titlecase}}{{{dataFormat}}}{{/lambda.titlecase}}{{/isArray}}() - { - return ({{{dataType}}})this.ActualInstance; - } - {{/isNull}} - {{/composedSchemas.oneOf}} - - /// - /// Returns the string presentation of the object - /// - /// String presentation of the object - public override string ToString() - { - var sb = new StringBuilder(); - sb.Append("class {{classname}} {\n"); - sb.Append(" ActualInstance: ").Append(this.ActualInstance).Append("\n"); - sb.Append("}\n"); - return sb.ToString(); - } - - /// - /// Returns the JSON string presentation of the object - /// - /// JSON string presentation of the object - public override string ToJson() - { - return JsonConvert.SerializeObject(this.ActualInstance, {{classname}}.SerializerSettings); - } - - /// - /// Converts the JSON string into an instance of {{classname}} - /// - /// JSON string - /// An instance of {{classname}} - public static {{classname}} FromJson(string jsonString) - { - {{classname}} new{{classname}} = null; - - if (string.IsNullOrEmpty(jsonString)) - { - return new{{classname}}; - } - {{#useOneOfDiscriminatorLookup}} - {{#discriminator}} - - try - { - var discriminatorObj = JObject.Parse(jsonString)["{{{propertyBaseName}}}"]; - string discriminatorValue = discriminatorObj == null ?string.Empty :discriminatorObj.ToString(); - switch (discriminatorValue) - { - {{#mappedModels}} - case "{{{mappingName}}}": - new{{classname}} = new {{classname}}(JsonConvert.DeserializeObject<{{{modelName}}}>(jsonString, {{classname}}.AdditionalPropertiesSerializerSettings)); - return new{{classname}}; - {{/mappedModels}} - default: - System.Diagnostics.Debug.WriteLine(string.Format("Failed to lookup discriminator value `{0}` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", discriminatorValue)); - break; - } - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine(string.Format("Failed to parse the json data : `{0}` {1}", jsonString, ex.ToString())); - } - - {{/discriminator}} - {{/useOneOfDiscriminatorLookup}} - int match = 0; - List matchedTypes = new List(); - JToken typeToken = JObject.Parse(jsonString).GetValue("type"); - string type = typeToken?.Value(); - // Throw exception if jsonString does not contain type param - if (type == null) - { - throw new InvalidDataException("JsonString does not contain required enum type for deserialization."); - } - try - { - {{#oneOf}} - // Check if the jsonString type enum matches the {{{.}}} type enums - if (ContainsValue<{{{.}}}.TypeEnum>(type)) - { - new{{classname}} = new {{classname}}(JsonConvert.DeserializeObject<{{{.}}}>(jsonString, {{classname}}.SerializerSettings)); - matchedTypes.Add("{{{.}}}"); - match++; - } - {{/oneOf}} - } - catch (Exception ex) - { - if (!(ex is JsonSerializationException)) - { - throw new InvalidDataException(string.Format("Failed to deserialize `{0}` into target: {1}", jsonString, ex.ToString())); - } - } - - if (match != 1) - { - throw new InvalidDataException("The JSON string `" + jsonString + "` cannot be deserialized into any schema defined. MatchedTypes are: " + matchedTypes); - } - - // deserialization is considered successful at this point if no exception has been thrown. - return new{{classname}}; - } - - /// - /// Returns true if objects are equal - /// - /// Object to be compared - /// Boolean - public override bool Equals(object input) - { - {{#useCompareNetObjects}} - return OpenAPIClientUtils.compareLogic.Compare(this, input as {{classname}}).AreEqual; - {{/useCompareNetObjects}} - {{^useCompareNetObjects}} - return this.Equals(input as {{classname}}); - {{/useCompareNetObjects}} - } - - /// - /// Returns true if {{classname}} instances are equal - /// - /// Instance of {{classname}} to be compared - /// Boolean - public bool Equals({{classname}} input) - { - {{#useCompareNetObjects}} - return OpenAPIClientUtils.compareLogic.Compare(this, input).AreEqual; - {{/useCompareNetObjects}} - {{^useCompareNetObjects}} - if (input == null) - return false; - - return this.ActualInstance.Equals(input.ActualInstance); - {{/useCompareNetObjects}} - } - - /// - /// Gets the hash code - /// - /// Hash code - public override int GetHashCode() - { - unchecked // Overflow is fine, just wrap - { - int hashCode = 41; - if (this.ActualInstance != null) - hashCode = hashCode * 59 + this.ActualInstance.GetHashCode(); - return hashCode; - } - } - - {{#validatable}} - /// - /// To validate all properties of the instance - /// - /// Validation context - /// Validation Result - IEnumerable IValidatableObject.Validate(ValidationContext validationContext) - { - yield break; - } - {{/validatable}} - } - - /// - /// Custom JSON converter for {{classname}} - /// - public class {{classname}}JsonConverter : JsonConverter - { - /// - /// To write the JSON string - /// - /// JSON writer - /// Object to be converted into a JSON string - /// JSON Serializer - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteRawValue((string)(typeof({{classname}}).GetMethod("ToJson").Invoke(value, null))); - } - - /// - /// To convert a JSON string into an object - /// - /// JSON reader - /// Object type - /// Existing value - /// JSON Serializer - /// The object converted from the JSON string - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - if(reader.TokenType != JsonToken.Null) - { - return {{classname}}.FromJson(JObject.Load(reader).ToString(Formatting.None)); - } - return null; - } - - /// - /// Check if the object can be converted - /// - /// Object type - /// True if the object can be converted - public override bool CanConvert(Type objectType) - { - return false; - } - } -{{/model}} diff --git a/templates/csharp/partial_header.mustache b/templates/csharp/partial_header.mustache deleted file mode 100644 index a4c7dd929..000000000 --- a/templates/csharp/partial_header.mustache +++ /dev/null @@ -1,12 +0,0 @@ -/* -{{#appName}} -* {{{.}}} -* -{{/appName}} -* -* {{#version}}The version of the OpenAPI document: {{{.}}}{{/version}} -* -* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). -* https://openapi-generator.tech -* Do not edit the class manually. -*/ From 33998d6ac7d7724c6c9700378d5f2b43249bbba7 Mon Sep 17 00:00:00 2001 From: Kwok He <105217051+Kwok-he-Chu@users.noreply.github.com> Date: Fri, 28 Nov 2025 13:46:05 +0100 Subject: [PATCH 4/4] Remove HttpSigningConfiguration and HttpSigningToken mustache templates --- .../generichost/ApiTestsBase.mustache | 66 -- .../DependencyInjectionTests.mustache | 211 ------ .../HttpSigningConfiguration.mustache | 679 ------------------ .../generichost/HttpSigningToken.mustache | 45 -- .../generichost/README.client.mustache | 2 +- .../csharp/libraries/generichost/api.mustache | 23 +- 6 files changed, 4 insertions(+), 1022 deletions(-) delete mode 100644 templates-v7/csharp/libraries/generichost/ApiTestsBase.mustache delete mode 100644 templates-v7/csharp/libraries/generichost/DependencyInjectionTests.mustache delete mode 100644 templates-v7/csharp/libraries/generichost/HttpSigningConfiguration.mustache delete mode 100644 templates-v7/csharp/libraries/generichost/HttpSigningToken.mustache diff --git a/templates-v7/csharp/libraries/generichost/ApiTestsBase.mustache b/templates-v7/csharp/libraries/generichost/ApiTestsBase.mustache deleted file mode 100644 index 789de490e..000000000 --- a/templates-v7/csharp/libraries/generichost/ApiTestsBase.mustache +++ /dev/null @@ -1,66 +0,0 @@ -{{>partial_header}} -using System; -using System.Collections.Generic; -using System.Security.Cryptography; -using Microsoft.Extensions.Hosting; -using {{packageName}}.{{clientPackage}};{{#hasImport}} -using {{packageName}}.{{modelPackage}};{{/hasImport}} -using {{packageName}}.Extensions; - - -{{>testInstructions}} - - - -namespace {{packageName}}.Test.{{apiPackage}} -{ - /// - /// Base class for API tests - /// - public class ApiTestsBase - { - protected readonly IHost _host; - - public ApiTestsBase(string[] args) - { - _host = CreateHostBuilder(args).Build(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) - .Configure{{apiName}}((context, services, options) => - { - {{#lambda.trimTrailingWithNewLine}} - {{#apiKeyMethods}} - string apiKeyTokenValue{{-index}} = context.Configuration[""] ?? throw new Exception("Token not found."); - ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}(apiKeyTokenValue{{-index}}, ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(apiKeyToken{{-index}}); - - {{/apiKeyMethods}} - {{#httpBearerMethods}} - string bearerTokenValue{{-index}} = context.Configuration[""] ?? throw new Exception("Token not found."); - BearerToken bearerToken{{-index}} = new{{^net70OrLater}} BearerToken{{/net70OrLater}}(bearerTokenValue{{-index}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(bearerToken{{-index}}); - - {{/httpBearerMethods}} - {{#httpBasicMethods}} - string basicTokenUsername{{-index}} = context.Configuration[""] ?? throw new Exception("Username not found."); - string basicTokenPassword{{-index}} = context.Configuration[""] ?? throw new Exception("Password not found."); - BasicToken basicToken{{-index}} = new{{^net70OrLater}} BasicToken{{/net70OrLater}}(basicTokenUsername{{-index}}, basicTokenPassword{{-index}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(basicToken{{-index}}); - - {{/httpBasicMethods}} - {{#httpSignatureMethods}} - HttpSigningConfiguration config{{-index}} = new{{^net70OrLater}} HttpSigningConfiguration{{/net70OrLater}}("", "", null, new List(), HashAlgorithmName.SHA256, "", 0); - HttpSignatureToken httpSignatureToken{{-index}} = new{{^net70OrLater}} HttpSignatureToken{{/net70OrLater}}(config{{-index}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(httpSignatureToken{{-index}}); - - {{/httpSignatureMethods}} - {{#oauthMethods}} - string oauthTokenValue{{-index}} = context.Configuration[""] ?? throw new Exception("Token not found."); - OAuthToken oauthToken{{-index}} = new{{^net70OrLater}} OAuthToken{{/net70OrLater}}(oauthTokenValue{{-index}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(oauthToken{{-index}}); - {{/oauthMethods}} - {{/lambda.trimTrailingWithNewLine}} - }); - } -} diff --git a/templates-v7/csharp/libraries/generichost/DependencyInjectionTests.mustache b/templates-v7/csharp/libraries/generichost/DependencyInjectionTests.mustache deleted file mode 100644 index 6085b51e5..000000000 --- a/templates-v7/csharp/libraries/generichost/DependencyInjectionTests.mustache +++ /dev/null @@ -1,211 +0,0 @@ -{{>partial_header}} -using System; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.DependencyInjection; -using System.Collections.Generic; -using System.Security.Cryptography; -using {{packageName}}.{{clientPackage}}; -using {{packageName}}.{{apiPackage}}; -using {{packageName}}.Extensions; -using Xunit; - -namespace {{packageName}}.Test.{{apiPackage}} -{ - /// - /// Tests the dependency injection. - /// - public class DependencyInjectionTest - { - private readonly IHost _hostUsingConfigureWithoutAClient = - Host.CreateDefaultBuilder({{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}Array.Empty(){{/net80OrLater}}).Configure{{apiName}}((context, services, options) => - { - {{#lambda.trimTrailingWithNewLine}} - {{#apiKeyMethods}} - ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(apiKeyToken{{-index}}); - - {{/apiKeyMethods}} - {{#httpBearerMethods}} - BearerToken bearerToken{{-index}} = new{{^net70OrLater}} BearerToken{{/net70OrLater}}("", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(bearerToken{{-index}}); - - {{/httpBearerMethods}} - {{#httpBasicMethods}} - BasicToken basicToken{{-index}} = new{{^net70OrLater}} BasicToken{{/net70OrLater}}("", "", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(basicToken{{-index}}); - - {{/httpBasicMethods}} - {{#httpSignatureMethods}} - HttpSigningConfiguration config{{-index}} = new{{^net70OrLater}} HttpSigningConfiguration{{/net70OrLater}}("", "", null, {{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}new List(){{/net80OrLater}}, HashAlgorithmName.SHA256, "", 0); - HttpSignatureToken httpSignatureToken{{-index}} = new{{^net70OrLater}} HttpSignatureToken{{/net70OrLater}}(config{{-index}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(httpSignatureToken{{-index}}); - - {{/httpSignatureMethods}} - {{#oauthMethods}} - OAuthToken oauthToken{{-index}} = new{{^net70OrLater}} OAuthToken{{/net70OrLater}}("token", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(oauthToken{{-index}}); - - {{/oauthMethods}} - {{/lambda.trimTrailingWithNewLine}} - }) - .Build(); - - private readonly IHost _hostUsingConfigureWithAClient = - Host.CreateDefaultBuilder({{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}Array.Empty(){{/net80OrLater}}).Configure{{apiName}}((context, services, options) => - { - {{#lambda.trimTrailingWithNewLine}} - {{#apiKeyMethods}} - ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(apiKeyToken{{-index}}); - - {{/apiKeyMethods}} - {{#httpBearerMethods}} - BearerToken bearerToken{{-index}} = new{{^net70OrLater}} BearerToken{{/net70OrLater}}("", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(bearerToken{{-index}}); - - {{/httpBearerMethods}} - {{#httpBasicMethods}} - BasicToken basicToken{{-index}} = new{{^net70OrLater}} BasicToken{{/net70OrLater}}("", "", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(basicToken{{-index}}); - - {{/httpBasicMethods}} - {{#httpSignatureMethods}} - HttpSigningConfiguration config{{-index}} = new{{^net70OrLater}} HttpSigningConfiguration{{/net70OrLater}}("", "", null, {{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}new List(){{/net80OrLater}}, HashAlgorithmName.SHA256, "", 0); - HttpSignatureToken httpSignatureToken{{-index}} = new{{^net70OrLater}} HttpSignatureToken{{/net70OrLater}}(config{{-index}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(httpSignatureToken{{-index}}); - - {{/httpSignatureMethods}} - {{#oauthMethods}} - OAuthToken oauthToken = new{{^net70OrLater}} OAuthToken{{/net70OrLater}}("token", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(oauthToken); - - {{/oauthMethods}} - {{/lambda.trimTrailingWithNewLine}} - options.Add{{apiName}}HttpClients(client => client.BaseAddress = new Uri(ClientUtils.BASE_ADDRESS)); - }) - .Build(); - - private readonly IHost _hostUsingAddWithoutAClient = - Host.CreateDefaultBuilder({{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}Array.Empty(){{/net80OrLater}}).ConfigureServices((host, services) => - { - services.Add{{apiName}}(options => - { - {{#lambda.trimTrailingWithNewLine}} - {{#apiKeyMethods}} - ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(apiKeyToken{{-index}}); - - {{/apiKeyMethods}} - {{#httpBearerMethods}} - BearerToken bearerToken{{-index}} = new{{^net70OrLater}} BearerToken{{/net70OrLater}}("", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(bearerToken{{-index}}); - - {{/httpBearerMethods}} - {{#httpBasicMethods}} - BasicToken basicToken{{-index}} = new{{^net70OrLater}} BasicToken{{/net70OrLater}}("", "", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(basicToken{{-index}}); - - {{/httpBasicMethods}} - {{#httpSignatureMethods}} - HttpSigningConfiguration config{{-index}} = new{{^net70OrLater}} HttpSigningConfiguration{{/net70OrLater}}("", "", null, {{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}new List(){{/net80OrLater}}, HashAlgorithmName.SHA256, "", 0); - HttpSignatureToken httpSignatureToken{{-index}} = new{{^net70OrLater}} HttpSignatureToken{{/net70OrLater}}(config{{-index}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(httpSignatureToken{{-index}}); - - {{/httpSignatureMethods}} - {{#oauthMethods}} - OAuthToken oauthToken{{-index}} = new{{^net70OrLater}} OAuthToken{{/net70OrLater}}("token", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(oauthToken{{-index}}); - - {{/oauthMethods}} - {{/lambda.trimTrailingWithNewLine}} - }); - }) - .Build(); - - private readonly IHost _hostUsingAddWithAClient = - Host.CreateDefaultBuilder({{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}Array.Empty(){{/net80OrLater}}).ConfigureServices((host, services) => - { - services.Add{{apiName}}(options => - { - {{#lambda.trimTrailingWithNewLine}} - {{#apiKeyMethods}} - ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(apiKeyToken{{-index}}); - - {{/apiKeyMethods}} - {{#httpBearerMethods}} - BearerToken bearerToken{{-index}} = new{{^net70OrLater}} BearerToken{{/net70OrLater}}("", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(bearerToken{{-index}}); - - {{/httpBearerMethods}} - {{#httpBasicMethods}} - BasicToken basicToken{{-index}} = new{{^net70OrLater}} BasicToken{{/net70OrLater}}("", "", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(basicToken{{-index}}); - - {{/httpBasicMethods}} - {{#httpSignatureMethods}} - HttpSigningConfiguration config{{-index}} = new{{^net70OrLater}} HttpSigningConfiguration{{/net70OrLater}}("", "", null, {{#net80OrLater}}[]{{/net80OrLater}}{{^net80OrLater}}new List(){{/net80OrLater}}, HashAlgorithmName.SHA256, "", 0); - HttpSignatureToken httpSignatureToken{{-index}} = new{{^net70OrLater}} HttpSignatureToken{{/net70OrLater}}(config{{-index}}, timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(httpSignatureToken{{-index}}); - - {{/httpSignatureMethods}} - {{#oauthMethods}} - OAuthToken oauthToken{{-index}} = new{{^net70OrLater}} OAuthToken{{/net70OrLater}}("token", timeout: TimeSpan.FromSeconds(1)); - options.AddTokens(oauthToken{{-index}}); - - {{/oauthMethods}} - {{/lambda.trimTrailingWithNewLine}} - options.Add{{apiName}}HttpClients(client => client.BaseAddress = new Uri(ClientUtils.BASE_ADDRESS)); - }); - }) - .Build(); - - /// - /// Test dependency injection when using the configure method - /// - [Fact] - public void ConfigureApiWithAClientTest() - { - {{#apiInfo}}{{#apis}}var {{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}} = _hostUsingConfigureWithAClient.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); - Assert.True({{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}}.HttpClient.BaseAddress != null);{{^-last}} - - {{/-last}}{{/apis}}{{/apiInfo}} - } - - /// - /// Test dependency injection when using the configure method - /// - [Fact] - public void ConfigureApiWithoutAClientTest() - { - {{#apiInfo}}{{#apis}}var {{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}} = _hostUsingConfigureWithoutAClient.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); - Assert.True({{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}}.HttpClient.BaseAddress != null);{{^-last}} - - {{/-last}}{{/apis}}{{/apiInfo}} - } - - /// - /// Test dependency injection when using the add method - /// - [Fact] - public void AddApiWithAClientTest() - { - {{#apiInfo}}{{#apis}}var {{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}} = _hostUsingAddWithAClient.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); - Assert.True({{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}}.HttpClient.BaseAddress != null);{{^-last}} - - {{/-last}}{{/apis}}{{/apiInfo}} - } - - /// - /// Test dependency injection when using the add method - /// - [Fact] - public void AddApiWithoutAClientTest() - { - {{#apiInfo}}{{#apis}}var {{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}} = _hostUsingAddWithoutAClient.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); - Assert.True({{#lambda.camel_case}}{{classname}}{{/lambda.camel_case}}.HttpClient.BaseAddress != null);{{^-last}} - - {{/-last}}{{/apis}}{{/apiInfo}} - } - } -} diff --git a/templates-v7/csharp/libraries/generichost/HttpSigningConfiguration.mustache b/templates-v7/csharp/libraries/generichost/HttpSigningConfiguration.mustache deleted file mode 100644 index 8b69a8c0b..000000000 --- a/templates-v7/csharp/libraries/generichost/HttpSigningConfiguration.mustache +++ /dev/null @@ -1,679 +0,0 @@ -// -{{>partial_header}} - -{{#nrt}} -#nullable enable - -{{/nrt}} -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using System.Security; -using System.Security.Cryptography; -using System.Text; -using System.Web; - -namespace {{packageName}}.{{clientPackage}} -{ - /// - /// Class for HttpSigning auth related parameter and methods - /// - {{>visibility}} class HttpSigningConfiguration - { - /// - /// Create an instance - /// - public HttpSigningConfiguration(string keyId, string keyFilePath, SecureString{{nrt?}} keyPassPhrase, List httpSigningHeader, HashAlgorithmName hashAlgorithm, string signingAlgorithm, int signatureValidityPeriod) - { - KeyId = keyId; - KeyFilePath = keyFilePath; - KeyPassPhrase = keyPassPhrase; - HttpSigningHeader = httpSigningHeader; - HashAlgorithm = hashAlgorithm; - SigningAlgorithm = signingAlgorithm; - SignatureValidityPeriod = signatureValidityPeriod; - } - - /// - ///Gets the Api keyId - /// - public string KeyId { get; set; } - - /// - /// Gets the Key file path - /// - public string KeyFilePath { get; set; } - - /// - /// Gets the key pass phrase for password protected key - /// - public SecureString{{nrt?}} KeyPassPhrase { get; set; } - - /// - /// Gets the HTTP signing header - /// - public List HttpSigningHeader { get; set; } - - /// - /// Gets the hash algorithm sha256 or sha512 - /// - public HashAlgorithmName HashAlgorithm { get; set; } = HashAlgorithmName.SHA256; - - /// - /// Gets the signing algorithm - /// - public string SigningAlgorithm { get; set; } - - /// - /// Gets the Signature validity period in seconds - /// - public int SignatureValidityPeriod { get; set; } - - private enum PrivateKeyType - { - None = 0, - RSA = 1, - ECDSA = 2, - } - - /// - /// Gets the Headers for HttpSigning - /// - /// - /// - /// - internal Dictionary GetHttpSignedHeader(global::System.Net.Http.HttpRequestMessage request, string requestBody, System.Threading.CancellationToken cancellationToken = default{{^netstandard20OrLater}}(global::System.Threading.CancellationToken){{/netstandard20OrLater}}) - { - if (request.RequestUri == null) - throw new NullReferenceException("The request URI was null"); - - const string HEADER_REQUEST_TARGET = "(request-target)"; - - // The time when the HTTP signature expires. The API server should reject HTTP requests that have expired. - const string HEADER_EXPIRES = "(expires)"; - - //The 'Date' header. - const string HEADER_DATE = "Date"; - - //The 'Host' header. - const string HEADER_HOST = "Host"; - - //The time when the HTTP signature was generated. - const string HEADER_CREATED = "(created)"; - - //When the 'Digest' header is included in the HTTP signature, the client automatically - //computes the digest of the HTTP request body, per RFC 3230. - const string HEADER_DIGEST = "Digest"; - - //The 'Authorization' header is automatically generated by the client. It includes - //the list of signed headers and a base64-encoded signature. - const string HEADER_AUTHORIZATION = "Authorization"; - - //Hash table to store singed headers - var HttpSignedRequestHeader = new Dictionary(); - - var httpSignatureHeader = new Dictionary(); - - if (HttpSigningHeader.Count == 0) - HttpSigningHeader.Add("(created)"); - - var dateTime = DateTime.Now; - string digest = String.Empty; - - if (HashAlgorithm == HashAlgorithmName.SHA256) - { - var bodyDigest = GetStringHash(HashAlgorithm, requestBody); - digest = string.Format("SHA-256={0}", Convert.ToBase64String(bodyDigest)); - } - else if (HashAlgorithm == HashAlgorithmName.SHA512) - { - var bodyDigest = GetStringHash(HashAlgorithm, requestBody); - digest = string.Format("SHA-512={0}", Convert.ToBase64String(bodyDigest)); - } - else - throw new Exception(string.Format("{0} not supported", HashAlgorithm)); - - foreach (var header in HttpSigningHeader) - if (header.Equals(HEADER_REQUEST_TARGET)) - httpSignatureHeader.Add(header.ToLower(), request.RequestUri.ToString()); - else if (header.Equals(HEADER_EXPIRES)) - { - var expireDateTime = dateTime.AddSeconds(SignatureValidityPeriod); - httpSignatureHeader.Add(header.ToLower(), GetUnixTime(expireDateTime).ToString()); - } - else if (header.Equals(HEADER_DATE)) - { - var utcDateTime = dateTime.ToString("r").ToString(); - httpSignatureHeader.Add(header.ToLower(), utcDateTime); - HttpSignedRequestHeader.Add(HEADER_DATE, utcDateTime); - } - else if (header.Equals(HEADER_HOST)) - { - httpSignatureHeader.Add(header.ToLower(), request.RequestUri.ToString()); - HttpSignedRequestHeader.Add(HEADER_HOST, request.RequestUri.ToString()); - } - else if (header.Equals(HEADER_CREATED)) - httpSignatureHeader.Add(header.ToLower(), GetUnixTime(dateTime).ToString()); - else if (header.Equals(HEADER_DIGEST)) - { - HttpSignedRequestHeader.Add(HEADER_DIGEST, digest); - httpSignatureHeader.Add(header.ToLower(), digest); - } - else - { - bool isHeaderFound = false; - foreach (var item in request.Headers) - { - if (string.Equals(item.Key, header, StringComparison.OrdinalIgnoreCase)) - { - httpSignatureHeader.Add(header.ToLower(), item.Value.ToString()); - isHeaderFound = true; - break; - } - } - - if (!isHeaderFound) - throw new Exception(string.Format("Cannot sign HTTP request.Request does not contain the {0} header.",header)); - } - - var headersKeysString = String.Join(" ", httpSignatureHeader.Keys); - var headerValuesList = new List(); - - foreach (var keyVal in httpSignatureHeader) - headerValuesList.Add(string.Format("{0}: {1}", keyVal.Key, keyVal.Value)); - - //Concatenate headers value separated by new line - var headerValuesString = string.Join("\n", headerValuesList); - var signatureStringHash = GetStringHash(HashAlgorithm, headerValuesString); - string{{nrt?}} headerSignatureStr = null; - var keyType = GetKeyType(KeyFilePath); - - if (keyType == PrivateKeyType.RSA) - headerSignatureStr = GetRSASignature(signatureStringHash); - - else if (keyType == PrivateKeyType.ECDSA) - headerSignatureStr = GetECDSASignature(signatureStringHash); - - var cryptographicScheme = "hs2019"; - var authorizationHeaderValue = string.Format("Signature keyId=\"{0}\",algorithm=\"{1}\"", - KeyId, cryptographicScheme); - - if (httpSignatureHeader.ContainsKey(HEADER_CREATED)) - authorizationHeaderValue += string.Format(",created={0}", httpSignatureHeader[HEADER_CREATED]); - - if (httpSignatureHeader.ContainsKey(HEADER_EXPIRES)) - authorizationHeaderValue += string.Format(",expires={0}", httpSignatureHeader[HEADER_EXPIRES]); - - authorizationHeaderValue += string.Format(",headers=\"{0}\",signature=\"{1}\"", headersKeysString, headerSignatureStr); - - HttpSignedRequestHeader.Add(HEADER_AUTHORIZATION, authorizationHeaderValue); - - return HttpSignedRequestHeader; - } - - private byte[] GetStringHash(HashAlgorithmName hashAlgorithmName, string stringToBeHashed) - { - HashAlgorithm{{nrt?}} hashAlgorithm = null; - - if (hashAlgorithmName == HashAlgorithmName.SHA1) - hashAlgorithm = SHA1.Create(); - - if (hashAlgorithmName == HashAlgorithmName.SHA256) - hashAlgorithm = SHA256.Create(); - - if (hashAlgorithmName == HashAlgorithmName.SHA512) - hashAlgorithm = SHA512.Create(); - - if (hashAlgorithmName == HashAlgorithmName.MD5) - hashAlgorithm = MD5.Create(); - - if (hashAlgorithm == null) - throw new NullReferenceException($"{ nameof(hashAlgorithm) } was null."); - - byte[] bytes = Encoding.UTF8.GetBytes(stringToBeHashed); - byte[] stringHash = hashAlgorithm.ComputeHash(bytes); - return stringHash; - } - - private int GetUnixTime(DateTime date2) - { - DateTime date1 = new DateTime(1970, 01, 01); - TimeSpan timeSpan = date2 - date1; - return (int)timeSpan.TotalSeconds; - } - - private string GetRSASignature(byte[] stringToSign) - { - if (KeyPassPhrase == null) - throw new NullReferenceException($"{ nameof(KeyPassPhrase) } was null."); - - RSA{{nrt?}} rsa = GetRSAProviderFromPemFile(KeyFilePath, KeyPassPhrase); - - if (rsa == null) - return string.Empty; - else if (SigningAlgorithm == "RSASSA-PSS") - { - var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pss); - return Convert.ToBase64String(signedbytes); - } - else if (SigningAlgorithm == "PKCS1-v15") - { - var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pkcs1); - return Convert.ToBase64String(signedbytes); - } - - return string.Empty; - } - - /// - /// Gets the ECDSA signature - /// - /// - /// - private string GetECDSASignature(byte[] dataToSign) - { - {{#net60OrLater}} - if (!File.Exists(KeyFilePath)) - throw new Exception("key file path does not exist."); - - var ecKeyHeader = "-----BEGIN EC PRIVATE KEY-----"; - var ecKeyFooter = "-----END EC PRIVATE KEY-----"; - var keyStr = File.ReadAllText(KeyFilePath); - var ecKeyBase64String = keyStr.Replace(ecKeyHeader, "").Replace(ecKeyFooter, "").Trim(); - var keyBytes = System.Convert.FromBase64String(ecKeyBase64String); - var ecdsa = ECDsa.Create(); - - var byteCount = 0; - if (KeyPassPhrase != null) - { - IntPtr unmanagedString = IntPtr.Zero; - try - { - // convert secure string to byte array - unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(KeyPassPhrase); - - string ptrToStringUni = Marshal.PtrToStringUni(unmanagedString) ?? throw new NullReferenceException(); - - ecdsa.ImportEncryptedPkcs8PrivateKey(Encoding.UTF8.GetBytes(ptrToStringUni), keyBytes, out byteCount); - } - finally - { - if (unmanagedString != IntPtr.Zero) - Marshal.ZeroFreeBSTR(unmanagedString); - } - } - else - ecdsa.ImportPkcs8PrivateKey(keyBytes, out byteCount); - - var signedBytes = ecdsa.SignHash(dataToSign); - var derBytes = ConvertToECDSAANS1Format(signedBytes); - var signedString = System.Convert.ToBase64String(derBytes); - - return signedString; - {{/net60OrLater}} - {{^net60OrLater}} - throw new Exception("ECDSA signing is supported only on NETCOREAPP3_0 and above"); - {{/net60OrLater}} - } - - private byte[] ConvertToECDSAANS1Format(byte[] signedBytes) - { - var derBytes = new List(); - byte derLength = 68; //default length for ECDSA code signing bit 0x44 - byte rbytesLength = 32; //R length 0x20 - byte sbytesLength = 32; //S length 0x20 - var rBytes = new List(); - var sBytes = new List(); - for (int i = 0; i < 32; i++) - rBytes.Add(signedBytes[i]); - - for (int i = 32; i < 64; i++) - sBytes.Add(signedBytes[i]); - - if (rBytes[0] > 0x7F) - { - derLength++; - rbytesLength++; - var tempBytes = new List(); - tempBytes.AddRange(rBytes); - rBytes.Clear(); - rBytes.Add(0x00); - rBytes.AddRange(tempBytes); - } - - if (sBytes[0] > 0x7F) - { - derLength++; - sbytesLength++; - var tempBytes = new List(); - tempBytes.AddRange(sBytes); - sBytes.Clear(); - sBytes.Add(0x00); - sBytes.AddRange(tempBytes); - - } - - derBytes.Add(48); //start of the sequence 0x30 - derBytes.Add(derLength); //total length r length, type and r bytes - - derBytes.Add(2); //tag for integer - derBytes.Add(rbytesLength); //length of r - derBytes.AddRange(rBytes); - - derBytes.Add(2); //tag for integer - derBytes.Add(sbytesLength); //length of s - derBytes.AddRange(sBytes); - return derBytes.ToArray(); - } - - private RSACryptoServiceProvider{{nrt?}} GetRSAProviderFromPemFile(String pemfile, SecureString{{nrt?}} keyPassPhrase = null) - { - const String pempubheader = "-----BEGIN PUBLIC KEY-----"; - const String pempubfooter = "-----END PUBLIC KEY-----"; - bool isPrivateKeyFile = true; - byte[]{{nrt?}} pemkey = null; - - if (!File.Exists(pemfile)) - throw new Exception("private key file does not exist."); - - string pemstr = File.ReadAllText(pemfile).Trim(); - - if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter)) - isPrivateKeyFile = false; - - if (isPrivateKeyFile) - { - pemkey = ConvertPrivateKeyToBytes(pemstr, keyPassPhrase); - - if (pemkey == null) - return null; - - return DecodeRSAPrivateKey(pemkey); - } - return null; - } - - private byte[]{{nrt?}} ConvertPrivateKeyToBytes(String instr, SecureString{{nrt?}} keyPassPhrase = null) - { - const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----"; - const String pemprivfooter = "-----END RSA PRIVATE KEY-----"; - String pemstr = instr.Trim(); - byte[] binkey; - - if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter)) - return null; - - StringBuilder sb = new StringBuilder(pemstr); - sb.Replace(pemprivheader, ""); - sb.Replace(pemprivfooter, ""); - String pvkstr = sb.ToString().Trim(); - - try - { // if there are no PEM encryption info lines, this is an UNencrypted PEM private key - binkey = Convert.FromBase64String(pvkstr); - return binkey; - } - catch (global::System.FormatException) - { - StringReader str = new StringReader(pvkstr); - - //-------- read PEM encryption info. lines and extract salt ----- - if (!str.ReadLine(){{nrt!}}.StartsWith("Proc-Type: 4,ENCRYPTED")) // TODO: what do we do here if ReadLine is null? - return null; - - String saltline = str.ReadLine(){{nrt!}}; // TODO: what do we do here if ReadLine is null? - if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,")) - return null; - - String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim(); - byte[] salt = new byte[saltstr.Length / 2]; - for (int i = 0; i < salt.Length; i++) - salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16); - - if (!(str.ReadLine() == "")) - return null; - - //------ remaining b64 data is encrypted RSA key ---- - String encryptedstr = str.ReadToEnd(); - - try - { //should have b64 encrypted RSA key now - binkey = Convert.FromBase64String(encryptedstr); - } - catch (global::System.FormatException) - { //data is not in base64 format - return null; - } - - // TODO: what do we do here if keyPassPhrase is null? - byte[] deskey = GetEncryptedKey(salt, keyPassPhrase{{nrt!}}, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes - if (deskey == null) - return null; - - //------ Decrypt the encrypted 3des-encrypted RSA private key ------ - byte[]{{nrt?}} rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV - - return rsakey; - } - } - - private RSACryptoServiceProvider{{nrt?}} DecodeRSAPrivateKey(byte[] privkey) - { - byte[] MODULUS, E, D, P, Q, DP, DQ, IQ; - - // --------- Set up stream to decode the asn.1 encoded RSA private key ------ - MemoryStream mem = new MemoryStream(privkey); - BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading - byte bt = 0; - ushort twobytes = 0; - int elems = 0; - try - { - twobytes = binr.ReadUInt16(); - if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) - binr.ReadByte(); //advance 1 byte - else if (twobytes == 0x8230) - binr.ReadInt16(); //advance 2 bytes - else - return null; - - twobytes = binr.ReadUInt16(); - if (twobytes != 0x0102) //version number - return null; - - bt = binr.ReadByte(); - if (bt != 0x00) - return null; - - //------ all private key components are Integer sequences ---- - elems = GetIntegerSize(binr); - MODULUS = binr.ReadBytes(elems); - - elems = GetIntegerSize(binr); - E = binr.ReadBytes(elems); - - elems = GetIntegerSize(binr); - D = binr.ReadBytes(elems); - - elems = GetIntegerSize(binr); - P = binr.ReadBytes(elems); - - elems = GetIntegerSize(binr); - Q = binr.ReadBytes(elems); - - elems = GetIntegerSize(binr); - DP = binr.ReadBytes(elems); - - elems = GetIntegerSize(binr); - DQ = binr.ReadBytes(elems); - - elems = GetIntegerSize(binr); - IQ = binr.ReadBytes(elems); - - // ------- create RSACryptoServiceProvider instance and initialize with public key ----- - RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); - RSAParameters RSAparams = new RSAParameters(); - RSAparams.Modulus = MODULUS; - RSAparams.Exponent = E; - RSAparams.D = D; - RSAparams.P = P; - RSAparams.Q = Q; - RSAparams.DP = DP; - RSAparams.DQ = DQ; - RSAparams.InverseQ = IQ; - RSA.ImportParameters(RSAparams); - return RSA; - } - catch (Exception) - { - return null; - } - finally - { - binr.Close(); - } - } - - private int GetIntegerSize(BinaryReader binr) - { - byte bt = 0; - byte lowbyte = 0x00; - byte highbyte = 0x00; - int count = 0; - bt = binr.ReadByte(); - if (bt != 0x02) //expect integer - return 0; - - bt = binr.ReadByte(); - - if (bt == 0x81) - count = binr.ReadByte(); // data size in next byte - else if (bt == 0x82) - { - highbyte = binr.ReadByte(); // data size in next 2 bytes - lowbyte = binr.ReadByte(); - byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; - count = BitConverter.ToInt32(modint, 0); - } - else - count = bt; // we already have the data size - - while (binr.ReadByte() == 0x00) - //remove high order zeros in data - count -= 1; - - binr.BaseStream.Seek(-1, SeekOrigin.Current); - - //last ReadByte wasn't a removed zero, so back up a byte - return count; - } - - private byte[] GetEncryptedKey(byte[] salt, SecureString secpswd, int count, int miter) - { - IntPtr unmanagedPswd = IntPtr.Zero; - int HASHLENGTH = 16; //MD5 bytes - byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store concatenated Mi hashed results - - byte[] psbytes = new byte[secpswd.Length]; - unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd); - Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length); - Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd); - - // --- concatenate salt and pswd bytes into fixed data array --- - byte[] data00 = new byte[psbytes.Length + salt.Length]; - Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes - Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes - - // ---- do multi-hashing and concatenate results D1, D2 ... into keymaterial bytes ---- - MD5 md5 = MD5.Create(); - byte[]{{nrt?}} result = null; - byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget - - for (int j = 0; j < miter; j++) - { - // ---- Now hash consecutively for count times ------ - if (j == 0) - result = data00; //initialize - else - { - Array.Copy(result{{nrt!}}, hashtarget, result{{nrt!}}.Length); // TODO: what do we do if result is null here? - Array.Copy(data00, 0, hashtarget, result.Length, data00.Length); - result = hashtarget; - } - - for (int i = 0; i < count; i++) - result = md5.ComputeHash(result); - - Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //concatenate to keymaterial - } - byte[] deskey = new byte[24]; - Array.Copy(keymaterial, deskey, deskey.Length); - - Array.Clear(psbytes, 0, psbytes.Length); - Array.Clear(data00, 0, data00.Length); - Array.Clear(result{{nrt!}}, 0, result{{nrt!}}.Length); // TODO: what do we do if result is null here? - Array.Clear(hashtarget, 0, hashtarget.Length); - Array.Clear(keymaterial, 0, keymaterial.Length); - return deskey; - } - - private byte[]{{nrt?}} DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV) - { - MemoryStream memst = new MemoryStream(); - TripleDES alg = TripleDES.Create(); - alg.Key = desKey; - alg.IV = IV; - try - { - CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write); - cs.Write(cipherData, 0, cipherData.Length); - cs.Close(); - } - catch (Exception) - { - return null; - } - byte[] decryptedData = memst.ToArray(); - return decryptedData; - } - - /// - /// Detect the key type from the pem file. - /// - /// key file path in pem format - /// - private PrivateKeyType GetKeyType(string keyFilePath) - { - if (!File.Exists(keyFilePath)) - throw new Exception("Key file path does not exist."); - - var ecPrivateKeyHeader = "BEGIN EC PRIVATE KEY"; - var ecPrivateKeyFooter = "END EC PRIVATE KEY"; - var rsaPrivateKeyHeader = "BEGIN RSA PRIVATE KEY"; - var rsaPrivateFooter = "END RSA PRIVATE KEY"; - //var pkcs8Header = "BEGIN PRIVATE KEY"; - //var pkcs8Footer = "END PRIVATE KEY"; - var keyType = PrivateKeyType.None; - var key = File.ReadAllLines(keyFilePath); - - if (key[0].ToString().Contains(rsaPrivateKeyHeader) && key[key.Length - 1].ToString().Contains(rsaPrivateFooter)) - keyType = PrivateKeyType.RSA; - else if (key[0].ToString().Contains(ecPrivateKeyHeader) && key[key.Length - 1].ToString().Contains(ecPrivateKeyFooter)) - keyType = PrivateKeyType.ECDSA; - - else if (key[0].ToString().Contains(ecPrivateKeyHeader) && key[key.Length - 1].ToString().Contains(ecPrivateKeyFooter)) - { - /* this type of key can hold many type different types of private key, but here due lack of pem header - Considering this as EC key - */ - //TODO :- update the key based on oid - keyType = PrivateKeyType.ECDSA; - } - else - throw new Exception("Either the key is invalid or key is not supported"); - - return keyType; - } - } -} diff --git a/templates-v7/csharp/libraries/generichost/HttpSigningToken.mustache b/templates-v7/csharp/libraries/generichost/HttpSigningToken.mustache deleted file mode 100644 index 881682e89..000000000 --- a/templates-v7/csharp/libraries/generichost/HttpSigningToken.mustache +++ /dev/null @@ -1,45 +0,0 @@ -// -{{partial_header}} -{{#nrt}} -#nullable enable - -{{/nrt}} -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace {{packageName}}.{{clientPackage}} -{ - /// - /// A token constructed from an HttpSigningConfiguration - /// - {{>visibility}} class HttpSignatureToken : TokenBase - { - private HttpSigningConfiguration _configuration; - - /// - /// Constructs an HttpSignatureToken object. - /// - /// - /// - public HttpSignatureToken(HttpSigningConfiguration configuration, TimeSpan? timeout = null) : base(timeout) - { - _configuration = configuration; - } - - /// - /// Places the token in the header. - /// - /// - /// - /// - public void UseInHeader(global::System.Net.Http.HttpRequestMessage request, string requestBody, CancellationToken cancellationToken = default{{^netstandard20OrLater}}(global::System.Threading.CancellationToken){{/netstandard20OrLater}}) - { - var signedHeaders = _configuration.GetHttpSignedHeader(request, requestBody, cancellationToken); - - foreach (var signedHeader in signedHeaders) - request.Headers.Add(signedHeader.Key, signedHeader.Value); - } - } -} \ No newline at end of file diff --git a/templates-v7/csharp/libraries/generichost/README.client.mustache b/templates-v7/csharp/libraries/generichost/README.client.mustache index 247cbe399..3494e0cd0 100644 --- a/templates-v7/csharp/libraries/generichost/README.client.mustache +++ b/templates-v7/csharp/libraries/generichost/README.client.mustache @@ -79,7 +79,7 @@ namespace YourProject {{#authMethods}} {{#-first}} // The type of token here depends on the api security specifications - // Available token types are ApiKeyToken, BearerToken, HttpSigningToken, and OAuthToken. + // Available token types are ApiKeyToken, BearerToken, and OAuthToken. BearerToken token = new(""); options.AddTokens(token); diff --git a/templates-v7/csharp/libraries/generichost/api.mustache b/templates-v7/csharp/libraries/generichost/api.mustache index 67264bf5a..e4c4372ec 100644 --- a/templates-v7/csharp/libraries/generichost/api.mustache +++ b/templates-v7/csharp/libraries/generichost/api.mustache @@ -173,12 +173,7 @@ namespace {{packageName}}.{{apiPackage}} /// /// A token provider of type . /// - public ITokenProvider BearerTokenProvider { get; }{{/hasHttpBearerMethods}}{{#hasHttpSignatureMethods}} - - /// - /// A token provider of type . - /// - public ITokenProvider HttpSignatureTokenProvider { get; }{{/hasHttpSignatureMethods}}{{#hasOAuthMethods}} + public ITokenProvider BearerTokenProvider { get; }{{/hasHttpBearerMethods}}{{#hasOAuthMethods}} /// /// A token provider of type . @@ -203,8 +198,7 @@ namespace {{packageName}}.{{apiPackage}} /// public {{classname}}(ILogger<{{classname}}> logger, ILoggerFactory loggerFactory, System.Net.Http.HttpClient httpClient, JsonSerializerOptionsProvider jsonSerializerOptionsProvider, {{classname}}Events {{#lambda.camelcase_sanitize_param}}{{classname}}Events{{/lambda.camelcase_sanitize_param}}{{#hasApiKeyMethods}}, ITokenProvider apiKeyProvider{{/hasApiKeyMethods}}{{#hasHttpBearerMethods}}, - ITokenProvider bearerTokenProvider{{/hasHttpBearerMethods}}{{#hasHttpSignatureMethods}}, - ITokenProvider httpSignatureTokenProvider{{/hasHttpSignatureMethods}}{{#hasOAuthMethods}}, + ITokenProvider bearerTokenProvider{{/hasHttpBearerMethods}}{{#hasOAuthMethods}}, ITokenProvider oauthTokenProvider{{/hasOAuthMethods}}{{#net80OrLater}}{{#operation}}{{#lambda.uniqueLines}}{{#vendorExtensions.x-set-cookie}}, {{packageName}}.{{clientPackage}}.CookieContainer cookieContainer{{/vendorExtensions.x-set-cookie}}{{/lambda.uniqueLines}}{{/operation}}{{/net80OrLater}}) { @@ -214,8 +208,7 @@ namespace {{packageName}}.{{apiPackage}} HttpClient = httpClient; Events = {{#lambda.camelcase_sanitize_param}}{{classname}}Events{{/lambda.camelcase_sanitize_param}};{{#hasApiKeyMethods}} ApiKeyProvider = apiKeyProvider;{{/hasApiKeyMethods}}{{#hasHttpBearerMethods}} - BearerTokenProvider = bearerTokenProvider;{{/hasHttpBearerMethods}}{{#hasHttpSignatureMethods}} - HttpSignatureTokenProvider = httpSignatureTokenProvider;{{/hasHttpSignatureMethods}}{{#hasOAuthMethods}} + BearerTokenProvider = bearerTokenProvider;{{/hasHttpBearerMethods}}{{#hasOAuthMethods}} OauthTokenProvider = oauthTokenProvider;{{/hasOAuthMethods}}{{#net80OrLater}}{{#operation}}{{#lambda.uniqueLines}}{{#vendorExtensions.x-set-cookie}} CookieContainer = cookieContainer;{{/vendorExtensions.x-set-cookie}}{{/lambda.uniqueLines}}{{/operation}}{{/net80OrLater}} } @@ -373,16 +366,6 @@ namespace {{packageName}}.{{apiPackage}} OAuthToken oauthToken{{-index}} = (OAuthToken) await OauthTokenProvider.GetAsync(cancellation: cancellationToken).ConfigureAwait(false); oauthToken{{-index}}.UseInHeader(httpRequestMessage, "{{keyParamName}}"); {{/isOAuth}} - {{#isHttpSignature}} - - HttpSignatureToken httpSignatureToken{{-index}} = (HttpSignatureToken) await HttpSignatureTokenProvider.GetAsync(cancellation: cancellationToken).ConfigureAwait(false); - - if (httpRequestMessage.Content != null) { - string requestBody = await httpRequestMessage.Content.ReadAsStringAsync({{#net60OrLater}}cancellationToken{{/net60OrLater}}).ConfigureAwait(false); - - httpSignatureToken{{-index}}.UseInHeader(httpRequestMessage, requestBody, cancellationToken); - } - {{/isHttpSignature}} {{/authMethods}} {{#consumes}} {{#-first}}