diff --git a/core/src/main/java/com/google/adk/SchemaUtils.java b/core/src/main/java/com/google/adk/SchemaUtils.java index 200865d33..6db93eac9 100644 --- a/core/src/main/java/com/google/adk/SchemaUtils.java +++ b/core/src/main/java/com/google/adk/SchemaUtils.java @@ -41,6 +41,9 @@ private SchemaUtils() {} // Private constructor for utility class */ @SuppressWarnings("unchecked") // For tool parameter type casting. private static Boolean matchType(Object value, Schema schema, Boolean isInput) { + if (value == null) { + return schema.nullable().orElse(false); + } // Based on types from https://cloud.google.com/vertex-ai/docs/reference/rest/v1/Schema Type.Known type = schema.type().get().knownEnum(); switch (type) { @@ -73,7 +76,6 @@ private static Boolean matchType(Object value, Schema schema, Boolean isInput) { throw new IllegalArgumentException( "Unsupported type: " + type + " is not a Open API data type."); default: - // This category includes NULL, which is not supported. break; } return false; diff --git a/core/src/main/java/com/google/adk/tools/FunctionCallingUtils.java b/core/src/main/java/com/google/adk/tools/FunctionCallingUtils.java index 3244b40e9..caf09dbe9 100644 --- a/core/src/main/java/com/google/adk/tools/FunctionCallingUtils.java +++ b/core/src/main/java/com/google/adk/tools/FunctionCallingUtils.java @@ -188,6 +188,14 @@ public static Schema buildSchemaFromType(Type type, ObjectMapper objectMapper) { */ private static Schema buildSchemaRecursive( JavaType javaType, SchemaGenerationContext context, ObjectMapper objectMapper) { + if (Optional.class.isAssignableFrom(javaType.getRawClass())) { + JavaType containedType = javaType.containedType(0); + if (containedType == null) { + return Schema.builder().type("OBJECT").nullable(true).build(); + } + Schema innerSchema = buildSchemaRecursive(containedType, context, objectMapper); + return innerSchema.toBuilder().nullable(true).build(); + } if (context.isProcessing(javaType)) { logger.warn("Type {} is recursive. Omitting from schema.", javaType.toCanonical()); return Schema.builder() diff --git a/core/src/main/java/com/google/adk/tools/FunctionTool.java b/core/src/main/java/com/google/adk/tools/FunctionTool.java index ea67d40d4..09cf6ba21 100644 --- a/core/src/main/java/com/google/adk/tools/FunctionTool.java +++ b/core/src/main/java/com/google/adk/tools/FunctionTool.java @@ -271,29 +271,45 @@ private Maybe> call(Map args, ToolContext to throws IllegalAccessException, InvocationTargetException { Object[] arguments = buildArguments(args, toolContext, null); Object result = func.invoke(instance, arguments); - if (result == null) { + if (result == null || isEmptyOptional(result)) { return Maybe.empty(); } else if (result instanceof Maybe) { return ((Maybe) result) - .map( - data -> objectMapper.convertValue(data, new TypeReference>() {})); + .filter(data -> !isEmptyOptional(data)) + .map(this::convertToMapOrResult); } else if (result instanceof Single) { return ((Single) result) - .map(data -> objectMapper.convertValue(data, new TypeReference>() {})) - .toMaybe(); + .toMaybe() + .filter(data -> !isEmptyOptional(data)) + .map(this::convertToMapOrResult); } else { - try { - return Maybe.just( - objectMapper.convertValue(result, new TypeReference>() {})); - } catch (IllegalArgumentException e) { - // Conversion to map failed, in this case we follow - // https://google.github.io/adk-docs/tools-custom/function-tools/#return-type and return - // the { "result": $result } - return Maybe.just(ImmutableMap.of("result", result)); + return Maybe.just(convertToMapOrResult(result)); + } + } + + private Map convertToMapOrResult(Object value) { + if (value instanceof Optional) { + value = ((Optional) value).get(); + } + try { + Map map = + objectMapper.convertValue(value, new TypeReference>() {}); + if (map == null) { + return ImmutableMap.of(); } + return map; + } catch (IllegalArgumentException e) { + // Conversion to map failed, in this case we follow + // https://google.github.io/adk-docs/tools-custom/function-tools/#return-type and return + // the { "result": $result } + return ImmutableMap.of("result", value); } } + private static boolean isEmptyOptional(Object value) { + return value instanceof Optional && ((Optional) value).isEmpty(); + } + @SuppressWarnings("unchecked") public Flowable> callLive( Map args, ToolContext toolContext, InvocationContext invocationContext) @@ -308,6 +324,21 @@ public Flowable> callLive( } } + @SuppressWarnings("unchecked") // For tool parameter type casting. + private @Nullable Object resolveArgumentValue( + @Nullable Object argValue, Class paramType, Type parameterizedType, String paramName) { + if (paramType.equals(List.class)) { + if (argValue instanceof List) { + Type type = ((ParameterizedType) parameterizedType).getActualTypeArguments()[0]; + Class typeArgClass = getTypeClass(type, paramName); + return createList((List) argValue, typeArgClass); + } + } else if (argValue instanceof Map) { + return objectMapper.convertValue(argValue, paramType); + } + return castValue(argValue, paramType); + } + @SuppressWarnings("unchecked") // For tool parameter type casting. private Object[] buildArguments( Map args, @@ -336,9 +367,14 @@ private Object[] buildArguments( continue; } Annotations.Schema schema = parameters[i].getAnnotation(Annotations.Schema.class); + Class paramType = parameters[i].getType(); if (!args.containsKey(paramName)) { if (schema != null && schema.optional()) { - arguments[i] = null; + if (paramType.equals(Optional.class)) { + arguments[i] = Optional.empty(); + } else { + arguments[i] = null; + } continue; } else { throw new IllegalArgumentException( @@ -347,22 +383,23 @@ private Object[] buildArguments( paramName)); } } - Class paramType = parameters[i].getType(); Object argValue = args.get(paramName); - if (paramType.equals(List.class)) { - if (argValue instanceof List) { - Type type = + if (paramType.equals(Optional.class)) { + if (argValue == null) { + arguments[i] = Optional.empty(); + } else { + Type innerType = ((ParameterizedType) parameters[i].getParameterizedType()) .getActualTypeArguments()[0]; - Class typeArgClass = getTypeClass(type, paramName); - arguments[i] = createList((List) argValue, typeArgClass); - continue; + Class innerClass = getTypeClass(innerType, paramName); + Object resolvedValue = resolveArgumentValue(argValue, innerClass, innerType, paramName); + arguments[i] = Optional.ofNullable(resolvedValue); } - } else if (argValue instanceof Map) { - arguments[i] = objectMapper.convertValue(argValue, paramType); - continue; + } else { + arguments[i] = + resolveArgumentValue( + argValue, paramType, parameters[i].getParameterizedType(), paramName); } - arguments[i] = castValue(argValue, paramType); } return arguments; } diff --git a/core/src/test/java/com/google/adk/SchemaUtilsTest.java b/core/src/test/java/com/google/adk/SchemaUtilsTest.java new file mode 100644 index 000000000..c87c91904 --- /dev/null +++ b/core/src/test/java/com/google/adk/SchemaUtilsTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.adk; + +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableMap; +import com.google.genai.types.Schema; +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link SchemaUtils}. */ +@RunWith(JUnit4.class) +public final class SchemaUtilsTest { + + @Test + public void validateMapOnSchema_nullableField_allowsNull() { + Schema schema = + Schema.builder() + .type("OBJECT") + .properties( + ImmutableMap.of( + "nullableField", Schema.builder().type("STRING").nullable(true).build())) + .build(); + + Map args = new HashMap<>(); + args.put("nullableField", null); + + // Should not throw exception + SchemaUtils.validateMapOnSchema(args, schema, /* isInput= */ true); + } + + @Test + public void validateMapOnSchema_nonNullableField_throwsException() { + Schema schema = + Schema.builder() + .type("OBJECT") + .properties( + ImmutableMap.of( + "nonNullableField", Schema.builder().type("STRING").nullable(false).build())) + .build(); + + Map args = new HashMap<>(); + args.put("nonNullableField", null); + + assertThrows( + IllegalArgumentException.class, + () -> SchemaUtils.validateMapOnSchema(args, schema, /* isInput= */ true)); + } + + @Test + public void validateMapOnSchema_implicitNonNullableField_throwsException() { + Schema schema = + Schema.builder() + .type("OBJECT") + .properties(ImmutableMap.of("defaultField", Schema.builder().type("STRING").build())) + .build(); + + Map args = new HashMap<>(); + args.put("defaultField", null); + + assertThrows( + IllegalArgumentException.class, + () -> SchemaUtils.validateMapOnSchema(args, schema, /* isInput= */ true)); + } +} diff --git a/core/src/test/java/com/google/adk/plugins/agentanalytics/PluginStateTest.java b/core/src/test/java/com/google/adk/plugins/agentanalytics/PluginStateTest.java index 14dcc390e..e55b5a634 100644 --- a/core/src/test/java/com/google/adk/plugins/agentanalytics/PluginStateTest.java +++ b/core/src/test/java/com/google/adk/plugins/agentanalytics/PluginStateTest.java @@ -213,7 +213,8 @@ public void ensureInvocationCompleted_timeout_cleansUpState() throws IOException // Wait for cleanup side effects which run after terminal signal. long deadline = Instant.now().plusMillis(1000).toEpochMilli(); - while (!pluginState.getPendingTasksForInvocation(invocationId).isEmpty() + while ((!pluginState.getBatchProcessors().isEmpty() + || !pluginState.getTraceManagers().isEmpty()) && Instant.now().toEpochMilli() < deadline) { try { Thread.sleep(10); diff --git a/core/src/test/java/com/google/adk/tools/FunctionCallingUtilsTest.java b/core/src/test/java/com/google/adk/tools/FunctionCallingUtilsTest.java new file mode 100644 index 000000000..40ea7b7fa --- /dev/null +++ b/core/src/test/java/com/google/adk/tools/FunctionCallingUtilsTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.adk.tools; + +import static com.google.common.truth.Truth.assertThat; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.collect.ImmutableMap; +import com.google.genai.types.Schema; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link FunctionCallingUtils}. */ +@RunWith(JUnit4.class) +public final class FunctionCallingUtilsTest { + + public static class PojoWithFields { + public String field1; + public int field2; + } + + public static class PojoWithOptionalFields { + public Optional optionalField; + public Optional optionalPojo; + public Optional> optionalList; + } + + @Test + public void buildSchemaFromType_optionalString_returnsNullableString() { + Type type = new TypeReference>() {}.getType(); + + Schema schema = FunctionCallingUtils.buildSchemaFromType(type); + + assertThat(schema).isEqualTo(Schema.builder().type("STRING").nullable(true).build()); + } + + @Test + public void buildSchemaFromType_optionalPojo_returnsNullablePojoWithProperties() { + Type type = new TypeReference>() {}.getType(); + + Schema schema = FunctionCallingUtils.buildSchemaFromType(type); + + assertThat(schema) + .isEqualTo( + Schema.builder() + .type("OBJECT") + .nullable(true) + .properties( + ImmutableMap.of( + "field1", Schema.builder().type("STRING").build(), + "field2", Schema.builder().type("INTEGER").build())) + .build()); + } + + @Test + public void buildSchemaFromType_pojoWithOptionalFields_generatesCorrectSchema() { + Type type = PojoWithOptionalFields.class; + + Schema schema = FunctionCallingUtils.buildSchemaFromType(type); + + Schema expectedSchema = + Schema.builder() + .type("OBJECT") + .properties( + ImmutableMap.of( + "optionalField", + Schema.builder().type("STRING").nullable(true).build(), + "optionalPojo", + Schema.builder() + .type("OBJECT") + .nullable(true) + .properties( + ImmutableMap.of( + "field1", Schema.builder().type("STRING").build(), + "field2", Schema.builder().type("INTEGER").build())) + .build(), + "optionalList", + Schema.builder() + .type("ARRAY") + .nullable(true) + .items(Schema.builder().type("STRING").build()) + .build())) + .build(); + + assertThat(schema).isEqualTo(expectedSchema); + } +} diff --git a/core/src/test/java/com/google/adk/tools/FunctionToolTest.java b/core/src/test/java/com/google/adk/tools/FunctionToolTest.java index 0939c6506..da827c0e5 100644 --- a/core/src/test/java/com/google/adk/tools/FunctionToolTest.java +++ b/core/src/test/java/com/google/adk/tools/FunctionToolTest.java @@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.NullNode; import com.google.adk.agents.InvocationContext; import com.google.adk.agents.LlmAgent; import com.google.adk.events.ToolConfirmation; @@ -36,6 +38,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -333,6 +336,276 @@ public void call_withPojoParamWithFields() throws Exception { assertThat(result).containsExactly("field1", "abc", "field2", 123); } + @Test + public void call_withPojoParamWithOptionalFields_present() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "pojoParamWithOptionalFields"); + PojoWithFields nestedPojo = new PojoWithFields(); + nestedPojo.field1 = "abc"; + nestedPojo.field2 = 123; + Map pojoMap = new HashMap<>(); + pojoMap.put("optionalField", "hello"); + pojoMap.put("optionalPojo", nestedPojo); + + Map result = + tool.runAsync(ImmutableMap.of("pojo", pojoMap), null).blockingGet(); + + assertThat(result) + .containsExactly( + "optionalFieldPresent", + true, + "optionalFieldValue", + "hello", + "optionalPojoPresent", + true, + "optionalPojoValueField1", + "abc", + "optionalPojoValueField2", + 123); + } + + @Test + public void call_withPojoParamWithOptionalFields_missing() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "pojoParamWithOptionalFields"); + Map pojoMap = new HashMap<>(); + + Map result = + tool.runAsync(ImmutableMap.of("pojo", pojoMap), null).blockingGet(); + + assertThat(result).containsExactly("optionalFieldPresent", false, "optionalPojoPresent", false); + } + + @Test + public void call_withOptionalReturn_present() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithOptionalReturn"); + + Map result = + tool.runAsync(ImmutableMap.of("returnPresent", true), null).blockingGet(); + + assertThat(result).containsExactly("result", "hello"); + } + + @Test + public void call_withOptionalReturn_empty() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithOptionalReturn"); + + Map result = + tool.runAsync(ImmutableMap.of("returnPresent", false), null).blockingGet(); + + assertThat(result).isEmpty(); + } + + @Test + public void call_withOptionalPojoReturn_present() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithOptionalPojoReturn"); + + Map result = + tool.runAsync(ImmutableMap.of("returnPresent", true), null).blockingGet(); + + assertThat(result).containsExactly("field1", "abc", "field2", 123); + } + + @Test + public void call_withOptionalPojoReturn_empty() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithOptionalPojoReturn"); + + Map result = + tool.runAsync(ImmutableMap.of("returnPresent", false), null).blockingGet(); + + assertThat(result).isEmpty(); + } + + @Test + public void call_withPojoOptionalFields_bothPresent() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithPojoOptionalFields"); + + Map result = + tool.runAsync( + ImmutableMap.of("includeOptionalField", true, "includeOptionalPojo", true), null) + .blockingGet(); + + assertThat(result) + .containsExactly( + "optionalField", + "hello", + "optionalPojo", + ImmutableMap.of("field1", "abc", "field2", 999)); + } + + @Test + public void call_withPojoOptionalFields_bothEmpty() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithPojoOptionalFields"); + + Map result = + tool.runAsync( + ImmutableMap.of("includeOptionalField", false, "includeOptionalPojo", false), null) + .blockingGet(); + + assertThat(result).isEmpty(); + } + + @Test + public void call_withMaybeOptionalReturn_present() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithMaybeOptionalReturn"); + + Map result = + tool.runAsync(ImmutableMap.of("returnPresent", true), null).blockingGet(); + + assertThat(result).containsExactly("result", "hello"); + } + + @Test + public void call_withMaybeOptionalReturn_empty() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithMaybeOptionalReturn"); + + Map result = + tool.runAsync(ImmutableMap.of("returnPresent", false), null).blockingGet(); + + assertThat(result).isEmpty(); + } + + @Test + public void call_withSingleOptionalReturn_present() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithSingleOptionalReturn"); + + Map result = + tool.runAsync(ImmutableMap.of("returnPresent", true), null).blockingGet(); + + assertThat(result).containsExactly("result", "hello"); + } + + @Test + public void call_withSingleOptionalReturn_empty() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithSingleOptionalReturn"); + + Map result = + tool.runAsync(ImmutableMap.of("returnPresent", false), null).blockingGet(); + + assertThat(result).isEmpty(); + } + + @Test + public void create_withPojoParamWithOptionalFields() { + FunctionTool tool = FunctionTool.create(Functions.class, "pojoParamWithOptionalFields"); + + assertThat(tool).isNotNull(); + assertThat(tool.declaration().get().parameters()) + .hasValue( + Schema.builder() + .type("OBJECT") + .properties( + ImmutableMap.of( + "pojo", + Schema.builder() + .type("OBJECT") + .properties( + ImmutableMap.of( + "optionalField", + Schema.builder().type("STRING").nullable(true).build(), + "optionalPojo", + Schema.builder() + .type("OBJECT") + .nullable(true) + .properties( + ImmutableMap.of( + "field1", + Schema.builder().type("STRING").build(), + "field2", + Schema.builder().type("INTEGER").build())) + .build())) + .build())) + .required(ImmutableList.of("pojo")) + .build()); + } + + @Test + public void create_withOptionalTypeParameter() { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithOptionalTypeParameter"); + + assertThat(tool).isNotNull(); + assertThat(tool.declaration().get().parameters()) + .hasValue( + Schema.builder() + .type("OBJECT") + .properties( + ImmutableMap.of( + "optionalParam", + Schema.builder() + .type("STRING") + .nullable(true) + .description("An Optional type parameter") + .build())) + .required(ImmutableList.of()) + .build()); + } + + @Test + public void call_withOptionalTypeParameter_present() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithOptionalTypeParameter"); + + Map result = + tool.runAsync(ImmutableMap.of("optionalParam", "hello"), null).blockingGet(); + + assertThat(result).containsExactly("present", true, "value", "hello"); + } + + @Test + public void call_withOptionalTypeParameter_missing() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithOptionalTypeParameter"); + + Map result = tool.runAsync(ImmutableMap.of(), null).blockingGet(); + + assertThat(result).containsExactly("present", false); + } + + @Test + public void call_withListPojoParam() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "listPojoParam"); + List> listArg = + ImmutableList.of( + ImmutableMap.of("field1", "v1", "field2", 1), + ImmutableMap.of("field1", "v2", "field2", 2)); + + Map result = + tool.runAsync(ImmutableMap.of("list", listArg), null).blockingGet(); + + assertThat(result).containsExactly("firstField1", "v1", "count", 2); + } + + @Test + public void create_withRawOptionalParameter() { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithRawOptional"); + assertThat(tool).isNotNull(); + assertThat(tool.declaration().get().parameters()) + .hasValue( + Schema.builder() + .type("OBJECT") + .properties( + ImmutableMap.of( + "rawOpt", Schema.builder().type("OBJECT").nullable(true).build())) + .required(ImmutableList.of()) + .build()); + } + + @Test + public void call_withOptionalTypeParameter_null() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionWithOptionalTypeParameter"); + + Map args = new HashMap<>(); + args.put("optionalParam", null); + Map result = tool.runAsync(args, null).blockingGet(); + + assertThat(result).containsExactly("present", false); + } + + @Test + public void call_withNullNodeReturnValue_returnsEmptyMap() throws Exception { + FunctionTool tool = FunctionTool.create(Functions.class, "functionThatReturnsNullNode"); + + Map result = tool.runAsync(ImmutableMap.of(), null).blockingGet(); + + assertThat(result).isEmpty(); + } + @Test public void call_withBooleanReturnValue_returnsMapWithResult() throws Exception { FunctionTool tool = FunctionTool.create(Functions.class, "returnsBoolean"); @@ -798,6 +1071,39 @@ public static ImmutableMap pojoParamWithGettersAndSetters( return ImmutableMap.of("field1", pojo.getField1(), "field2", pojo.getField2()); } + public static ImmutableMap pojoParamWithOptionalFields( + PojoWithOptionalFields pojo) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.put("optionalFieldPresent", pojo.optionalField.isPresent()); + if (pojo.optionalField.isPresent()) { + builder.put("optionalFieldValue", pojo.optionalField.get()); + } + builder.put("optionalPojoPresent", pojo.optionalPojo.isPresent()); + if (pojo.optionalPojo.isPresent()) { + builder + .put("optionalPojoValueField1", pojo.optionalPojo.get().field1) + .put("optionalPojoValueField2", pojo.optionalPojo.get().field2); + } + return builder.buildOrThrow(); + } + + public static ImmutableMap functionWithOptionalTypeParameter( + @Annotations.Schema( + name = "optionalParam", + description = "An Optional type parameter", + optional = true) + Optional optionalParam) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + if (optionalParam != null) { + if (optionalParam.isPresent()) { + builder.put("present", true).put("value", optionalParam.get()); + } else { + builder.put("present", false); + } + } + return builder.buildOrThrow(); + } + public static void processDiamond(DiamondTop top) {} public static Maybe> returnsMaybeMap() { @@ -899,6 +1205,61 @@ public ImmutableMap nonStaticReturnAllSupportedParametersAsMap( .put("toolContext", toolContext.toString()) .buildOrThrow(); } + + public static Optional functionWithOptionalReturn(boolean returnPresent) { + return returnPresent ? Optional.of("hello") : Optional.empty(); + } + + public static Optional functionWithOptionalPojoReturn(boolean returnPresent) { + if (returnPresent) { + PojoWithFields pojo = new PojoWithFields(); + pojo.field1 = "abc"; + pojo.field2 = 123; + return Optional.of(pojo); + } else { + return Optional.empty(); + } + } + + public static PojoWithOptionalFields functionWithPojoOptionalFields( + boolean includeOptionalField, boolean includeOptionalPojo) { + PojoWithOptionalFields pojo = new PojoWithOptionalFields(); + if (includeOptionalField) { + pojo.optionalField = Optional.of("hello"); + } + if (includeOptionalPojo) { + PojoWithFields inner = new PojoWithFields(); + inner.field1 = "abc"; + inner.field2 = 999; + pojo.optionalPojo = Optional.of(inner); + } + return pojo; + } + + public static Maybe> functionWithMaybeOptionalReturn(boolean returnPresent) { + return Maybe.just(returnPresent ? Optional.of("hello") : Optional.empty()); + } + + public static Single> functionWithSingleOptionalReturn(boolean returnPresent) { + return Single.just(returnPresent ? Optional.of("hello") : Optional.empty()); + } + + public static ImmutableMap listPojoParam(List list) { + if (list == null || list.isEmpty()) { + return ImmutableMap.of("count", 0); + } + return ImmutableMap.of("firstField1", list.get(0).field1, "count", list.size()); + } + + @SuppressWarnings("rawtypes") + public static ImmutableMap functionWithRawOptional( + @Annotations.Schema(name = "rawOpt", optional = true) Optional rawOpt) { + return ImmutableMap.of("present", rawOpt != null && rawOpt.isPresent()); + } + + public static JsonNode functionThatReturnsNullNode() { + return NullNode.getInstance(); + } } public static class PojoWithFields { @@ -906,6 +1267,11 @@ public static class PojoWithFields { public int field2; } + public static class PojoWithOptionalFields { + public Optional optionalField = Optional.empty(); + public Optional optionalPojo = Optional.empty(); + } + public static class PojoWithGettersAndSetters { private String privateField1; private int privateField2;