Skip to content

Cloud Device generation adjustments#1906

Open
gcatanese wants to merge 3 commits intomainfrom
cloud-device-generation-adjustments
Open

Cloud Device generation adjustments#1906
gcatanese wants to merge 3 commits intomainfrom
cloud-device-generation-adjustments

Conversation

@gcatanese
Copy link
Copy Markdown
Contributor

@gcatanese gcatanese commented Apr 10, 2026

PR to consolidate the Cloud Device generation.

Findings

  • Naming conventions for classes, attributes and enums don't change
  • Enum class no longer used the Type postfix, from DeviceType to Device (this is a design decision to align with the content of the spec)
  • Several classes have disappeared (this is expected)
  • TimeStamp is now OffsetDateTime instead of XMLGregorianCalendar (this is expected)
  • TAPI can handle unknown enum values and unknown attributes like all other services
  • The signatures of the service methods in CloudDeviceApi are aligned to the OpenAPI spec

@gcatanese gcatanese requested review from a team as code owners April 10, 2026 11:53
Copy link
Copy Markdown
Contributor

@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 updates several TAPI model enums to handle unexpected values by logging a warning and returning null instead of throwing an IllegalArgumentException. It also introduces a Logger to these enums and updates the corresponding mustache template to reflect these changes in generated code. Feedback suggests adding missing imports for Logger and Arrays to the template and handling null values explicitly in the fromValue method to prevent excessive log noise for optional fields.

@thomasc-adyen thomasc-adyen self-requested a review April 13, 2026 16:22
}
throw new IllegalArgumentException("Unexpected value '" + value + "'");
// handling unexpected value
LOG.warning(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor, but could we use String templating here using String.format?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Java 11 doesn't have String.formatted() (that's Java 15+), but String.format() works. However, looking at the code more carefully, the concatenation in LOG.warning(...) is the pattern you're pointing at.
The idiomatic Java way for Java 11 would be String.format(...). That said, the concatenation here is fairly readable and there's no performance concern since it only runs on the unexpected-value path.

Looks like we could use String.format(...)., but readability seems not to be an issue. However this change will involve updating the Mustache templates and regenerate all models. Still to be considerred? WDYT @thomasc-adyen

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ignore this, I see now the comment "Approach for centralizing unknown enum handling" 😁

+ value
+ "' - Supported values are "
+ Arrays.toString(AccountType.values()));
return null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is technically a breaking change, right? In the past this would have failed and now it will return null - will we update the release notes to document the difference?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am not sure, the nexo AccountType uses gson that implements a similar approach, without throwing an exception when the enum is unknown

  public static AccountType fromValue(String v) {
    return Arrays.stream(values()).filter(s -> s.value.equals(v)).findFirst().orElse(null);
  }

Did you mean this change? or am I missing something?

}
throw new IllegalArgumentException("Unexpected value '" + value + "'");
// handling unexpected value
LOG.warning(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@gcatanese if this is not generated, rather than doing this per enum, it would be cleaner to make a generic fromValue helper class that handles this globally for all enums

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see we are repeating this across all enums, so centralizing it in one class will help with maintainability and consistency longer-term

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It would also remove the Logger from enum classes, which feels beneficial although I can't articulate why 😆

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

tapi is using the same templates as all other APIs, so we would need to update the templates and re-generate all models. Maybe could be an improvement to follow up?

I think the Logger is necessary because the application doesn't get a value for the enum (only null), the Logger is the only way to avoid this happening silently (in an ideal world we would never do that, but rather throw an exception). This helps merchant to figure our what happens.
An alternative is to move the serialization/deserialization code outside the enum (in a supporting class?), again adjusting templates and regenerating.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ignore this, I see now the comment below 😁

@thomasc-adyen
Copy link
Copy Markdown
Contributor

Approach for centralizing unknown enum handling

Following up on the suggestion to centralize the fromValue logic — here's what that would look like concretely.

1. New EnumUtils utility (hand-written, non-generated)

src/main/java/com/adyen/util/EnumUtils.java

public final class EnumUtils {
    private static final Logger LOG = Logger.getLogger(EnumUtils.class.getName());

    private EnumUtils() {}

    public static <T extends Enum<T>> T fromValue(
            Class<T> enumClass, String value, Function<T, String> getValue) {
        for (T b : enumClass.getEnumConstants()) {
            if (getValue.apply(b).equals(value)) return b;
        }
        LOG.warning(String.format("%s: unexpected enum value '%s' - Supported values are %s",
            enumClass.getSimpleName(), value, Arrays.toString(enumClass.getEnumConstants())));
        return null;
    }
}

2. Template changes (modelEnum.mustache + modelInnerEnum.mustache)

Gated behind a useEnumUtils flag — same pattern as useReflectionEqualsHashCode, handleNullableProperties, and useNullForUnknownEnumValue already in the templates.

Imports — drop Logger when flag is on:

{{^useEnumUtils}}
import java.util.logging.Logger;
{{/useEnumUtils}}

Enum body — drop the LOG field when flag is on:

{{^useEnumUtils}}
  private static final Logger LOG = Logger.getLogger({{{datatypeWithEnum}}}.class.getName());
{{/useEnumUtils}}

fromValue — delegate to the utility:

  public static {{{datatypeWithEnum}}} fromValue({{{dataType}}} value) {
{{#useEnumUtils}}
    return com.adyen.util.EnumUtils.fromValue(
        {{{datatypeWithEnum}}}.class, value, {{{datatypeWithEnum}}}::getValue);
{{/useEnumUtils}}
{{^useEnumUtils}}
    for ({{{datatypeWithEnum}}} b : {{{datatypeWithEnum}}}.values()) {
      if (b.value.equals(value)) return b;
    }
    LOG.warning(String.format(
        "{{{datatypeWithEnum}}}: unexpected enum value '%s' - Supported values are %s",
        value, Arrays.toString({{{datatypeWithEnum}}}.values())));
    return null;
{{/useEnumUtils}}
  }

3. Scope — TAPI-only vs global

Both options are feasible:

  • TAPI-only: Set useEnumUtils: true only in the TAPI config in adyen-sdk-automation. Safe and scoped to this PR, but leaves an inconsistency: TAPI enums use EnumUtils, all other services keep the per-enum Logger.
  • Global (recommended): Set the flag across all service configs in adyen-sdk-automation, re-generate all services. Eliminates Logger from every generated enum consistently — which seems to be the intent of the original suggestion.

TAPI-only is a one-liner per config file in the automation repo and easy to do now; globally applying it later is equally straightforward since it's just a flag flip.


Also: the String.format() suggestion applies to the fallback (non-useEnumUtils) path in the template as well — worth fixing there too regardless of which direction we go.

@gcatanese
Copy link
Copy Markdown
Contributor Author

Approach for centralizing unknown enum handling

Following up on the suggestion to centralize the fromValue logic — here's what that would look like concretely.

@thomasc-adyen This is a good improvement, I have created a new issue to finalise the approach and scope.

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.

2 participants