Skip to content

Conversation

@Kwok-he-Chu
Copy link
Member

Description

This PR addresses the upgrade of the OpenApiGenerator to v7.16.0 used in the Adyen sdk-automation to generate .NET models & services from the Adyen OpenAPI Specifications. This PR addresses allOf support and native support for System.Text.Json.

This branch/PR contains the changes in: #1207 1208 1209 1213


Release notes (WIP)

Added new functionality

Folder-structure #1207

  • Introduced a /Core-folder with the following functionality:
    • /Auth contains classes to add authorization to HttpHeaders.
    • /Client contains classes that wrap the ApiResponses and help-functionality to construct the LiveUrl (see UrlBuilderExtensions.cs)
    • /Client/Extensions/ contains the Polly-library that allows user to configure Timeouts, Retries and CircuitBreakers.
    • /Converters contains JsonConverters for DateOnly, DateOnlyNullable, DateTime and DateTimeNullable
    • /Options contains classes to configure the AdyenOptions or AdyenEnvironment (Test/Live).
  • All APIs are now part of their own sub-folders
  • Support DateTimeOffSet
    • Before: new DateTime(2025, 12, 25, 11, 59, 59)
    • -> After: new DateTimeOffset(2025, 12, 25, 11, 59, 59, TimeSpan.Zero)
  • Support DateTimeOnly
    • Before: new DateTime(2025, 12, 25, 11, 59, 59)
    • -> After: new DateOnly(2025, 12, 25)

Mustache templates #1208

  • Added api_doc.mustache - Allows documentation generation in /docs folder, only the Service-classes are generated, showing developers how to interact with the API
  • Added WebhookHandler.mustache - Allows the WebhookHandler.cs to be generated for deserializing webhooks
  • Added modelInnerEnum.mustache, all generated enums are now strings and forward compatible, this fixes Discussion: Should we make the enums forward-compatible? #1071
  • Added templates to generate the new models (modelGeneric.mustache) and services (api.mustache), supporting allOf

Changes - Moved classes #1209

  • Removed .NET 6.0 in .csproj.
  • Moved all TerminalApi classes to the /TerminalApi folder
    • Adyen/Service/TerminalApiAsyncService -> Adyen/**TerminalApi**/Service/TerminalApiService
    • Adyen/Service/TerminalApiSyncService -> Adyen/**TerminalApi**/Service/TerminalSyncService
    • Adyen/Service/TerminalApiLocalService -> Adyen/**TerminalApi**/Service/TerminaLocalService
    • Adyen/Service/TerminalLocalApiUnencrypted -> Adyen/**TerminalApi**/Service/TerminalLocalApiUnencrypted
    • Adyen/Service/TerminalCloudApi -> Adyen/**TerminalApi**/Service/TerminalCloudApi
    • Adyen/Service/TerminalLocalApi -> Adyen/**TerminalApi**/Service/TerminalLocalApi

Breaking changes - Renames for consistency

  • Renamed & Moved Adyen/Util/HMACValidator.cs -> Adyen/Webhooks/HmacValidatorUtility.
  • Removed BalancePlatformWebhookHandler, ClassicPaltformWebhookhandler, ManagementWebhookHandler

Review and merge the following into the v7-generator/development-branch

