Skip to content

chore: update templates to improve oneOf support#22

Merged
necipallef merged 5 commits into
mainfrom
fix/INTER-2034-start-end-datetime-support
May 12, 2026
Merged

chore: update templates to improve oneOf support#22
necipallef merged 5 commits into
mainfrom
fix/INTER-2034-start-end-datetime-support

Conversation

@mcnulty-fp
Copy link
Copy Markdown
Contributor

@mcnulty-fp mcnulty-fp commented May 8, 2026

  • Update the API template to support optional query parameters that alias one another. This will allow start and end to be split into two parameters, one for each type of value supported for the query parameters: a int64 UNIX milliseconds timestamp and a date-time string.

This update allows RFC3339 timestamps to be supported while maintaining backward-compatibility. When code is generated with the v4 normalized schema that splits start and end into multiple, aliased parameters, here's the diff for FingerprintApi.java:

diff --git a/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java b/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java
index e49a9e5..394258e 100644
--- a/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java
+++ b/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java
@@ -15,6 +15,7 @@ import com.fingerprint.v4.sdk.Configuration;
 import com.fingerprint.v4.sdk.Pair;
 import com.fingerprint.v4.sdk.Region;
 import jakarta.ws.rs.core.GenericType;
+import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -282,7 +283,9 @@ public class FingerprintApi {
     private String packageName;
     private String origin;
     private Long start;
+    private OffsetDateTime startDateTime;
     private Long end;
+    private OffsetDateTime endDateTime;
     private Boolean reverse;
     private Boolean suspect;
     private Boolean vpn;
@@ -495,32 +498,90 @@ public class FingerprintApi {
     }
 
     /**
-     * getter for start - Include events that happened after this point (with timestamp greater than or equal the provided `start` Unix milliseconds value). Defaults to 7 days ago. Setting `start` does not change `end`'s default of `now` — adjust it separately if needed.
+     * getter for start - Include events that happened after this point (with timestamp greater than or equal to the provided `start` Unix milliseconds value). Defaults to 7 days ago. Setting `start` does not change the default of `now` for `end`/`end_date_time` — adjust it separately if needed.
+     *
+     * @see {@link #getStartDateTime}
      */
     public Long getStart() {
       return start;
     }
 
     /**
-     * setter for start - Include events that happened after this point (with timestamp greater than or equal the provided `start` Unix milliseconds value). Defaults to 7 days ago. Setting `start` does not change `end`'s default of `now` — adjust it separately if needed.
+     * setter for start - Include events that happened after this point (with timestamp greater than or equal to the provided `start` Unix milliseconds value). Defaults to 7 days ago. Setting `start` does not change the default of `now` for `end`/`end_date_time` — adjust it separately if needed.
+     *
+     * start is an alias for startDateTime. Invoking {@link #setStart} will also set `startDateTime` to `null` to clear an existing `startDateTime` parameter value.
+     *
+     * @see {@link #setStartDateTime}
      */
     public SearchEventsOptionalParams setStart(Long start) {
       this.start = start;
+      this.startDateTime = null;
       return this;
     }
 
     /**
-     * getter for end - Include events that happened before this point (with timestamp less than or equal the provided `end` Unix milliseconds value). Defaults to now. Setting `end` does not change `start`'s default of `7 days ago` — adjust it separately if needed.
+     * getter for startDateTime - Include events that happened after this point (with timestamp greater than or equal to the provided `start_date_time` RFC3339 timestamp). Defaults to 7 days ago. Setting `start_date_time` does not the default of `now` for `end`/`end_date_time` — adjust it separately if needed. This parameter is an alias for `start`.
+     *
+     * @see {@link #getStart}
+     */
+    public OffsetDateTime getStartDateTime() {
+      return startDateTime;
+    }
+
+    /**
+     * setter for startDateTime - Include events that happened after this point (with timestamp greater than or equal to the provided `start_date_time` RFC3339 timestamp). Defaults to 7 days ago. Setting `start_date_time` does not the default of `now` for `end`/`end_date_time` — adjust it separately if needed. This parameter is an alias for `start`.
+     *
+     * startDateTime is an alias for start. Invoking {@link #setStartDateTime} will also set `start` to `null` to clear an existing `start` parameter value.
+     *
+     * @see {@link #setStart}
+     */
+    public SearchEventsOptionalParams setStartDateTime(OffsetDateTime startDateTime) {
+      this.startDateTime = startDateTime;
+      this.start = null;
+      return this;
+    }
+
+    /**
+     * getter for end - Include events that happened before this point (with timestamp less than or equal the provided `end` Unix milliseconds value). Defaults to now. Setting `end` does not change the default of `7 days ago` for `start`/`start_date_time` — adjust it separately if needed.
+     *
+     * @see {@link #getEndDateTime}
      */
     public Long getEnd() {
       return end;
     }
 
     /**
-     * setter for end - Include events that happened before this point (with timestamp less than or equal the provided `end` Unix milliseconds value). Defaults to now. Setting `end` does not change `start`'s default of `7 days ago` — adjust it separately if needed.
+     * setter for end - Include events that happened before this point (with timestamp less than or equal the provided `end` Unix milliseconds value). Defaults to now. Setting `end` does not change the default of `7 days ago` for `start`/`start_date_time` — adjust it separately if needed.
+     *
+     * end is an alias for endDateTime. Invoking {@link #setEnd} will also set `endDateTime` to `null` to clear an existing `endDateTime` parameter value.
+     *
+     * @see {@link #setEndDateTime}
      */
     public SearchEventsOptionalParams setEnd(Long end) {
       this.end = end;
+      this.endDateTime = null;
+      return this;
+    }
+
+    /**
+     * getter for endDateTime - Include events that happened before this point (with timestamp less than or equal the provided `end_date_time` RFC3339 timestamp). Defaults to now. Setting `end_date_time` does not change the default of `7 days ago` for `start`/`start_date_time` — adjust it separately if needed. This parameter is an alias for `end`.
+     *
+     * @see {@link getEnd}
+     */
+    public OffsetDateTime getEndDateTime() {
+      return endDateTime;
+    }
+
+    /**
+     * setter for endDateTime - Include events that happened before this point (with timestamp less than or equal the provided `end_date_time` RFC3339 timestamp). Defaults to now. Setting `end_date_time` does not change the default of `7 days ago` for `start`/`start_date_time` — adjust it separately if needed. This parameter is an alias for `end`.
+     *
+     * endDateTime is an alias for end. Invoking {@link setEndDateTime} will also set `end` to `null` to clear an existing `end` parameter value.
+     *
+     * @see {@link setEnd}
+     */
+    public SearchEventsOptionalParams setEndDateTime(OffsetDateTime endDateTime) {
+      this.endDateTime = endDateTime;
+      this.end = null;
       return this;
     }
 
@@ -1056,8 +1117,12 @@ public class FingerprintApi {
           apiClient.parameterToPairs("", "origin", searchEventsOptionalParams.getOrigin()));
       localVarQueryParams.addAll(
           apiClient.parameterToPairs("", "start", searchEventsOptionalParams.getStart()));
+      localVarQueryParams.addAll(
+          apiClient.parameterToPairs("", "start", searchEventsOptionalParams.getStartDateTime()));
       localVarQueryParams.addAll(
           apiClient.parameterToPairs("", "end", searchEventsOptionalParams.getEnd()));
+      localVarQueryParams.addAll(
+          apiClient.parameterToPairs("", "end", searchEventsOptionalParams.getEndDateTime()));
       localVarQueryParams.addAll(
           apiClient.parameterToPairs("", "reverse", searchEventsOptionalParams.getReverse()));
       localVarQueryParams.addAll(
  • Update the oneof_model template to handle the case where a oneOf construct does not use a discriminator. This case occurs when a oneOf construct is used with a query parameter. The generated model is a wrapper around a generic Object value that can take on a type for each schema in the oneOf's schemas.

This update lays the groundwork for consolidating start/start_date_time and end/end_date_time back into a single parameter when the next major release occurs. Here's the diff for the new SearchEventsStartParameter model that would be generated when using the standard v4 schema:

/*
 * Server API
 * Fingerprint Server API allows you to get, search, and update Events in a server environment. It can be used for data exports, decision-making, and data analysis scenarios. Server API is intended for server-side usage, it's not intended to be used from the client side, whether it's a browser or a mobile device.
 *
 * The version of the OpenAPI document: 4
 * Contact: support@fingerprint.com
 *
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 * https://openapi-generator.tech
 * Do not edit the class manually.
 */

package com.fingerprint.v4.model;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.Locale;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;

@jakarta.annotation.Generated(
    value = "org.openapitools.codegen.languages.JavaClientCodegen",
    comments = "Generator version: 7.16.0")
@JsonDeserialize(using = SearchEventsStartParameter.SearchEventsStartParameterDeserializer.class)
@JsonSerialize(using = SearchEventsStartParameter.SearchEventsStartParameterSerializer.class)
public final class SearchEventsStartParameter {
  private static final Logger log = Logger.getLogger(SearchEventsStartParameter.class.getName());

  @jakarta.annotation.Nonnull private final Object value;

  private SearchEventsStartParameter(Object o) {
    this.value = Objects.requireNonNull(o);
  }

  public SearchEventsStartParameter(Long o) {
    this.value = Objects.requireNonNull(o);
  }

  public SearchEventsStartParameter(OffsetDateTime o) {
    this.value = Objects.requireNonNull(o);
  }

  /**
   * Get the value of `SearchEventsStartParameter` as an instance of `Long`. If the value is not `Long`, a ClassCastException will be thrown.
   *
   * @return The value of `SearchEventsStartParameter`
   * @throws ClassCastException if the instance is not `Long`
   */
  public Long getLong() throws ClassCastException {
    return (Long) value;
  }

  /**
   * Get the value of `SearchEventsStartParameter` as an instance of `OffsetDateTime`. If the value is not `OffsetDateTime`, a ClassCastException will be thrown.
   *
   * @return The value of `SearchEventsStartParameter`
   * @throws ClassCastException if the instance is not `OffsetDateTime`
   */
  public OffsetDateTime getOffsetDateTime() throws ClassCastException {
    return (OffsetDateTime) value;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    SearchEventsStartParameter searchEventsStartParameter = (SearchEventsStartParameter) o;
    return Objects.equals(this.value, searchEventsStartParameter.value);
  }

  @Override
  public int hashCode() {
    return Objects.hash(this.value);
  }

  @Override
  public String toString() {
    return String.valueOf(value);
  }

  public static class SearchEventsStartParameterSerializer
      extends StdSerializer<SearchEventsStartParameter> {
    public SearchEventsStartParameterSerializer(Class<SearchEventsStartParameter> t) {
      super(t);
    }

    public SearchEventsStartParameterSerializer() {
      this(null);
    }

    @Override
    public void serialize(
        SearchEventsStartParameter value, JsonGenerator jgen, SerializerProvider provider)
        throws IOException, JsonProcessingException {
      if (value == null || value.value == null) {
        jgen.writeNull();
      } else {
        provider.defaultSerializeValue(value.value, jgen);
      }
    }
  }

  public static class SearchEventsStartParameterDeserializer
      extends StdDeserializer<SearchEventsStartParameter> {
    public SearchEventsStartParameterDeserializer() {
      this(SearchEventsStartParameter.class);
    }

    public SearchEventsStartParameterDeserializer(Class<?> vc) {
      super(vc);
    }

    @Override
    public SearchEventsStartParameter deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException {
      JsonNode tree = jp.readValueAsTree();
      Object deserialized = null;
      boolean typeCoercion = ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS);
      int match = 0;
      JsonToken token = tree.traverse(jp.getCodec()).nextToken();
      // deserialize Long
      try {
        boolean attemptParsing = true;
        attemptParsing = typeCoercion; // respect type coercion setting
        if (!attemptParsing) {
          attemptParsing |= (token == JsonToken.VALUE_NUMBER_INT);
        }
        if (attemptParsing) {
          deserialized = tree.traverse(jp.getCodec()).readValueAs(Long.class);
          // TODO: there is no validation against JSON schema constraints
          // (min, max, enum, pattern...), this does not perform a strict JSON
          // validation, which means the 'match' count may be higher than it should be.
          match++;
          log.log(Level.FINER, "Input data matches schema 'Long'");
        }
      } catch (Exception e) {
        // deserialization failed, continue
        log.log(Level.FINER, "Input data does not match schema 'Long'", e);
      }

      // deserialize OffsetDateTime
      try {
        boolean attemptParsing = true;
        if (attemptParsing) {
          deserialized = tree.traverse(jp.getCodec()).readValueAs(OffsetDateTime.class);
          // TODO: there is no validation against JSON schema constraints
          // (min, max, enum, pattern...), this does not perform a strict JSON
          // validation, which means the 'match' count may be higher than it should be.
          match++;
          log.log(Level.FINER, "Input data matches schema 'OffsetDateTime'");
        }
      } catch (Exception e) {
        // deserialization failed, continue
        log.log(Level.FINER, "Input data does not match schema 'OffsetDateTime'", e);
      }

      if (match == 1) {
        return new SearchEventsStartParameter(deserialized);
      }
      throw new IOException(
          String.format(
              Locale.ROOT,
              "Failed deserialization for SearchEventsStartParameter: %d classes match result, expected 1",
              match));
    }

    /**
     * Handle deserialization of the 'null' value.
     */
    @Override
    public SearchEventsStartParameter getNullValue(DeserializationContext ctxt)
        throws JsonMappingException {
      throw new JsonMappingException(ctxt.getParser(), "SearchEventsStartParameter cannot be null");
    }
  }
}

mcnulty-fp added 2 commits May 8, 2026 08:27
- Update the API template to support optional query parameters that
  alias one another. This will allow `start` and `end` to be split
  into two parameters, one for each type of value supported for
  the query parameters: a `int64` UNIX milliseconds timestamp and
  a `date-time` string.
- Update the `oneof_model` template to handle the case where
  a `oneOf` construct does not use a `discriminator`. This case
  occurs when a `oneOf` construct is used with a query parameter.
  The generated model is a wrapper around a generic Object value
  that can take on a type for each schema in the `oneOf`'s schemas.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the custom OpenAPI Generator Jersey3 templates to improve oneOf support for query parameters (including non-discriminator oneOf) and to support aliased optional query parameters (e.g., splitting start/end into both unix-ms and RFC3339 variants while keeping backward compatibility).

Changes:

  • Extend api.mustache optional-parameter generation with alias-aware getters/setters and query key mapping.
  • Add a non-discriminator oneOf wrapper class path to oneof_model.mustache (serializer/deserializer-based wrapper around a generic Object).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
template/libraries/jersey3/oneof_model.mustache Adds generation for oneOf models without a discriminator via a wrapper + Jackson (de)serializer.
template/libraries/jersey3/api.mustache Adds optional query param aliasing behavior and adjusts query param naming for aliased parameters.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread template/libraries/jersey3/oneof_model.mustache
Comment thread template/libraries/jersey3/oneof_model.mustache Outdated
Comment thread template/libraries/jersey3/oneof_model.mustache
Comment thread template/libraries/jersey3/api.mustache Outdated
Comment thread template/libraries/jersey3/api.mustache
mcnulty-fp added 2 commits May 8, 2026 09:27
- Add missing semicolons in nullable param branches
- Fix infinite recursion in serialization
- Add missing import
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread template/libraries/jersey3/oneof_model.mustache
Comment thread template/libraries/jersey3/api.mustache
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

⚠️ This PR doesn't contain any changesets. If there are user-facing changes, don't forget to run:

pnpm exec changeset

to create a changeset.

@mcnulty-fp mcnulty-fp marked this pull request as ready for review May 8, 2026 16:34
@mcnulty-fp
Copy link
Copy Markdown
Contributor Author

NOTE: the functional test failures are due to a known, unrelated issue

Copy link
Copy Markdown
Member

@ilfa ilfa left a comment

Choose a reason for hiding this comment

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

Looks valid

@necipallef necipallef merged commit de00ece into main May 12, 2026
15 of 19 checks passed
@necipallef necipallef deleted the fix/INTER-2034-start-end-datetime-support branch May 12, 2026 10:02
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.

4 participants