diff --git a/CHANGELOG.md b/CHANGELOG.md index cc7fffe6c..844055437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). # [8.16.2] - 2025-02-05 +- Added `disconnected_by` enum to `com.vonage.client.voice.EventWebhook` - Changed `AnswerWebhook#getUuid()` return type to String - Updated Voice `Call` documentation diff --git a/src/main/java/com/vonage/client/voice/DisconnectedBy.java b/src/main/java/com/vonage/client/voice/DisconnectedBy.java new file mode 100644 index 000000000..9283741fc --- /dev/null +++ b/src/main/java/com/vonage/client/voice/DisconnectedBy.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 Vonage + * + * 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.vonage.client.voice; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Represents the call terminator in {@linkplain EventWebhook#getDisconnectedBy()}. + * + * @since 8.16.2 + */ +public enum DisconnectedBy { + + /** + * The call was terminated by the Voice API platform, + * for example the NCCO finished its last action and call was disconnected. + */ + PLATFORM, + + /** + * The call was terminated by the user, + * for example the user hung up the call, rejected the call, or didn't answer. + */ + USER, + + /** + * The call was terminator is unmapped by this enum. + */ + UNKNOWN; + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } + + /** + * Converts the string representation of the enum to the enum value. + * + * @param name The disconnected_by field from the webhook as a string. + * + * @return The enum value mapping, or {@code null} if the string is null. + */ + @JsonCreator + public static DisconnectedBy fromString(String name) { + if (name == null) return null; + try { + return valueOf(name.toUpperCase()); + } + catch (IllegalArgumentException ex) { + return UNKNOWN; + } + } +} diff --git a/src/main/java/com/vonage/client/voice/EventWebhook.java b/src/main/java/com/vonage/client/voice/EventWebhook.java index 850589bb1..ec95efdf3 100644 --- a/src/main/java/com/vonage/client/voice/EventWebhook.java +++ b/src/main/java/com/vonage/client/voice/EventWebhook.java @@ -36,6 +36,7 @@ public class EventWebhook extends JsonableBaseObject { private CallStatus status; private CallDirection direction; private CallStatusDetail detail; + private DisconnectedBy disconnectedBy; private MachineDetectionStatus machineDetectionSubstate; private DtmfResult dtmf; private SpeechResults speech; @@ -78,6 +79,17 @@ public CallStatusDetail getDetail() { return detail; } + /** + * If {@linkplain #getStatus()} is {@linkplain CallStatus#COMPLETED}, this field will indicate who ended the call. + * + * @return The disconnection initiator as an enum, or {@code null} if not applicable. + * @since 8.16.2 + */ + @JsonProperty("disconnected_by") + public DisconnectedBy getDisconnectedBy() { + return disconnectedBy; + } + /** * Advanced machine detection status, when call is answered by voicemail and the beep is detected. This is * present if {@linkplain #getStatus()} is {@linkplain CallStatus#HUMAN} or {@linkplain CallStatus#MACHINE}. diff --git a/src/test/java/com/vonage/client/voice/EventWebhookTest.java b/src/test/java/com/vonage/client/voice/EventWebhookTest.java index 1215b38cf..7aa9bb40e 100644 --- a/src/test/java/com/vonage/client/voice/EventWebhookTest.java +++ b/src/test/java/com/vonage/client/voice/EventWebhookTest.java @@ -16,6 +16,7 @@ package com.vonage.client.voice; import com.vonage.client.TestUtils; +import static com.vonage.client.TestUtils.testJsonableBaseObject; import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; import java.net.URI; @@ -30,6 +31,7 @@ public void testSerdesAllFields() { String from = "442079460000", to = "447700900000", status = "completed", + disconnectedBy = "platform", subState = "beep_start", conversationUuid = "CON-aaaaaaaa-bbbb-cccc-dddd-0123456789ab", direction = "inbound", @@ -63,6 +65,7 @@ public void testSerdesAllFields() { " \"from\": \"" + from + "\",\n" + " \"to\": \"" + to + "\",\n" + " \"status\": \"" + status + "\",\n" + + " \"disconnected_by\": \"" + disconnectedBy + "\",\n" + " \"sub_state\": \"" + subState + "\",\n" + " \"conversation_uuid\": \"" + conversationUuid + "\",\n" + " \"direction\": \"" + direction + "\",\n" + @@ -102,7 +105,7 @@ public void testSerdesAllFields() { "}\n"; EventWebhook event = EventWebhook.fromJson(json); - TestUtils.testJsonableBaseObject(event); + testJsonableBaseObject(event); assertEquals(callUuid, event.getCallUuid()); assertEquals(conversationUuid, event.getConversationUuid()); assertEquals(conversationUuidFrom, event.getConversationUuidFrom()); @@ -110,6 +113,7 @@ public void testSerdesAllFields() { assertEquals(from, event.getFrom()); assertEquals(to, event.getTo()); assertEquals(status, event.getStatus().toString()); + assertEquals(disconnectedBy, event.getDisconnectedBy().toString()); assertEquals(subState, event.getMachineDetectionSubstate().toString()); assertEquals(detail, event.getDetail().toString()); assertEquals(direction, event.getDirection().toString()); @@ -149,7 +153,7 @@ public void testSerdesAllFields() { @Test public void testParseEmptyJson() { EventWebhook event = EventWebhook.fromJson("{}"); - TestUtils.testJsonableBaseObject(event); + testJsonableBaseObject(event); assertNull(event.getDetail()); assertNull(event.getCallUuid()); assertNull(event.getReason()); @@ -170,6 +174,7 @@ public void testParseEmptyJson() { assertNull(event.getConversationUuidFrom()); assertNull(event.getDirection()); assertNull(event.getMachineDetectionSubstate()); + assertNull(event.getDisconnectedBy()); assertNull(event.getStatus()); assertNull(event.getDtmf()); assertNull(event.getSpeech()); @@ -181,7 +186,7 @@ public void testCallUuidPrimaryOnly() { uuid = UUID.randomUUID().toString().replace("-", ""); String json = "{\"call_uuid\":\""+callUuid+"\",\"uuid\":\""+uuid+"\"}"; EventWebhook event = EventWebhook.fromJson(json); - TestUtils.testJsonableBaseObject(event); + testJsonableBaseObject(event); assertEquals(uuid, event.getCallUuid()); assertEquals(uuid, EventWebhook.fromJson("{\"uuid\": \""+uuid+"\"}").getCallUuid()); } @@ -222,4 +227,12 @@ public void testSpeechTimeoutReasonUnknown() { assertNull(speech.getRecordingUrl()); assertNull(speech.getError()); } + + @Test + public void testDisconnectedByEnum() { + assertEquals(DisconnectedBy.PLATFORM, DisconnectedBy.fromString("platform")); + assertEquals(DisconnectedBy.USER, DisconnectedBy.fromString("user")); + assertNull(DisconnectedBy.fromString(null)); + assertEquals(DisconnectedBy.UNKNOWN, DisconnectedBy.fromString("Somebody Else")); + } }