From f03b2c0fc7a0240d1cfa9aaf81c3c90ae4483bdf Mon Sep 17 00:00:00 2001 From: Adam Hooper Date: Mon, 20 Jul 2026 11:22:36 -0400 Subject: [PATCH] Support nested oneOf --- .../src/generator/templates/modelOneOf.j2 | 17 +++++-- ...ipelineEventRequestAttributesResource.java | 7 ++- .../v2/model/UpsertCatalogEntityRequest.java | 3 +- .../v2/api/CIAppPipelineEventJobTest.java | 44 +++++++++++++++++++ 4 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 src/test/java/com/datadog/api/client/v2/api/CIAppPipelineEventJobTest.java diff --git a/.generator/src/generator/templates/modelOneOf.j2 b/.generator/src/generator/templates/modelOneOf.j2 index 1f311ae79d0..3348cc6f842 100644 --- a/.generator/src/generator/templates/modelOneOf.j2 +++ b/.generator/src/generator/templates/modelOneOf.j2 @@ -83,6 +83,9 @@ public class {{ name }} extends AbstractOpenApiSchema { {%- set parameterizedDataType = get_type(oneOf) %} {%- set unParameterizedDataType = parameterizedDataType|un_parameterize_type %} {%- set isParameterized = parameterizedDataType|is_parameterized_type %} + {%- set isModelMember = not oneOf|is_primitive and not unParameterizedDataType|lower|is_java_base_type and "enum" not in oneOf %} + {%- set isComposedMember = "oneOf" in oneOf or "anyOf" in oneOf %} + {%- set isModelListMember = "items" in oneOf and oneOf.get("items")|is_model and not oneOf|is_primitive %} // deserialize {{ parameterizedDataType }} try { boolean attemptParsing = true; @@ -108,12 +111,20 @@ public class {{ name }} extends AbstractOpenApiSchema { // 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. - {%- if not oneOf|is_primitive and not unParameterizedDataType|lower|is_java_base_type and "enum" not in oneOf %} - if (!(({{ unParameterizedDataType }})tmp).unparsed) { + {%- if isModelMember %} + if (!(({{ unParameterizedDataType }}) tmp).unparsed + {%- if isComposedMember %} + {#- unmatched oneOf doesn't propagate to "tmp.unparsed": we aim + for today's client to be forward-compatible with future oneOf + branches. But a oneOf branch that it itself a oneOf? We _must_ + respect the nested oneOf's `unparsed` or the oneOf branch will + always match. #} + && !((({{ unParameterizedDataType }}) tmp).getActualInstance() instanceof UnparsedObject) + {%- endif %}) { deserialized = tmp; match++; } - {%- elif "items" in oneOf and oneOf.get("items")|is_model and not oneOf|is_primitive %} + {%- elif isModelListMember %} {%- set itemsDataType = get_type(oneOf.get("items")) %} // keep the matched list, but propagate 'unparsed' from any invalid item boolean itemsUnparsed = false; diff --git a/src/main/java/com/datadog/api/client/v2/model/CIAppCreatePipelineEventRequestAttributesResource.java b/src/main/java/com/datadog/api/client/v2/model/CIAppCreatePipelineEventRequestAttributesResource.java index e6047c4a92c..79e2f6349a1 100644 --- a/src/main/java/com/datadog/api/client/v2/model/CIAppCreatePipelineEventRequestAttributesResource.java +++ b/src/main/java/com/datadog/api/client/v2/model/CIAppCreatePipelineEventRequestAttributesResource.java @@ -124,7 +124,9 @@ public CIAppCreatePipelineEventRequestAttributesResource deserialize( // 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. - if (!((CIAppPipelineEventPipeline) tmp).unparsed) { + if (!((CIAppPipelineEventPipeline) tmp).unparsed + && !(((CIAppPipelineEventPipeline) tmp).getActualInstance() + instanceof UnparsedObject)) { deserialized = tmp; match++; } @@ -214,7 +216,8 @@ public CIAppCreatePipelineEventRequestAttributesResource deserialize( // 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. - if (!((CIAppPipelineEventJob) tmp).unparsed) { + if (!((CIAppPipelineEventJob) tmp).unparsed + && !(((CIAppPipelineEventJob) tmp).getActualInstance() instanceof UnparsedObject)) { deserialized = tmp; match++; } diff --git a/src/main/java/com/datadog/api/client/v2/model/UpsertCatalogEntityRequest.java b/src/main/java/com/datadog/api/client/v2/model/UpsertCatalogEntityRequest.java index dc9f1f8152f..099aa01c738 100644 --- a/src/main/java/com/datadog/api/client/v2/model/UpsertCatalogEntityRequest.java +++ b/src/main/java/com/datadog/api/client/v2/model/UpsertCatalogEntityRequest.java @@ -111,7 +111,8 @@ public UpsertCatalogEntityRequest deserialize(JsonParser jp, DeserializationCont // 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. - if (!((EntityV3) tmp).unparsed) { + if (!((EntityV3) tmp).unparsed + && !(((EntityV3) tmp).getActualInstance() instanceof UnparsedObject)) { deserialized = tmp; match++; } diff --git a/src/test/java/com/datadog/api/client/v2/api/CIAppPipelineEventJobTest.java b/src/test/java/com/datadog/api/client/v2/api/CIAppPipelineEventJobTest.java new file mode 100644 index 00000000000..fdae025e5bc --- /dev/null +++ b/src/test/java/com/datadog/api/client/v2/api/CIAppPipelineEventJobTest.java @@ -0,0 +1,44 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +package com.datadog.api.client.v2.api; + +import static com.datadog.api.World.fromJSON; +import static org.junit.Assert.assertEquals; + +import com.datadog.api.client.v2.model.CIAppCreatePipelineEventRequestAttributesResource; +import com.datadog.api.client.v2.model.CIAppPipelineEventFinishedJob; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.BeforeClass; +import org.junit.Test; + +public class CIAppPipelineEventJobTest extends V2APITest { + + private static ObjectMapper objectMapper; + + @Override + public String getTracingEndpoint() { + return "ci-app"; + } + + @BeforeClass + public static void initApi() { + objectMapper = generalApiUnitTestClient.getJSON().getMapper(); + } + + @Test + public void testDeserializeNestedOneOfJob() throws JsonProcessingException { + String body = + "{\"end\":\"2023-01-01T12:00:01+00:00\",\"id\":\"job-id\",\"level\":\"job\",\"name\":\"job-name\",\"pipeline_name\":\"my-pipeline\",\"pipeline_unique_id\":\"pipeline-unique-id\",\"start\":\"2023-01-01T12:00:00+00:00\",\"status\":\"success\",\"url\":\"https://example.com/job\"}"; + CIAppCreatePipelineEventRequestAttributesResource res = + fromJSON(objectMapper, CIAppCreatePipelineEventRequestAttributesResource.class, body); + + CIAppPipelineEventFinishedJob finishedJob = + res.getCIAppPipelineEventJob().getCIAppPipelineEventFinishedJob(); + assertEquals("job-id", finishedJob.getId()); + } +}