…ts (#1207)

* Added /Core classes and tests in preparations for the v7-generator-upgrade

* Remove workflow.yml for .NET6.0

* Include updated core classes for review

* Fix workflow file to target current HttpRequestMessageExtension

* DateOnlyNullable returns null when the format is not supported
@Kwok-he-Chu Kwok-he-Chu requested review from a team as code owners November 28, 2025 12:33
@Kwok-he-Chu Kwok-he-Chu changed the title OpenAPIGenerator upgrade to v7.16.0 for Adyen's .NET Library OpenAPIGenerator upgrade [development-branch] to v7.16.0 for Adyen's .NET Library Nov 28, 2025
@gemini-code-assist
Copy link

Summary of Changes

Hello @Kwok-he-Chu, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request represents a significant architectural and infrastructural update for the Adyen .NET library. It upgrades the underlying OpenAPI Generator to version 7.16.0 and consolidates the target framework to .NET 8.0, leveraging modern .NET features. The changes introduce a new '/Core' folder for better modularity, enhance JSON serialization with native System.Text.Json support, and integrate resilience patterns via the Polly library. These updates aim to improve code generation, maintainability, and the overall robustness of the SDK.

Highlights

  • OpenAPIGenerator Upgrade: The underlying OpenAPI Generator has been updated to v7.16.0, bringing new capabilities and improvements to the generated code, including native support for 'allOf' in models.
  • Native System.Text.Json Support: The library now natively supports System.Text.Json for improved performance and modern .NET integration, replacing older serialization methods.
  • New Core Folder Structure: A new '/Core' folder has been introduced, centralizing authentication, client utilities, converters, and options for better modularity and organization.
  • Enhanced Date and Time Handling: Added robust support for 'DateTimeOffset' and 'DateOnly' types, along with dedicated JSON converters for accurate serialization and deserialization.
  • Polly Integration for Resilience: The Polly library is now integrated into the client extensions, allowing for configurable timeouts, retries, and circuit breakers to enhance API client resilience.
  • Improved Enum Generation: Generated enums are now string-based and forward-compatible, addressing previous issues and improving consistency.
  • Consolidated .NET Target Framework: The project now exclusively targets .NET 8.0, removing support for older frameworks like .NET 6.0, .NET 4.6.2, and .NET Standard 2.0, streamlining development and leveraging modern features.
  • New HMAC Validator Utility: A new 'HmacValidatorUtility' class has been added to simplify and standardize the validation of HMAC signatures for incoming webhooks.
Ignored Files
  • Ignored by pattern: .github/workflows/** (2)
    • .github/workflows/build-net6.0.yml
    • .github/workflows/release.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a significant upgrade, moving the library to .NET 8, introducing a new core structure, and improving serialization with System.Text.Json. The changes are extensive and well-organized. My review focuses on improving the robustness of the new utility classes, enhancing test quality, and ensuring the code is resilient to repeated calls. I've identified several areas where methods could fail if called multiple times and suggested fixes to make them idempotent. Additionally, I've pointed out some issues in the new tests, such as incorrect assertions and misleading test names, along with suggestions to improve them.

// Add application name if set.
if (!string.IsNullOrWhiteSpace(ApplicationName))
{
httpRequestMessage.Headers.Add("UserAgent", $"{ApplicationName} {AdyenLibraryName}/{AdyenLibraryVersion}");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Calling Headers.Add will throw an ArgumentException if the header already exists. The corresponding test Given_AddUserAgentToHeaders_When_Called_MultipleTimes_Should_Not_Throw_Any_Exceptions expects that no exception is thrown on multiple calls. To make this method idempotent, you should use TryAddWithoutValidation which does not throw if the header is already present.

                httpRequestMessage.Headers.TryAddWithoutValidation("UserAgent", $"{ApplicationName} {AdyenLibraryName}/{AdyenLibraryVersion}");

Comment on lines +24 to +25
string value = reader.GetString();
return Encoding.UTF8.GetBytes(value);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

reader.GetString() can return null. Encoding.UTF8.GetBytes(value) will throw an ArgumentNullException if value is null. You should handle the null case to prevent this.

            string value = reader.GetString();
            return value == null ? null : Encoding.UTF8.GetBytes(value);

private T DeserializeRaw() => JsonSerializer.Deserialize<T>(RawContent, _jsonSerializerOptions)!;

public T Ok() => DeserializeRaw();
public bool TryDeserializeOkResponse(out T? result) { result = string.IsNullOrEmpty(RawContent) ? default : DeserializeRaw(); return result != null; }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This implementation of TryDeserializeOkResponse (and other similar TryDeserialize... methods in this class) has a few issues:

  1. Potential bugs with value types: If T is a value type (e.g., a struct), default(T) is not null. So if RawContent is empty, result != null will be true, and the method will incorrectly indicate success.
  2. Unhandled exceptions: JsonSerializer.Deserialize can throw a JsonException if the RawContent is not valid JSON. The Try... pattern should typically not throw exceptions for expected failure cases like this.
  3. Readability: The single-line implementation is dense and harder to understand.

A more robust implementation would handle exceptions and be clearer. This applies to all TryDeserialize... methods in this class.

Example for TryDeserializeOkResponse:

public bool TryDeserializeOkResponse(out T? result)
{
    if (string.IsNullOrEmpty(RawContent))
    {
        result = default;
        return false;
    }

    try
    {
        result = JsonSerializer.Deserialize<T>(RawContent, _jsonSerializerOptions);
        return result != null;
    }
    catch (JsonException)
    {
        result = default;
        return false;
    }
}

public System.Net.Http.HttpRequestMessage AddHeadersToHttpRequestMessage(System.Net.Http.HttpRequestMessage httpRequestMessage)
{
foreach (KeyValuePair<string, string> header in this.Headers)
httpRequestMessage.Headers.Add(header.Key, header.Value);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using Headers.Add can throw an ArgumentException if the header already exists on the HttpRequestMessage. It's safer to use TryAddWithoutValidation to avoid exceptions if a header is already present.

                httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value);

/// <returns><see cref="RequestOptions"/>.</returns>
public RequestOptions AddIdempotencyKey(string idempotencyKey)
{
this.Headers.Add("Idempotency-Key", idempotencyKey);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using Dictionary.Add will throw an ArgumentException if the key already exists. This means calling this method multiple times will cause a crash. To make this method more robust and idempotent, you should use the indexer to set the value, which will add the key if it doesn't exist or update it if it does. This applies to AddAdditionalHeaders, AddWWWAuthenticateHeader, and AddxRequestedVerificationCodeHeader as well.

            this.Headers["Idempotency-Key"] = idempotencyKey;

}

[TestMethod]
public void Given_NullToken_When_Read_Then_ThrowsNotSupportedException()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test name Given_NullToken_When_Read_Then_ThrowsNotSupportedException is misleading. The test correctly asserts that null is returned, but the name suggests an exception is expected. The name should be updated to reflect the actual behavior.

        public void Given_NullToken_When_Read_Then_Returns_Null()

}

[TestMethod]
public async Task Given_TokenProviderWithToken_When_GetCalled_Then_SameInstanceAndValueAreReturned()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test method and Given_TokenProvider_When_GetCalledMultipleTimes_Then_SameInstanceReturnedEachTime are marked as async but don't use any await operators. This will result in a compiler warning. Since the methods' execution is synchronous, you can change their signatures to return void instead of Task.

        public void Given_TokenProviderWithToken_When_GetCalled_Then_SameInstanceAndValueAreReturned()

Comment on lines +78 to +92
try
{
request.AddUserAgentToHeaders();
request.AddUserAgentToHeaders();

request.AddLibraryNameToHeader();
request.AddLibraryNameToHeader();

request.AddLibraryVersionToHeader();
request.AddLibraryVersionToHeader();
}
catch (Exception e)
{
Assert.Fail();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The try-catch block with Assert.Fail() is redundant here. A test method in MSTest will automatically fail if an unhandled exception is thrown. You can simplify this test by removing the try-catch block and just keeping the calls to the extension methods.

            request.AddUserAgentToHeaders();
            request.AddUserAgentToHeaders();
            
            request.AddLibraryNameToHeader();
            request.AddLibraryNameToHeader();
            
            request.AddLibraryVersionToHeader();
            request.AddLibraryVersionToHeader();

[DataRow(HttpStatusCode.TooManyRequests, false)]
[DataRow(HttpStatusCode.UnprocessableEntity, false)]
[DataRow(HttpStatusCode.InternalServerError, false)]
public async Task Given_ApiResponse_When_SuccessStatusCode_Then_Result_ShouldMatchHttpResponseMessage(HttpStatusCode code, bool expected)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test method, and others in this file (lines 43, 63, 122), are marked as async but don't use any await operators. This will result in a compiler warning. Since the methods' execution is synchronous, you can change their signatures to return void instead of Task.

        public void Given_ApiResponse_When_SuccessStatusCode_Then_Result_ShouldMatchHttpResponseMessage(HttpStatusCode code, bool expected)

Comment on lines +23 to +37
try
{
using (HMACSHA256 hmac = new HMACSHA256(key))
{
// Compute the hmac on input data bytes
byte[] rawHmac = hmac.ComputeHash(data);

// Base64-encode the hmac
return Convert.ToBase64String(rawHmac);
}
}
catch (Exception e)
{
throw new Exception("Failed to generate HMAC signature: " + e.Message, e);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Catching a generic System.Exception is generally discouraged as it can obscure the original error. It's better to let more specific exceptions propagate. This makes debugging easier as the caller gets more precise information about what went wrong. You can simplify the code by removing the try-catch block.

            using (HMACSHA256 hmac = new HMACSHA256(key))
            {
                // Compute the hmac on input data bytes
                byte[] rawHmac = hmac.ComputeHash(data);

                // Base64-encode the hmac
                return Convert.ToBase64String(rawHmac);
            }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Discussion: Should we make the enums forward-compatible?

2 participants