From e2c53d3dc555873fd3c9e2e7dd6c40fc725fa6ff Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier Date: Mon, 4 May 2026 16:41:22 +0200 Subject: [PATCH 01/37] fix (HttpClientApache): define headers to be added as volatile for multi-threading consistency --- client/src/main/com/sinch/sdk/http/HttpClientApache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/com/sinch/sdk/http/HttpClientApache.java b/client/src/main/com/sinch/sdk/http/HttpClientApache.java index ed4918628..5cbecbef7 100644 --- a/client/src/main/com/sinch/sdk/http/HttpClientApache.java +++ b/client/src/main/com/sinch/sdk/http/HttpClientApache.java @@ -47,7 +47,7 @@ public class HttpClientApache implements com.sinch.sdk.core.http.HttpClient { private static final Logger LOGGER = Logger.getLogger(HttpClientApache.class.getName()); - private Map headersToBeAdded; + private volatile Map headersToBeAdded; private volatile CloseableHttpClient client; From 690575df22f196eef8bb67c0cbf28cd5b5ab583e Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier Date: Mon, 4 May 2026 15:30:07 +0200 Subject: [PATCH 02/37] fix (HttpClientApache): Use 'try with resource' scanner to avoid possible resource leak --- .../com/sinch/sdk/http/HttpClientApache.java | 13 ++++--- .../sinch/sdk/http/HttpClientApacheTest.java | 35 +++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/client/src/main/com/sinch/sdk/http/HttpClientApache.java b/client/src/main/com/sinch/sdk/http/HttpClientApache.java index 5cbecbef7..5625c4512 100644 --- a/client/src/main/com/sinch/sdk/http/HttpClientApache.java +++ b/client/src/main/com/sinch/sdk/http/HttpClientApache.java @@ -71,10 +71,15 @@ private static HttpResponse processResponse(ClassicHttpResponse response) throws } HttpEntity entity = response.getEntity(); - Scanner s = new Scanner(entity.getContent()).useDelimiter("\\A"); - String content = (s.hasNext() ? s.next() : ""); - - return new HttpResponse(statusCode, message, headers, content.getBytes(StandardCharsets.UTF_8)); + if (null == entity) { + return new HttpResponse(statusCode, message, headers, null); + } + try (Scanner s = + new Scanner(entity.getContent(), StandardCharsets.UTF_8.name()).useDelimiter("\\A")) { + String content = (s.hasNext() ? s.next() : ""); + return new HttpResponse( + statusCode, message, headers, content.getBytes(StandardCharsets.UTF_8)); + } } private static Map> transformResponseHeaders(Header[] headers) { diff --git a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTest.java b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTest.java index 075fc7f3e..329580abd 100644 --- a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTest.java +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTest.java @@ -9,9 +9,16 @@ import com.sinch.sdk.core.http.HttpRequest; import com.sinch.sdk.core.http.HttpResponse; import com.sinch.sdk.core.models.ServerConfiguration; +import java.io.ByteArrayInputStream; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; import java.util.*; +import java.util.Scanner; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -75,4 +82,32 @@ void testInvokeApiHandles401WithEmptyHeaders() throws Exception { // AND: resetToken() is not called (no headers to check) verify(mockAuthManager, never()).resetToken(); } + + @Test + void processResponseDecodesBodyAsUtf8() throws Exception { + String nonAscii = "Ünïcödé resülts"; + byte[] utf8Bytes = nonAscii.getBytes(StandardCharsets.UTF_8); + + HttpEntity entity = mock(HttpEntity.class); + when(entity.getContent()).thenReturn(new ByteArrayInputStream(utf8Bytes)); + + ClassicHttpResponse httpResponse = mock(ClassicHttpResponse.class); + when(httpResponse.getCode()).thenReturn(200); + when(httpResponse.getReasonPhrase()).thenReturn("OK"); + when(httpResponse.getHeaders()).thenReturn(new Header[0]); + when(httpResponse.getEntity()).thenReturn(entity); + + Method processResponse = + HttpClientApache.class.getDeclaredMethod("processResponse", ClassicHttpResponse.class); + processResponse.setAccessible(true); + + HttpResponse result = (HttpResponse) processResponse.invoke(null, httpResponse); + + assertNotNull(result.getContent()); + try (Scanner s = + new Scanner(result.getContent(), StandardCharsets.UTF_8.name()).useDelimiter("\\A")) { + String decoded = s.hasNext() ? s.next() : ""; + assertEquals(nonAscii, decoded, "Response body must round-trip through UTF-8 correctly"); + } + } } From 2a59bff5c06aae9c789090e9fa7160d8b44530d6 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier Date: Mon, 4 May 2026 16:42:29 +0200 Subject: [PATCH 03/37] fix (SinchClient): prevent exception in case of using AUXILIARY_FLAG for User-Agent --- .../src/main/com/sinch/sdk/SinchClient.java | 15 +++++--- .../java/com/sinch/sdk/SinchClientTest.java | 36 +++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/client/src/main/com/sinch/sdk/SinchClient.java b/client/src/main/com/sinch/sdk/SinchClient.java index 9e7897162..253482297 100644 --- a/client/src/main/com/sinch/sdk/SinchClient.java +++ b/client/src/main/com/sinch/sdk/SinchClient.java @@ -19,8 +19,8 @@ import com.sinch.sdk.models.adapters.DualToneMultiFrequencyMapper; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Objects; import java.util.Properties; import java.util.logging.Logger; @@ -430,11 +430,16 @@ private String formatSdkUserAgentHeader() { } private String formatAuxiliaryFlag() { + return formatAuxiliaryFlag(SDK.AUXILIARY_FLAG); + } - Collection values = Collections.singletonList(System.getProperty("java.vendor")); - - if (!StringUtil.isEmpty(SDK.AUXILIARY_FLAG)) { - values.add(SDK.AUXILIARY_FLAG); + // Package-private to allow unit-testing with an arbitrary flag value + String formatAuxiliaryFlag(String auxiliaryFlag) { + Collection values = new ArrayList<>(); + String vendor = System.getProperty("java.vendor"); + values.add(StringUtil.isEmpty(vendor) ? "" : vendor); + if (!StringUtil.isEmpty(auxiliaryFlag)) { + values.add(auxiliaryFlag); } return String.join(",", values); } diff --git a/client/src/test/java/com/sinch/sdk/SinchClientTest.java b/client/src/test/java/com/sinch/sdk/SinchClientTest.java index 7a9610b4c..0ae0c941f 100644 --- a/client/src/test/java/com/sinch/sdk/SinchClientTest.java +++ b/client/src/test/java/com/sinch/sdk/SinchClientTest.java @@ -218,4 +218,40 @@ void voiceApplicationManagementUrlFromUrl() { client.getConfiguration().getVoiceContext().get().getVoiceApplicationManagementUrl(), "my foo url"); } + + @Test + void formatAuxiliaryFlagReturnsVendorWhenFlagIsEmpty() { + SinchClient client = new SinchClient(); + // AUXILIARY_FLAG is "" in SDK — only vendor should be present, no comma separator + String result = client.formatAuxiliaryFlag(""); + assertNotNull(result); + assertFalse(result.contains(","), "Should not contain a comma when auxiliaryFlag is empty"); + } + + @Test + void formatAuxiliaryFlagHandlesNullJavaVendor() { + String original = System.getProperty("java.vendor"); + try { + System.clearProperty("java.vendor"); + SinchClient client = new SinchClient(); + String result = client.formatAuxiliaryFlag(""); + assertNotNull(result); + assertFalse(result.contains("null"), "Null java.vendor must not render as 'null' string"); + } finally { + if (null != original) { + System.setProperty("java.vendor", original); + } + } + } + + @Test + void formatAuxiliaryFlagAppendsNonEmptyFlag() { + SinchClient client = new SinchClient(); + String result = client.formatAuxiliaryFlag("my-wrapper/1.0"); + assertNotNull(result); + assertTrue(result.contains(","), "A comma must separate vendor from the auxiliary flag"); + assertTrue( + result.endsWith(",my-wrapper/1.0"), + "Auxiliary flag must be the last element after the comma"); + } } From 4facb28461e3588b9e92e9bd80be03a7fdfc4f72 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 May 2026 12:37:11 +0000 Subject: [PATCH 04/37] fix: correct conversationContext label in Configuration.toString() --- client/src/main/com/sinch/sdk/models/Configuration.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client/src/main/com/sinch/sdk/models/Configuration.java b/client/src/main/com/sinch/sdk/models/Configuration.java index 913a52713..398a448d8 100644 --- a/client/src/main/com/sinch/sdk/models/Configuration.java +++ b/client/src/main/com/sinch/sdk/models/Configuration.java @@ -51,8 +51,6 @@ public String toString() { + verificationContext + ", voiceContext=" + voiceContext - + ", conversationRegion=" - + conversationContext + ", conversationContext=" + conversationContext + "}"; @@ -153,9 +151,9 @@ public Optional getApplicationCredentials() { } /** - * Get Voice domain related execution context + * Get Conversation domain related execution context * - * @return Current Voice context + * @return Current Conversation context * @since 1.0 */ public Optional getConversationContext() { From 87b32d02ec984b4c6a71f8c4539b136bc60d81ae Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier Date: Tue, 12 May 2026 19:23:00 +0200 Subject: [PATCH 05/37] feat (Snippets): Fix typos --- .../src/main/java/conversation/conversations/Create.java | 2 +- .../src/main/java/voice/applications/GetEventDestinations.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/snippets/src/main/java/conversation/conversations/Create.java b/examples/snippets/src/main/java/conversation/conversations/Create.java index 9461388eb..8a923575b 100644 --- a/examples/snippets/src/main/java/conversation/conversations/Create.java +++ b/examples/snippets/src/main/java/conversation/conversations/Create.java @@ -52,7 +52,7 @@ public static void main(String[] args) { LOGGER.info( String.format( - "Create conversation for applicatoin with ID '%s'", conversationApplicationId)); + "Create conversation for application with ID '%s'", conversationApplicationId)); Conversation response = conversationsService.create(request); diff --git a/examples/snippets/src/main/java/voice/applications/GetEventDestinations.java b/examples/snippets/src/main/java/voice/applications/GetEventDestinations.java index 65caf228e..40f8a54c6 100644 --- a/examples/snippets/src/main/java/voice/applications/GetEventDestinations.java +++ b/examples/snippets/src/main/java/voice/applications/GetEventDestinations.java @@ -33,7 +33,7 @@ public static void main(String[] args) { ApplicationsService applicationsService = client.voice().v1().applications(); - LOGGER.info(String.format("Get even destinations for application key '%s'", applicationKey)); + LOGGER.info(String.format("Get event destinations for application key '%s'", applicationKey)); EventDestinations response = applicationsService.getEventDestinations(applicationKey); From 6c380bd1b9cfe5c1f9ffa8c55b60fc7d048b71b5 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier <141755467+JPPortier@users.noreply.github.com> Date: Wed, 13 May 2026 11:55:55 +0200 Subject: [PATCH 06/37] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/test/java/com/sinch/sdk/SinchClientTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/test/java/com/sinch/sdk/SinchClientTest.java b/client/src/test/java/com/sinch/sdk/SinchClientTest.java index 0ae0c941f..9ec7a2ef5 100644 --- a/client/src/test/java/com/sinch/sdk/SinchClientTest.java +++ b/client/src/test/java/com/sinch/sdk/SinchClientTest.java @@ -225,7 +225,10 @@ void formatAuxiliaryFlagReturnsVendorWhenFlagIsEmpty() { // AUXILIARY_FLAG is "" in SDK — only vendor should be present, no comma separator String result = client.formatAuxiliaryFlag(""); assertNotNull(result); - assertFalse(result.contains(","), "Should not contain a comma when auxiliaryFlag is empty"); + assertEquals( + System.getProperty("java.vendor"), + result, + "When auxiliaryFlag is empty, the result should match java.vendor exactly"); } @Test From c337f2503599a223b1779e0ff78cbd6749a1d8cb Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier Date: Tue, 26 May 2026 16:03:21 +0200 Subject: [PATCH 07/37] doc: CHANGELOG update --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7683628b2..ec1015e6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,19 @@ All notable changes to the **Sinch Java SDK** are documented in this file. --- +## v2.1 – unreleased + +### SDK +- **[fix]** `HttpClientApache`: declare now `headersToBeAdded` as `volatile` to guarantee visibility across threads in concurrent usage +- **[fix]** `HttpClientApache`: wrap response-body `Scanner` in a try-with-resources block to prevent resource leaks; gracefully handle empty (`null`) response entities +- **[fix]** `SinchClient`: guard against a `NullPointerException` when `java.vendor` system property is absent while building the `User-Agent` auxiliary flag +- **[fix]** `Configuration`: correct copy-paste error in `toString()` and Javadoc — `conversationContext` label was incorrectly attributed to the Voice domain + +### Examples / Snippets +- **[doc]** Fix typos in `conversation/conversations/Create` and `voice/applications/GetEventDestinations` snippets + +--- + ## v2.0 – 2026-03-31 ### Major breaking changes with major release From eabdc8ab5c959e1229efe7726a78d155eba1c904 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier <141755467+JPPortier@users.noreply.github.com> Date: Tue, 26 May 2026 16:40:09 +0200 Subject: [PATCH 08/37] feat (pagination): Support requets builder for pagination based onto Function to consume a requested path parameters list (#339) Co-authored-by: Eduardo San Segundo --- .../api/v1/adapters/ContactsServiceImpl.java | 28 ++++++++++------- .../v1/adapters/ConversationsServiceImpl.java | 25 +++++++++++----- .../api/v1/adapters/EventsServiceImpl.java | 13 +++++--- .../api/v1/adapters/MessagesServiceImpl.java | 30 ++++++++++++------- .../v2/adapters/TemplatesV2ServiceImpl.java | 11 +++++-- .../v1/adapters/ActiveNumberServiceImpl.java | 12 +++++--- .../adapters/AvailableNumberServiceImpl.java | 10 +++++-- .../adapters/AvailableRegionsServiceImpl.java | 8 +++-- .../api/v1/adapters/BatchesServiceImpl.java | 14 ++++++--- .../adapters/DeliveryReportsServiceImpl.java | 12 +++++--- .../api/v1/adapters/GroupsServiceImpl.java | 14 ++++++--- .../api/v1/adapters/InboundsServiceImpl.java | 13 +++++--- 12 files changed, 132 insertions(+), 58 deletions(-) diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ContactsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ContactsServiceImpl.java index 713266dff..9850bd462 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ContactsServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ContactsServiceImpl.java @@ -46,6 +46,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class ContactsServiceImpl @@ -84,11 +85,15 @@ public ContactsListResponse list(ContactsListQueryParameters queryParameter) thr LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listRequestBuilder(queryParameter); - return _fetchListPage(queryParameter, httpRequest); + return _fetchListPage( + (queryParameters) -> listRequestBuilder(queryParameters), queryParameter, httpRequest); } private ContactsListResponse _fetchListPage( - ContactsListQueryParameters queryParameter, HttpRequest httpRequest) throws ApiException { + Function requestBuilder, + ContactsListQueryParameters queryParameter, + HttpRequest httpRequest) + throws ApiException { HttpResponse response = httpClient.invokeAPI( this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); @@ -104,10 +109,10 @@ private ContactsListResponse _fetchListPage( ContactsListQueryParameters.builder(queryParameter).setPageToken(nextToken).build(); final HttpRequest nextHttpRequest = - !StringUtil.isEmpty(nextToken) ? listRequestBuilder(nextParameters) : null; + !StringUtil.isEmpty(nextToken) ? requestBuilder.apply(nextParameters) : null; return new ContactsListResponse( - () -> _fetchListPage(nextParameters, nextHttpRequest), + () -> _fetchListPage(requestBuilder, nextParameters, nextHttpRequest), new Page<>(deserialized.getContacts(), new PageNavigator<>(nextHttpRequest))); } // fallback to default errors handling: @@ -480,11 +485,16 @@ public IdentityConflictsListResponse listIdentityConflicts( LOGGER.finest("[listIdentityConflicts]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listIdentityConflictsRequestBuilder(queryParameter); - return _fetchListIdentityConflictsPage(queryParameter, httpRequest); + return _fetchListIdentityConflictsPage( + (queryParameters) -> listIdentityConflictsRequestBuilder(queryParameters), + queryParameter, + httpRequest); } private IdentityConflictsListResponse _fetchListIdentityConflictsPage( - IdentityConflictsListQueryParameters queryParameter, HttpRequest httpRequest) + Function requestBuilder, + IdentityConflictsListQueryParameters queryParameter, + HttpRequest httpRequest) throws ApiException { HttpResponse response = httpClient.invokeAPI( @@ -504,12 +514,10 @@ private IdentityConflictsListResponse _fetchListIdentityConflictsPage( .build(); final HttpRequest nextHttpRequest = - !StringUtil.isEmpty(nextToken) - ? listIdentityConflictsRequestBuilder(nextParameters) - : null; + !StringUtil.isEmpty(nextToken) ? requestBuilder.apply(nextParameters) : null; return new IdentityConflictsListResponse( - () -> _fetchListIdentityConflictsPage(nextParameters, nextHttpRequest), + () -> _fetchListIdentityConflictsPage(requestBuilder, nextParameters, nextHttpRequest), new Page<>(deserialized.getConflicts(), new PageNavigator<>(nextHttpRequest))); } // fallback to default errors handling: diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationsServiceImpl.java index dae6bdfb0..1387c112a 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationsServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationsServiceImpl.java @@ -45,6 +45,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class ConversationsServiceImpl @@ -84,11 +85,14 @@ public ConversationsListResponse list(ConversationsListQueryParameters queryPara LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listRequestBuilder(queryParameter); - return _fetchListPage(queryParameter, httpRequest); + return _fetchListPage( + (queryParameters) -> listRequestBuilder(queryParameters), queryParameter, httpRequest); } private ConversationsListResponse _fetchListPage( - ConversationsListQueryParameters queryParameter, HttpRequest httpRequest) + Function requestBuilder, + ConversationsListQueryParameters queryParameter, + HttpRequest httpRequest) throws ApiException { HttpResponse response = httpClient.invokeAPI( @@ -105,10 +109,10 @@ private ConversationsListResponse _fetchListPage( ConversationsListQueryParameters.builder(queryParameter).setPageToken(nextToken).build(); final HttpRequest nextHttpRequest = - !StringUtil.isEmpty(nextToken) ? listRequestBuilder(nextParameters) : null; + !StringUtil.isEmpty(nextToken) ? requestBuilder.apply(nextParameters) : null; return new ConversationsListResponse( - () -> _fetchListPage(nextParameters, nextHttpRequest), + () -> _fetchListPage(requestBuilder, nextParameters, nextHttpRequest), new Page<>(deserialized.getConversations(), new PageNavigator<>(nextHttpRequest))); } // fallback to default errors handling: @@ -563,11 +567,16 @@ public RecentConversationsListResponse listRecent( LOGGER.finest("[listRecent]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listRecentRequestBuilder(queryParameter); - return _fetchListRecentPage(queryParameter, httpRequest); + return _fetchListRecentPage( + (queryParameters) -> listRecentRequestBuilder(queryParameters), + queryParameter, + httpRequest); } private RecentConversationsListResponse _fetchListRecentPage( - RecentConversationsListQueryParameters queryParameter, HttpRequest httpRequest) + Function requestBuilder, + RecentConversationsListQueryParameters queryParameter, + HttpRequest httpRequest) throws ApiException { HttpResponse response = httpClient.invokeAPI( @@ -587,10 +596,10 @@ private RecentConversationsListResponse _fetchListRecentPage( .build(); final HttpRequest nextHttpRequest = - !StringUtil.isEmpty(nextToken) ? listRecentRequestBuilder(nextParameters) : null; + !StringUtil.isEmpty(nextToken) ? requestBuilder.apply(nextParameters) : null; return new RecentConversationsListResponse( - () -> _fetchListRecentPage(nextParameters, nextHttpRequest), + () -> _fetchListRecentPage(requestBuilder, nextParameters, nextHttpRequest), new Page<>(deserialized.getConversations(), new PageNavigator<>(nextHttpRequest))); } // fallback to default errors handling: diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/EventsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/EventsServiceImpl.java index e797c5dbc..a929ece67 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/EventsServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/EventsServiceImpl.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class EventsServiceImpl implements com.sinch.sdk.domains.conversation.api.v1.EventsService { @@ -76,11 +77,15 @@ public EventsListResponse list(EventsListQueryParameters queryParameter) throws LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listRequestBuilder(queryParameter); - return _fetchListPage(queryParameter, httpRequest); + return _fetchListPage( + (queryParameters) -> listRequestBuilder(queryParameters), queryParameter, httpRequest); } private EventsListResponse _fetchListPage( - EventsListQueryParameters queryParameter, HttpRequest httpRequest) throws ApiException { + Function requestBuilder, + EventsListQueryParameters queryParameter, + HttpRequest httpRequest) + throws ApiException { HttpResponse response = httpClient.invokeAPI( this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); @@ -96,10 +101,10 @@ private EventsListResponse _fetchListPage( EventsListQueryParameters.builder(queryParameter).setPageToken(nextToken).build(); final HttpRequest nextHttpRequest = - !StringUtil.isEmpty(nextToken) ? listRequestBuilder(nextParameters) : null; + !StringUtil.isEmpty(nextToken) ? requestBuilder.apply(nextParameters) : null; return new EventsListResponse( - () -> _fetchListPage(nextParameters, nextHttpRequest), + () -> _fetchListPage(requestBuilder, nextParameters, nextHttpRequest), new Page<>(deserialized.getEvents(), new PageNavigator<>(nextHttpRequest))); } // fallback to default errors handling: diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/MessagesServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/MessagesServiceImpl.java index 390b2cf0c..b152a134a 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/MessagesServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/MessagesServiceImpl.java @@ -54,6 +54,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class MessagesServiceImpl @@ -92,11 +93,15 @@ public MessagesListResponse list(MessagesListQueryParameters queryParameter) thr LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listRequestBuilder(queryParameter); - return _fetchListPage(queryParameter, httpRequest); + return _fetchListPage( + (queryParameters) -> listRequestBuilder(queryParameters), queryParameter, httpRequest); } private MessagesListResponse _fetchListPage( - MessagesListQueryParameters queryParameter, HttpRequest httpRequest) throws ApiException { + Function requestBuilder, + MessagesListQueryParameters queryParameter, + HttpRequest httpRequest) + throws ApiException { HttpResponse response = httpClient.invokeAPI( this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); @@ -112,10 +117,10 @@ private MessagesListResponse _fetchListPage( MessagesListQueryParameters.builder(queryParameter).setPageToken(nextToken).build(); final HttpRequest nextHttpRequest = - !StringUtil.isEmpty(nextToken) ? listRequestBuilder(nextParameters) : null; + !StringUtil.isEmpty(nextToken) ? requestBuilder.apply(nextParameters) : null; return new MessagesListResponse( - () -> _fetchListPage(nextParameters, nextHttpRequest), + () -> _fetchListPage(requestBuilder, nextParameters, nextHttpRequest), new Page<>(deserialized.getMessages(), new PageNavigator<>(nextHttpRequest))); } // fallback to default errors handling: @@ -438,11 +443,16 @@ public MessagesListResponse listLastMessagesByChannelIdentity( "[listLastMessagesByChannelIdentity]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listLastMessagesByChannelIdentityRequestBuilder(queryParameter); - return _fetchListLastMessagesByChannelIdentityPage(queryParameter, httpRequest); + return _fetchListLastMessagesByChannelIdentityPage( + (queryParameters) -> listLastMessagesByChannelIdentityRequestBuilder(queryParameters), + queryParameter, + httpRequest); } private MessagesListResponse _fetchListLastMessagesByChannelIdentityPage( - LastMessagesByChannelIdentityListQueryParameters queryParameter, HttpRequest httpRequest) + Function requestBuilder, + LastMessagesByChannelIdentityListQueryParameters queryParameter, + HttpRequest httpRequest) throws ApiException { HttpResponse response = httpClient.invokeAPI( @@ -461,12 +471,12 @@ private MessagesListResponse _fetchListLastMessagesByChannelIdentityPage( .build(); final HttpRequest nextHttpRequest = - !StringUtil.isEmpty(nextToken) - ? listLastMessagesByChannelIdentityRequestBuilder(nextParameters) - : null; + !StringUtil.isEmpty(nextToken) ? requestBuilder.apply(nextParameters) : null; return new MessagesListResponse( - () -> _fetchListLastMessagesByChannelIdentityPage(nextParameters, nextHttpRequest), + () -> + _fetchListLastMessagesByChannelIdentityPage( + requestBuilder, nextParameters, nextHttpRequest), new Page<>(deserialized.getMessages(), new PageNavigator<>(nextHttpRequest))); } // fallback to default errors handling: diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/adapters/TemplatesV2ServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/adapters/TemplatesV2ServiceImpl.java index 024c95949..ca1abf239 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/adapters/TemplatesV2ServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/adapters/TemplatesV2ServiceImpl.java @@ -37,6 +37,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class TemplatesV2ServiceImpl @@ -339,11 +340,17 @@ public TranslationsV2ListResponse listTranslations( + queryParameter); HttpRequest httpRequest = listTranslationsRequestBuilder(templateId, queryParameter); - return _fetchListTranslationsPage(queryParameter, httpRequest); + return _fetchListTranslationsPage( + (queryParameters) -> listTranslationsRequestBuilder(templateId, queryParameters), + queryParameter, + httpRequest); } private TranslationsV2ListResponse _fetchListTranslationsPage( - ListTranslationsQueryParameters queryParameter, HttpRequest httpRequest) throws ApiException { + Function requestBuilder, + ListTranslationsQueryParameters queryParameter, + HttpRequest httpRequest) + throws ApiException { HttpResponse response = httpClient.invokeAPI( this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/ActiveNumberServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/ActiveNumberServiceImpl.java index 21d74b1d4..e8a54731a 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/ActiveNumberServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/ActiveNumberServiceImpl.java @@ -41,6 +41,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class ActiveNumberServiceImpl @@ -84,11 +85,14 @@ public ActiveNumbersListResponse list(ActiveNumbersListQueryParameters queryPara LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listRequestBuilder(queryParameter); - return _fetchListPage(queryParameter, httpRequest); + return _fetchListPage( + (queryParameters) -> listRequestBuilder(queryParameters), queryParameter, httpRequest); } private ActiveNumbersListResponse _fetchListPage( - ActiveNumbersListQueryParameters queryParameter, HttpRequest httpRequest) + Function requestBuilder, + ActiveNumbersListQueryParameters queryParameter, + HttpRequest httpRequest) throws ApiException { HttpResponse response = httpClient.invokeAPI( @@ -105,10 +109,10 @@ private ActiveNumbersListResponse _fetchListPage( ActiveNumbersListQueryParameters.builder(queryParameter).setPageToken(nextToken).build(); final HttpRequest nextHttpRequest = - !StringUtil.isEmpty(nextToken) ? listRequestBuilder(nextParameters) : null; + !StringUtil.isEmpty(nextToken) ? requestBuilder.apply(nextParameters) : null; return new ActiveNumbersListResponse( - () -> _fetchListPage(nextParameters, nextHttpRequest), + () -> _fetchListPage(requestBuilder, nextParameters, nextHttpRequest), new Page<>(deserialized.getActiveNumbers(), new PageNavigator<>(nextHttpRequest))); } // fallback to default errors handling: diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableNumberServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableNumberServiceImpl.java index d12668042..8e8d48791 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableNumberServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableNumberServiceImpl.java @@ -38,6 +38,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class AvailableNumberServiceImpl @@ -284,11 +285,16 @@ public AvailableNumbersListResponse searchForAvailableNumbers( LOGGER.finest("[searchForAvailableNumbers]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = searchForAvailableNumbersRequestBuilder(queryParameter); - return _fetchSearchForAvailableNumbersPage(queryParameter, httpRequest); + return _fetchSearchForAvailableNumbersPage( + (queryParameters) -> searchForAvailableNumbersRequestBuilder(queryParameters), + queryParameter, + httpRequest); } private AvailableNumbersListResponse _fetchSearchForAvailableNumbersPage( - AvailableNumbersListQueryParameters queryParameter, HttpRequest httpRequest) + Function requestBuilder, + AvailableNumbersListQueryParameters queryParameter, + HttpRequest httpRequest) throws ApiException { HttpResponse response = httpClient.invokeAPI( diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableRegionsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableRegionsServiceImpl.java index 8e2ce0c64..2d94e25a3 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableRegionsServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableRegionsServiceImpl.java @@ -34,6 +34,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class AvailableRegionsServiceImpl @@ -74,11 +75,14 @@ public AvailableRegionsListResponse list(AvailableRegionsListQueryParameters que LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listRequestBuilder(queryParameter); - return _fetchListPage(queryParameter, httpRequest); + return _fetchListPage( + (queryParameters) -> listRequestBuilder(queryParameters), queryParameter, httpRequest); } private AvailableRegionsListResponse _fetchListPage( - AvailableRegionsListQueryParameters queryParameter, HttpRequest httpRequest) + Function requestBuilder, + AvailableRegionsListQueryParameters queryParameter, + HttpRequest httpRequest) throws ApiException { HttpResponse response = httpClient.invokeAPI( diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/BatchesServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/BatchesServiceImpl.java index 660f9866a..427fe7d8c 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/BatchesServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/BatchesServiceImpl.java @@ -42,6 +42,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class BatchesServiceImpl implements com.sinch.sdk.domains.sms.api.v1.BatchesService { @@ -79,11 +80,15 @@ public ListBatchesResponse list(ListBatchesQueryParameters queryParameter) throw LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listRequestBuilder(queryParameter); - return _fetchListPage(queryParameter, httpRequest); + return _fetchListPage( + (queryParameters) -> listRequestBuilder(queryParameters), queryParameter, httpRequest); } private ListBatchesResponse _fetchListPage( - ListBatchesQueryParameters queryParameter, HttpRequest httpRequest) throws ApiException { + Function requestBuilder, + ListBatchesQueryParameters queryParameter, + HttpRequest httpRequest) + throws ApiException { HttpResponse response = httpClient.invokeAPI( this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); @@ -113,9 +118,10 @@ private ListBatchesResponse _fetchListPage( ListBatchesQueryParameters.builder(queryParameter).setPage(nextPage).build(); final HttpRequest nextHttpRequest = - nextPage != null ? listRequestBuilder(nextParameters) : null; + nextPage != null ? requestBuilder.apply(nextParameters) : null; - return new ListBatchesResponse(() -> _fetchListPage(nextParameters, nextHttpRequest), page); + return new ListBatchesResponse( + () -> _fetchListPage(requestBuilder, nextParameters, nextHttpRequest), page); } // fallback to default errors handling: // all error cases definition are not required from specs: will try some "hardcoded" content diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/DeliveryReportsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/DeliveryReportsServiceImpl.java index 1624a0fd3..4887acfcb 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/DeliveryReportsServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/DeliveryReportsServiceImpl.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class DeliveryReportsServiceImpl @@ -78,11 +79,14 @@ public ListDeliveryReportsResponse list(ListDeliveryReportsQueryParameters query LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listRequestBuilder(queryParameter); - return _fetchListPage(queryParameter, httpRequest); + return _fetchListPage( + (queryParameters) -> listRequestBuilder(queryParameters), queryParameter, httpRequest); } private ListDeliveryReportsResponse _fetchListPage( - ListDeliveryReportsQueryParameters queryParameter, HttpRequest httpRequest) + Function requestBuilder, + ListDeliveryReportsQueryParameters queryParameter, + HttpRequest httpRequest) throws ApiException { HttpResponse response = httpClient.invokeAPI( @@ -113,10 +117,10 @@ private ListDeliveryReportsResponse _fetchListPage( ListDeliveryReportsQueryParameters.builder(queryParameter).setPage(nextPage).build(); final HttpRequest nextHttpRequest = - nextPage != null ? listRequestBuilder(nextParameters) : null; + nextPage != null ? requestBuilder.apply(nextParameters) : null; return new ListDeliveryReportsResponse( - () -> _fetchListPage(nextParameters, nextHttpRequest), page); + () -> _fetchListPage(requestBuilder, nextParameters, nextHttpRequest), page); } // fallback to default errors handling: // all error cases definition are not required from specs: will try some "hardcoded" content diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/GroupsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/GroupsServiceImpl.java index 790fd9a69..f2e103992 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/GroupsServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/GroupsServiceImpl.java @@ -38,6 +38,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class GroupsServiceImpl implements com.sinch.sdk.domains.sms.api.v1.GroupsService { @@ -75,11 +76,15 @@ public ListGroupsResponse list(ListGroupsQueryParameters queryParameter) throws LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listRequestBuilder(queryParameter); - return _fetchListPage(queryParameter, httpRequest); + return _fetchListPage( + (queryParameters) -> listRequestBuilder(queryParameters), queryParameter, httpRequest); } private ListGroupsResponse _fetchListPage( - ListGroupsQueryParameters queryParameter, HttpRequest httpRequest) throws ApiException { + Function requestBuilder, + ListGroupsQueryParameters queryParameter, + HttpRequest httpRequest) + throws ApiException { HttpResponse response = httpClient.invokeAPI( this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); @@ -109,9 +114,10 @@ private ListGroupsResponse _fetchListPage( ListGroupsQueryParameters.builder(queryParameter).setPage(nextPage).build(); final HttpRequest nextHttpRequest = - nextPage != null ? listRequestBuilder(nextParameters) : null; + nextPage != null ? requestBuilder.apply(nextParameters) : null; - return new ListGroupsResponse(() -> _fetchListPage(nextParameters, nextHttpRequest), page); + return new ListGroupsResponse( + () -> _fetchListPage(requestBuilder, nextParameters, nextHttpRequest), page); } // fallback to default errors handling: // all error cases definition are not required from specs: will try some "hardcoded" content diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/InboundsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/InboundsServiceImpl.java index 6a24d5b3b..7d2193d1a 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/InboundsServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/InboundsServiceImpl.java @@ -37,6 +37,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; public class InboundsServiceImpl implements com.sinch.sdk.domains.sms.api.v1.InboundsService { @@ -75,11 +76,14 @@ public ListInboundsResponse list(ListInboundMessagesQueryParameters queryParamet LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); HttpRequest httpRequest = listRequestBuilder(queryParameter); - return _fetchListPage(queryParameter, httpRequest); + return _fetchListPage( + (queryParameters) -> listRequestBuilder(queryParameters), queryParameter, httpRequest); } private ListInboundsResponse _fetchListPage( - ListInboundMessagesQueryParameters queryParameter, HttpRequest httpRequest) + Function requestBuilder, + ListInboundMessagesQueryParameters queryParameter, + HttpRequest httpRequest) throws ApiException { HttpResponse response = httpClient.invokeAPI( @@ -110,9 +114,10 @@ private ListInboundsResponse _fetchListPage( ListInboundMessagesQueryParameters.builder(queryParameter).setPage(nextPage).build(); final HttpRequest nextHttpRequest = - nextPage != null ? listRequestBuilder(nextParameters) : null; + nextPage != null ? requestBuilder.apply(nextParameters) : null; - return new ListInboundsResponse(() -> _fetchListPage(nextParameters, nextHttpRequest), page); + return new ListInboundsResponse( + () -> _fetchListPage(requestBuilder, nextParameters, nextHttpRequest), page); } // fallback to default errors handling: // all error cases definition are not required from specs: will try some "hardcoded" content From 96a7944e861f06020bb4b56e7c25cb5881f6f874 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier Date: Wed, 27 May 2026 10:50:43 +0200 Subject: [PATCH 09/37] CI: Update CODEOWNERS --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index df9fe6b14..d8b2918be 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -* @asein-sinch @JPPortier @matsk-sinch @rpredescu-sinch @edu-sinch @Hurus111 +* @asein-sinch @JPPortier @matsk-sinch @edu-sinch @Hurus111 @marcos-sinch From c7ab2e682c0eccbe1c7d0acfcb51858785f6062e Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier <141755467+JPPortier@users.noreply.github.com> Date: Wed, 27 May 2026 13:54:07 +0200 Subject: [PATCH 10/37] Merge pull request #340 from sinch/jpportier/DEVEXP-1224-consent-management DEVEXP-1224: Consent Management support --- CHANGELOG.md | 3 + .../api/v1/ConversationService.java | 8 + .../api/v1/adapters/ConversationService.java | 20 ++ .../api/v1/adapters/ConsentsServiceTest.java | 205 +++++++++++++ .../domains/conversation/ConsentsSteps.java | 70 ++++- .../src/main/java/conversation/README.md | 3 + .../consents/ListAuditRecords.java | 53 ++++ .../conversation/consents/ListIdentities.java | 57 ++++ .../conversation/api/v1/ConsentsService.java | 68 +++++ .../conversation/api/v1/ContactsService.java | 2 +- .../api/v1/ConversationsService.java | 2 +- .../api/v1/adapters/ConsentsServiceImpl.java | 271 ++++++++++++++++++ .../conversation/models/v1/ReasonCode.java | 2 +- .../response/QueryCapabilityResponse.java | 4 +- .../models/v1/consents/ConsentsListType.java | 47 +++ .../models/v1/consents/Identity.java | 56 ++++ .../models/v1/consents/IdentityImpl.java | 90 ++++++ .../ConsentsListResponseInternal.java | 72 +++++ .../ConsentsListResponseInternalImpl.java | 122 ++++++++ .../request/ConsentsListQueryParameters.java | 78 +++++ .../ConsentsListQueryParametersImpl.java | 94 ++++++ .../v1/consents/response/AuditRecord.java | 211 ++++++++++++++ .../v1/consents/response/AuditRecordImpl.java | 230 +++++++++++++++ .../response/AuditRecordsResponse.java | 72 +++++ .../response/AuditRecordsResponseImpl.java | 121 ++++++++ .../response/ConsentsListResponse.java | 59 ++++ .../contact/ContactDeleteEvent.java | 2 +- .../contact/ContactUpdateEvent.java | 2 +- .../sinchevents/delivery/DeliveryStatus.java | 2 +- .../request/TranscodeMessageRequest.java | 2 +- .../models/v1/consents/ConsentsDtoTest.java | 79 +++++ .../v1/consents/AuditRecordsResponseDto.json | 15 + .../ConsentsListResponseDtoPage0.json | 11 + .../ConsentsListResponseDtoPage1.json | 8 + 34 files changed, 2119 insertions(+), 22 deletions(-) create mode 100644 client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/ConsentsServiceTest.java create mode 100644 examples/snippets/src/main/java/conversation/consents/ListAuditRecords.java create mode 100644 examples/snippets/src/main/java/conversation/consents/ListIdentities.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ConsentsService.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConsentsServiceImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/ConsentsListType.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/Identity.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/IdentityImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/internal/ConsentsListResponseInternal.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/internal/ConsentsListResponseInternalImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/request/ConsentsListQueryParameters.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/request/ConsentsListQueryParametersImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecord.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordsResponse.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordsResponseImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/ConsentsListResponse.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/models/v1/consents/ConsentsDtoTest.java create mode 100644 openapi-contracts/src/test/resources/domains/conversation/v1/consents/AuditRecordsResponseDto.json create mode 100644 openapi-contracts/src/test/resources/domains/conversation/v1/consents/ConsentsListResponseDtoPage0.json create mode 100644 openapi-contracts/src/test/resources/domains/conversation/v1/consents/ConsentsListResponseDtoPage1.json diff --git a/CHANGELOG.md b/CHANGELOG.md index ec1015e6f..81d4f6052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ All notable changes to the **Sinch Java SDK** are documented in this file. ## v2.1 – unreleased +### Conversation +- **[feature]** Support `Consents` API: `listIdentities` and `listAuditRecords` endpoints + ### SDK - **[fix]** `HttpClientApache`: declare now `headersToBeAdded` as `volatile` to guarantee visibility across threads in concurrent usage - **[fix]** `HttpClientApache`: wrap response-body `Scanner` in a try-with-resources block to prevent resource leaks; gracefully handle empty (`null`) response entities diff --git a/client/src/main/com/sinch/sdk/domains/conversation/api/v1/ConversationService.java b/client/src/main/com/sinch/sdk/domains/conversation/api/v1/ConversationService.java index dd36aec6c..d26ac5d4e 100644 --- a/client/src/main/com/sinch/sdk/domains/conversation/api/v1/ConversationService.java +++ b/client/src/main/com/sinch/sdk/domains/conversation/api/v1/ConversationService.java @@ -98,4 +98,12 @@ public interface ConversationService { * @since 2.0 */ TemplatesService templates(); + + /** + * Consents Service instance + * + * @return service instance for project + * @since 2.1 + */ + ConsentsService consents(); } diff --git a/client/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationService.java b/client/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationService.java index ce9c0160d..aa6ad3230 100644 --- a/client/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationService.java +++ b/client/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationService.java @@ -9,6 +9,7 @@ import com.sinch.sdk.core.utils.StringUtil; import com.sinch.sdk.domains.conversation.api.v1.AppsService; import com.sinch.sdk.domains.conversation.api.v1.CapabilityService; +import com.sinch.sdk.domains.conversation.api.v1.ConsentsService; import com.sinch.sdk.domains.conversation.api.v1.ContactsService; import com.sinch.sdk.domains.conversation.api.v1.ConversationsService; import com.sinch.sdk.domains.conversation.api.v1.EventDestinationsService; @@ -66,6 +67,7 @@ public class ConversationService private volatile Map authManagers; private volatile AppsService apps; + private volatile ConsentsService consents; private volatile ContactsService contacts; private volatile MessagesService messages; private volatile ConversationsService conversations; @@ -112,6 +114,24 @@ public AppsService apps() { return this.apps; } + public ConsentsService consents() { + if (null == this.consents) { + synchronized (this) { + if (null == this.consents) { + instanceLazyInit(); + this.consents = + new ConsentsServiceImpl( + httpClientSupplier.get(), + context.getServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } + } + return this.consents; + } + public ContactsService contacts() { if (null == this.contacts) { synchronized (this) { diff --git a/client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/ConsentsServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/ConsentsServiceTest.java new file mode 100644 index 000000000..9d5c47eb2 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/ConsentsServiceTest.java @@ -0,0 +1,205 @@ +package com.sinch.sdk.domains.conversation.api.v1.adapters; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.core.http.AuthManager; +import com.sinch.sdk.core.http.HttpClient; +import com.sinch.sdk.core.http.HttpContentType; +import com.sinch.sdk.core.http.HttpMapper; +import com.sinch.sdk.core.http.HttpMethod; +import com.sinch.sdk.core.http.HttpRequest; +import com.sinch.sdk.core.http.HttpRequestTest.HttpRequestMatcher; +import com.sinch.sdk.core.http.HttpResponse; +import com.sinch.sdk.core.http.URLParameter; +import com.sinch.sdk.core.http.URLParameter.STYLE; +import com.sinch.sdk.core.http.URLPathUtils; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.domains.conversation.api.v1.ConsentsService; +import com.sinch.sdk.domains.conversation.models.v1.consents.ConsentsDtoTest; +import com.sinch.sdk.domains.conversation.models.v1.consents.ConsentsListType; +import com.sinch.sdk.domains.conversation.models.v1.consents.Identity; +import com.sinch.sdk.domains.conversation.models.v1.consents.request.ConsentsListQueryParameters; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.AuditRecordsResponse; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.ConsentsListResponse; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +@TestWithResources +class ConsentsServiceTest extends BaseTest { + + @Mock HttpClient httpClient; + @Mock ServerConfiguration serverConfiguration; + @Mock Map authManagers; + + static final String uriUUID = "foo"; + static final String APP_ID = "an app id"; + static final String IDENTITY = "an identity value"; + static final Collection AUTH_NAMES = Arrays.asList("Basic", "oAuth2"); + + ConsentsService service; + + @GivenTextResource("/domains/conversation/v1/consents/ConsentsListResponseDtoPage0.json") + String jsonConsentsListResponseDtoPage0; + + @GivenTextResource("/domains/conversation/v1/consents/ConsentsListResponseDtoPage1.json") + String jsonConsentsListResponseDtoPage1; + + @GivenTextResource("/domains/conversation/v1/consents/AuditRecordsResponseDto.json") + String jsonAuditRecordsResponseDto; + + @BeforeEach + public void initMocks() { + service = + new ConsentsServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), uriUUID); + } + + @Test + void listIdentities() throws ApiException { + + HttpRequest httpRequest1 = + new HttpRequest( + String.format( + "/v1/projects/%s/apps/%s/consents/%s", + URLPathUtils.encodePathSegment(uriUUID), + URLPathUtils.encodePathSegment(APP_ID), + URLPathUtils.encodePathSegment(ConsentsListType.OPT_OUT_ALL.toString())), + HttpMethod.GET, + Collections.emptyList(), + (String) null, + Collections.emptyMap(), + Collections.singletonList(HttpContentType.APPLICATION_JSON), + Collections.emptyList(), + AUTH_NAMES); + HttpRequest httpRequest2 = + new HttpRequest( + String.format( + "/v1/projects/%s/apps/%s/consents/%s", + URLPathUtils.encodePathSegment(uriUUID), + URLPathUtils.encodePathSegment(APP_ID), + URLPathUtils.encodePathSegment(ConsentsListType.OPT_OUT_ALL.toString())), + HttpMethod.GET, + Collections.singletonList( + new URLParameter("page_token", "the next page token value", STYLE.FORM, true)), + (String) null, + Collections.emptyMap(), + Collections.singletonList(HttpContentType.APPLICATION_JSON), + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse1 = + new HttpResponse( + 200, null, Collections.emptyMap(), jsonConsentsListResponseDtoPage0.getBytes()); + HttpResponse httpResponse2 = + new HttpResponse( + 200, null, Collections.emptyMap(), jsonConsentsListResponseDtoPage1.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest1)))) + .thenReturn(httpResponse1); + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest2)))) + .thenReturn(httpResponse2); + + ConsentsListResponse response = service.listIdentities(APP_ID, ConsentsListType.OPT_OUT_ALL); + + Iterator iterator = response.iterator(); + + Assertions.assertThat(iterator.hasNext()).isEqualTo(true); + Identity item = iterator.next(); + TestHelpers.recursiveEquals(item, ConsentsDtoTest.expectedIdentityDto); + + item = iterator.next(); + TestHelpers.recursiveEquals( + item, Identity.builder().setIdentity("a 2nd identity value").build()); + Assertions.assertThat(iterator.hasNext()).isEqualTo(true); + + item = iterator.next(); + TestHelpers.recursiveEquals( + item, Identity.builder().setIdentity("a 3rd identity value").build()); + Assertions.assertThat(iterator.hasNext()).isEqualTo(false); + } + + @Test + void listIdentitiesWithQueryParameters() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + String.format( + "/v1/projects/%s/apps/%s/consents/%s", + URLPathUtils.encodePathSegment(uriUUID), + URLPathUtils.encodePathSegment(APP_ID), + URLPathUtils.encodePathSegment(ConsentsListType.OPT_OUT_MARKETING.toString())), + HttpMethod.GET, + Collections.singletonList(new URLParameter("page_size", 5, STYLE.FORM, true)), + (String) null, + Collections.emptyMap(), + Collections.singletonList(HttpContentType.APPLICATION_JSON), + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse( + 200, null, Collections.emptyMap(), jsonConsentsListResponseDtoPage1.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + ConsentsListQueryParameters queryParams = + ConsentsListQueryParameters.builder().setPageSize(5).build(); + ConsentsListResponse response = + service.listIdentities(APP_ID, ConsentsListType.OPT_OUT_MARKETING, queryParams); + + Assertions.assertThat(response.getContent()).isNotEmpty(); + } + + @Test + void listAuditRecords() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + String.format( + "/v1/projects/%s/apps/%s/consents/identities/%s", + URLPathUtils.encodePathSegment(uriUUID), + URLPathUtils.encodePathSegment(APP_ID), + URLPathUtils.encodePathSegment(IDENTITY)), + HttpMethod.GET, + Collections.emptyList(), + (String) null, + Collections.emptyMap(), + Collections.singletonList(HttpContentType.APPLICATION_JSON), + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonAuditRecordsResponseDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + AuditRecordsResponse response = service.listAuditRecords(APP_ID, IDENTITY); + + TestHelpers.recursiveEquals(response, ConsentsDtoTest.expectedAuditRecordsResponseDto); + } +} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConsentsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConsentsSteps.java index 8c04b3c41..8597f86aa 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConsentsSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConsentsSteps.java @@ -1,57 +1,101 @@ package com.sinch.sdk.e2e.domains.conversation; +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.domains.conversation.api.v1.ConsentsService; +import com.sinch.sdk.domains.conversation.models.v1.consents.ConsentsListType; +import com.sinch.sdk.domains.conversation.models.v1.consents.request.ConsentsListQueryParameters; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.AuditRecord; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.AuditRecordsResponse; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.ConsentsListResponse; +import com.sinch.sdk.e2e.Config; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; +import java.time.Instant; +import java.util.Iterator; +import org.junit.jupiter.api.Assertions; public class ConsentsSteps { + static final String APP_ID = AppsSteps.APP_ID; + static final String IDENTITY = "33612345678"; + + ConsentsService service; + ConsentsListResponse listPageResponse; + AuditRecordsResponse listAuditRecordsResponse; + @Given("^the Conversation service \"Consents\" is available$") public void serviceAvailable() { - // TODO implement conversation consents steps + service = Config.getSinchClient().conversation().v1().consents(); } @When("^I send a request to list the existing Consent Identities$") public void listPage() { - - // TODO implement conversation consents steps + ConsentsListQueryParameters queryParams = + ConsentsListQueryParameters.builder().setPageSize(10).build(); + listPageResponse = service.listIdentities(APP_ID, ConsentsListType.OPT_OUT_ALL, queryParams); } @When("^I send a request to list all the Consent Identities$") public void listAll() { - - // TODO implement conversation consents steps + ConsentsListQueryParameters queryParams = + ConsentsListQueryParameters.builder().setPageSize(10).build(); + listPageResponse = service.listIdentities(APP_ID, ConsentsListType.OPT_OUT_ALL, queryParams); } @When("^I iterate manually over the Consent Identities pages$") public void listIterateManually() { - - // TODO implement conversation consents steps + ConsentsListQueryParameters queryParams = + ConsentsListQueryParameters.builder().setPageSize(10).build(); + listPageResponse = service.listIdentities(APP_ID, ConsentsListType.OPT_OUT_ALL, queryParams); } @When("^I send a request to list the Audit Records associated with an identity$") public void listAuditRecords() { - - // TODO implement conversation consents steps + listAuditRecordsResponse = service.listAuditRecords(APP_ID, IDENTITY); } @Then("the response contains \"{int}\" Consent Identities") public void listPageResult(int count) { - // TODO implement conversation consents steps + Assertions.assertEquals(count, listPageResponse.getContent().size()); } @Then("the Consent Identities list contains \"{int}\" Consent Identities") public void listAllResult(int count) { - // TODO implement conversation consents steps + Iterator iterator = listPageResponse.iterator(); + TestHelpers.checkIteratorItems(iterator, count); } @Then("the Consent Identities iteration result contains the data from \"{int}\" pages") public void listPageIterateResult(int count) { - // TODO implement conversation consents steps + int pageCount = 0; + ConsentsListResponse currentPage = listPageResponse; + do { + pageCount++; + if (!currentPage.hasNextPage()) { + break; + } + currentPage = currentPage.nextPage(); + } while (true); + + Assertions.assertEquals(pageCount, count); } @Then("the response contains list of the Audit Records associated with an identity") public void listAuditRecordsResult() { - // TODO implement conversation consents steps + Assertions.assertNotNull(listAuditRecordsResponse); + Assertions.assertNotNull(listAuditRecordsResponse.getIdentity()); + Assertions.assertEquals("33612345678", listAuditRecordsResponse.getIdentity().getIdentity()); + + Assertions.assertNotNull(listAuditRecordsResponse.getAuditRecords()); + Assertions.assertEquals(1, listAuditRecordsResponse.getAuditRecords().size()); + + AuditRecord record = listAuditRecordsResponse.getAuditRecords().get(0); + Assertions.assertEquals(AuditRecord.OriginEnum.ORIGIN_MO, record.getOrigin()); + Assertions.assertEquals(AuditRecord.OperationEnum.OPERATION_INSERT, record.getOperation()); + Assertions.assertEquals(ConsentsListType.OPT_OUT_ALL, record.getListType()); + Assertions.assertEquals("123coffee-dada-beef-cafe-baadc0de5678", record.getProjectId()); + Assertions.assertEquals(APP_ID, record.getAppId()); + Assertions.assertEquals(Instant.parse("2025-06-06T14:42:56.031323Z"), record.getDatetime()); } } diff --git a/examples/snippets/src/main/java/conversation/README.md b/examples/snippets/src/main/java/conversation/README.md index c39413f40..161f1365f 100644 --- a/examples/snippets/src/main/java/conversation/README.md +++ b/examples/snippets/src/main/java/conversation/README.md @@ -11,6 +11,9 @@ See main [README.md](../../../../README.md) for how to execute snippets - [conversation/applications/Update](./applications/Update.java) - Capability - [conversation/capability/Capability](./capability/Capability.java) +- Consents + - [conversation/consents/ListIdentities](./consents/ListIdentities.java) + - [conversation/consents/ListAuditRecords](./consents/ListAuditRecords.java) - Contacts - [conversation/contacts/List](./contacts/List.java) - [conversation/contacts/Create](./contacts/Create.java) diff --git a/examples/snippets/src/main/java/conversation/consents/ListAuditRecords.java b/examples/snippets/src/main/java/conversation/consents/ListAuditRecords.java new file mode 100644 index 000000000..0663ce475 --- /dev/null +++ b/examples/snippets/src/main/java/conversation/consents/ListAuditRecords.java @@ -0,0 +1,53 @@ +/** + * Sinch Java Snippet + * + *

This snippet is available at https://github.com/sinch/sinch-sdk-java + * + *

See https://github.com/sinch/sinch-sdk-java/blob/main/examples/snippets/README.md for details + */ +package conversation.consents; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.conversation.api.v1.ConsentsService; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.AuditRecordsResponse; +import com.sinch.sdk.models.Configuration; +import com.sinch.sdk.models.ConversationRegion; +import java.util.logging.Logger; +import utils.Settings; + +public class ListAuditRecords { + + private static final Logger LOGGER = Logger.getLogger(ListAuditRecords.class.getName()); + + public static void main(String[] args) { + + String projectId = Settings.getProjectId().orElse("MY_PROJECT_ID"); + String keyId = Settings.getKeyId().orElse("MY_KEY_ID"); + String keySecret = Settings.getKeySecret().orElse("MY_KEY_SECRET"); + String conversationRegion = Settings.getConversationRegion().orElse("MY_CONVERSATION_REGION"); + + // The ID of the Conversation App + String appId = "CONVERSATION_APP_ID"; + + // The identity to retrieve audit records for (e.g. a phone number) + String identity = "MY_IDENTITY"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .setConversationRegion(ConversationRegion.from(conversationRegion)) + .build(); + + SinchClient client = new SinchClient(configuration); + + ConsentsService consentsService = client.conversation().v1().consents(); + + LOGGER.info(String.format("List audit records for identity '%s' in app '%s'", identity, appId)); + + AuditRecordsResponse response = consentsService.listAuditRecords(appId, identity); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/conversation/consents/ListIdentities.java b/examples/snippets/src/main/java/conversation/consents/ListIdentities.java new file mode 100644 index 000000000..cbd0388be --- /dev/null +++ b/examples/snippets/src/main/java/conversation/consents/ListIdentities.java @@ -0,0 +1,57 @@ +/** + * Sinch Java Snippet + * + *

This snippet is available at https://github.com/sinch/sinch-sdk-java + * + *

See https://github.com/sinch/sinch-sdk-java/blob/main/examples/snippets/README.md for details + */ +package conversation.consents; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.conversation.api.v1.ConsentsService; +import com.sinch.sdk.domains.conversation.models.v1.consents.ConsentsListType; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.ConsentsListResponse; +import com.sinch.sdk.models.Configuration; +import com.sinch.sdk.models.ConversationRegion; +import java.util.logging.Logger; +import utils.Settings; + +public class ListIdentities { + + private static final Logger LOGGER = Logger.getLogger(ListIdentities.class.getName()); + + public static void main(String[] args) { + + String projectId = Settings.getProjectId().orElse("MY_PROJECT_ID"); + String keyId = Settings.getKeyId().orElse("MY_KEY_ID"); + String keySecret = Settings.getKeySecret().orElse("MY_KEY_SECRET"); + String conversationRegion = Settings.getConversationRegion().orElse("MY_CONVERSATION_REGION"); + + // The ID of the Conversation App + String appId = "CONVERSATION_APP_ID"; + + // The consent list type to query + ConsentsListType listType = ConsentsListType.OPT_OUT_ALL; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .setConversationRegion(ConversationRegion.from(conversationRegion)) + .build(); + + SinchClient client = new SinchClient(configuration); + + ConsentsService consentsService = client.conversation().v1().consents(); + + LOGGER.info( + String.format("List identities from consent list '%s' for app '%s'", listType, appId)); + + ConsentsListResponse response = consentsService.listIdentities(appId, listType); + + LOGGER.info("Response: "); + + response.iterator().forEachRemaining(f -> LOGGER.info(f.toString())); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ConsentsService.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ConsentsService.java new file mode 100644 index 000000000..4e70cfb3b --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ConsentsService.java @@ -0,0 +1,68 @@ +/* + * Conversation API | Sinch + * + * OpenAPI document version: 1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.conversation.api.v1; + +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.domains.conversation.models.v1.consents.ConsentsListType; +import com.sinch.sdk.domains.conversation.models.v1.consents.request.ConsentsListQueryParameters; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.AuditRecordsResponse; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.ConsentsListResponse; + +/** Consents Service */ +public interface ConsentsService { + + /** + * Get audit records from consent lists + * + *

Get all audit records associated with the given identity from the consent lists within the + * specified project and app. + * + * @param appId The unique ID of the app. You can find this on the [Sinch + * Dashboard](https://dashboard.sinch.com/convapi/apps). (required) + * @param identity An identity to use on Consent audit records queries. (required) + * @return AuditRecordsResponse + * @throws ApiException if fails to make API call + */ + AuditRecordsResponse listAuditRecords(String appId, String identity) throws ApiException; + + /** + * Get identities from consent list (using default parameters) + * + *

Get all identities contained within a consent list for a given project id, app id, and list + * type. + * + * @param appId The unique ID of the app. You can find this on the [Sinch + * Dashboard](https://dashboard.sinch.com/convapi/apps). (required) + * @param listType The consent list type. One of `OPT_OUT_ALL`, + * `OPT_OUT_MARKETING`, and or `OPT_OUT_NOTIFICATION`. (required) + * @return ConsentsListResponse + * @throws ApiException if fails to make API call + */ + ConsentsListResponse listIdentities(String appId, ConsentsListType listType) throws ApiException; + + /** + * Get identities from consent list + * + *

Get all identities contained within a consent list for a given project id, app id, and list + * type. + * + * @param appId The unique ID of the app. You can find this on the [Sinch + * Dashboard](https://dashboard.sinch.com/convapi/apps). (required) + * @param listType The consent list type. One of `OPT_OUT_ALL`, + * `OPT_OUT_MARKETING`, and or `OPT_OUT_NOTIFICATION`. (required) + * @param queryParameter (optional) + * @return ConsentsListResponse + * @throws ApiException if fails to make API call + */ + ConsentsListResponse listIdentities( + String appId, ConsentsListType listType, ConsentsListQueryParameters queryParameter) + throws ApiException; +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ContactsService.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ContactsService.java index 6f2101292..5b423eff9 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ContactsService.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ContactsService.java @@ -168,7 +168,7 @@ IdentityConflictsListResponse listIdentityConflicts( * will be moved from the source contact to the destination contact only for channels that * weren't present there before. Moved channel identities will be placed at the bottom of the * channel priority list. Optional fields from the source contact will be copied only if - * corresponding fields in the destination contact are empty The contact being removed cannot be + * corresponding fields in the destination contact are empty. The contact being removed cannot be * referenced after this call. * * @param destinationId The unique ID of the contact that should be kept when merging two diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ConversationsService.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ConversationsService.java index f4751ae3e..9d39a8a92 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ConversationsService.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/ConversationsService.java @@ -136,7 +136,7 @@ RecentConversationsListResponse listRecent(RecentConversationsListQueryParameter * Stop conversation * *

This operation stops the referenced conversation, if the conversation is still active. In - * Conversation mode, A new conversation will be created if a new message is exchanged between the + * Conversation mode, a new conversation will be created if a new message is exchanged between the * app or contact that was part of the stopped conversation. * * @param conversationId The unique ID of the conversation. This is generated by the system. diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConsentsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConsentsServiceImpl.java new file mode 100644 index 000000000..20e4017c1 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConsentsServiceImpl.java @@ -0,0 +1,271 @@ +/* + * Conversation API | Sinch + * + * OpenAPI document version: 1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.conversation.api.v1.adapters; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.core.exceptions.ApiExceptionBuilder; +import com.sinch.sdk.core.http.AuthManager; +import com.sinch.sdk.core.http.HttpClient; +import com.sinch.sdk.core.http.HttpMapper; +import com.sinch.sdk.core.http.HttpMethod; +import com.sinch.sdk.core.http.HttpRequest; +import com.sinch.sdk.core.http.HttpResponse; +import com.sinch.sdk.core.http.HttpStatus; +import com.sinch.sdk.core.http.URLParameter; +import com.sinch.sdk.core.http.URLParameterUtils; +import com.sinch.sdk.core.http.URLPathUtils; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.core.models.pagination.Page; +import com.sinch.sdk.core.models.pagination.PageNavigator; +import com.sinch.sdk.core.utils.StringUtil; +import com.sinch.sdk.domains.conversation.models.v1.consents.ConsentsListType; +import com.sinch.sdk.domains.conversation.models.v1.consents.internal.ConsentsListResponseInternal; +import com.sinch.sdk.domains.conversation.models.v1.consents.request.ConsentsListQueryParameters; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.AuditRecordsResponse; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.ConsentsListResponse; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.logging.Logger; + +public class ConsentsServiceImpl + implements com.sinch.sdk.domains.conversation.api.v1.ConsentsService { + + private static final Logger LOGGER = Logger.getLogger(ConsentsServiceImpl.class.getName()); + private final HttpClient httpClient; + private final ServerConfiguration serverConfiguration; + private final Map authManagersByOasSecuritySchemes; + private final HttpMapper mapper; + + private final String projectId; + + public ConsentsServiceImpl( + HttpClient httpClient, + ServerConfiguration serverConfiguration, + Map authManagersByOasSecuritySchemes, + HttpMapper mapper, + String projectId) { + this.httpClient = httpClient; + this.serverConfiguration = serverConfiguration; + this.authManagersByOasSecuritySchemes = authManagersByOasSecuritySchemes; + this.mapper = mapper; + this.projectId = projectId; + } + + @Override + public AuditRecordsResponse listAuditRecords(String appId, String identity) throws ApiException { + + LOGGER.finest("[listAuditRecords]" + " " + "appId: " + appId + ", " + "identity: " + identity); + + HttpRequest httpRequest = listAuditRecordsRequestBuilder(appId, identity); + HttpResponse response = + httpClient.invokeAPI( + this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); + + if (HttpStatus.isSuccessfulStatus(response.getCode())) { + return mapper.deserialize(response, new TypeReference() {}); + } + // fallback to default errors handling: + // all error cases definition are not required from specs: will try some "hardcoded" content + // parsing + throw ApiExceptionBuilder.build( + response.getMessage(), + response.getCode(), + mapper.deserialize(response, new TypeReference>() {})); + } + + private HttpRequest listAuditRecordsRequestBuilder(String appId, String identity) + throws ApiException { + // verify the required parameter 'this.projectId' is set + if (this.projectId == null) { + throw new ApiException( + 400, "Missing the required parameter 'this.projectId' when calling listAuditRecords"); + } + // verify the required parameter 'appId' is set + if (appId == null) { + throw new ApiException( + 400, "Missing the required parameter 'appId' when calling listAuditRecords"); + } + // verify the required parameter 'identity' is set + if (identity == null) { + throw new ApiException( + 400, "Missing the required parameter 'identity' when calling listAuditRecords"); + } + + String localVarPath = + "/v1/projects/{project_id}/apps/{app_id}/consents/identities/{identity}" + .replaceAll( + "\\{" + "project_id" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll("\\{" + "app_id" + "\\}", URLPathUtils.encodePathSegment(appId.toString())) + .replaceAll( + "\\{" + "identity" + "\\}", URLPathUtils.encodePathSegment(identity.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = Arrays.asList("application/json"); + + final Collection localVarContentTypes = Arrays.asList(); + + final Collection localVarAuthNames = Arrays.asList("Basic", "oAuth2"); + final String serializedBody = null; + + return new HttpRequest( + localVarPath, + HttpMethod.GET, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } + + @Override + public ConsentsListResponse listIdentities(String appId, ConsentsListType listType) + throws ApiException { + return listIdentities(appId, listType, (ConsentsListQueryParameters) null); + } + + @Override + public ConsentsListResponse listIdentities( + String appId, ConsentsListType listType, ConsentsListQueryParameters queryParameter) + throws ApiException { + + LOGGER.finest( + "[listIdentities]" + + " " + + "appId: " + + appId + + ", " + + "listType: " + + listType + + ", " + + "queryParameter: " + + queryParameter); + + HttpRequest httpRequest = listIdentitiesRequestBuilder(appId, listType, queryParameter); + return _fetchListIdentitiesPage( + (queryParameters) -> listIdentitiesRequestBuilder(appId, listType, queryParameters), + queryParameter, + httpRequest); + } + + private ConsentsListResponse _fetchListIdentitiesPage( + Function requestBuilder, + ConsentsListQueryParameters queryParameter, + HttpRequest httpRequest) + throws ApiException { + HttpResponse response = + httpClient.invokeAPI( + this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); + + if (HttpStatus.isSuccessfulStatus(response.getCode())) { + + ConsentsListResponseInternal deserialized = + mapper.deserialize(response, new TypeReference() {}); + + String nextToken = deserialized.getNextPageToken(); + + ConsentsListQueryParameters nextParameters = + ConsentsListQueryParameters.builder(queryParameter).setPageToken(nextToken).build(); + + final HttpRequest nextHttpRequest = + !StringUtil.isEmpty(nextToken) ? requestBuilder.apply(nextParameters) : null; + + return new ConsentsListResponse( + () -> _fetchListIdentitiesPage(requestBuilder, nextParameters, nextHttpRequest), + new Page<>(deserialized.getIdentities(), new PageNavigator<>(nextHttpRequest))); + } + // fallback to default errors handling: + // all error cases definition are not required from specs: will try some "hardcoded" content + // parsing + throw ApiExceptionBuilder.build( + response.getMessage(), + response.getCode(), + mapper.deserialize(response, new TypeReference>() {})); + } + + private HttpRequest listIdentitiesRequestBuilder( + String appId, ConsentsListType listType, ConsentsListQueryParameters queryParameter) + throws ApiException { + // verify the required parameter 'this.projectId' is set + if (this.projectId == null) { + throw new ApiException( + 400, "Missing the required parameter 'this.projectId' when calling listIdentities"); + } + // verify the required parameter 'appId' is set + if (appId == null) { + throw new ApiException( + 400, "Missing the required parameter 'appId' when calling listIdentities"); + } + // verify the required parameter 'listType' is set + if (listType == null) { + throw new ApiException( + 400, "Missing the required parameter 'listType' when calling listIdentities"); + } + + String localVarPath = + "/v1/projects/{project_id}/apps/{app_id}/consents/{list_type}" + .replaceAll( + "\\{" + "project_id" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll("\\{" + "app_id" + "\\}", URLPathUtils.encodePathSegment(appId.toString())) + .replaceAll( + "\\{" + "list_type" + "\\}", URLPathUtils.encodePathSegment(listType.toString())); + + List localVarQueryParams = new ArrayList<>(); + if (null != queryParameter) { + + URLParameterUtils.addQueryParam( + queryParameter.getPageSize(), + "page_size", + URLParameter.form, + null, + localVarQueryParams, + true); + + URLParameterUtils.addQueryParam( + queryParameter.getPageToken(), + "page_token", + URLParameter.form, + null, + localVarQueryParams, + true); + } + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = Arrays.asList("application/json"); + + final Collection localVarContentTypes = Arrays.asList(); + + final Collection localVarAuthNames = Arrays.asList("Basic", "oAuth2"); + final String serializedBody = null; + + return new HttpRequest( + localVarPath, + HttpMethod.GET, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/ReasonCode.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/ReasonCode.java index 4955f0dbd..6e9ab0086 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/ReasonCode.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/ReasonCode.java @@ -59,7 +59,7 @@ public class ReasonCode extends EnumDynamic { new ReasonCode("CHANNEL_CONFIGURATION_MISSING"); /** - * Some of the referenced media files is of a unsupported media type. Please read the channel support * documentation page to find out the limitations on media that the different channels impose. */ diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/capability/response/QueryCapabilityResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/capability/response/QueryCapabilityResponse.java index b8934c16c..e06991ff5 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/capability/response/QueryCapabilityResponse.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/capability/response/QueryCapabilityResponse.java @@ -14,8 +14,8 @@ import com.sinch.sdk.domains.conversation.models.v1.Recipient; /** - * An CapabilityResponse contains the identity of the recipient for which will be perform a - * capability lookup. + * A CapabilityResponse contains the identity of the recipient for which a capability lookup will be + * performed. */ @JsonDeserialize(builder = QueryCapabilityResponseImpl.Builder.class) public interface QueryCapabilityResponse { diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/ConsentsListType.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/ConsentsListType.java new file mode 100644 index 000000000..45a29e214 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/ConsentsListType.java @@ -0,0 +1,47 @@ +package com.sinch.sdk.domains.conversation.models.v1.consents; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * The consent list type. See consent management + * page for more information. + */ +public class ConsentsListType extends EnumDynamic { + + /** List type that blocks all kinds of messages */ + public static final ConsentsListType OPT_OUT_ALL = new ConsentsListType("OPT_OUT_ALL"); + + /** List type that blocks only marketing messages */ + public static final ConsentsListType OPT_OUT_MARKETING = + new ConsentsListType("OPT_OUT_MARKETING"); + + /** List type that blocks only notification messages */ + public static final ConsentsListType OPT_OUT_NOTIFICATION = + new ConsentsListType("OPT_OUT_NOTIFICATION"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + ConsentsListType.class, + ConsentsListType::new, + Arrays.asList(OPT_OUT_ALL, OPT_OUT_MARKETING, OPT_OUT_NOTIFICATION)); + + private ConsentsListType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static ConsentsListType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(ConsentsListType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/Identity.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/Identity.java new file mode 100644 index 000000000..b04f75e05 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/Identity.java @@ -0,0 +1,56 @@ +/* + * Conversation API | Sinch + * + * OpenAPI document version: 1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.conversation.models.v1.consents; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** Represents a identity from the Conversation API. */ +@JsonDeserialize(builder = IdentityImpl.Builder.class) +public interface Identity { + + /** + * The identity from Conversation API. + * + *

Field is required + * + * @return identity + */ + String getIdentity(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new IdentityImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param identity see getter + * @return Current builder + * @see #getIdentity + */ + Builder setIdentity(String identity); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + Identity build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/IdentityImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/IdentityImpl.java new file mode 100644 index 000000000..4d28c5203 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/IdentityImpl.java @@ -0,0 +1,90 @@ +package com.sinch.sdk.domains.conversation.models.v1.consents; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import java.util.Objects; + +@JsonPropertyOrder({IdentityImpl.JSON_PROPERTY_IDENTITY}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class IdentityImpl implements Identity { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_IDENTITY = "identity"; + + private OptionalValue identity; + + public IdentityImpl() {} + + protected IdentityImpl(OptionalValue identity) { + this.identity = identity; + } + + @JsonIgnore + public String getIdentity() { + return identity.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_IDENTITY) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue identity() { + return identity; + } + + /** Return true if this Identity object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IdentityImpl identity = (IdentityImpl) o; + return Objects.equals(this.identity, identity.identity); + } + + @Override + public int hashCode() { + return Objects.hash(identity); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class IdentityImpl {\n"); + sb.append(" identity: ").append(toIndentedString(identity)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements Identity.Builder { + OptionalValue identity = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_IDENTITY, required = true) + public Builder setIdentity(String identity) { + this.identity = OptionalValue.of(identity); + return this; + } + + public Identity build() { + return new IdentityImpl(identity); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/internal/ConsentsListResponseInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/internal/ConsentsListResponseInternal.java new file mode 100644 index 000000000..39dc2aa6e --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/internal/ConsentsListResponseInternal.java @@ -0,0 +1,72 @@ +/* + * Conversation API | Sinch + * + * OpenAPI document version: 1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.conversation.models.v1.consents.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.conversation.models.v1.consents.Identity; +import java.util.List; + +/** ConsentsListResponseInternal */ +@JsonDeserialize(builder = ConsentsListResponseInternalImpl.Builder.class) +public interface ConsentsListResponseInternal { + + /** + * List of identities belonging to a specific project ID, app ID, and list type + * + * @return identities + */ + List getIdentities(); + + /** + * Token for the next page of the search, to be used in the next call. + * + * @return nextPageToken + */ + String getNextPageToken(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new ConsentsListResponseInternalImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param identities see getter + * @return Current builder + * @see #getIdentities + */ + Builder setIdentities(List identities); + + /** + * see getter + * + * @param nextPageToken see getter + * @return Current builder + * @see #getNextPageToken + */ + Builder setNextPageToken(String nextPageToken); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + ConsentsListResponseInternal build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/internal/ConsentsListResponseInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/internal/ConsentsListResponseInternalImpl.java new file mode 100644 index 000000000..13dddc29c --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/internal/ConsentsListResponseInternalImpl.java @@ -0,0 +1,122 @@ +package com.sinch.sdk.domains.conversation.models.v1.consents.internal; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import com.sinch.sdk.domains.conversation.models.v1.consents.Identity; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + ConsentsListResponseInternalImpl.JSON_PROPERTY_IDENTITIES, + ConsentsListResponseInternalImpl.JSON_PROPERTY_NEXT_PAGE_TOKEN +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class ConsentsListResponseInternalImpl implements ConsentsListResponseInternal { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_IDENTITIES = "identities"; + + private OptionalValue> identities; + + public static final String JSON_PROPERTY_NEXT_PAGE_TOKEN = "next_page_token"; + + private OptionalValue nextPageToken; + + public ConsentsListResponseInternalImpl() {} + + protected ConsentsListResponseInternalImpl( + OptionalValue> identities, OptionalValue nextPageToken) { + this.identities = identities; + this.nextPageToken = nextPageToken; + } + + @JsonIgnore + public List getIdentities() { + return identities.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_IDENTITIES) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> identities() { + return identities; + } + + @JsonIgnore + public String getNextPageToken() { + return nextPageToken.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_NEXT_PAGE_TOKEN) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue nextPageToken() { + return nextPageToken; + } + + /** Return true if this GetIdentitiesFromConsentResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConsentsListResponseInternalImpl getIdentitiesFromConsentResponse = + (ConsentsListResponseInternalImpl) o; + return Objects.equals(this.identities, getIdentitiesFromConsentResponse.identities) + && Objects.equals(this.nextPageToken, getIdentitiesFromConsentResponse.nextPageToken); + } + + @Override + public int hashCode() { + return Objects.hash(identities, nextPageToken); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ConsentsListResponseInternalImpl {\n"); + sb.append(" identities: ").append(toIndentedString(identities)).append("\n"); + sb.append(" nextPageToken: ").append(toIndentedString(nextPageToken)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements ConsentsListResponseInternal.Builder { + OptionalValue> identities = OptionalValue.empty(); + OptionalValue nextPageToken = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_IDENTITIES) + public Builder setIdentities(List identities) { + this.identities = OptionalValue.of(identities); + return this; + } + + @JsonProperty(JSON_PROPERTY_NEXT_PAGE_TOKEN) + public Builder setNextPageToken(String nextPageToken) { + this.nextPageToken = OptionalValue.of(nextPageToken); + return this; + } + + public ConsentsListResponseInternal build() { + return new ConsentsListResponseInternalImpl(identities, nextPageToken); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/request/ConsentsListQueryParameters.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/request/ConsentsListQueryParameters.java new file mode 100644 index 000000000..6dfa04a86 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/request/ConsentsListQueryParameters.java @@ -0,0 +1,78 @@ +/* + * Conversation API | Sinch + * + * OpenAPI document version: 1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.conversation.models.v1.consents.request; + +import com.sinch.sdk.core.models.OptionalValue; + +/** ConsentsListQueryParameters */ +public interface ConsentsListQueryParameters { + + /** + * Get pageSize + * + * @return pageSize + */ + OptionalValue getPageSize(); + + /** + * Get pageToken + * + * @return pageToken + */ + OptionalValue getPageToken(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new ConsentsListQueryParametersImpl.Builder(); + } + + /** + * Getting builder from existing instance + * + * @return New Builder instance + */ + static Builder builder(ConsentsListQueryParameters parameters) { + return new ConsentsListQueryParametersImpl.Builder(parameters); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param pageSize see getter + * @return Current builder + * @see #getPageSize + */ + Builder setPageSize(Integer pageSize); + + /** + * see getter + * + * @param pageToken see getter + * @return Current builder + * @see #getPageToken + */ + Builder setPageToken(String pageToken); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + ConsentsListQueryParameters build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/request/ConsentsListQueryParametersImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/request/ConsentsListQueryParametersImpl.java new file mode 100644 index 000000000..f2077f61a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/request/ConsentsListQueryParametersImpl.java @@ -0,0 +1,94 @@ +package com.sinch.sdk.domains.conversation.models.v1.consents.request; + +import com.sinch.sdk.core.models.OptionalValue; +import java.util.Objects; + +public class ConsentsListQueryParametersImpl implements ConsentsListQueryParameters { + + private final OptionalValue pageSize; + private final OptionalValue pageToken; + + private ConsentsListQueryParametersImpl( + OptionalValue pageSize, OptionalValue pageToken) { + this.pageSize = pageSize; + this.pageToken = pageToken; + } + + public OptionalValue getPageSize() { + return pageSize; + } + + public OptionalValue getPageToken() { + return pageToken; + } + + /** Return true if this ConsentsGetConsentsQueryParameters object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConsentsListQueryParametersImpl consentsGetConsentsQueryParameters = + (ConsentsListQueryParametersImpl) o; + return Objects.equals(this.pageSize, consentsGetConsentsQueryParameters.pageSize) + && Objects.equals(this.pageToken, consentsGetConsentsQueryParameters.pageToken); + } + + @Override + public int hashCode() { + return Objects.hash(pageSize, pageToken); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ConsentsListQueryParametersImpl {\n"); + sb.append(" pageSize: ").append(toIndentedString(pageSize)).append("\n"); + sb.append(" pageToken: ").append(toIndentedString(pageToken)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + static class Builder implements ConsentsListQueryParameters.Builder { + OptionalValue pageSize = OptionalValue.empty(); + OptionalValue pageToken = OptionalValue.empty(); + + protected Builder() {} + + protected Builder(ConsentsListQueryParameters _parameters) { + if (null == _parameters) { + return; + } + ConsentsListQueryParametersImpl parameters = (ConsentsListQueryParametersImpl) _parameters; + this.pageSize = parameters.getPageSize(); + this.pageToken = parameters.getPageToken(); + } + + public Builder setPageSize(Integer pageSize) { + this.pageSize = OptionalValue.of(pageSize); + return this; + } + + public Builder setPageToken(String pageToken) { + this.pageToken = OptionalValue.of(pageToken); + return this; + } + + public ConsentsListQueryParameters build() { + return new ConsentsListQueryParametersImpl(pageSize, pageToken); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecord.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecord.java new file mode 100644 index 000000000..a4fb5c34f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecord.java @@ -0,0 +1,211 @@ +/* + * Conversation API | Sinch + * + * OpenAPI document version: 1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.conversation.models.v1.consents.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.conversation.models.v1.consents.ConsentsListType; +import java.time.Instant; +import java.util.Arrays; +import java.util.stream.Stream; + +/** Represents a consent audit record from the Conversation API. */ +@JsonDeserialize(builder = AuditRecordImpl.Builder.class) +public interface AuditRecord { + + /** + * The origin from the opt-out or opt-in. This could be the Batch API or an MO (when matched with + * an end-user MO). + */ + public class OriginEnum extends EnumDynamic { + public static final OriginEnum ORIGIN_API = new OriginEnum("ORIGIN_API"); + public static final OriginEnum ORIGIN_MO = new OriginEnum("ORIGIN_MO"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + OriginEnum.class, OriginEnum::new, Arrays.asList(ORIGIN_API, ORIGIN_MO)); + + private OriginEnum(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static OriginEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(OriginEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + /** + * The origin from the opt-out or opt-in. This could be the Batch API or an MO (when matched with + * an end-user MO). + * + *

Field is required + * + * @return origin + */ + OriginEnum getOrigin(); + + /** The operation that the audit record refers to. Can be INSERT, UPDATE or DELETE. */ + public class OperationEnum extends EnumDynamic { + public static final OperationEnum OPERATION_INSERT = new OperationEnum("OPERATION_INSERT"); + public static final OperationEnum OPERATION_UPDATE = new OperationEnum("OPERATION_UPDATE"); + public static final OperationEnum OPERATION_DELETE = new OperationEnum("OPERATION_DELETE"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + OperationEnum.class, + OperationEnum::new, + Arrays.asList(OPERATION_INSERT, OPERATION_UPDATE, OPERATION_DELETE)); + + private OperationEnum(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static OperationEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(OperationEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + /** + * The operation that the audit record refers to. Can be INSERT, UPDATE or DELETE. + * + *

Field is required + * + * @return operation + */ + OperationEnum getOperation(); + + /** + * The list type of the consent list the audit record refers to. + * + *

Field is required + * + * @return listType + */ + ConsentsListType getListType(); + + /** + * The project ID associated with the consent list the audit record refers to. + * + *

Field is required + * + * @return projectId + */ + String getProjectId(); + + /** + * The app ID associated with the consent list the audit record refers to. + * + *

Field is required + * + * @return appId + */ + String getAppId(); + + /** + * Datetime of when the consent list audit record happened. + * + *

Field is required + * + * @return datetime + */ + Instant getDatetime(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new AuditRecordImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param origin see getter + * @return Current builder + * @see #getOrigin + */ + Builder setOrigin(OriginEnum origin); + + /** + * see getter + * + * @param operation see getter + * @return Current builder + * @see #getOperation + */ + Builder setOperation(OperationEnum operation); + + /** + * see getter + * + * @param listType see getter + * @return Current builder + * @see #getListType + */ + Builder setListType(ConsentsListType listType); + + /** + * see getter + * + * @param projectId see getter + * @return Current builder + * @see #getProjectId + */ + Builder setProjectId(String projectId); + + /** + * see getter + * + * @param appId see getter + * @return Current builder + * @see #getAppId + */ + Builder setAppId(String appId); + + /** + * see getter + * + * @param datetime see getter + * @return Current builder + * @see #getDatetime + */ + Builder setDatetime(Instant datetime); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + AuditRecord build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordImpl.java new file mode 100644 index 000000000..3bab79796 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordImpl.java @@ -0,0 +1,230 @@ +package com.sinch.sdk.domains.conversation.models.v1.consents.response; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import com.sinch.sdk.domains.conversation.models.v1.consents.ConsentsListType; +import java.time.Instant; +import java.util.Objects; + +@JsonPropertyOrder({ + AuditRecordImpl.JSON_PROPERTY_ORIGIN, + AuditRecordImpl.JSON_PROPERTY_OPERATION, + AuditRecordImpl.JSON_PROPERTY_LIST_TYPE, + AuditRecordImpl.JSON_PROPERTY_PROJECT_ID, + AuditRecordImpl.JSON_PROPERTY_APP_ID, + AuditRecordImpl.JSON_PROPERTY_DATETIME +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class AuditRecordImpl implements AuditRecord { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ORIGIN = "origin"; + + private OptionalValue origin; + + public static final String JSON_PROPERTY_OPERATION = "operation"; + + private OptionalValue operation; + + public static final String JSON_PROPERTY_LIST_TYPE = "list_type"; + + private OptionalValue listType; + + public static final String JSON_PROPERTY_PROJECT_ID = "project_id"; + + private OptionalValue projectId; + + public static final String JSON_PROPERTY_APP_ID = "app_id"; + + private OptionalValue appId; + + public static final String JSON_PROPERTY_DATETIME = "datetime"; + + private OptionalValue datetime; + + public AuditRecordImpl() {} + + protected AuditRecordImpl( + OptionalValue origin, + OptionalValue operation, + OptionalValue listType, + OptionalValue projectId, + OptionalValue appId, + OptionalValue datetime) { + this.origin = origin; + this.operation = operation; + this.listType = listType; + this.projectId = projectId; + this.appId = appId; + this.datetime = datetime; + } + + @JsonIgnore + public OriginEnum getOrigin() { + return origin.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ORIGIN) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue origin() { + return origin; + } + + @JsonIgnore + public OperationEnum getOperation() { + return operation.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_OPERATION) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue operation() { + return operation; + } + + @JsonIgnore + public ConsentsListType getListType() { + return listType.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_LIST_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue listType() { + return listType; + } + + @JsonIgnore + public String getProjectId() { + return projectId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_PROJECT_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue projectId() { + return projectId; + } + + @JsonIgnore + public String getAppId() { + return appId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_APP_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue appId() { + return appId; + } + + @JsonIgnore + public Instant getDatetime() { + return datetime.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DATETIME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue datetime() { + return datetime; + } + + /** Return true if this AuditRecord object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AuditRecordImpl auditRecord = (AuditRecordImpl) o; + return Objects.equals(this.origin, auditRecord.origin) + && Objects.equals(this.operation, auditRecord.operation) + && Objects.equals(this.listType, auditRecord.listType) + && Objects.equals(this.projectId, auditRecord.projectId) + && Objects.equals(this.appId, auditRecord.appId) + && Objects.equals(this.datetime, auditRecord.datetime); + } + + @Override + public int hashCode() { + return Objects.hash(origin, operation, listType, projectId, appId, datetime); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class AuditRecordImpl {\n"); + sb.append(" origin: ").append(toIndentedString(origin)).append("\n"); + sb.append(" operation: ").append(toIndentedString(operation)).append("\n"); + sb.append(" listType: ").append(toIndentedString(listType)).append("\n"); + sb.append(" projectId: ").append(toIndentedString(projectId)).append("\n"); + sb.append(" appId: ").append(toIndentedString(appId)).append("\n"); + sb.append(" datetime: ").append(toIndentedString(datetime)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements AuditRecord.Builder { + OptionalValue origin = OptionalValue.empty(); + OptionalValue operation = OptionalValue.empty(); + OptionalValue listType = OptionalValue.empty(); + OptionalValue projectId = OptionalValue.empty(); + OptionalValue appId = OptionalValue.empty(); + OptionalValue datetime = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_ORIGIN, required = true) + public Builder setOrigin(OriginEnum origin) { + this.origin = OptionalValue.of(origin); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_OPERATION, required = true) + public Builder setOperation(OperationEnum operation) { + this.operation = OptionalValue.of(operation); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_LIST_TYPE, required = true) + public Builder setListType(ConsentsListType listType) { + this.listType = OptionalValue.of(listType); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_PROJECT_ID, required = true) + public Builder setProjectId(String projectId) { + this.projectId = OptionalValue.of(projectId); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_APP_ID, required = true) + public Builder setAppId(String appId) { + this.appId = OptionalValue.of(appId); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_DATETIME, required = true) + public Builder setDatetime(Instant datetime) { + this.datetime = OptionalValue.of(datetime); + return this; + } + + public AuditRecord build() { + return new AuditRecordImpl(origin, operation, listType, projectId, appId, datetime); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordsResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordsResponse.java new file mode 100644 index 000000000..3ad8a1753 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordsResponse.java @@ -0,0 +1,72 @@ +/* + * Conversation API | Sinch + * + * OpenAPI document version: 1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.conversation.models.v1.consents.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.conversation.models.v1.consents.Identity; +import java.util.List; + +/** AuditRecordsResponse */ +@JsonDeserialize(builder = AuditRecordsResponseImpl.Builder.class) +public interface AuditRecordsResponse { + + /** + * Get identity + * + * @return identity + */ + Identity getIdentity(); + + /** + * List of audit records associated with the given identity + * + * @return auditRecords + */ + List getAuditRecords(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new AuditRecordsResponseImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param identity see getter + * @return Current builder + * @see #getIdentity + */ + Builder setIdentity(Identity identity); + + /** + * see getter + * + * @param auditRecords see getter + * @return Current builder + * @see #getAuditRecords + */ + Builder setAuditRecords(List auditRecords); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + AuditRecordsResponse build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordsResponseImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordsResponseImpl.java new file mode 100644 index 000000000..fdba1bd1a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecordsResponseImpl.java @@ -0,0 +1,121 @@ +package com.sinch.sdk.domains.conversation.models.v1.consents.response; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import com.sinch.sdk.domains.conversation.models.v1.consents.Identity; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + AuditRecordsResponseImpl.JSON_PROPERTY_IDENTITY, + AuditRecordsResponseImpl.JSON_PROPERTY_AUDIT_RECORDS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class AuditRecordsResponseImpl implements AuditRecordsResponse { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_IDENTITY = "identity"; + + private OptionalValue identity; + + public static final String JSON_PROPERTY_AUDIT_RECORDS = "audit_records"; + + private OptionalValue> auditRecords; + + public AuditRecordsResponseImpl() {} + + protected AuditRecordsResponseImpl( + OptionalValue identity, OptionalValue> auditRecords) { + this.identity = identity; + this.auditRecords = auditRecords; + } + + @JsonIgnore + public Identity getIdentity() { + return identity.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_IDENTITY) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue identity() { + return identity; + } + + @JsonIgnore + public List getAuditRecords() { + return auditRecords.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_AUDIT_RECORDS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> auditRecords() { + return auditRecords; + } + + /** Return true if this GetAuditRecordsFromConsentResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AuditRecordsResponseImpl getAuditRecordsFromConsentResponse = (AuditRecordsResponseImpl) o; + return Objects.equals(this.identity, getAuditRecordsFromConsentResponse.identity) + && Objects.equals(this.auditRecords, getAuditRecordsFromConsentResponse.auditRecords); + } + + @Override + public int hashCode() { + return Objects.hash(identity, auditRecords); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class AuditRecordsResponseImpl {\n"); + sb.append(" identity: ").append(toIndentedString(identity)).append("\n"); + sb.append(" auditRecords: ").append(toIndentedString(auditRecords)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements AuditRecordsResponse.Builder { + OptionalValue identity = OptionalValue.empty(); + OptionalValue> auditRecords = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_IDENTITY) + public Builder setIdentity(Identity identity) { + this.identity = OptionalValue.of(identity); + return this; + } + + @JsonProperty(JSON_PROPERTY_AUDIT_RECORDS) + public Builder setAuditRecords(List auditRecords) { + this.auditRecords = OptionalValue.of(auditRecords); + return this; + } + + public AuditRecordsResponse build() { + return new AuditRecordsResponseImpl(identity, auditRecords); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/ConsentsListResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/ConsentsListResponse.java new file mode 100644 index 000000000..ae32a8ea0 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/ConsentsListResponse.java @@ -0,0 +1,59 @@ +/* + * Conversation API | Sinch + * + * OpenAPI document version: 1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.conversation.models.v1.consents.response; + +import com.sinch.sdk.core.http.HttpRequest; +import com.sinch.sdk.core.models.pagination.ListResponse; +import com.sinch.sdk.core.models.pagination.Page; +import com.sinch.sdk.domains.conversation.models.v1.consents.Identity; +import java.util.Collection; +import java.util.Collections; +import java.util.NoSuchElementException; +import java.util.function.Supplier; + +/** Auto paginated response for list of Identity */ +public class ConsentsListResponse extends ListResponse { + + private final Page page; + private final Supplier supplier; + + public ConsentsListResponse( + Supplier supplier, Page page) { + this.supplier = supplier; + this.page = page; + } + + @Override + public boolean hasNextPage() { + if (null == page.getNextPageToken() || null == getContent() || getContent().isEmpty()) { + return false; + } + return true; + } + + @Override + public ConsentsListResponse nextPage() { + if (!hasNextPage()) { + throw new NoSuchElementException("Reached the last page of the API response"); + } + return supplier.get(); + } + + @Override + public Collection getContent() { + return page == null ? Collections.emptyList() : page.getEntities(); + } + + @Override + public String toString() { + return "ConsentsListResponse {" + "page=" + page + '}'; + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/contact/ContactDeleteEvent.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/contact/ContactDeleteEvent.java index 96b509c99..83264ea89 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/contact/ContactDeleteEvent.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/contact/ContactDeleteEvent.java @@ -13,7 +13,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.time.Instant; -/** This callback is sent when a new contact is deleted. */ +/** This callback is sent when a contact is deleted. */ @JsonDeserialize(builder = ContactDeleteEventImpl.Builder.class) public interface ContactDeleteEvent extends com.sinch.sdk.domains.conversation.models.v1.sinchevents.ConversationSinchEvent { diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/contact/ContactUpdateEvent.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/contact/ContactUpdateEvent.java index 924e111c8..6b08cf3d7 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/contact/ContactUpdateEvent.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/contact/ContactUpdateEvent.java @@ -13,7 +13,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.time.Instant; -/** This callback is sent when a new contact is updated. */ +/** This callback is sent when a contact is updated. */ @JsonDeserialize(builder = ContactUpdateEventImpl.Builder.class) public interface ContactUpdateEvent extends com.sinch.sdk.domains.conversation.models.v1.sinchevents.ConversationSinchEvent { diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/delivery/DeliveryStatus.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/delivery/DeliveryStatus.java index 3e1ff8740..09564f4f7 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/delivery/DeliveryStatus.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/delivery/DeliveryStatus.java @@ -20,7 +20,7 @@ public class DeliveryStatus extends EnumDynamic { /** Message delivery failed. */ public static final DeliveryStatus FAILED = new DeliveryStatus("FAILED"); - /** The Conversation API app is switch to a different channel to deliver the message. */ + /** The Conversation API app is switching to a different channel to deliver the message. */ public static final DeliveryStatus SWITCHING_CHANNEL = new DeliveryStatus("SWITCHING_CHANNEL"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/transcoding/request/TranscodeMessageRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/transcoding/request/TranscodeMessageRequest.java index 1948e234e..4ac3f3ec6 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/transcoding/request/TranscodeMessageRequest.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/transcoding/request/TranscodeMessageRequest.java @@ -38,7 +38,7 @@ public interface TranscodeMessageRequest { AppMessage getAppMessage(); /** - * The list of channels for which the message shall be transcoded to. + * The list of channels for which the message shall be transcoded. * *

Field is required * diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/models/v1/consents/ConsentsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/models/v1/consents/ConsentsDtoTest.java new file mode 100644 index 000000000..b9d25f487 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/models/v1/consents/ConsentsDtoTest.java @@ -0,0 +1,79 @@ +package com.sinch.sdk.domains.conversation.models.v1.consents; + +import com.adelean.inject.resources.junit.jupiter.GivenJsonResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.domains.conversation.api.v1.adapters.ConversationBaseTest; +import com.sinch.sdk.domains.conversation.models.v1.consents.internal.ConsentsListResponseInternal; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.AuditRecord; +import com.sinch.sdk.domains.conversation.models.v1.consents.response.AuditRecordsResponse; +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class ConsentsDtoTest extends ConversationBaseTest { + + @GivenJsonResource("/domains/conversation/v1/consents/ConsentsListResponseDtoPage0.json") + ConsentsListResponseInternal loadedConsentsListResponseDtoPage0; + + @GivenJsonResource("/domains/conversation/v1/consents/ConsentsListResponseDtoPage1.json") + ConsentsListResponseInternal loadedConsentsListResponseDtoPage1; + + @GivenJsonResource("/domains/conversation/v1/consents/AuditRecordsResponseDto.json") + AuditRecordsResponse loadedAuditRecordsResponseDto; + + public static Identity expectedIdentityDto = + Identity.builder().setIdentity("an identity value").build(); + + public static ConsentsListResponseInternal expectedConsentsListResponseDtoPage0 = + ConsentsListResponseInternal.builder() + .setIdentities( + Arrays.asList( + expectedIdentityDto, + Identity.builder().setIdentity("a 2nd identity value").build())) + .setNextPageToken("the next page token value") + .build(); + + public static ConsentsListResponseInternal expectedConsentsListResponseDtoPage1 = + ConsentsListResponseInternal.builder() + .setIdentities( + Collections.singletonList( + Identity.builder().setIdentity("a 3rd identity value").build())) + .setNextPageToken("") + .build(); + + public static AuditRecord expectedAuditRecordDto = + AuditRecord.builder() + .setOrigin(AuditRecord.OriginEnum.ORIGIN_API) + .setOperation(AuditRecord.OperationEnum.OPERATION_INSERT) + .setListType(ConsentsListType.OPT_OUT_ALL) + .setProjectId("a project id") + .setAppId("an app id") + .setDatetime(Instant.parse("2025-06-06T14:42:56.031323Z")) + .build(); + + public static AuditRecordsResponse expectedAuditRecordsResponseDto = + AuditRecordsResponse.builder() + .setIdentity(expectedIdentityDto) + .setAuditRecords(Collections.singletonList(expectedAuditRecordDto)) + .build(); + + @Test + void deserializeConsentsListResponseDtoPage0() { + TestHelpers.recursiveEquals( + loadedConsentsListResponseDtoPage0, expectedConsentsListResponseDtoPage0); + } + + @Test + void deserializeConsentsListResponseDtoPage1() { + TestHelpers.recursiveEquals( + loadedConsentsListResponseDtoPage1, expectedConsentsListResponseDtoPage1); + } + + @Test + void deserializeAuditRecordsResponseDto() { + TestHelpers.recursiveEquals(loadedAuditRecordsResponseDto, expectedAuditRecordsResponseDto); + } +} diff --git a/openapi-contracts/src/test/resources/domains/conversation/v1/consents/AuditRecordsResponseDto.json b/openapi-contracts/src/test/resources/domains/conversation/v1/consents/AuditRecordsResponseDto.json new file mode 100644 index 000000000..186d70673 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/conversation/v1/consents/AuditRecordsResponseDto.json @@ -0,0 +1,15 @@ +{ + "identity": { + "identity": "an identity value" + }, + "audit_records": [ + { + "origin": "ORIGIN_API", + "operation": "OPERATION_INSERT", + "list_type": "OPT_OUT_ALL", + "project_id": "a project id", + "app_id": "an app id", + "datetime": "2025-06-06T14:42:56.031323Z" + } + ] +} diff --git a/openapi-contracts/src/test/resources/domains/conversation/v1/consents/ConsentsListResponseDtoPage0.json b/openapi-contracts/src/test/resources/domains/conversation/v1/consents/ConsentsListResponseDtoPage0.json new file mode 100644 index 000000000..8bcaa092d --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/conversation/v1/consents/ConsentsListResponseDtoPage0.json @@ -0,0 +1,11 @@ +{ + "identities": [ + { + "identity": "an identity value" + }, + { + "identity": "a 2nd identity value" + } + ], + "next_page_token": "the next page token value" +} diff --git a/openapi-contracts/src/test/resources/domains/conversation/v1/consents/ConsentsListResponseDtoPage1.json b/openapi-contracts/src/test/resources/domains/conversation/v1/consents/ConsentsListResponseDtoPage1.json new file mode 100644 index 000000000..171b46a24 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/conversation/v1/consents/ConsentsListResponseDtoPage1.json @@ -0,0 +1,8 @@ +{ + "identities": [ + { + "identity": "a 3rd identity value" + } + ], + "next_page_token": "" +} From 17389fbfbbf9004bf3581fd811443102f622beb1 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier <141755467+JPPortier@users.noreply.github.com> Date: Wed, 27 May 2026 18:32:13 +0200 Subject: [PATCH 11/37] DEVEXP-1378: auto close (#346) * feat (SinchClient): Support 'close()' method to shut down the underlying HTTP connection pool --- CHANGELOG.md | 1 + README.md | 20 ++++++++++ .../src/main/com/sinch/sdk/SinchClient.java | 36 ++++++++++++++++- .../com/sinch/sdk/http/HttpClientApache.java | 17 +++++--- .../java/com/sinch/sdk/SinchClientTest.java | 13 +++++++ .../java/com/sinch/sdk/SinchClientTestIT.java | 1 + .../http/HttpClientApacheLifecycleTest.java | 39 +++++++++++++++++++ 7 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 client/src/test/java/com/sinch/sdk/http/HttpClientApacheLifecycleTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d4f6052..a648f00c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ All notable changes to the **Sinch Java SDK** are documented in this file. - **[feature]** Support `Consents` API: `listIdentities` and `listAuditRecords` endpoints ### SDK +- **[feature]** `SinchClient` exposes a `close()` method to shut down the underlying HTTP connection pool and release all associated resources deterministically - **[fix]** `HttpClientApache`: declare now `headersToBeAdded` as `volatile` to guarantee visibility across threads in concurrent usage - **[fix]** `HttpClientApache`: wrap response-body `Scanner` in a try-with-resources block to prevent resource leaks; gracefully handle empty (`null`) response entities - **[fix]** `SinchClient`: guard against a `NullPointerException` when `java.vendor` system property is absent while building the `User-Agent` auxiliary flag diff --git a/README.md b/README.md index 134e68513..4967d9b74 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ For more information on the SDK, refer to the dedicated [Java SDK documentation - [Prerequisites](#prerequisites) - [Installation](#installation) - [Getting started](#getting-started) + - [Client initialization](#client-initialization) + - [Client lifecycle](#client-lifecycle) - [Supported APIs](#supported-apis) - [Logging](#logging) - [Onboarding](#onboarding) @@ -64,6 +66,24 @@ Configuration configuration = Configuration.builder() SinchClient client = new SinchClient(configuration); ``` +### Client lifecycle + +`SinchClient` exposes a `close()` method to shut down the underlying HTTP connection pool and release all associated resources. + +**Explicit close at shutdown:** + +```java +// Create once at startup (e.g. as a Spring bean) +SinchClient client = new SinchClient(configuration); + +// ... use throughout the application lifetime ... + +// Release resources at shutdown +client.close(); +``` + +Failing to close a `SinchClient` that has made at least one request will leave the HTTP connection pool open until the object is garbage-collected, which may delay JVM shutdown and exhaust file descriptors in long-running processes. + ## Supported APIs Here is the list of the Sinch products and their level of support by the Java SDK: diff --git a/client/src/main/com/sinch/sdk/SinchClient.java b/client/src/main/com/sinch/sdk/SinchClient.java index 253482297..eee79badf 100644 --- a/client/src/main/com/sinch/sdk/SinchClient.java +++ b/client/src/main/com/sinch/sdk/SinchClient.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.Objects; import java.util.Properties; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -400,8 +401,6 @@ private HttpClientApache getHttpClient() { synchronized (this) { local = httpClient; if (null == local || local.isClosed()) { - // TODO: by adding a setter, we could imagine having another HTTP client provided - // programmatically or use configuration file referencing another class by name local = new HttpClientApache(); // set SDK User-Agent @@ -444,6 +443,39 @@ String formatAuxiliaryFlag(String auxiliaryFlag) { return String.join(",", values); } + /** + * Releases the underlying HTTP client and its connection pool, and resets all lazily-initialized + * domain services. + * + *

After this call, in-flight requests may be affected by shutdown of the underlying HTTP + * client and are not guaranteed to complete normally. The next API call on any domain service + * will transparently re-initialize both the service and the HTTP client. Idempotent: safe to call + * more than once. + * + *

Any exception thrown by the underlying HTTP client during close is caught and logged at + * WARNING level. + * + * @since 2.1 + */ + public void close() { + synchronized (this) { + HttpClientApache local = httpClient; + httpClient = null; + numbers = null; + sms = null; + verification = null; + voice = null; + conversation = null; + if (local != null) { + try { + local.close(); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Exception while closing HTTP client", e); + } + } + } + } + static { LocalLazyInit.init(); } diff --git a/client/src/main/com/sinch/sdk/http/HttpClientApache.java b/client/src/main/com/sinch/sdk/http/HttpClientApache.java index 5625c4512..e5882a487 100644 --- a/client/src/main/com/sinch/sdk/http/HttpClientApache.java +++ b/client/src/main/com/sinch/sdk/http/HttpClientApache.java @@ -104,6 +104,11 @@ public HttpResponse invokeAPI( HttpRequest httpRequest) throws ApiException { + CloseableHttpClient activeClient = this.client; + if (activeClient == null) { + throw new ApiException("HTTP client has been closed"); + } + try { String path = httpRequest @@ -148,7 +153,7 @@ public HttpResponse invokeAPI( ClassicHttpRequest request = requestBuilder.build(); - HttpResponse response = processRequest(client, request); + HttpResponse response = processRequest(activeClient, request); LOGGER.finest("connection response: " + response); // UNAUTHORIZED (HTTP 401) error code could imply refreshing the OAuth token @@ -159,7 +164,7 @@ public HttpResponse invokeAPI( // refresh authorization addAuth(requestBuilder, authManagersByOasSecuritySchemes, authNames, body); request = requestBuilder.build(); - response = processRequest(client, request); + response = processRequest(activeClient, request); LOGGER.finest("connection response on retry: " + response); } } @@ -364,10 +369,12 @@ public boolean isClosed() { @Override public void close() throws Exception { if (!isClosed()) { - try { - client.close(); - } finally { + synchronized (this) { + CloseableHttpClient local = client; client = null; + if (local != null) { + local.close(); + } } } } diff --git a/client/src/test/java/com/sinch/sdk/SinchClientTest.java b/client/src/test/java/com/sinch/sdk/SinchClientTest.java index 9ec7a2ef5..25de15ecf 100644 --- a/client/src/test/java/com/sinch/sdk/SinchClientTest.java +++ b/client/src/test/java/com/sinch/sdk/SinchClientTest.java @@ -257,4 +257,17 @@ void formatAuxiliaryFlagAppendsNonEmptyFlag() { result.endsWith(",my-wrapper/1.0"), "Auxiliary flag must be the last element after the comma"); } + + @Test + void closeBeforeAnyCall() { + SinchClient client = new SinchClient(); + assertDoesNotThrow(client::close); + } + + @Test + void doubleCloseBeforeAnyCall() { + SinchClient client = new SinchClient(); + client.close(); + assertDoesNotThrow(client::close); + } } diff --git a/client/src/test/java/com/sinch/sdk/SinchClientTestIT.java b/client/src/test/java/com/sinch/sdk/SinchClientTestIT.java index c95624e5e..1a5bdf7e2 100644 --- a/client/src/test/java/com/sinch/sdk/SinchClientTestIT.java +++ b/client/src/test/java/com/sinch/sdk/SinchClientTestIT.java @@ -52,6 +52,7 @@ void sdkUserAgent() throws InterruptedException { // noop } RecordedRequest recordedRequest = mockBackEnd.takeRequest(); + assertThat(recordedRequest).isNotNull(); String header = recordedRequest.getHeader("User-Agent"); assertThat(header).matches("^sinch-sdk/.* \\(Java/.*; Apache; .*\\)$"); } diff --git a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheLifecycleTest.java b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheLifecycleTest.java new file mode 100644 index 000000000..2c637f5dd --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheLifecycleTest.java @@ -0,0 +1,39 @@ +package com.sinch.sdk.http; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class HttpClientApacheLifecycleTest { + + @Test + void freshInstanceIsNotClosed() throws Exception { + try (HttpClientApache client = new HttpClientApache()) { + assertFalse(client.isClosed()); + } + } + + @Test + void closeMarksInstanceAsClosed() throws Exception { + HttpClientApache client = new HttpClientApache(); + client.close(); + assertTrue(client.isClosed()); + } + + @Test + void doubleCloseIsIdempotent() throws Exception { + HttpClientApache client = new HttpClientApache(); + client.close(); + assertDoesNotThrow(client::close); + } + + @Test + void closeViaAutoCloseable() { + assertDoesNotThrow( + () -> { + try (HttpClientApache client = new HttpClientApache()) { + assertFalse(client.isClosed()); + } + }); + } +} From d73c013a2de4e97592ba109a740a052ed27a5ac5 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Mon, 8 Jun 2026 10:25:02 +0200 Subject: [PATCH 12/37] NumberLookup Api V2 implementation (#348) * NumberLookup Api V2 implementation * New sources generated for number-lookup api v2 --- client/resources/config-default.properties | 2 + .../src/main/com/sinch/sdk/SinchClient.java | 49 ++++ .../numberlookup/NumberLookupService.java | 19 ++ .../adapters/NumberLookupService.java | 42 +++ .../api/v2/NumberLookupService.java | 19 ++ .../api/v2/adapters/NumberLookupService.java | 103 +++++++ .../com/sinch/sdk/models/Configuration.java | 50 +++- .../sinch/sdk/models/NumberLookupContext.java | 93 +++++++ .../java/com/sinch/sdk/SinchClientTest.java | 8 + .../adapters/CredentialsValidationHelper.java | 153 +++++++++++ .../v2/adapters/NumberLookupServiceTest.java | 15 + .../adapters/NumberLookupV2ServiceTest.java | 173 ++++++++++++ .../test/java/com/sinch/sdk/e2e/Config.java | 5 + .../domains/numberlookup/v2/LookupsSteps.java | 124 +++++++++ .../numberlookup/v2/NumberLookupIT.java | 18 ++ .../ChannelSpecificContactMessage.java | 2 +- ...SpecificContactMessageMessageInternal.java | 2 +- .../api/v2/NumberLookupV2Service.java | 28 ++ .../adapters/NumberLookupV2ServiceImpl.java | 117 ++++++++ .../models/v2/errors/LookupError.java | 102 +++++++ .../models/v2/errors/LookupErrorImpl.java | 174 ++++++++++++ .../models/v2/errors/ProblemDetails.java | 102 +++++++ .../models/v2/errors/ProblemDetailsImpl.java | 174 ++++++++++++ .../models/v2/request/LookupFeatureType.java | 44 +++ .../v2/request/NumberLookupRequest.java | 89 ++++++ .../v2/request/NumberLookupRequestImpl.java | 148 ++++++++++ .../models/v2/request/RndFeatureOptions.java | 55 ++++ .../v2/request/RndFeatureOptionsImpl.java | 91 ++++++ .../numberlookup/models/v2/response/Line.java | 156 +++++++++++ .../models/v2/response/LineImpl.java | 259 ++++++++++++++++++ .../models/v2/response/LineType.java | 50 ++++ .../v2/response/NumberLookupResponse.java | 150 ++++++++++ .../v2/response/NumberLookupResponseImpl.java | 256 +++++++++++++++++ .../numberlookup/models/v2/response/Rnd.java | 71 +++++ .../models/v2/response/RndImpl.java | 116 ++++++++ .../models/v2/response/SimSwap.java | 87 ++++++ .../models/v2/response/SimSwapImpl.java | 148 ++++++++++ .../models/v2/response/SwapPeriodType.java | 63 +++++ .../models/v2/response/VoIPDetection.java | 71 +++++ .../models/v2/response/VoIPDetectionImpl.java | 120 ++++++++ .../v2/response/VoIPProbabilityType.java | 44 +++ .../models/v2/errors/LookupErrorDtoTest.java | 42 +++ .../v2/errors/ProblemDetailsDtoTest.java | 31 +++ .../request/NumberLookupRequestDtoTest.java | 120 ++++++++ .../v2/request/RndFeatureOptionsDtoTest.java | 36 +++ .../v2/response/LineResponseDtoTest.java | 54 ++++ .../response/NumberLookupResponseDtoTest.java | 63 +++++ .../v2/response/RndResponseDtoTest.java | 45 +++ .../v2/response/SimSwapResponseDtoTest.java | 62 +++++ .../VoIPDetectionResponseDtoTest.java | 47 ++++ .../v2/errors/LookupErrorDto.json | 6 + .../v2/errors/LookupErrorEmptyDto.json | 1 + .../v2/errors/ProblemDetailsDto.json | 6 + .../NumberLookupRequestAllFeaturesDto.json | 9 + .../NumberLookupRequestMinimalDto.json | 3 + .../NumberLookupRequestWithFeaturesDto.json | 8 + .../NumberLookupRequestWithRndOptionsDto.json | 9 + .../v2/request/RndFeatureOptionsDto.json | 3 + .../v2/response/LineResponseDto.json | 8 + .../v2/response/LineResponseEmptyDto.json | 1 + .../response/NumberLookupResponseFullDto.json | 23 ++ .../NumberLookupResponseMinimalDto.json | 5 + .../v2/response/RndResponseDto.json | 3 + .../v2/response/RndResponseEmptyDto.json | 1 + .../v2/response/SimSwapResponseDto.json | 4 + .../response/SimSwapResponseWithErrorDto.json | 7 + .../v2/response/VoIPDetectionResponseDto.json | 3 + .../VoIPDetectionResponseEmptyDto.json | 1 + pom.xml | 4 + 69 files changed, 4193 insertions(+), 4 deletions(-) create mode 100644 client/src/main/com/sinch/sdk/domains/numberlookup/NumberLookupService.java create mode 100644 client/src/main/com/sinch/sdk/domains/numberlookup/adapters/NumberLookupService.java create mode 100644 client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupService.java create mode 100644 client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupService.java create mode 100644 client/src/main/com/sinch/sdk/models/NumberLookupContext.java create mode 100644 client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/CredentialsValidationHelper.java create mode 100644 client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceTest.java create mode 100644 client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupV2ServiceTest.java create mode 100644 client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/LookupsSteps.java create mode 100644 client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/NumberLookupIT.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupV2Service.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupV2ServiceImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupError.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupErrorImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetails.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetailsImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/LookupFeatureType.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequest.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequestImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptions.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptionsImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/Line.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/LineImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/LineType.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponse.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponseImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/Rnd.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/RndImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwap.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwapImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SwapPeriodType.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetection.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetectionImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPProbabilityType.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupErrorDtoTest.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetailsDtoTest.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequestDtoTest.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptionsDtoTest.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/LineResponseDtoTest.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponseDtoTest.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/RndResponseDtoTest.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwapResponseDtoTest.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetectionResponseDtoTest.java create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/LookupErrorDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/LookupErrorEmptyDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/ProblemDetailsDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestAllFeaturesDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestMinimalDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestWithFeaturesDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestWithRndOptionsDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/request/RndFeatureOptionsDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/response/LineResponseDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/response/LineResponseEmptyDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/response/NumberLookupResponseFullDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/response/NumberLookupResponseMinimalDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/response/RndResponseDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/response/RndResponseEmptyDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/response/SimSwapResponseDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/response/SimSwapResponseWithErrorDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/response/VoIPDetectionResponseDto.json create mode 100644 openapi-contracts/src/test/resources/domains/numberlookup/v2/response/VoIPDetectionResponseEmptyDto.json diff --git a/client/resources/config-default.properties b/client/resources/config-default.properties index 0009c6269..102be6245 100644 --- a/client/resources/config-default.properties +++ b/client/resources/config-default.properties @@ -21,3 +21,5 @@ voice-server-south-east-asia2=https://calling-apse2.api.sinch.com conversation-region= conversation-server=https://%s.conversation.api.sinch.com template-management-conversation-server=https://%s.template.api.sinch.com + +number-lookup-server=https://lookup.api.sinch.com diff --git a/client/src/main/com/sinch/sdk/SinchClient.java b/client/src/main/com/sinch/sdk/SinchClient.java index eee79badf..1612ea26a 100644 --- a/client/src/main/com/sinch/sdk/SinchClient.java +++ b/client/src/main/com/sinch/sdk/SinchClient.java @@ -2,6 +2,7 @@ import com.sinch.sdk.core.utils.StringUtil; import com.sinch.sdk.domains.conversation.ConversationService; +import com.sinch.sdk.domains.numberlookup.api.v2.adapters.NumberLookupService; import com.sinch.sdk.domains.numbers.NumbersService; import com.sinch.sdk.domains.sms.SMSService; import com.sinch.sdk.domains.verification.VerificationService; @@ -10,6 +11,7 @@ import com.sinch.sdk.models.Configuration; import com.sinch.sdk.models.ConversationContext; import com.sinch.sdk.models.ConversationRegion; +import com.sinch.sdk.models.NumberLookupContext; import com.sinch.sdk.models.NumbersContext; import com.sinch.sdk.models.SMSRegion; import com.sinch.sdk.models.SmsContext; @@ -50,6 +52,8 @@ public class SinchClient { private static final String CONVERSATION_TEMPLATE_SERVER_KEY = "template-management-conversation-server"; + private static final String NUMBER_LOOKUP_SERVER_KEY = "number-lookup-server"; + // sinch-sdk/{sdk_version} ({language}/{language_version}; {implementation_type}; // {auxiliary_flag}) private static final String SDK_USER_AGENT_HEADER = "User-Agent"; @@ -63,6 +67,7 @@ public class SinchClient { private volatile VerificationService verification; private volatile VoiceService voice; private volatile ConversationService conversation; + private volatile NumberLookupService lookup; private volatile HttpClientApache httpClient; /** @@ -88,6 +93,7 @@ public SinchClient(Configuration configuration) { handleDefaultVerificationSettings(configurationGuard, props, builder); handleDefaultVoiceSettings(configurationGuard, props, builder); handleDefaultConversationSettings(configurationGuard, props, builder); + handleDefaultNumberLookupSettings(configurationGuard, props, builder); Configuration newConfiguration = builder.build(); checkConfiguration(newConfiguration); @@ -228,6 +234,23 @@ private void handleDefaultConversationSettings( builder.setConversationContext(contextBuilder.build()); } + private void handleDefaultNumberLookupSettings( + Configuration configuration, Properties props, Configuration.Builder builder) { + + String url = + configuration + .getNumberLookupContext() + .map(NumberLookupContext::getNumberLookupUrl) + .orElse(null); + + if (null == url && props.containsKey(NUMBER_LOOKUP_SERVER_KEY)) { + builder.setNumberLookupContext( + NumberLookupContext.builder() + .setNumberLookupUrl(props.getProperty(NUMBER_LOOKUP_SERVER_KEY)) + .build()); + } + } + /** * Get current configuration * @@ -333,6 +356,23 @@ public ConversationService conversation() { return conversation; } + /** + * Get Number Lookup domain service + * + * @return Return instance onto Number Lookup API service + * @since 2.1 + */ + public NumberLookupService lookup() { + if (null == lookup) { + synchronized (this) { + if (null == lookup) { + lookup = lookupInit(); + } + } + } + return lookup; + } + private void checkConfiguration(Configuration configuration) throws NullPointerException { Objects.requireNonNull(configuration.getOAuthUrl(), "'oauthUrl' cannot be null"); } @@ -384,6 +424,14 @@ private ConversationService conversationInit() { this::getHttpClient); } + private NumberLookupService lookupInit() { + return new com.sinch.sdk.domains.numberlookup.api.v2.adapters.NumberLookupService( + getConfiguration().getUnifiedCredentials().orElse(null), + getConfiguration().getNumberLookupContext().orElse(null), + getConfiguration().getOAuthServer(), + this::getHttpClient); + } + private Properties handlePropertiesFile(String fileName) { Properties prop = new Properties(); @@ -466,6 +514,7 @@ public void close() { verification = null; voice = null; conversation = null; + lookup = null; if (local != null) { try { local.close(); diff --git a/client/src/main/com/sinch/sdk/domains/numberlookup/NumberLookupService.java b/client/src/main/com/sinch/sdk/domains/numberlookup/NumberLookupService.java new file mode 100644 index 000000000..0fbda1df4 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/numberlookup/NumberLookupService.java @@ -0,0 +1,19 @@ +package com.sinch.sdk.domains.numberlookup; + +/** + * Number Lookup Service + * + * @see https://developers.sinch.com/docs/number-lookup-api-v2 + * @since 2.1 + */ +public interface NumberLookupService { + + /** + * Number Lookup Service V2 + * + * @return V2 service instance for project + * @since 2.1 + */ + com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupService v2(); +} diff --git a/client/src/main/com/sinch/sdk/domains/numberlookup/adapters/NumberLookupService.java b/client/src/main/com/sinch/sdk/domains/numberlookup/adapters/NumberLookupService.java new file mode 100644 index 000000000..4e21d9ac8 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/numberlookup/adapters/NumberLookupService.java @@ -0,0 +1,42 @@ +package com.sinch.sdk.domains.numberlookup.adapters; + +import com.sinch.sdk.core.http.HttpClient; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.models.NumberLookupContext; +import com.sinch.sdk.models.UnifiedCredentials; +import java.util.function.Supplier; + +public class NumberLookupService implements com.sinch.sdk.domains.numberlookup.NumberLookupService { + + private final UnifiedCredentials credentials; + private final NumberLookupContext context; + private final ServerConfiguration oAuthServer; + private final Supplier httpClientSupplier; + + private volatile com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupService v2; + + public NumberLookupService( + UnifiedCredentials credentials, + NumberLookupContext context, + ServerConfiguration oAuthServer, + Supplier httpClientSupplier) { + this.credentials = credentials; + this.context = context; + this.oAuthServer = oAuthServer; + this.httpClientSupplier = httpClientSupplier; + } + + @Override + public com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupService v2() { + if (null == this.v2) { + synchronized (this) { + if (null == this.v2) { + this.v2 = + new com.sinch.sdk.domains.numberlookup.api.v2.adapters.NumberLookupService( + credentials, context, oAuthServer, httpClientSupplier); + } + } + } + return this.v2; + } +} diff --git a/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupService.java b/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupService.java new file mode 100644 index 000000000..cfeb8b218 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupService.java @@ -0,0 +1,19 @@ +package com.sinch.sdk.domains.numberlookup.api.v2; + +/** + * Number Lookup Service V2 + * + * @see https://developers.sinch.com/docs/number-lookup-api-v2 + * @since 2.1 + */ +public interface NumberLookupService { + + /** + * Number Lookup Service instance + * + * @return service instance for project + * @since 2.1 + */ + NumberLookupV2Service lookup(); +} diff --git a/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupService.java b/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupService.java new file mode 100644 index 000000000..6a943c298 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupService.java @@ -0,0 +1,103 @@ +package com.sinch.sdk.domains.numberlookup.api.v2.adapters; + +import com.sinch.sdk.auth.adapters.OAuthManager; +import com.sinch.sdk.core.http.AuthManager; +import com.sinch.sdk.core.http.HttpClient; +import com.sinch.sdk.core.http.HttpMapper; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.core.utils.StringUtil; +import com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupV2Service; +import com.sinch.sdk.models.NumberLookupContext; +import com.sinch.sdk.models.UnifiedCredentials; +import java.util.AbstractMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class NumberLookupService + implements com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupService { + + private static final Logger LOGGER = Logger.getLogger(NumberLookupService.class.getName()); + private static final String SECURITY_SCHEME_KEYWORD_NUMBER_LOOKUP = "OAuth2"; + + private final UnifiedCredentials credentials; + private final NumberLookupContext context; + private final ServerConfiguration oAuthServer; + private final Supplier httpClientSupplier; + + private volatile String uriUUID; + private volatile Map authManagers; + + private volatile NumberLookupV2Service lookup; + + public NumberLookupService( + UnifiedCredentials credentials, + NumberLookupContext context, + ServerConfiguration oAuthServer, + Supplier httpClientSupplier) { + this.credentials = credentials; + this.context = context; + this.oAuthServer = oAuthServer; + this.httpClientSupplier = httpClientSupplier; + } + + @Override + public NumberLookupV2Service lookup() { + if (null == this.lookup) { + synchronized (this) { + if (null == this.lookup) { + instanceLazyInit(); + this.lookup = + new NumberLookupV2ServiceImpl( + httpClientSupplier.get(), + context.getNumberLookupServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } + } + return this.lookup; + } + + private void instanceLazyInit() { + if (null != this.authManagers) { + return; + } + synchronized (this) { + if (null == this.authManagers) { + Objects.requireNonNull( + credentials, "Number Lookup service requires unified credentials to be defined"); + Objects.requireNonNull(context, "Number Lookup service requires context to be defined"); + StringUtil.requireNonEmpty( + credentials.getKeyId(), "Number Lookup service requires 'keyId' to be defined"); + StringUtil.requireNonEmpty( + credentials.getKeySecret(), "Number Lookup service requires 'keySecret' to be defined"); + StringUtil.requireNonEmpty( + credentials.getProjectId(), "Number Lookup service requires 'projectId' to be defined"); + StringUtil.requireNonEmpty( + context.getNumberLookupUrl(), + "Number Lookup service requires 'numberLookupUrl' to be defined"); + + LOGGER.fine( + "Activate Number Lookup API with server='" + + context.getNumberLookupServer().getUrl() + + "'"); + + AuthManager authManager = + new OAuthManager( + credentials, oAuthServer, HttpMapper.getInstance(), httpClientSupplier); + + uriUUID = credentials.getProjectId(); + authManagers = + Stream.of( + new AbstractMap.SimpleEntry<>( + SECURITY_SCHEME_KEYWORD_NUMBER_LOOKUP, authManager)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + } + } +} diff --git a/client/src/main/com/sinch/sdk/models/Configuration.java b/client/src/main/com/sinch/sdk/models/Configuration.java index 398a448d8..962f03190 100644 --- a/client/src/main/com/sinch/sdk/models/Configuration.java +++ b/client/src/main/com/sinch/sdk/models/Configuration.java @@ -15,6 +15,7 @@ public class Configuration { private final VerificationContext verificationContext; private final VoiceContext voiceContext; private final ConversationContext conversationContext; + private final NumberLookupContext numberLookupContext; private Configuration( UnifiedCredentials unifiedCredentials, @@ -25,7 +26,8 @@ private Configuration( SmsContext smsContext, VerificationContext verificationContext, VoiceContext voiceContext, - ConversationContext conversationContext) { + ConversationContext conversationContext, + NumberLookupContext numberLookupContext) { this.unifiedCredentials = unifiedCredentials; this.applicationCredentials = applicationCredentials; this.smsServicePlanCredentials = smsServicePlanCredentials; @@ -35,6 +37,7 @@ private Configuration( this.voiceContext = voiceContext; this.verificationContext = verificationContext; this.conversationContext = conversationContext; + this.numberLookupContext = numberLookupContext; } @Override @@ -53,6 +56,8 @@ public String toString() { + voiceContext + ", conversationContext=" + conversationContext + + ", numberLookupContext=" + + numberLookupContext + "}"; } @@ -160,6 +165,16 @@ public Optional getConversationContext() { return Optional.ofNullable(conversationContext); } + /** + * Get Number Lookup domain related execution context + * + * @return Current Number Lookup context + * @since 2.1 + */ + public Optional getNumberLookupContext() { + return Optional.ofNullable(numberLookupContext); + } + /** * Getting Builder * @@ -197,6 +212,7 @@ public static class Builder { VerificationContext.Builder verificationContext; VoiceContext.Builder voiceContext; ConversationContext.Builder conversationContext; + NumberLookupContext.Builder numberLookupContext; protected Builder() {} @@ -228,6 +244,8 @@ protected Builder(Configuration configuration) { this.voiceContext = configuration.getVoiceContext().map(VoiceContext::builder).orElse(null); this.conversationContext = configuration.getConversationContext().map(ConversationContext::builder).orElse(null); + this.numberLookupContext = + configuration.getNumberLookupContext().map(NumberLookupContext::builder).orElse(null); } /** @@ -486,6 +504,33 @@ public Builder setConversationContext(ConversationContext context) { return this; } + /** + * Set Number Lookup API URL + * + * @param numberLookupUrl Number Lookup API URL + * @return Current builder + * @since 2.1 + */ + public Builder setNumberLookupUrl(String numberLookupUrl) { + if (null == this.numberLookupContext) { + this.numberLookupContext = NumberLookupContext.builder(); + } + this.numberLookupContext.setNumberLookupUrl(numberLookupUrl); + return this; + } + + /** + * Set Number Lookup related context + * + * @param context {@link #getNumberLookupContext() getter} + * @return Current builder + * @since 2.1 + */ + public Builder setNumberLookupContext(NumberLookupContext context) { + this.numberLookupContext = null != context ? NumberLookupContext.builder(context) : null; + return this; + } + /** * Build a Configuration instance from builder current state * @@ -503,7 +548,8 @@ public Configuration build() { null != smsContext ? smsContext.build() : null, null != verificationContext ? verificationContext.build() : null, null != voiceContext ? voiceContext.build() : null, - null != conversationContext ? conversationContext.build() : null); + null != conversationContext ? conversationContext.build() : null, + null != numberLookupContext ? numberLookupContext.build() : null); } } } diff --git a/client/src/main/com/sinch/sdk/models/NumberLookupContext.java b/client/src/main/com/sinch/sdk/models/NumberLookupContext.java new file mode 100644 index 000000000..8813767b1 --- /dev/null +++ b/client/src/main/com/sinch/sdk/models/NumberLookupContext.java @@ -0,0 +1,93 @@ +package com.sinch.sdk.models; + +import com.sinch.sdk.core.models.ServerConfiguration; + +/** Execution context related to Number Lookup domain */ +public class NumberLookupContext { + + private final String numberLookupUrl; + + private NumberLookupContext(String numberLookupUrl) { + this.numberLookupUrl = numberLookupUrl; + } + + /** + * Number Lookup Server Configuration + * + * @return Number Lookup Server configuration to be used + * @since 2.1 + */ + public ServerConfiguration getNumberLookupServer() { + return new ServerConfiguration(getNumberLookupUrl()); + } + + /** + * Number Lookup URL + * + * @return Number Lookup Server URL + * @since 2.1 + */ + public String getNumberLookupUrl() { + return numberLookupUrl; + } + + /** + * Getting Builder + * + * @return New Builder instance + * @since 2.1 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Getting Builder + * + * @param context Source context to fill initial builder state + * @return New Builder instance + * @since 2.1 + */ + public static Builder builder(NumberLookupContext context) { + return new Builder(context); + } + + /** + * Dedicated Builder + * + * @since 2.1 + */ + public static class Builder { + + String numberLookupUrl; + + protected Builder() {} + + protected Builder(NumberLookupContext context) { + this.numberLookupUrl = null != context ? context.getNumberLookupUrl() : null; + } + + /** + * Set Number Lookup API URL + * + * @param numberLookupUrl Number Lookup API URL + * @return Current builder + * @since 2.1 + */ + public Builder setNumberLookupUrl(String numberLookupUrl) { + this.numberLookupUrl = numberLookupUrl; + return this; + } + + /** + * Create instance + * + * @return The instance build with current builder values + * @since 2.1 + */ + public NumberLookupContext build() { + + return new NumberLookupContext(numberLookupUrl); + } + } +} diff --git a/client/src/test/java/com/sinch/sdk/SinchClientTest.java b/client/src/test/java/com/sinch/sdk/SinchClientTest.java index 25de15ecf..5ec89d15a 100644 --- a/client/src/test/java/com/sinch/sdk/SinchClientTest.java +++ b/client/src/test/java/com/sinch/sdk/SinchClientTest.java @@ -142,6 +142,14 @@ void defaultVerificationUrlAvailable() { assertNotNull(client.getConfiguration().getVerificationContext().get().getVerificationUrl()); } + @Test + void defaultNumberLookupUrlAvailable() { + Configuration configuration = + Configuration.builder().setKeyId("foo").setKeySecret("foo").setProjectId("foo").build(); + SinchClient client = new SinchClient(configuration); + assertNotNull(client.getConfiguration().getNumberLookupContext().get().getNumberLookupUrl()); + } + @Test void defaultVoiceRegion() { Configuration configuration = Configuration.builder().build(); diff --git a/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/CredentialsValidationHelper.java b/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/CredentialsValidationHelper.java new file mode 100644 index 000000000..4df84f691 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/CredentialsValidationHelper.java @@ -0,0 +1,153 @@ +package com.sinch.sdk.domains.numberlookup.api.v2.adapters; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.sinch.sdk.core.http.HttpClient; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.models.NumberLookupContext; +import com.sinch.sdk.models.UnifiedCredentials; +import java.util.function.Consumer; +import java.util.function.Supplier; + +class CredentialsValidationHelper { + + static ServerConfiguration oAuthServer = new ServerConfiguration("https://oauth.foo.url"); + + static void checkCredentials( + Supplier httpClientSupplier, Consumer service) { + doNotAcceptNullCredentials(httpClientSupplier, service); + doNotAcceptNullKey(httpClientSupplier, service); + doNotAcceptNullKeySecret(httpClientSupplier, service); + doNotAcceptNullProject(httpClientSupplier, service); + doNotAcceptNullContext(httpClientSupplier, service); + doNotAcceptNullNumberLookupUrl(httpClientSupplier, service); + initPassed(httpClientSupplier, service); + } + + static void doNotAcceptNullCredentials( + Supplier httpClientSupplier, Consumer service) { + NumberLookupContext context = + NumberLookupContext.builder().setNumberLookupUrl("foo url").build(); + + Exception exception = + assertThrows( + NullPointerException.class, + () -> + service.accept( + new NumberLookupService(null, context, oAuthServer, httpClientSupplier))); + assertTrue( + exception + .getMessage() + .contains("Number Lookup service requires unified credentials to be defined")); + } + + static void doNotAcceptNullKey( + Supplier httpClientSupplier, Consumer service) { + UnifiedCredentials credentials = + UnifiedCredentials.builder().setKeyId(null).setKeySecret("foo").setProjectId("foo").build(); + NumberLookupContext context = + NumberLookupContext.builder().setNumberLookupUrl("foo url").build(); + + Exception exception = + assertThrows( + IllegalArgumentException.class, + () -> + service.accept( + new NumberLookupService( + credentials, context, oAuthServer, httpClientSupplier))); + assertTrue(exception.getMessage().contains("keyId")); + } + + static void doNotAcceptNullKeySecret( + Supplier httpClientSupplier, Consumer service) { + UnifiedCredentials credentials = + UnifiedCredentials.builder().setKeyId("foo").setKeySecret(null).setProjectId("foo").build(); + NumberLookupContext context = + NumberLookupContext.builder().setNumberLookupUrl("foo url").build(); + + Exception exception = + assertThrows( + IllegalArgumentException.class, + () -> + service.accept( + new NumberLookupService( + credentials, context, oAuthServer, httpClientSupplier))); + assertTrue(exception.getMessage().contains("keySecret")); + } + + static void doNotAcceptNullProject( + Supplier httpClientSupplier, Consumer service) { + UnifiedCredentials credentials = + UnifiedCredentials.builder().setKeyId("foo").setKeySecret("foo").setProjectId(null).build(); + NumberLookupContext context = + NumberLookupContext.builder().setNumberLookupUrl("foo url").build(); + + Exception exception = + assertThrows( + IllegalArgumentException.class, + () -> + service.accept( + new NumberLookupService( + credentials, context, oAuthServer, httpClientSupplier))); + assertTrue(exception.getMessage().contains("projectId")); + } + + static void doNotAcceptNullContext( + Supplier httpClientSupplier, Consumer service) { + UnifiedCredentials credentials = + UnifiedCredentials.builder() + .setKeyId("foo") + .setKeySecret("foo") + .setProjectId("foo") + .build(); + + Exception exception = + assertThrows( + NullPointerException.class, + () -> + service.accept( + new NumberLookupService(credentials, null, oAuthServer, httpClientSupplier))); + assertTrue( + exception.getMessage().contains("Number Lookup service requires context to be defined")); + } + + static void doNotAcceptNullNumberLookupUrl( + Supplier httpClientSupplier, Consumer service) { + UnifiedCredentials credentials = + UnifiedCredentials.builder() + .setKeyId("foo") + .setKeySecret("foo") + .setProjectId("foo") + .build(); + NumberLookupContext context = NumberLookupContext.builder().build(); + + Exception exception = + assertThrows( + IllegalArgumentException.class, + () -> + service.accept( + new NumberLookupService( + credentials, context, oAuthServer, httpClientSupplier))); + assertTrue(exception.getMessage().contains("numberLookupUrl")); + } + + static void initPassed( + Supplier httpClientSupplier, Consumer service) { + UnifiedCredentials credentials = + UnifiedCredentials.builder() + .setKeyId("foo") + .setKeySecret("foo") + .setProjectId("foo") + .build(); + NumberLookupContext context = + NumberLookupContext.builder().setNumberLookupUrl("foo url").build(); + + assertDoesNotThrow( + () -> + service.accept( + new NumberLookupService(credentials, context, oAuthServer, httpClientSupplier)), + "Init passed"); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceTest.java new file mode 100644 index 000000000..c6d7db2d7 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceTest.java @@ -0,0 +1,15 @@ +package com.sinch.sdk.domains.numberlookup.api.v2.adapters; + +import com.sinch.sdk.core.http.HttpClient; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +class NumberLookupServiceTest { + + @Mock HttpClient httpClient; + + @Test + void checkCredentialsNumberLookup() { + CredentialsValidationHelper.checkCredentials(() -> httpClient, NumberLookupService::lookup); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupV2ServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupV2ServiceTest.java new file mode 100644 index 000000000..9aeaa5312 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupV2ServiceTest.java @@ -0,0 +1,173 @@ +package com.sinch.sdk.domains.numberlookup.api.v2.adapters; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.core.http.AuthManager; +import com.sinch.sdk.core.http.HttpClient; +import com.sinch.sdk.core.http.HttpContentType; +import com.sinch.sdk.core.http.HttpMapper; +import com.sinch.sdk.core.http.HttpMethod; +import com.sinch.sdk.core.http.HttpRequest; +import com.sinch.sdk.core.http.HttpRequestTest.HttpRequestMatcher; +import com.sinch.sdk.core.http.HttpResponse; +import com.sinch.sdk.core.http.URLPathUtils; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupV2Service; +import com.sinch.sdk.domains.numberlookup.models.v2.request.NumberLookupRequestDtoTest; +import com.sinch.sdk.domains.numberlookup.models.v2.response.NumberLookupResponse; +import com.sinch.sdk.domains.numberlookup.models.v2.response.NumberLookupResponseDtoTest; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +@TestWithResources +public class NumberLookupV2ServiceTest extends BaseTest { + + @Mock HttpClient httpClient; + @Mock ServerConfiguration serverConfiguration; + @Mock Map authManagers; + + static final String PROJECT_ID = "test_project_id"; + static final Collection AUTH_NAMES = Arrays.asList("Basic", "OAuth2"); + + NumberLookupV2Service service; + + @GivenTextResource("/domains/numberlookup/v2/request/NumberLookupRequestMinimalDto.json") + String jsonNumberLookupRequestMinimalDto; + + @GivenTextResource("/domains/numberlookup/v2/request/NumberLookupRequestWithFeaturesDto.json") + String jsonNumberLookupRequestWithFeaturesDto; + + @GivenTextResource("/domains/numberlookup/v2/request/NumberLookupRequestWithRndOptionsDto.json") + String jsonNumberLookupRequestWithRndOptionsDto; + + @GivenTextResource("/domains/numberlookup/v2/response/NumberLookupResponseMinimalDto.json") + String jsonNumberLookupResponseMinimalDto; + + @GivenTextResource("/domains/numberlookup/v2/response/NumberLookupResponseFullDto.json") + String jsonNumberLookupResponseFullDto; + + @BeforeEach + public void initMocks() { + service = + new NumberLookupV2ServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), PROJECT_ID); + } + + @Test + void lookupWithRequiredParameters() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + URLPathUtils.encodePathSegment(PROJECT_ID) + "/lookups", + HttpMethod.POST, + Collections.emptyList(), + jsonNumberLookupRequestMinimalDto, + Collections.emptyMap(), + Collections.singletonList(HttpContentType.APPLICATION_JSON), + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse( + 200, null, Collections.emptyMap(), jsonNumberLookupResponseMinimalDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + NumberLookupResponse response = + service.lookup(NumberLookupRequestDtoTest.numberLookupRequestMinimalDto); + + TestHelpers.recursiveEquals( + response, NumberLookupResponseDtoTest.numberLookupResponseMinimalDto); + } + + @Test + void lookupWithFeatures() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + URLPathUtils.encodePathSegment(PROJECT_ID) + "/lookups", + HttpMethod.POST, + Collections.emptyList(), + jsonNumberLookupRequestWithFeaturesDto, + Collections.emptyMap(), + Collections.singletonList(HttpContentType.APPLICATION_JSON), + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse( + 200, null, Collections.emptyMap(), jsonNumberLookupResponseFullDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + NumberLookupResponse response = + service.lookup(NumberLookupRequestDtoTest.numberLookupRequestWithFeaturesDto); + + TestHelpers.recursiveEquals(response, NumberLookupResponseDtoTest.numberLookupResponseFullDto); + } + + @Test + void lookupWithRndOptions() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + URLPathUtils.encodePathSegment(PROJECT_ID) + "/lookups", + HttpMethod.POST, + Collections.emptyList(), + jsonNumberLookupRequestWithRndOptionsDto, + Collections.emptyMap(), + Collections.singletonList(HttpContentType.APPLICATION_JSON), + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse( + 200, null, Collections.emptyMap(), jsonNumberLookupResponseFullDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + NumberLookupResponse response = + service.lookup(NumberLookupRequestDtoTest.numberLookupRequestWithRndOptionsDto); + + TestHelpers.recursiveEquals(response, NumberLookupResponseDtoTest.numberLookupResponseFullDto); + } + + @Test + void lookupMissingProjectIdThrows() { + + NumberLookupV2Service serviceWithoutProjectId = + new NumberLookupV2ServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> + serviceWithoutProjectId.lookup( + NumberLookupRequestDtoTest.numberLookupRequestMinimalDto)); + + Assertions.assertEquals(400, thrown.getCode()); + } +} diff --git a/client/src/test/java/com/sinch/sdk/e2e/Config.java b/client/src/test/java/com/sinch/sdk/e2e/Config.java index 830149340..badb5846a 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/Config.java +++ b/client/src/test/java/com/sinch/sdk/e2e/Config.java @@ -4,6 +4,7 @@ import com.sinch.sdk.models.Configuration; import com.sinch.sdk.models.ConversationContext; import com.sinch.sdk.models.ConversationRegion; +import com.sinch.sdk.models.NumberLookupContext; import com.sinch.sdk.models.NumbersContext; import com.sinch.sdk.models.SMSRegion; import com.sinch.sdk.models.SmsContext; @@ -34,6 +35,8 @@ public class Config { public static final String VERIFICATION_HOST_NAME = "http://localhost:3018"; + public static final String NUMBER_LOOKUP_HOST_NAME = "http://localhost:3022"; + private final SinchClient client; private final SinchClient clientServicePlanId; @@ -63,6 +66,8 @@ private Config() { SmsContext.builder().setSmsUrl(SMS_HOST_NAME).setSmsRegion(SMSRegion.EU).build()) .setVerificationContext( VerificationContext.builder().setVerificationUrl(VERIFICATION_HOST_NAME).build()) + .setNumberLookupContext( + NumberLookupContext.builder().setNumberLookupUrl(NUMBER_LOOKUP_HOST_NAME).build()) .build(); client = new SinchClient(configuration); diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/LookupsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/LookupsSteps.java new file mode 100644 index 000000000..0423e77da --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/LookupsSteps.java @@ -0,0 +1,124 @@ +package com.sinch.sdk.e2e.domains.numberlookup.v2; + +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupV2Service; +import com.sinch.sdk.domains.numberlookup.models.v2.errors.LookupError; +import com.sinch.sdk.domains.numberlookup.models.v2.request.LookupFeatureType; +import com.sinch.sdk.domains.numberlookup.models.v2.request.NumberLookupRequest; +import com.sinch.sdk.domains.numberlookup.models.v2.request.RndFeatureOptions; +import com.sinch.sdk.domains.numberlookup.models.v2.response.Line; +import com.sinch.sdk.domains.numberlookup.models.v2.response.LineType; +import com.sinch.sdk.domains.numberlookup.models.v2.response.NumberLookupResponse; +import com.sinch.sdk.domains.numberlookup.models.v2.response.Rnd; +import com.sinch.sdk.domains.numberlookup.models.v2.response.SimSwap; +import com.sinch.sdk.domains.numberlookup.models.v2.response.VoIPDetection; +import com.sinch.sdk.e2e.Config; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import java.time.Instant; +import java.time.LocalDate; +import java.util.Arrays; +import org.junit.jupiter.api.Assertions; + +public class LookupsSteps { + + NumberLookupV2Service service; + NumberLookupResponse lookupNoFeaturesResponse; + NumberLookupResponse lookupAllFeaturesResponse; + + @Given("the Number Lookup service is available") + public void serviceAvailable() { + service = Config.getSinchClient().lookup().lookup(); + Assertions.assertNotNull(service, "Number Lookup service is not available"); + } + + @When("I send a request to lookup for a phone number with no additional features") + public void lookupNoFeatures() { + NumberLookupRequest request = NumberLookupRequest.builder().setNumber("+12016666666").build(); + lookupNoFeaturesResponse = service.lookup(request); + } + + @Then("the response contains the details of the phone number lookup with line details only") + public void lookupNoFeaturesResult() { + + NumberLookupResponse expected = + NumberLookupResponse.builder() + .setNumber("+12016666666") + .setCountryCode("US") + .setTraceId("84c1fd4063c38d9f3900d06e56542d48") + .setLine( + Line.builder() + .setCarrier("T-Mobile USA") + .setType(LineType.MOBILE) + .setMobileCountryCode("310") + .setMobileNetworkCode("260") + .build()) + .build(); + + TestHelpers.recursiveEquals(lookupNoFeaturesResponse, expected); + } + + @When("I send a request to lookup for a phone number with all the features") + public void lookupAllFeatures() { + NumberLookupRequest request = + NumberLookupRequest.builder() + .setNumber("+12015555555") + .setFeatures( + Arrays.asList( + LookupFeatureType.LINE_TYPE, + LookupFeatureType.RND, + LookupFeatureType.SIM_SWAP, + LookupFeatureType.VO_IP_DETECTION)) + .setRndFeatureOptions( + RndFeatureOptions.builder().setContactDate(LocalDate.parse("2025-09-09")).build()) + .build(); + lookupAllFeaturesResponse = service.lookup(request); + } + + @Then("the response contains the details of the phone number lookup with all the features") + public void lookupAllFeaturesResult() { + + LookupError simSwapError = + LookupError.builder() + .setStatus(100) + .setTitle("Feature Disabled") + .setDetail("SimSwap feature is currently disabled.") + .build(); + + LookupError voIPDetectionError = + LookupError.builder() + .setStatus(100) + .setTitle("Feature Disabled") + .setDetail("VoIPDetection feature is currently disabled.") + .build(); + + LookupError rndError = + LookupError.builder() + .setStatus(100) + .setTitle("Feature Disabled") + .setDetail("RND feature is currently disabled.") + .build(); + + NumberLookupResponse expected = + NumberLookupResponse.builder() + .setNumber("+12015555555") + .setCountryCode("US") + .setTraceId("5c817a6b7351d80a6b1d8007e5c145b8") + .setLine( + Line.builder() + .setCarrier("AT&T") + .setType(LineType.MOBILE) + .setMobileCountryCode("310") + .setMobileNetworkCode("070") + .setPorted(true) + .setPortingDate(Instant.parse("2010-08-07T23:45:49Z")) + .build()) + .setSimSwap(SimSwap.builder().setError(simSwapError).build()) + .setVoIPDetection(VoIPDetection.builder().setError(voIPDetectionError).build()) + .setRnd(Rnd.builder().setError(rndError).build()) + .build(); + + TestHelpers.recursiveEquals(lookupAllFeaturesResponse, expected); + } +} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/NumberLookupIT.java b/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/NumberLookupIT.java new file mode 100644 index 000000000..668d5466a --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/NumberLookupIT.java @@ -0,0 +1,18 @@ +package com.sinch.sdk.e2e.domains.numberlookup.v2; + +import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; + +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.api.SuiteDisplayName; + +@Suite +@SuiteDisplayName("Number Lookup V2") +@IncludeEngines("cucumber") +@SelectClasspathResource("features/number-lookup") +@ConfigurationParameter( + key = GLUE_PROPERTY_NAME, + value = "com.sinch.sdk.e2e.domains.numberlookup.v2") +public class NumberLookupIT {} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/ChannelSpecificContactMessage.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/ChannelSpecificContactMessage.java index 74bebb7b3..2885f34a2 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/ChannelSpecificContactMessage.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/ChannelSpecificContactMessage.java @@ -54,7 +54,7 @@ public static String valueOf(MessageTypeEnum e) { MessageTypeEnum getMessageType(); /** - * Get message + * The message content. * * @return message */ diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/internal/ChannelSpecificContactMessageMessageInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/internal/ChannelSpecificContactMessageMessageInternal.java index ca74a564f..a69f9a605 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/internal/ChannelSpecificContactMessageMessageInternal.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/internal/ChannelSpecificContactMessageMessageInternal.java @@ -54,7 +54,7 @@ public static String valueOf(MessageTypeEnum e) { MessageTypeEnum getMessageType(); /** - * Get message + * The message content. * * @return message */ diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupV2Service.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupV2Service.java new file mode 100644 index 000000000..9cf5d65e1 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupV2Service.java @@ -0,0 +1,28 @@ +/* + * Number Lookup Api v2 + * + * OpenAPI document version: 0.1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.numberlookup.api.v2; + +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.domains.numberlookup.models.v2.request.NumberLookupRequest; +import com.sinch.sdk.domains.numberlookup.models.v2.response.NumberLookupResponse; + +/** NumberLookupV2 Service */ +public interface NumberLookupV2Service { + + /** + * Performs a number lookup. + * + * @param numberLookupRequest (optional) + * @return NumberLookupResponse + * @throws ApiException if fails to make API call + */ + NumberLookupResponse lookup(NumberLookupRequest numberLookupRequest) throws ApiException; +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupV2ServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupV2ServiceImpl.java new file mode 100644 index 000000000..6f74a84dd --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupV2ServiceImpl.java @@ -0,0 +1,117 @@ +/* + * Number Lookup Api v2 + * + * OpenAPI document version: 0.1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.numberlookup.api.v2.adapters; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.core.exceptions.ApiExceptionBuilder; +import com.sinch.sdk.core.http.AuthManager; +import com.sinch.sdk.core.http.HttpClient; +import com.sinch.sdk.core.http.HttpMapper; +import com.sinch.sdk.core.http.HttpMethod; +import com.sinch.sdk.core.http.HttpRequest; +import com.sinch.sdk.core.http.HttpResponse; +import com.sinch.sdk.core.http.HttpStatus; +import com.sinch.sdk.core.http.URLParameter; +import com.sinch.sdk.core.http.URLPathUtils; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.domains.numberlookup.models.v2.request.NumberLookupRequest; +import com.sinch.sdk.domains.numberlookup.models.v2.response.NumberLookupResponse; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +public class NumberLookupV2ServiceImpl + implements com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupV2Service { + + private static final Logger LOGGER = Logger.getLogger(NumberLookupV2ServiceImpl.class.getName()); + private final HttpClient httpClient; + private final ServerConfiguration serverConfiguration; + private final Map authManagersByOasSecuritySchemes; + private final HttpMapper mapper; + + private final String projectId; + + public NumberLookupV2ServiceImpl( + HttpClient httpClient, + ServerConfiguration serverConfiguration, + Map authManagersByOasSecuritySchemes, + HttpMapper mapper, + String projectId) { + this.httpClient = httpClient; + this.serverConfiguration = serverConfiguration; + this.authManagersByOasSecuritySchemes = authManagersByOasSecuritySchemes; + this.mapper = mapper; + this.projectId = projectId; + } + + @Override + public NumberLookupResponse lookup(NumberLookupRequest numberLookupRequest) throws ApiException { + + LOGGER.finest("[lookup]" + " " + "numberLookupRequest: " + numberLookupRequest); + + HttpRequest httpRequest = lookupRequestBuilder(numberLookupRequest); + HttpResponse response = + httpClient.invokeAPI( + this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); + + if (HttpStatus.isSuccessfulStatus(response.getCode())) { + return mapper.deserialize(response, new TypeReference() {}); + } + // fallback to default errors handling: + // all error cases definition are not required from specs: will try some "hardcoded" content + // parsing + throw ApiExceptionBuilder.build( + response.getMessage(), + response.getCode(), + mapper.deserialize(response, new TypeReference>() {})); + } + + private HttpRequest lookupRequestBuilder(NumberLookupRequest numberLookupRequest) + throws ApiException { + // verify the required parameter 'this.projectId' is set + if (this.projectId == null) { + throw new ApiException( + 400, "Missing the required parameter 'this.projectId' when calling lookup"); + } + + String localVarPath = + "/v2/projects/{projectId}/lookups" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = Arrays.asList("application/json"); + + final Collection localVarContentTypes = Arrays.asList("application/json"); + + final Collection localVarAuthNames = Arrays.asList("Basic", "OAuth2"); + final String serializedBody = mapper.serialize(localVarContentTypes, numberLookupRequest); + + return new HttpRequest( + localVarPath, + HttpMethod.POST, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupError.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupError.java new file mode 100644 index 000000000..d6c42a708 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupError.java @@ -0,0 +1,102 @@ +/* + * Number Lookup Api v2 + * + * OpenAPI document version: 0.1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.numberlookup.models.v2.errors; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** LookupError */ +@JsonDeserialize(builder = LookupErrorImpl.Builder.class) +public interface LookupError { + + /** + * The error status code. + * + * @return status + */ + Integer getStatus(); + + /** + * A short, human-readable summary of the error type. + * + * @return title + */ + String getTitle(); + + /** + * A human-readable explanation specific to this occurrence of the error. + * + * @return detail + */ + String getDetail(); + + /** + * A URI reference that identifies the error type. + * + * @return type + */ + String getType(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new LookupErrorImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param status see getter + * @return Current builder + * @see #getStatus + */ + Builder setStatus(Integer status); + + /** + * see getter + * + * @param title see getter + * @return Current builder + * @see #getTitle + */ + Builder setTitle(String title); + + /** + * see getter + * + * @param detail see getter + * @return Current builder + * @see #getDetail + */ + Builder setDetail(String detail); + + /** + * see getter + * + * @param type see getter + * @return Current builder + * @see #getType + */ + Builder setType(String type); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + LookupError build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupErrorImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupErrorImpl.java new file mode 100644 index 000000000..8501d0679 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupErrorImpl.java @@ -0,0 +1,174 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.errors; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import java.util.Objects; + +@JsonPropertyOrder({ + LookupErrorImpl.JSON_PROPERTY_STATUS, + LookupErrorImpl.JSON_PROPERTY_TITLE, + LookupErrorImpl.JSON_PROPERTY_DETAIL, + LookupErrorImpl.JSON_PROPERTY_TYPE +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class LookupErrorImpl implements LookupError { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_STATUS = "status"; + + private OptionalValue status; + + public static final String JSON_PROPERTY_TITLE = "title"; + + private OptionalValue title; + + public static final String JSON_PROPERTY_DETAIL = "detail"; + + private OptionalValue detail; + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public LookupErrorImpl() {} + + protected LookupErrorImpl( + OptionalValue status, + OptionalValue title, + OptionalValue detail, + OptionalValue type) { + this.status = status; + this.title = title; + this.detail = detail; + this.type = type; + } + + @JsonIgnore + public Integer getStatus() { + return status.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue status() { + return status; + } + + @JsonIgnore + public String getTitle() { + return title.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue title() { + return title; + } + + @JsonIgnore + public String getDetail() { + return detail.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DETAIL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue detail() { + return detail; + } + + @JsonIgnore + public String getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue type() { + return type; + } + + /** Return true if this ErrorResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LookupErrorImpl errorResponse = (LookupErrorImpl) o; + return Objects.equals(this.status, errorResponse.status) + && Objects.equals(this.title, errorResponse.title) + && Objects.equals(this.detail, errorResponse.detail) + && Objects.equals(this.type, errorResponse.type); + } + + @Override + public int hashCode() { + return Objects.hash(status, title, detail, type); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class LookupErrorImpl {\n"); + sb.append(" status: ").append(toIndentedString(status)).append("\n"); + sb.append(" title: ").append(toIndentedString(title)).append("\n"); + sb.append(" detail: ").append(toIndentedString(detail)).append("\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements LookupError.Builder { + OptionalValue status = OptionalValue.empty(); + OptionalValue title = OptionalValue.empty(); + OptionalValue detail = OptionalValue.empty(); + OptionalValue type = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_STATUS) + public Builder setStatus(Integer status) { + this.status = OptionalValue.of(status); + return this; + } + + @JsonProperty(JSON_PROPERTY_TITLE) + public Builder setTitle(String title) { + this.title = OptionalValue.of(title); + return this; + } + + @JsonProperty(JSON_PROPERTY_DETAIL) + public Builder setDetail(String detail) { + this.detail = OptionalValue.of(detail); + return this; + } + + @JsonProperty(JSON_PROPERTY_TYPE) + public Builder setType(String type) { + this.type = OptionalValue.of(type); + return this; + } + + public LookupError build() { + return new LookupErrorImpl(status, title, detail, type); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetails.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetails.java new file mode 100644 index 000000000..db503a96a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetails.java @@ -0,0 +1,102 @@ +/* + * Number Lookup Api v2 + * + * OpenAPI document version: 0.1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.numberlookup.models.v2.errors; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** ProblemDetails */ +@JsonDeserialize(builder = ProblemDetailsImpl.Builder.class) +public interface ProblemDetails { + + /** + * A URI reference that identifies the problem type. + * + * @return type + */ + String getType(); + + /** + * A short, human-readable summary of the problem type. + * + * @return title + */ + String getTitle(); + + /** + * The HTTP status code. + * + * @return status + */ + Integer getStatus(); + + /** + * The identifier for a trace. + * + * @return traceId + */ + String getTraceId(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new ProblemDetailsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param type see getter + * @return Current builder + * @see #getType + */ + Builder setType(String type); + + /** + * see getter + * + * @param title see getter + * @return Current builder + * @see #getTitle + */ + Builder setTitle(String title); + + /** + * see getter + * + * @param status see getter + * @return Current builder + * @see #getStatus + */ + Builder setStatus(Integer status); + + /** + * see getter + * + * @param traceId see getter + * @return Current builder + * @see #getTraceId + */ + Builder setTraceId(String traceId); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + ProblemDetails build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetailsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetailsImpl.java new file mode 100644 index 000000000..0ba6140b3 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetailsImpl.java @@ -0,0 +1,174 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.errors; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import java.util.Objects; + +@JsonPropertyOrder({ + ProblemDetailsImpl.JSON_PROPERTY_TYPE, + ProblemDetailsImpl.JSON_PROPERTY_TITLE, + ProblemDetailsImpl.JSON_PROPERTY_STATUS, + ProblemDetailsImpl.JSON_PROPERTY_TRACE_ID +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class ProblemDetailsImpl implements ProblemDetails { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public static final String JSON_PROPERTY_TITLE = "title"; + + private OptionalValue title; + + public static final String JSON_PROPERTY_STATUS = "status"; + + private OptionalValue status; + + public static final String JSON_PROPERTY_TRACE_ID = "traceId"; + + private OptionalValue traceId; + + public ProblemDetailsImpl() {} + + protected ProblemDetailsImpl( + OptionalValue type, + OptionalValue title, + OptionalValue status, + OptionalValue traceId) { + this.type = type; + this.title = title; + this.status = status; + this.traceId = traceId; + } + + @JsonIgnore + public String getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue type() { + return type; + } + + @JsonIgnore + public String getTitle() { + return title.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue title() { + return title; + } + + @JsonIgnore + public Integer getStatus() { + return status.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue status() { + return status; + } + + @JsonIgnore + public String getTraceId() { + return traceId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TRACE_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue traceId() { + return traceId; + } + + /** Return true if this ProblemDetails object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProblemDetailsImpl problemDetails = (ProblemDetailsImpl) o; + return Objects.equals(this.type, problemDetails.type) + && Objects.equals(this.title, problemDetails.title) + && Objects.equals(this.status, problemDetails.status) + && Objects.equals(this.traceId, problemDetails.traceId); + } + + @Override + public int hashCode() { + return Objects.hash(type, title, status, traceId); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ProblemDetailsImpl {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" title: ").append(toIndentedString(title)).append("\n"); + sb.append(" status: ").append(toIndentedString(status)).append("\n"); + sb.append(" traceId: ").append(toIndentedString(traceId)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements ProblemDetails.Builder { + OptionalValue type = OptionalValue.empty(); + OptionalValue title = OptionalValue.empty(); + OptionalValue status = OptionalValue.empty(); + OptionalValue traceId = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_TYPE) + public Builder setType(String type) { + this.type = OptionalValue.of(type); + return this; + } + + @JsonProperty(JSON_PROPERTY_TITLE) + public Builder setTitle(String title) { + this.title = OptionalValue.of(title); + return this; + } + + @JsonProperty(JSON_PROPERTY_STATUS) + public Builder setStatus(Integer status) { + this.status = OptionalValue.of(status); + return this; + } + + @JsonProperty(JSON_PROPERTY_TRACE_ID) + public Builder setTraceId(String traceId) { + this.traceId = OptionalValue.of(traceId); + return this; + } + + public ProblemDetails build() { + return new ProblemDetailsImpl(type, title, status, traceId); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/LookupFeatureType.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/LookupFeatureType.java new file mode 100644 index 000000000..c93224cc9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/LookupFeatureType.java @@ -0,0 +1,44 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.request; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** */ +public class LookupFeatureType extends EnumDynamic { + + /** Use to return line type information for the phone number. */ + public static final LookupFeatureType LINE_TYPE = new LookupFeatureType("LineType"); + + /** Use to determine whether the phone number is SIM swapped. */ + public static final LookupFeatureType SIM_SWAP = new LookupFeatureType("SimSwap"); + + /** Use to determine if the carrier is associated with a VoIP provider (alpha). */ + public static final LookupFeatureType VO_IP_DETECTION = new LookupFeatureType("VoIPDetection"); + + /** Use to determine whether the phone number has been disconnected (alpha). */ + public static final LookupFeatureType RND = new LookupFeatureType("RND"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + LookupFeatureType.class, + LookupFeatureType::new, + Arrays.asList(LINE_TYPE, SIM_SWAP, VO_IP_DETECTION, RND)); + + private LookupFeatureType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static LookupFeatureType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(LookupFeatureType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequest.java new file mode 100644 index 000000000..94d358a3b --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequest.java @@ -0,0 +1,89 @@ +/* + * Number Lookup Api v2 + * + * OpenAPI document version: 0.1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.numberlookup.models.v2.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.List; + +/** NumberLookupRequest */ +@JsonDeserialize(builder = NumberLookupRequestImpl.Builder.class) +public interface NumberLookupRequest { + + /** + * MSISDN in E.164 format to query. + * + *

Field is required + * + * @return number + */ + String getNumber(); + + /** + * Contains requested features. Fallback to LineType if not provided. + * + * @return features + */ + List getFeatures(); + + /** + * Required when RND feature is requested. + * + * @return rndFeatureOptions + */ + RndFeatureOptions getRndFeatureOptions(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new NumberLookupRequestImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param number see getter + * @return Current builder + * @see #getNumber + */ + Builder setNumber(String number); + + /** + * see getter + * + * @param features see getter + * @return Current builder + * @see #getFeatures + */ + Builder setFeatures(List features); + + /** + * see getter + * + * @param rndFeatureOptions see getter + * @return Current builder + * @see #getRndFeatureOptions + */ + Builder setRndFeatureOptions(RndFeatureOptions rndFeatureOptions); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + NumberLookupRequest build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequestImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequestImpl.java new file mode 100644 index 000000000..53a6f397b --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequestImpl.java @@ -0,0 +1,148 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.request; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + NumberLookupRequestImpl.JSON_PROPERTY_NUMBER, + NumberLookupRequestImpl.JSON_PROPERTY_FEATURES, + NumberLookupRequestImpl.JSON_PROPERTY_RND_FEATURE_OPTIONS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class NumberLookupRequestImpl implements NumberLookupRequest { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_NUMBER = "number"; + + private OptionalValue number; + + public static final String JSON_PROPERTY_FEATURES = "features"; + + private OptionalValue> features; + + public static final String JSON_PROPERTY_RND_FEATURE_OPTIONS = "rndFeatureOptions"; + + private OptionalValue rndFeatureOptions; + + public NumberLookupRequestImpl() {} + + protected NumberLookupRequestImpl( + OptionalValue number, + OptionalValue> features, + OptionalValue rndFeatureOptions) { + this.number = number; + this.features = features; + this.rndFeatureOptions = rndFeatureOptions; + } + + @JsonIgnore + public String getNumber() { + return number.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_NUMBER) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue number() { + return number; + } + + @JsonIgnore + public List getFeatures() { + return features.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_FEATURES) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> features() { + return features; + } + + @JsonIgnore + public RndFeatureOptions getRndFeatureOptions() { + return rndFeatureOptions.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_RND_FEATURE_OPTIONS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue rndFeatureOptions() { + return rndFeatureOptions; + } + + /** Return true if this NumberLookupRequest object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NumberLookupRequestImpl numberLookupRequest = (NumberLookupRequestImpl) o; + return Objects.equals(this.number, numberLookupRequest.number) + && Objects.equals(this.features, numberLookupRequest.features) + && Objects.equals(this.rndFeatureOptions, numberLookupRequest.rndFeatureOptions); + } + + @Override + public int hashCode() { + return Objects.hash(number, features, rndFeatureOptions); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class NumberLookupRequestImpl {\n"); + sb.append(" number: ").append(toIndentedString(number)).append("\n"); + sb.append(" features: ").append(toIndentedString(features)).append("\n"); + sb.append(" rndFeatureOptions: ").append(toIndentedString(rndFeatureOptions)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements NumberLookupRequest.Builder { + OptionalValue number = OptionalValue.empty(); + OptionalValue> features = OptionalValue.empty(); + OptionalValue rndFeatureOptions = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_NUMBER, required = true) + public Builder setNumber(String number) { + this.number = OptionalValue.of(number); + return this; + } + + @JsonProperty(JSON_PROPERTY_FEATURES) + public Builder setFeatures(List features) { + this.features = OptionalValue.of(features); + return this; + } + + @JsonProperty(JSON_PROPERTY_RND_FEATURE_OPTIONS) + public Builder setRndFeatureOptions(RndFeatureOptions rndFeatureOptions) { + this.rndFeatureOptions = OptionalValue.of(rndFeatureOptions); + return this; + } + + public NumberLookupRequest build() { + return new NumberLookupRequestImpl(number, features, rndFeatureOptions); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptions.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptions.java new file mode 100644 index 000000000..0c120d1ec --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptions.java @@ -0,0 +1,55 @@ +/* + * Number Lookup Api v2 + * + * OpenAPI document version: 0.1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.numberlookup.models.v2.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.time.LocalDate; + +/** RndFeatureOptions */ +@JsonDeserialize(builder = RndFeatureOptionsImpl.Builder.class) +public interface RndFeatureOptions { + + /** + * Last contact date in YYYY-MM-DD format. + * + * @return contactDate + */ + LocalDate getContactDate(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new RndFeatureOptionsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param contactDate see getter + * @return Current builder + * @see #getContactDate + */ + Builder setContactDate(LocalDate contactDate); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + RndFeatureOptions build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptionsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptionsImpl.java new file mode 100644 index 000000000..e4c1856b8 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptionsImpl.java @@ -0,0 +1,91 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.request; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import java.time.LocalDate; +import java.util.Objects; + +@JsonPropertyOrder({RndFeatureOptionsImpl.JSON_PROPERTY_CONTACT_DATE}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class RndFeatureOptionsImpl implements RndFeatureOptions { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_CONTACT_DATE = "contactDate"; + + private OptionalValue contactDate; + + public RndFeatureOptionsImpl() {} + + protected RndFeatureOptionsImpl(OptionalValue contactDate) { + this.contactDate = contactDate; + } + + @JsonIgnore + public LocalDate getContactDate() { + return contactDate.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CONTACT_DATE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue contactDate() { + return contactDate; + } + + /** Return true if this RndFeatureOptions object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RndFeatureOptionsImpl rndFeatureOptions = (RndFeatureOptionsImpl) o; + return Objects.equals(this.contactDate, rndFeatureOptions.contactDate); + } + + @Override + public int hashCode() { + return Objects.hash(contactDate); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class RndFeatureOptionsImpl {\n"); + sb.append(" contactDate: ").append(toIndentedString(contactDate)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements RndFeatureOptions.Builder { + OptionalValue contactDate = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_CONTACT_DATE) + public Builder setContactDate(LocalDate contactDate) { + this.contactDate = OptionalValue.of(contactDate); + return this; + } + + public RndFeatureOptions build() { + return new RndFeatureOptionsImpl(contactDate); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/Line.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/Line.java new file mode 100644 index 000000000..183787d36 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/Line.java @@ -0,0 +1,156 @@ +/* + * Number Lookup Api v2 + * + * OpenAPI document version: 0.1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.numberlookup.models.v2.errors.LookupError; +import java.time.Instant; + +/** Line */ +@JsonDeserialize(builder = LineImpl.Builder.class) +public interface Line { + + /** + * Name of the carrier. + * + * @return carrier + */ + String getCarrier(); + + /** + * Number type e.g. wireless, mobile, fixed. + * + * @return type + */ + LineType getType(); + + /** + * A numeric code (MCC) that uniquely identifies a country within the international + * telecommunications network. + * + * @return mobileCountryCode + */ + String getMobileCountryCode(); + + /** + * A distinct identifier assigned to a mobile network operator within a specific country, used in + * combination with MCC. + * + * @return mobileNetworkCode + */ + String getMobileNetworkCode(); + + /** + * Indicates if a phone number has been transferred from its original network to a different + * provider. + * + * @return ported + */ + Boolean getPorted(); + + /** + * Specifies the date, in ISO 8601 format, when a phone number was transferred from its original + * network to a new provider. + * + * @return portingDate + */ + Instant getPortingDate(); + + /** + * Get error + * + * @return error + */ + LookupError getError(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new LineImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param carrier see getter + * @return Current builder + * @see #getCarrier + */ + Builder setCarrier(String carrier); + + /** + * see getter + * + * @param type see getter + * @return Current builder + * @see #getType + */ + Builder setType(LineType type); + + /** + * see getter + * + * @param mobileCountryCode see getter + * @return Current builder + * @see #getMobileCountryCode + */ + Builder setMobileCountryCode(String mobileCountryCode); + + /** + * see getter + * + * @param mobileNetworkCode see getter + * @return Current builder + * @see #getMobileNetworkCode + */ + Builder setMobileNetworkCode(String mobileNetworkCode); + + /** + * see getter + * + * @param ported see getter + * @return Current builder + * @see #getPorted + */ + Builder setPorted(Boolean ported); + + /** + * see getter + * + * @param portingDate see getter + * @return Current builder + * @see #getPortingDate + */ + Builder setPortingDate(Instant portingDate); + + /** + * see getter + * + * @param error see getter + * @return Current builder + * @see #getError + */ + Builder setError(LookupError error); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + Line build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/LineImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/LineImpl.java new file mode 100644 index 000000000..0cf94544b --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/LineImpl.java @@ -0,0 +1,259 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import com.sinch.sdk.domains.numberlookup.models.v2.errors.LookupError; +import java.time.Instant; +import java.util.Objects; + +@JsonPropertyOrder({ + LineImpl.JSON_PROPERTY_CARRIER, + LineImpl.JSON_PROPERTY_TYPE, + LineImpl.JSON_PROPERTY_MOBILE_COUNTRY_CODE, + LineImpl.JSON_PROPERTY_MOBILE_NETWORK_CODE, + LineImpl.JSON_PROPERTY_PORTED, + LineImpl.JSON_PROPERTY_PORTING_DATE, + LineImpl.JSON_PROPERTY_ERROR +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class LineImpl implements Line { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_CARRIER = "carrier"; + + private OptionalValue carrier; + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public static final String JSON_PROPERTY_MOBILE_COUNTRY_CODE = "mobileCountryCode"; + + private OptionalValue mobileCountryCode; + + public static final String JSON_PROPERTY_MOBILE_NETWORK_CODE = "mobileNetworkCode"; + + private OptionalValue mobileNetworkCode; + + public static final String JSON_PROPERTY_PORTED = "ported"; + + private OptionalValue ported; + + public static final String JSON_PROPERTY_PORTING_DATE = "portingDate"; + + private OptionalValue portingDate; + + public static final String JSON_PROPERTY_ERROR = "error"; + + private OptionalValue error; + + public LineImpl() {} + + protected LineImpl( + OptionalValue carrier, + OptionalValue type, + OptionalValue mobileCountryCode, + OptionalValue mobileNetworkCode, + OptionalValue ported, + OptionalValue portingDate, + OptionalValue error) { + this.carrier = carrier; + this.type = type; + this.mobileCountryCode = mobileCountryCode; + this.mobileNetworkCode = mobileNetworkCode; + this.ported = ported; + this.portingDate = portingDate; + this.error = error; + } + + @JsonIgnore + public String getCarrier() { + return carrier.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CARRIER) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue carrier() { + return carrier; + } + + @JsonIgnore + public LineType getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue type() { + return type; + } + + @JsonIgnore + public String getMobileCountryCode() { + return mobileCountryCode.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MOBILE_COUNTRY_CODE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue mobileCountryCode() { + return mobileCountryCode; + } + + @JsonIgnore + public String getMobileNetworkCode() { + return mobileNetworkCode.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MOBILE_NETWORK_CODE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue mobileNetworkCode() { + return mobileNetworkCode; + } + + @JsonIgnore + public Boolean getPorted() { + return ported.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_PORTED) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue ported() { + return ported; + } + + @JsonIgnore + public Instant getPortingDate() { + return portingDate.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_PORTING_DATE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue portingDate() { + return portingDate; + } + + @JsonIgnore + public LookupError getError() { + return error.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ERROR) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue error() { + return error; + } + + /** Return true if this LineResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LineImpl lineResponse = (LineImpl) o; + return Objects.equals(this.carrier, lineResponse.carrier) + && Objects.equals(this.type, lineResponse.type) + && Objects.equals(this.mobileCountryCode, lineResponse.mobileCountryCode) + && Objects.equals(this.mobileNetworkCode, lineResponse.mobileNetworkCode) + && Objects.equals(this.ported, lineResponse.ported) + && Objects.equals(this.portingDate, lineResponse.portingDate) + && Objects.equals(this.error, lineResponse.error); + } + + @Override + public int hashCode() { + return Objects.hash( + carrier, type, mobileCountryCode, mobileNetworkCode, ported, portingDate, error); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class LineImpl {\n"); + sb.append(" carrier: ").append(toIndentedString(carrier)).append("\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" mobileCountryCode: ").append(toIndentedString(mobileCountryCode)).append("\n"); + sb.append(" mobileNetworkCode: ").append(toIndentedString(mobileNetworkCode)).append("\n"); + sb.append(" ported: ").append(toIndentedString(ported)).append("\n"); + sb.append(" portingDate: ").append(toIndentedString(portingDate)).append("\n"); + sb.append(" error: ").append(toIndentedString(error)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements Line.Builder { + OptionalValue carrier = OptionalValue.empty(); + OptionalValue type = OptionalValue.empty(); + OptionalValue mobileCountryCode = OptionalValue.empty(); + OptionalValue mobileNetworkCode = OptionalValue.empty(); + OptionalValue ported = OptionalValue.empty(); + OptionalValue portingDate = OptionalValue.empty(); + OptionalValue error = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_CARRIER) + public Builder setCarrier(String carrier) { + this.carrier = OptionalValue.of(carrier); + return this; + } + + @JsonProperty(JSON_PROPERTY_TYPE) + public Builder setType(LineType type) { + this.type = OptionalValue.of(type); + return this; + } + + @JsonProperty(JSON_PROPERTY_MOBILE_COUNTRY_CODE) + public Builder setMobileCountryCode(String mobileCountryCode) { + this.mobileCountryCode = OptionalValue.of(mobileCountryCode); + return this; + } + + @JsonProperty(JSON_PROPERTY_MOBILE_NETWORK_CODE) + public Builder setMobileNetworkCode(String mobileNetworkCode) { + this.mobileNetworkCode = OptionalValue.of(mobileNetworkCode); + return this; + } + + @JsonProperty(JSON_PROPERTY_PORTED) + public Builder setPorted(Boolean ported) { + this.ported = OptionalValue.of(ported); + return this; + } + + @JsonProperty(JSON_PROPERTY_PORTING_DATE) + public Builder setPortingDate(Instant portingDate) { + this.portingDate = OptionalValue.of(portingDate); + return this; + } + + @JsonProperty(JSON_PROPERTY_ERROR) + public Builder setError(LookupError error) { + this.error = OptionalValue.of(error); + return this; + } + + public Line build() { + return new LineImpl( + carrier, type, mobileCountryCode, mobileNetworkCode, ported, portingDate, error); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/LineType.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/LineType.java new file mode 100644 index 000000000..7dc2cc9f4 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/LineType.java @@ -0,0 +1,50 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** Represents the type of a phone line. */ +public class LineType extends EnumDynamic { + + /** A landline phone number. */ + public static final LineType LANDLINE = new LineType("Landline"); + + /** A mobile network phone number. */ + public static final LineType MOBILE = new LineType("Mobile"); + + /** A Voice over IP phone number. */ + public static final LineType VO_IP = new LineType("VoIP"); + + /** A specialty type of number, such as a satellite phone or fax line. */ + public static final LineType SPECIAL = new LineType("Special"); + + /** A freephone type phone number. */ + public static final LineType FREEPHONE = new LineType("Freephone"); + + /** Another type. */ + public static final LineType OTHER = new LineType("Other"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + LineType.class, + LineType::new, + Arrays.asList(LANDLINE, MOBILE, VO_IP, SPECIAL, FREEPHONE, OTHER)); + + private LineType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static LineType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(LineType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponse.java new file mode 100644 index 000000000..cf692ca44 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponse.java @@ -0,0 +1,150 @@ +/* + * Number Lookup Api v2 + * + * OpenAPI document version: 0.1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** NumberLookupResponse */ +@JsonDeserialize(builder = NumberLookupResponseImpl.Builder.class) +public interface NumberLookupResponse { + + /** + * An object containing information about the line type of the number. + * + * @return line + */ + Line getLine(); + + /** + * An object containing information about the SimSwap verification performed on the number. + * + * @return simSwap + */ + SimSwap getSimSwap(); + + /** + * An object containing information about the VoIP detection performed on the number. + * + * @return voIPDetection + */ + VoIPDetection getVoIPDetection(); + + /** + * An object containing information whether number is disconnected. + * + * @return rnd + */ + Rnd getRnd(); + + /** + * ISO 3166-1 alpha-2 two-letter country identifier + * + * @return countryCode + */ + String getCountryCode(); + + /** + * The identifier for a trace. + * + * @return traceId + */ + String getTraceId(); + + /** + * Requested MSISDN in E.164 format. + * + * @return number + */ + String getNumber(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new NumberLookupResponseImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param line see getter + * @return Current builder + * @see #getLine + */ + Builder setLine(Line line); + + /** + * see getter + * + * @param simSwap see getter + * @return Current builder + * @see #getSimSwap + */ + Builder setSimSwap(SimSwap simSwap); + + /** + * see getter + * + * @param voIPDetection see getter + * @return Current builder + * @see #getVoIPDetection + */ + Builder setVoIPDetection(VoIPDetection voIPDetection); + + /** + * see getter + * + * @param rnd see getter + * @return Current builder + * @see #getRnd + */ + Builder setRnd(Rnd rnd); + + /** + * see getter + * + * @param countryCode see getter + * @return Current builder + * @see #getCountryCode + */ + Builder setCountryCode(String countryCode); + + /** + * see getter + * + * @param traceId see getter + * @return Current builder + * @see #getTraceId + */ + Builder setTraceId(String traceId); + + /** + * see getter + * + * @param number see getter + * @return Current builder + * @see #getNumber + */ + Builder setNumber(String number); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + NumberLookupResponse build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponseImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponseImpl.java new file mode 100644 index 000000000..a7168f17e --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponseImpl.java @@ -0,0 +1,256 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import java.util.Objects; + +@JsonPropertyOrder({ + NumberLookupResponseImpl.JSON_PROPERTY_LINE, + NumberLookupResponseImpl.JSON_PROPERTY_SIM_SWAP, + NumberLookupResponseImpl.JSON_PROPERTY_VO_I_P_DETECTION, + NumberLookupResponseImpl.JSON_PROPERTY_RND, + NumberLookupResponseImpl.JSON_PROPERTY_COUNTRY_CODE, + NumberLookupResponseImpl.JSON_PROPERTY_TRACE_ID, + NumberLookupResponseImpl.JSON_PROPERTY_NUMBER +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class NumberLookupResponseImpl implements NumberLookupResponse { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_LINE = "line"; + + private OptionalValue line; + + public static final String JSON_PROPERTY_SIM_SWAP = "simSwap"; + + private OptionalValue simSwap; + + public static final String JSON_PROPERTY_VO_I_P_DETECTION = "voIPDetection"; + + private OptionalValue voIPDetection; + + public static final String JSON_PROPERTY_RND = "rnd"; + + private OptionalValue rnd; + + public static final String JSON_PROPERTY_COUNTRY_CODE = "countryCode"; + + private OptionalValue countryCode; + + public static final String JSON_PROPERTY_TRACE_ID = "traceId"; + + private OptionalValue traceId; + + public static final String JSON_PROPERTY_NUMBER = "number"; + + private OptionalValue number; + + public NumberLookupResponseImpl() {} + + protected NumberLookupResponseImpl( + OptionalValue line, + OptionalValue simSwap, + OptionalValue voIPDetection, + OptionalValue rnd, + OptionalValue countryCode, + OptionalValue traceId, + OptionalValue number) { + this.line = line; + this.simSwap = simSwap; + this.voIPDetection = voIPDetection; + this.rnd = rnd; + this.countryCode = countryCode; + this.traceId = traceId; + this.number = number; + } + + @JsonIgnore + public Line getLine() { + return line.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_LINE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue line() { + return line; + } + + @JsonIgnore + public SimSwap getSimSwap() { + return simSwap.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SIM_SWAP) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue simSwap() { + return simSwap; + } + + @JsonIgnore + public VoIPDetection getVoIPDetection() { + return voIPDetection.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_VO_I_P_DETECTION) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue voIPDetection() { + return voIPDetection; + } + + @JsonIgnore + public Rnd getRnd() { + return rnd.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_RND) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue rnd() { + return rnd; + } + + @JsonIgnore + public String getCountryCode() { + return countryCode.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COUNTRY_CODE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue countryCode() { + return countryCode; + } + + @JsonIgnore + public String getTraceId() { + return traceId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TRACE_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue traceId() { + return traceId; + } + + @JsonIgnore + public String getNumber() { + return number.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_NUMBER) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue number() { + return number; + } + + /** Return true if this NumberLookupResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NumberLookupResponseImpl numberLookupResponse = (NumberLookupResponseImpl) o; + return Objects.equals(this.line, numberLookupResponse.line) + && Objects.equals(this.simSwap, numberLookupResponse.simSwap) + && Objects.equals(this.voIPDetection, numberLookupResponse.voIPDetection) + && Objects.equals(this.rnd, numberLookupResponse.rnd) + && Objects.equals(this.countryCode, numberLookupResponse.countryCode) + && Objects.equals(this.traceId, numberLookupResponse.traceId) + && Objects.equals(this.number, numberLookupResponse.number); + } + + @Override + public int hashCode() { + return Objects.hash(line, simSwap, voIPDetection, rnd, countryCode, traceId, number); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class NumberLookupResponseImpl {\n"); + sb.append(" line: ").append(toIndentedString(line)).append("\n"); + sb.append(" simSwap: ").append(toIndentedString(simSwap)).append("\n"); + sb.append(" voIPDetection: ").append(toIndentedString(voIPDetection)).append("\n"); + sb.append(" rnd: ").append(toIndentedString(rnd)).append("\n"); + sb.append(" countryCode: ").append(toIndentedString(countryCode)).append("\n"); + sb.append(" traceId: ").append(toIndentedString(traceId)).append("\n"); + sb.append(" number: ").append(toIndentedString(number)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements NumberLookupResponse.Builder { + OptionalValue line = OptionalValue.empty(); + OptionalValue simSwap = OptionalValue.empty(); + OptionalValue voIPDetection = OptionalValue.empty(); + OptionalValue rnd = OptionalValue.empty(); + OptionalValue countryCode = OptionalValue.empty(); + OptionalValue traceId = OptionalValue.empty(); + OptionalValue number = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_LINE) + public Builder setLine(Line line) { + this.line = OptionalValue.of(line); + return this; + } + + @JsonProperty(JSON_PROPERTY_SIM_SWAP) + public Builder setSimSwap(SimSwap simSwap) { + this.simSwap = OptionalValue.of(simSwap); + return this; + } + + @JsonProperty(JSON_PROPERTY_VO_I_P_DETECTION) + public Builder setVoIPDetection(VoIPDetection voIPDetection) { + this.voIPDetection = OptionalValue.of(voIPDetection); + return this; + } + + @JsonProperty(JSON_PROPERTY_RND) + public Builder setRnd(Rnd rnd) { + this.rnd = OptionalValue.of(rnd); + return this; + } + + @JsonProperty(JSON_PROPERTY_COUNTRY_CODE) + public Builder setCountryCode(String countryCode) { + this.countryCode = OptionalValue.of(countryCode); + return this; + } + + @JsonProperty(JSON_PROPERTY_TRACE_ID) + public Builder setTraceId(String traceId) { + this.traceId = OptionalValue.of(traceId); + return this; + } + + @JsonProperty(JSON_PROPERTY_NUMBER) + public Builder setNumber(String number) { + this.number = OptionalValue.of(number); + return this; + } + + public NumberLookupResponse build() { + return new NumberLookupResponseImpl( + line, simSwap, voIPDetection, rnd, countryCode, traceId, number); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/Rnd.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/Rnd.java new file mode 100644 index 000000000..28dfb1be8 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/Rnd.java @@ -0,0 +1,71 @@ +/* + * Number Lookup Api v2 + * + * OpenAPI document version: 0.1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.numberlookup.models.v2.errors.LookupError; + +/** Rnd */ +@JsonDeserialize(builder = RndImpl.Builder.class) +public interface Rnd { + + /** + * Provides information whether the number was disconnected after provided last contact date. + * + * @return disconnected + */ + Boolean getDisconnected(); + + /** + * Get error + * + * @return error + */ + LookupError getError(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new RndImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param disconnected see getter + * @return Current builder + * @see #getDisconnected + */ + Builder setDisconnected(Boolean disconnected); + + /** + * see getter + * + * @param error see getter + * @return Current builder + * @see #getError + */ + Builder setError(LookupError error); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + Rnd build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/RndImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/RndImpl.java new file mode 100644 index 000000000..5b8d83a71 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/RndImpl.java @@ -0,0 +1,116 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import com.sinch.sdk.domains.numberlookup.models.v2.errors.LookupError; +import java.util.Objects; + +@JsonPropertyOrder({RndImpl.JSON_PROPERTY_DISCONNECTED, RndImpl.JSON_PROPERTY_ERROR}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class RndImpl implements Rnd { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_DISCONNECTED = "disconnected"; + + private OptionalValue disconnected; + + public static final String JSON_PROPERTY_ERROR = "error"; + + private OptionalValue error; + + public RndImpl() {} + + protected RndImpl(OptionalValue disconnected, OptionalValue error) { + this.disconnected = disconnected; + this.error = error; + } + + @JsonIgnore + public Boolean getDisconnected() { + return disconnected.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DISCONNECTED) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue disconnected() { + return disconnected; + } + + @JsonIgnore + public LookupError getError() { + return error.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ERROR) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue error() { + return error; + } + + /** Return true if this RndResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RndImpl rndResponse = (RndImpl) o; + return Objects.equals(this.disconnected, rndResponse.disconnected) + && Objects.equals(this.error, rndResponse.error); + } + + @Override + public int hashCode() { + return Objects.hash(disconnected, error); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class RndImpl {\n"); + sb.append(" disconnected: ").append(toIndentedString(disconnected)).append("\n"); + sb.append(" error: ").append(toIndentedString(error)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements Rnd.Builder { + OptionalValue disconnected = OptionalValue.empty(); + OptionalValue error = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_DISCONNECTED) + public Builder setDisconnected(Boolean disconnected) { + this.disconnected = OptionalValue.of(disconnected); + return this; + } + + @JsonProperty(JSON_PROPERTY_ERROR) + public Builder setError(LookupError error) { + this.error = OptionalValue.of(error); + return this; + } + + public Rnd build() { + return new RndImpl(disconnected, error); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwap.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwap.java new file mode 100644 index 000000000..ad7872612 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwap.java @@ -0,0 +1,87 @@ +/* + * Number Lookup Api v2 + * + * OpenAPI document version: 0.1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.numberlookup.models.v2.errors.LookupError; + +/** SimSwap */ +@JsonDeserialize(builder = SimSwapImpl.Builder.class) +public interface SimSwap { + + /** + * Indicates whether SIM was changed. + * + * @return swapped + */ + Boolean getSwapped(); + + /** + * Threshold for sim swap check. + * + * @return swapPeriod + */ + SwapPeriodType getSwapPeriod(); + + /** + * Get error + * + * @return error + */ + LookupError getError(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SimSwapImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param swapped see getter + * @return Current builder + * @see #getSwapped + */ + Builder setSwapped(Boolean swapped); + + /** + * see getter + * + * @param swapPeriod see getter + * @return Current builder + * @see #getSwapPeriod + */ + Builder setSwapPeriod(SwapPeriodType swapPeriod); + + /** + * see getter + * + * @param error see getter + * @return Current builder + * @see #getError + */ + Builder setError(LookupError error); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + SimSwap build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwapImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwapImpl.java new file mode 100644 index 000000000..e0ab9aab4 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwapImpl.java @@ -0,0 +1,148 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import com.sinch.sdk.domains.numberlookup.models.v2.errors.LookupError; +import java.util.Objects; + +@JsonPropertyOrder({ + SimSwapImpl.JSON_PROPERTY_SWAPPED, + SimSwapImpl.JSON_PROPERTY_SWAP_PERIOD, + SimSwapImpl.JSON_PROPERTY_ERROR +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SimSwapImpl implements SimSwap { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_SWAPPED = "swapped"; + + private OptionalValue swapped; + + public static final String JSON_PROPERTY_SWAP_PERIOD = "swapPeriod"; + + private OptionalValue swapPeriod; + + public static final String JSON_PROPERTY_ERROR = "error"; + + private OptionalValue error; + + public SimSwapImpl() {} + + protected SimSwapImpl( + OptionalValue swapped, + OptionalValue swapPeriod, + OptionalValue error) { + this.swapped = swapped; + this.swapPeriod = swapPeriod; + this.error = error; + } + + @JsonIgnore + public Boolean getSwapped() { + return swapped.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SWAPPED) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue swapped() { + return swapped; + } + + @JsonIgnore + public SwapPeriodType getSwapPeriod() { + return swapPeriod.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SWAP_PERIOD) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue swapPeriod() { + return swapPeriod; + } + + @JsonIgnore + public LookupError getError() { + return error.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ERROR) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue error() { + return error; + } + + /** Return true if this SimSwapResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SimSwapImpl simSwapResponse = (SimSwapImpl) o; + return Objects.equals(this.swapped, simSwapResponse.swapped) + && Objects.equals(this.swapPeriod, simSwapResponse.swapPeriod) + && Objects.equals(this.error, simSwapResponse.error); + } + + @Override + public int hashCode() { + return Objects.hash(swapped, swapPeriod, error); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SimSwapImpl {\n"); + sb.append(" swapped: ").append(toIndentedString(swapped)).append("\n"); + sb.append(" swapPeriod: ").append(toIndentedString(swapPeriod)).append("\n"); + sb.append(" error: ").append(toIndentedString(error)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements SimSwap.Builder { + OptionalValue swapped = OptionalValue.empty(); + OptionalValue swapPeriod = OptionalValue.empty(); + OptionalValue error = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_SWAPPED) + public Builder setSwapped(Boolean swapped) { + this.swapped = OptionalValue.of(swapped); + return this; + } + + @JsonProperty(JSON_PROPERTY_SWAP_PERIOD) + public Builder setSwapPeriod(SwapPeriodType swapPeriod) { + this.swapPeriod = OptionalValue.of(swapPeriod); + return this; + } + + @JsonProperty(JSON_PROPERTY_ERROR) + public Builder setError(LookupError error) { + this.error = OptionalValue.of(error); + return this; + } + + public SimSwap build() { + return new SimSwapImpl(swapped, swapPeriod, error); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SwapPeriodType.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SwapPeriodType.java new file mode 100644 index 000000000..c5a1de7b2 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/SwapPeriodType.java @@ -0,0 +1,63 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** */ +public class SwapPeriodType extends EnumDynamic { + + /** Undefined */ + public static final SwapPeriodType UNDEFINED = new SwapPeriodType("Undefined"); + + /** 4 Hours */ + public static final SwapPeriodType SP4_H = new SwapPeriodType("SP4H"); + + /** 12 Hours */ + public static final SwapPeriodType SP12_H = new SwapPeriodType("SP12H"); + + /** 24 Hours */ + public static final SwapPeriodType SP24_H = new SwapPeriodType("SP24H"); + + /** 48 Hours */ + public static final SwapPeriodType SP48_H = new SwapPeriodType("SP48H"); + + /** 5 Days */ + public static final SwapPeriodType SP5_D = new SwapPeriodType("SP5D"); + + /** 7 Days */ + public static final SwapPeriodType SP7_D = new SwapPeriodType("SP7D"); + + /** 14 Days */ + public static final SwapPeriodType SP14_D = new SwapPeriodType("SP14D"); + + /** 30 Days */ + public static final SwapPeriodType SP30_D = new SwapPeriodType("SP30D"); + + /** Maximum */ + public static final SwapPeriodType SPMAX = new SwapPeriodType("SPMAX"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + SwapPeriodType.class, + SwapPeriodType::new, + Arrays.asList( + UNDEFINED, SP4_H, SP12_H, SP24_H, SP48_H, SP5_D, SP7_D, SP14_D, SP30_D, SPMAX)); + + private SwapPeriodType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static SwapPeriodType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(SwapPeriodType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetection.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetection.java new file mode 100644 index 000000000..f5814dc5c --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetection.java @@ -0,0 +1,71 @@ +/* + * Number Lookup Api v2 + * + * OpenAPI document version: 0.1.0 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.numberlookup.models.v2.errors.LookupError; + +/** VoIPDetection */ +@JsonDeserialize(builder = VoIPDetectionImpl.Builder.class) +public interface VoIPDetection { + + /** + * Probability of number being VoIP based on the AI analysis. + * + * @return probability + */ + VoIPProbabilityType getProbability(); + + /** + * Get error + * + * @return error + */ + LookupError getError(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new VoIPDetectionImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param probability see getter + * @return Current builder + * @see #getProbability + */ + Builder setProbability(VoIPProbabilityType probability); + + /** + * see getter + * + * @param error see getter + * @return Current builder + * @see #getError + */ + Builder setError(LookupError error); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + VoIPDetection build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetectionImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetectionImpl.java new file mode 100644 index 000000000..e6ebe85d9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetectionImpl.java @@ -0,0 +1,120 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import com.sinch.sdk.domains.numberlookup.models.v2.errors.LookupError; +import java.util.Objects; + +@JsonPropertyOrder({ + VoIPDetectionImpl.JSON_PROPERTY_PROBABILITY, + VoIPDetectionImpl.JSON_PROPERTY_ERROR +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class VoIPDetectionImpl implements VoIPDetection { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_PROBABILITY = "probability"; + + private OptionalValue probability; + + public static final String JSON_PROPERTY_ERROR = "error"; + + private OptionalValue error; + + public VoIPDetectionImpl() {} + + protected VoIPDetectionImpl( + OptionalValue probability, OptionalValue error) { + this.probability = probability; + this.error = error; + } + + @JsonIgnore + public VoIPProbabilityType getProbability() { + return probability.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_PROBABILITY) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue probability() { + return probability; + } + + @JsonIgnore + public LookupError getError() { + return error.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ERROR) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue error() { + return error; + } + + /** Return true if this VoIPDetectionResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VoIPDetectionImpl voIPDetectionResponse = (VoIPDetectionImpl) o; + return Objects.equals(this.probability, voIPDetectionResponse.probability) + && Objects.equals(this.error, voIPDetectionResponse.error); + } + + @Override + public int hashCode() { + return Objects.hash(probability, error); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class VoIPDetectionImpl {\n"); + sb.append(" probability: ").append(toIndentedString(probability)).append("\n"); + sb.append(" error: ").append(toIndentedString(error)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements VoIPDetection.Builder { + OptionalValue probability = OptionalValue.empty(); + OptionalValue error = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_PROBABILITY) + public Builder setProbability(VoIPProbabilityType probability) { + this.probability = OptionalValue.of(probability); + return this; + } + + @JsonProperty(JSON_PROPERTY_ERROR) + public Builder setError(LookupError error) { + this.error = OptionalValue.of(error); + return this; + } + + public VoIPDetection build() { + return new VoIPDetectionImpl(probability, error); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPProbabilityType.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPProbabilityType.java new file mode 100644 index 000000000..d6e81a332 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPProbabilityType.java @@ -0,0 +1,44 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** */ +public class VoIPProbabilityType extends EnumDynamic { + + /** Undefined */ + public static final VoIPProbabilityType UNKNOWN = new VoIPProbabilityType("Unknown"); + + /** Low */ + public static final VoIPProbabilityType LOW = new VoIPProbabilityType("Low"); + + /** Likely */ + public static final VoIPProbabilityType LIKELY = new VoIPProbabilityType("Likely"); + + /** High */ + public static final VoIPProbabilityType HIGH = new VoIPProbabilityType("High"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + VoIPProbabilityType.class, + VoIPProbabilityType::new, + Arrays.asList(UNKNOWN, LOW, LIKELY, HIGH)); + + private VoIPProbabilityType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static VoIPProbabilityType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(VoIPProbabilityType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupErrorDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupErrorDtoTest.java new file mode 100644 index 000000000..faf6e7261 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/errors/LookupErrorDtoTest.java @@ -0,0 +1,42 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.errors; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.core.TestHelpers; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class LookupErrorDtoTest extends BaseTest { + + public static LookupError lookupErrorDto = + LookupError.builder() + .setStatus(100) + .setTitle("Feature Disabled") + .setDetail("VoIPDetection feature is currently disabled.") + .setType("validation_error") + .build(); + + public static LookupError lookupErrorEmptyDto = LookupError.builder().build(); + + @GivenTextResource("/domains/numberlookup/v2/errors/LookupErrorDto.json") + String jsonLookupErrorDto; + + @GivenTextResource("/domains/numberlookup/v2/errors/LookupErrorEmptyDto.json") + String jsonLookupErrorEmptyDto; + + @Test + void deserializeDto() throws JsonProcessingException { + LookupError deserialized = objectMapper.readValue(jsonLookupErrorDto, LookupError.class); + + TestHelpers.recursiveEquals(deserialized, lookupErrorDto); + } + + @Test + void deserializeEmptyDto() throws JsonProcessingException { + LookupError deserialized = objectMapper.readValue(jsonLookupErrorEmptyDto, LookupError.class); + + TestHelpers.recursiveEquals(deserialized, lookupErrorEmptyDto); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetailsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetailsDtoTest.java new file mode 100644 index 000000000..d94af3def --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/errors/ProblemDetailsDtoTest.java @@ -0,0 +1,31 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.errors; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.core.TestHelpers; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class ProblemDetailsDtoTest extends BaseTest { + + public static ProblemDetails problemDetailsDto = + ProblemDetails.builder() + .setType("https://developers.sinch.com/docs/lookup/api/errors/validation") + .setTitle("Validation error") + .setStatus(400) + .setTraceId("84c1fd4063c38d9f3900d06e56542d48") + .build(); + + @GivenTextResource("/domains/numberlookup/v2/errors/ProblemDetailsDto.json") + String jsonProblemDetailsDto; + + @Test + void deserializeDto() throws JsonProcessingException { + ProblemDetails deserialized = + objectMapper.readValue(jsonProblemDetailsDto, ProblemDetails.class); + + TestHelpers.recursiveEquals(deserialized, problemDetailsDto); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequestDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequestDtoTest.java new file mode 100644 index 000000000..8d20f0465 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/request/NumberLookupRequestDtoTest.java @@ -0,0 +1,120 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.request; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.core.TestHelpers; +import java.time.LocalDate; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class NumberLookupRequestDtoTest extends BaseTest { + + public static NumberLookupRequest numberLookupRequestMinimalDto = + NumberLookupRequest.builder().setNumber("+15551234567").build(); + + public static NumberLookupRequest numberLookupRequestWithFeaturesDto = + NumberLookupRequest.builder() + .setNumber("+15552345678") + .setFeatures( + Arrays.asList( + LookupFeatureType.LINE_TYPE, + LookupFeatureType.SIM_SWAP, + LookupFeatureType.VO_IP_DETECTION)) + .build(); + + public static NumberLookupRequest numberLookupRequestWithRndOptionsDto = + NumberLookupRequest.builder() + .setNumber("+15553456789") + .setFeatures(Arrays.asList(LookupFeatureType.RND)) + .setRndFeatureOptions( + RndFeatureOptions.builder().setContactDate(LocalDate.parse("2025-01-01")).build()) + .build(); + + public static NumberLookupRequest numberLookupRequestAllFeaturesDto = + NumberLookupRequest.builder() + .setNumber("+15554567890") + .setFeatures( + Arrays.asList( + LookupFeatureType.LINE_TYPE, + LookupFeatureType.SIM_SWAP, + LookupFeatureType.VO_IP_DETECTION, + LookupFeatureType.RND)) + .build(); + + @GivenTextResource("/domains/numberlookup/v2/request/NumberLookupRequestMinimalDto.json") + String jsonNumberLookupRequestMinimalDto; + + @GivenTextResource("/domains/numberlookup/v2/request/NumberLookupRequestWithFeaturesDto.json") + String jsonNumberLookupRequestWithFeaturesDto; + + @GivenTextResource("/domains/numberlookup/v2/request/NumberLookupRequestWithRndOptionsDto.json") + String jsonNumberLookupRequestWithRndOptionsDto; + + @GivenTextResource("/domains/numberlookup/v2/request/NumberLookupRequestAllFeaturesDto.json") + String jsonNumberLookupRequestAllFeaturesDto; + + @Test + void serializeMinimalDto() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(numberLookupRequestMinimalDto); + + JSONAssert.assertEquals(jsonNumberLookupRequestMinimalDto, serializedString, true); + } + + @Test + void deserializeMinimalDto() throws JsonProcessingException { + NumberLookupRequest deserialized = + objectMapper.readValue(jsonNumberLookupRequestMinimalDto, NumberLookupRequest.class); + + TestHelpers.recursiveEquals(deserialized, numberLookupRequestMinimalDto); + } + + @Test + void serializeWithFeaturesDto() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(numberLookupRequestWithFeaturesDto); + + JSONAssert.assertEquals(jsonNumberLookupRequestWithFeaturesDto, serializedString, true); + } + + @Test + void deserializeWithFeaturesDto() throws JsonProcessingException { + NumberLookupRequest deserialized = + objectMapper.readValue(jsonNumberLookupRequestWithFeaturesDto, NumberLookupRequest.class); + + TestHelpers.recursiveEquals(deserialized, numberLookupRequestWithFeaturesDto); + } + + @Test + void serializeWithRndOptionsDto() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(numberLookupRequestWithRndOptionsDto); + + JSONAssert.assertEquals(jsonNumberLookupRequestWithRndOptionsDto, serializedString, true); + } + + @Test + void deserializeWithRndOptionsDto() throws JsonProcessingException { + NumberLookupRequest deserialized = + objectMapper.readValue(jsonNumberLookupRequestWithRndOptionsDto, NumberLookupRequest.class); + + TestHelpers.recursiveEquals(deserialized, numberLookupRequestWithRndOptionsDto); + } + + @Test + void serializeAllFeaturesDto() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(numberLookupRequestAllFeaturesDto); + + JSONAssert.assertEquals(jsonNumberLookupRequestAllFeaturesDto, serializedString, true); + } + + @Test + void deserializeAllFeaturesDto() throws JsonProcessingException { + NumberLookupRequest deserialized = + objectMapper.readValue(jsonNumberLookupRequestAllFeaturesDto, NumberLookupRequest.class); + + TestHelpers.recursiveEquals(deserialized, numberLookupRequestAllFeaturesDto); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptionsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptionsDtoTest.java new file mode 100644 index 000000000..305d7d031 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/request/RndFeatureOptionsDtoTest.java @@ -0,0 +1,36 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.request; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.core.TestHelpers; +import java.time.LocalDate; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class RndFeatureOptionsDtoTest extends BaseTest { + + public static RndFeatureOptions rndFeatureOptionsDto = + RndFeatureOptions.builder().setContactDate(LocalDate.parse("2025-01-15")).build(); + + @GivenTextResource("/domains/numberlookup/v2/request/RndFeatureOptionsDto.json") + String jsonRndFeatureOptionsDto; + + @Test + void serializeDto() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(rndFeatureOptionsDto); + + JSONAssert.assertEquals(jsonRndFeatureOptionsDto, serializedString, true); + } + + @Test + void deserializeDto() throws JsonProcessingException { + RndFeatureOptions deserialized = + objectMapper.readValue(jsonRndFeatureOptionsDto, RndFeatureOptions.class); + + TestHelpers.recursiveEquals(deserialized, rndFeatureOptionsDto); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/LineResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/LineResponseDtoTest.java new file mode 100644 index 000000000..b16e73c01 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/LineResponseDtoTest.java @@ -0,0 +1,54 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.core.TestHelpers; +import java.time.Instant; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class LineResponseDtoTest extends BaseTest { + + public static Line lineResponseDto = + Line.builder() + .setCarrier("T-Mobile USA") + .setType(LineType.MOBILE) + .setMobileCountryCode("310") + .setMobileNetworkCode("260") + .setPorted(true) + .setPortingDate(Instant.parse("2024-06-15T14:30:00Z")) + .build(); + + public static Line lineResponseEmptyDto = Line.builder().build(); + + @GivenTextResource("/domains/numberlookup/v2/response/LineResponseDto.json") + String jsonLineResponseDto; + + @GivenTextResource("/domains/numberlookup/v2/response/LineResponseEmptyDto.json") + String jsonLineResponseEmptyDto; + + @Test + void serializeDto() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(lineResponseDto); + + JSONAssert.assertEquals(jsonLineResponseDto, serializedString, true); + } + + @Test + void deserializeDto() throws JsonProcessingException { + Line deserialized = objectMapper.readValue(jsonLineResponseDto, Line.class); + + TestHelpers.recursiveEquals(deserialized, lineResponseDto); + } + + @Test + void deserializeEmptyDto() throws JsonProcessingException { + Line deserialized = objectMapper.readValue(jsonLineResponseEmptyDto, Line.class); + + TestHelpers.recursiveEquals(deserialized, lineResponseEmptyDto); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponseDtoTest.java new file mode 100644 index 000000000..1aa55dbc4 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/NumberLookupResponseDtoTest.java @@ -0,0 +1,63 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.core.TestHelpers; +import java.time.Instant; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class NumberLookupResponseDtoTest extends BaseTest { + + public static NumberLookupResponse numberLookupResponseMinimalDto = + NumberLookupResponse.builder() + .setNumber("+15551234567") + .setCountryCode("US") + .setTraceId("test-trace-id") + .build(); + + public static NumberLookupResponse numberLookupResponseFullDto = + NumberLookupResponse.builder() + .setLine( + Line.builder() + .setCarrier("T-Mobile USA") + .setType(LineType.MOBILE) + .setMobileCountryCode("310") + .setMobileNetworkCode("260") + .setPorted(true) + .setPortingDate(Instant.parse("2024-08-20T10:15:30Z")) + .build()) + .setSimSwap( + SimSwap.builder().setSwapped(true).setSwapPeriod(SwapPeriodType.SP24_H).build()) + .setVoIPDetection( + VoIPDetection.builder().setProbability(VoIPProbabilityType.HIGH).build()) + .setRnd(Rnd.builder().setDisconnected(true).build()) + .setCountryCode("US") + .setTraceId("84c1fd4063c38d9f3900d06e56542d48") + .setNumber("+15557890123") + .build(); + + @GivenTextResource("/domains/numberlookup/v2/response/NumberLookupResponseMinimalDto.json") + String jsonNumberLookupResponseMinimalDto; + + @GivenTextResource("/domains/numberlookup/v2/response/NumberLookupResponseFullDto.json") + String jsonNumberLookupResponseFullDto; + + @Test + void deserializeMinimalDto() throws JsonProcessingException { + NumberLookupResponse deserialized = + objectMapper.readValue(jsonNumberLookupResponseMinimalDto, NumberLookupResponse.class); + + TestHelpers.recursiveEquals(deserialized, numberLookupResponseMinimalDto); + } + + @Test + void deserializeFullDto() throws JsonProcessingException { + NumberLookupResponse deserialized = + objectMapper.readValue(jsonNumberLookupResponseFullDto, NumberLookupResponse.class); + + TestHelpers.recursiveEquals(deserialized, numberLookupResponseFullDto); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/RndResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/RndResponseDtoTest.java new file mode 100644 index 000000000..36f25d8e4 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/RndResponseDtoTest.java @@ -0,0 +1,45 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.core.TestHelpers; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class RndResponseDtoTest extends BaseTest { + + public static Rnd rndResponseDto = Rnd.builder().setDisconnected(true).build(); + + public static Rnd rndResponseEmptyDto = Rnd.builder().build(); + + @GivenTextResource("/domains/numberlookup/v2/response/RndResponseDto.json") + String jsonRndResponseDto; + + @GivenTextResource("/domains/numberlookup/v2/response/RndResponseEmptyDto.json") + String jsonRndResponseEmptyDto; + + @Test + void serializeDto() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(rndResponseDto); + + JSONAssert.assertEquals(jsonRndResponseDto, serializedString, true); + } + + @Test + void deserializeDto() throws JsonProcessingException { + Rnd deserialized = objectMapper.readValue(jsonRndResponseDto, Rnd.class); + + TestHelpers.recursiveEquals(deserialized, rndResponseDto); + } + + @Test + void deserializeEmptyDto() throws JsonProcessingException { + Rnd deserialized = objectMapper.readValue(jsonRndResponseEmptyDto, Rnd.class); + + TestHelpers.recursiveEquals(deserialized, rndResponseEmptyDto); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwapResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwapResponseDtoTest.java new file mode 100644 index 000000000..ff7a6d510 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/SimSwapResponseDtoTest.java @@ -0,0 +1,62 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.domains.numberlookup.models.v2.errors.LookupError; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class SimSwapResponseDtoTest extends BaseTest { + + public static SimSwap simSwapResponseDto = + SimSwap.builder().setSwapped(true).setSwapPeriod(SwapPeriodType.SP24_H).build(); + + public static SimSwap simSwapResponseWithErrorDto = + SimSwap.builder() + .setError( + LookupError.builder() + .setStatus(100) + .setTitle("Feature Disabled") + .setDetail("SimSwap feature is currently disabled.") + .build()) + .build(); + + @GivenTextResource("/domains/numberlookup/v2/response/SimSwapResponseDto.json") + String jsonSimSwapDto; + + @GivenTextResource("/domains/numberlookup/v2/response/SimSwapResponseWithErrorDto.json") + String jsonSimSwapWithErrorDto; + + @Test + void serializeDto() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(simSwapResponseDto); + + JSONAssert.assertEquals(jsonSimSwapDto, serializedString, true); + } + + @Test + void deserializeDto() throws JsonProcessingException { + SimSwap deserialized = objectMapper.readValue(jsonSimSwapDto, SimSwap.class); + + TestHelpers.recursiveEquals(deserialized, simSwapResponseDto); + } + + @Test + void serializeWithErrorDto() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(simSwapResponseWithErrorDto); + + JSONAssert.assertEquals(jsonSimSwapWithErrorDto, serializedString, true); + } + + @Test + void deserializeWithErrorDto() throws JsonProcessingException { + SimSwap deserialized = objectMapper.readValue(jsonSimSwapWithErrorDto, SimSwap.class); + + TestHelpers.recursiveEquals(deserialized, simSwapResponseWithErrorDto); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetectionResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetectionResponseDtoTest.java new file mode 100644 index 000000000..d7e86ef47 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numberlookup/models/v2/response/VoIPDetectionResponseDtoTest.java @@ -0,0 +1,47 @@ +package com.sinch.sdk.domains.numberlookup.models.v2.response; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.core.TestHelpers; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class VoIPDetectionResponseDtoTest extends BaseTest { + + public static VoIPDetection voIPDetectionResponseDto = + VoIPDetection.builder().setProbability(VoIPProbabilityType.HIGH).build(); + + public static VoIPDetection voIPDetectionResponseEmptyDto = VoIPDetection.builder().build(); + + @GivenTextResource("/domains/numberlookup/v2/response/VoIPDetectionResponseDto.json") + String jsonVoIPDetectionDto; + + @GivenTextResource("/domains/numberlookup/v2/response/VoIPDetectionResponseEmptyDto.json") + String jsonVoIPDetectionEmptyDto; + + @Test + void serializeDto() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(voIPDetectionResponseDto); + + JSONAssert.assertEquals(jsonVoIPDetectionDto, serializedString, true); + } + + @Test + void deserializeDto() throws JsonProcessingException { + VoIPDetection deserialized = objectMapper.readValue(jsonVoIPDetectionDto, VoIPDetection.class); + + TestHelpers.recursiveEquals(deserialized, voIPDetectionResponseDto); + } + + @Test + void deserializeEmptyDto() throws JsonProcessingException { + VoIPDetection deserialized = + objectMapper.readValue(jsonVoIPDetectionEmptyDto, VoIPDetection.class); + + TestHelpers.recursiveEquals(deserialized, voIPDetectionResponseEmptyDto); + } +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/LookupErrorDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/LookupErrorDto.json new file mode 100644 index 000000000..86220e9d1 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/LookupErrorDto.json @@ -0,0 +1,6 @@ +{ + "status": 100, + "title": "Feature Disabled", + "detail": "VoIPDetection feature is currently disabled.", + "type": "validation_error" +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/LookupErrorEmptyDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/LookupErrorEmptyDto.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/LookupErrorEmptyDto.json @@ -0,0 +1 @@ +{} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/ProblemDetailsDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/ProblemDetailsDto.json new file mode 100644 index 000000000..1e1590f3c --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/errors/ProblemDetailsDto.json @@ -0,0 +1,6 @@ +{ + "type": "https://developers.sinch.com/docs/lookup/api/errors/validation", + "title": "Validation error", + "status": 400, + "traceId": "84c1fd4063c38d9f3900d06e56542d48" +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestAllFeaturesDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestAllFeaturesDto.json new file mode 100644 index 000000000..84b20c39e --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestAllFeaturesDto.json @@ -0,0 +1,9 @@ +{ + "number": "+15554567890", + "features": [ + "LineType", + "SimSwap", + "VoIPDetection", + "RND" + ] +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestMinimalDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestMinimalDto.json new file mode 100644 index 000000000..b190f89ba --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestMinimalDto.json @@ -0,0 +1,3 @@ +{ + "number": "+15551234567" +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestWithFeaturesDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestWithFeaturesDto.json new file mode 100644 index 000000000..33bad4262 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestWithFeaturesDto.json @@ -0,0 +1,8 @@ +{ + "number": "+15552345678", + "features": [ + "LineType", + "SimSwap", + "VoIPDetection" + ] +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestWithRndOptionsDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestWithRndOptionsDto.json new file mode 100644 index 000000000..7ef54e3e4 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/NumberLookupRequestWithRndOptionsDto.json @@ -0,0 +1,9 @@ +{ + "number": "+15553456789", + "features": [ + "RND" + ], + "rndFeatureOptions": { + "contactDate": "2025-01-01" + } +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/RndFeatureOptionsDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/RndFeatureOptionsDto.json new file mode 100644 index 000000000..45a365f9c --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/request/RndFeatureOptionsDto.json @@ -0,0 +1,3 @@ +{ + "contactDate": "2025-01-15" +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/LineResponseDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/LineResponseDto.json new file mode 100644 index 000000000..cc7ff9007 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/LineResponseDto.json @@ -0,0 +1,8 @@ +{ + "carrier": "T-Mobile USA", + "type": "Mobile", + "mobileCountryCode": "310", + "mobileNetworkCode": "260", + "ported": true, + "portingDate": "2024-06-15T14:30:00Z" +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/LineResponseEmptyDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/LineResponseEmptyDto.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/LineResponseEmptyDto.json @@ -0,0 +1 @@ +{} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/NumberLookupResponseFullDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/NumberLookupResponseFullDto.json new file mode 100644 index 000000000..b43781206 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/NumberLookupResponseFullDto.json @@ -0,0 +1,23 @@ +{ + "line": { + "carrier": "T-Mobile USA", + "type": "Mobile", + "mobileCountryCode": "310", + "mobileNetworkCode": "260", + "ported": true, + "portingDate": "2024-08-20T10:15:30Z" + }, + "simSwap": { + "swapped": true, + "swapPeriod": "SP24H" + }, + "voIPDetection": { + "probability": "High" + }, + "rnd": { + "disconnected": true + }, + "countryCode": "US", + "traceId": "84c1fd4063c38d9f3900d06e56542d48", + "number": "+15557890123" +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/NumberLookupResponseMinimalDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/NumberLookupResponseMinimalDto.json new file mode 100644 index 000000000..d6334b7e9 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/NumberLookupResponseMinimalDto.json @@ -0,0 +1,5 @@ +{ + "countryCode": "US", + "traceId": "test-trace-id", + "number": "+15551234567" +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/RndResponseDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/RndResponseDto.json new file mode 100644 index 000000000..29102b577 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/RndResponseDto.json @@ -0,0 +1,3 @@ +{ + "disconnected": true +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/RndResponseEmptyDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/RndResponseEmptyDto.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/RndResponseEmptyDto.json @@ -0,0 +1 @@ +{} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/SimSwapResponseDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/SimSwapResponseDto.json new file mode 100644 index 000000000..9815704d5 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/SimSwapResponseDto.json @@ -0,0 +1,4 @@ +{ + "swapped": true, + "swapPeriod": "SP24H" +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/SimSwapResponseWithErrorDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/SimSwapResponseWithErrorDto.json new file mode 100644 index 000000000..4ef301ba4 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/SimSwapResponseWithErrorDto.json @@ -0,0 +1,7 @@ +{ + "error": { + "status": 100, + "title": "Feature Disabled", + "detail": "SimSwap feature is currently disabled." + } +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/VoIPDetectionResponseDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/VoIPDetectionResponseDto.json new file mode 100644 index 000000000..792988b4c --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/VoIPDetectionResponseDto.json @@ -0,0 +1,3 @@ +{ + "probability": "High" +} diff --git a/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/VoIPDetectionResponseEmptyDto.json b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/VoIPDetectionResponseEmptyDto.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/numberlookup/v2/response/VoIPDetectionResponseEmptyDto.json @@ -0,0 +1 @@ +{} diff --git a/pom.xml b/pom.xml index 656cd3dda..021a0bf4d 100644 --- a/pom.xml +++ b/pom.xml @@ -262,6 +262,9 @@ openapi-contracts/src/test/resources + + sinch-sdk-mockserver + @@ -297,6 +300,7 @@ com.sinch.sdk.e2e.domains.sms.v1.SmsIT com.sinch.sdk.e2e.domains.voice.v1.VoiceIT com.sinch.sdk.e2e.domains.verification.v1.VerificationIT + com.sinch.sdk.e2e.domains.numberlookup.v2.NumberLookupIT From 5ec550b9729b870b28a1930842584649f66c6171 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Fri, 12 Jun 2026 08:33:43 +0200 Subject: [PATCH 13/37] Add NumberLookup API v2 snippet (#349) * feat (NumberLookup): Refactor access to number lookup service to have 'v2' accessor and facade reducing layers before calls --- .../src/main/com/sinch/sdk/SinchClient.java | 4 +- .../api/v2/NumberLookupService.java | 11 +--- .../api/v2/adapters/NumberLookupService.java | 21 +++---- .../adapters/NumberLookupServiceFacade.java | 37 ++++++++++++ .../v2/adapters/NumberLookupServiceTest.java | 3 +- .../domains/numberlookup/v2/LookupsSteps.java | 2 +- examples/snippets/README.md | 1 + .../src/main/java/numberlookup/Lookup.java | 56 +++++++++++++++++++ .../src/main/java/numberlookup/README.md | 7 +++ 9 files changed, 118 insertions(+), 24 deletions(-) create mode 100644 client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceFacade.java create mode 100644 examples/snippets/src/main/java/numberlookup/Lookup.java create mode 100644 examples/snippets/src/main/java/numberlookup/README.md diff --git a/client/src/main/com/sinch/sdk/SinchClient.java b/client/src/main/com/sinch/sdk/SinchClient.java index 1612ea26a..2fe0adc7f 100644 --- a/client/src/main/com/sinch/sdk/SinchClient.java +++ b/client/src/main/com/sinch/sdk/SinchClient.java @@ -2,7 +2,7 @@ import com.sinch.sdk.core.utils.StringUtil; import com.sinch.sdk.domains.conversation.ConversationService; -import com.sinch.sdk.domains.numberlookup.api.v2.adapters.NumberLookupService; +import com.sinch.sdk.domains.numberlookup.NumberLookupService; import com.sinch.sdk.domains.numbers.NumbersService; import com.sinch.sdk.domains.sms.SMSService; import com.sinch.sdk.domains.verification.VerificationService; @@ -425,7 +425,7 @@ private ConversationService conversationInit() { } private NumberLookupService lookupInit() { - return new com.sinch.sdk.domains.numberlookup.api.v2.adapters.NumberLookupService( + return new com.sinch.sdk.domains.numberlookup.adapters.NumberLookupService( getConfiguration().getUnifiedCredentials().orElse(null), getConfiguration().getNumberLookupContext().orElse(null), getConfiguration().getOAuthServer(), diff --git a/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupService.java b/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupService.java index cfeb8b218..52e3df737 100644 --- a/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupService.java +++ b/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/NumberLookupService.java @@ -7,13 +7,4 @@ * href="https://developers.sinch.com/docs/number-lookup-api-v2">https://developers.sinch.com/docs/number-lookup-api-v2 * @since 2.1 */ -public interface NumberLookupService { - - /** - * Number Lookup Service instance - * - * @return service instance for project - * @since 2.1 - */ - NumberLookupV2Service lookup(); -} +public interface NumberLookupService extends NumberLookupV2Service {} diff --git a/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupService.java b/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupService.java index 6a943c298..5fdb4dcf8 100644 --- a/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupService.java +++ b/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupService.java @@ -1,12 +1,14 @@ package com.sinch.sdk.domains.numberlookup.api.v2.adapters; import com.sinch.sdk.auth.adapters.OAuthManager; +import com.sinch.sdk.core.exceptions.ApiException; import com.sinch.sdk.core.http.AuthManager; import com.sinch.sdk.core.http.HttpClient; import com.sinch.sdk.core.http.HttpMapper; import com.sinch.sdk.core.models.ServerConfiguration; import com.sinch.sdk.core.utils.StringUtil; -import com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupV2Service; +import com.sinch.sdk.domains.numberlookup.models.v2.request.NumberLookupRequest; +import com.sinch.sdk.domains.numberlookup.models.v2.response.NumberLookupResponse; import com.sinch.sdk.models.NumberLookupContext; import com.sinch.sdk.models.UnifiedCredentials; import java.util.AbstractMap; @@ -31,7 +33,7 @@ public class NumberLookupService private volatile String uriUUID; private volatile Map authManagers; - private volatile NumberLookupV2Service lookup; + private volatile NumberLookupServiceFacade lookup; public NumberLookupService( UnifiedCredentials credentials, @@ -44,25 +46,24 @@ public NumberLookupService( this.httpClientSupplier = httpClientSupplier; } - @Override - public NumberLookupV2Service lookup() { + NumberLookupServiceFacade getLookupFacade() { if (null == this.lookup) { synchronized (this) { if (null == this.lookup) { instanceLazyInit(); this.lookup = - new NumberLookupV2ServiceImpl( - httpClientSupplier.get(), - context.getNumberLookupServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + new NumberLookupServiceFacade(uriUUID, context, httpClientSupplier, authManagers); } } } return this.lookup; } + @Override + public NumberLookupResponse lookup(NumberLookupRequest numberLookupRequest) throws ApiException { + return getLookupFacade().lookup(numberLookupRequest); + } + private void instanceLazyInit() { if (null != this.authManagers) { return; diff --git a/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceFacade.java b/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceFacade.java new file mode 100644 index 000000000..14b3aedf1 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceFacade.java @@ -0,0 +1,37 @@ +package com.sinch.sdk.domains.numberlookup.api.v2.adapters; + +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.core.http.AuthManager; +import com.sinch.sdk.core.http.HttpClient; +import com.sinch.sdk.core.http.HttpMapper; +import com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupV2Service; +import com.sinch.sdk.domains.numberlookup.models.v2.request.NumberLookupRequest; +import com.sinch.sdk.domains.numberlookup.models.v2.response.NumberLookupResponse; +import com.sinch.sdk.models.NumberLookupContext; +import java.util.Map; +import java.util.function.Supplier; + +final class NumberLookupServiceFacade + implements com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupService { + + private final NumberLookupV2Service lookup; + + NumberLookupServiceFacade( + String uriUUID, + NumberLookupContext context, + Supplier httpClientSupplier, + Map authManagers) { + this.lookup = + new NumberLookupV2ServiceImpl( + httpClientSupplier.get(), + context.getNumberLookupServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + + @Override + public NumberLookupResponse lookup(NumberLookupRequest numberLookupRequest) throws ApiException { + return lookup.lookup(numberLookupRequest); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceTest.java index c6d7db2d7..85cfe07a6 100644 --- a/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/numberlookup/api/v2/adapters/NumberLookupServiceTest.java @@ -10,6 +10,7 @@ class NumberLookupServiceTest { @Test void checkCredentialsNumberLookup() { - CredentialsValidationHelper.checkCredentials(() -> httpClient, NumberLookupService::lookup); + CredentialsValidationHelper.checkCredentials( + () -> httpClient, NumberLookupService::getLookupFacade); } } diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/LookupsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/LookupsSteps.java index 0423e77da..f2b0daf88 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/LookupsSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/LookupsSteps.java @@ -29,7 +29,7 @@ public class LookupsSteps { @Given("the Number Lookup service is available") public void serviceAvailable() { - service = Config.getSinchClient().lookup().lookup(); + service = Config.getSinchClient().lookup().v2(); Assertions.assertNotNull(service, "Number Lookup service is not available"); } diff --git a/examples/snippets/README.md b/examples/snippets/README.md index 6b832b4d3..bc9d380b8 100644 --- a/examples/snippets/README.md +++ b/examples/snippets/README.md @@ -38,6 +38,7 @@ launcher numbers/regions/List ## Available Snippets - Conversation: [README.md](src/main/java/conversation/README.md) +- Number Lookup: [README.md](src/main/java/numberlookup/README.md) - Numbers: [README.md](src/main/java/numbers/README.md) - SMS: [README.md](src/main/java/sms/README.md) - Verification: [README.md](src/main/java/verification/README.md) diff --git a/examples/snippets/src/main/java/numberlookup/Lookup.java b/examples/snippets/src/main/java/numberlookup/Lookup.java new file mode 100644 index 000000000..40bf4dfaf --- /dev/null +++ b/examples/snippets/src/main/java/numberlookup/Lookup.java @@ -0,0 +1,56 @@ +/** + * Sinch Java Snippet + * + *

This snippet is available at https://github.com/sinch/sinch-sdk-java + * + *

See https://github.com/sinch/sinch-sdk-java/blob/main/examples/snippets/README.md for details + */ +package numberlookup; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupV2Service; +import com.sinch.sdk.domains.numberlookup.models.v2.request.LookupFeatureType; +import com.sinch.sdk.domains.numberlookup.models.v2.request.NumberLookupRequest; +import com.sinch.sdk.domains.numberlookup.models.v2.response.NumberLookupResponse; +import com.sinch.sdk.models.Configuration; +import java.util.Collections; +import java.util.logging.Logger; +import utils.Settings; + +public class Lookup { + + private static final Logger LOGGER = Logger.getLogger(Lookup.class.getName()); + + public static void main(String[] args) { + + String projectId = Settings.getProjectId().orElse("MY_PROJECT_ID"); + String keyId = Settings.getKeyId().orElse("MY_KEY_ID"); + String keySecret = Settings.getKeySecret().orElse("MY_KEY_SECRET"); + + // The phone number you want to lookup in E.164 format + String phoneNumber = "PHONE_NUMBER"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + NumberLookupV2Service numberLookupService = client.lookup().v2(); + + NumberLookupRequest request = + NumberLookupRequest.builder() + .setNumber(phoneNumber) + .setFeatures(Collections.singletonList(LookupFeatureType.LINE_TYPE)) + .build(); + + LOGGER.info("Lookup for: " + phoneNumber); + + NumberLookupResponse response = numberLookupService.lookup(request); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/numberlookup/README.md b/examples/snippets/src/main/java/numberlookup/README.md new file mode 100644 index 000000000..bbb2b0d1e --- /dev/null +++ b/examples/snippets/src/main/java/numberlookup/README.md @@ -0,0 +1,7 @@ +# Number Lookup snippets +Sinch Java SDK Code Snippets Repository for NumberLookup APIs + +See main [README.md](../../../../README.md) for how to execute snippets + +- Number Lookup + - [numberlookup/Lookup](Lookup.java) From 0824c82f0c43f6b737c0b53b523526072cd8f4ef Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Fri, 12 Jun 2026 09:18:55 +0200 Subject: [PATCH 14/37] Added workflow to publish generated documentation on Sinch for developers --- .../generate-upload-documentation.yml | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 .github/workflows/generate-upload-documentation.yml diff --git a/.github/workflows/generate-upload-documentation.yml b/.github/workflows/generate-upload-documentation.yml new file mode 100644 index 000000000..b2a5fd038 --- /dev/null +++ b/.github/workflows/generate-upload-documentation.yml @@ -0,0 +1,78 @@ +name: Generate and upload documentation + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: 'Version to use for the documentation package in semver format (e.g. 1.2.3)' + required: true + +jobs: + upload-documentation: + runs-on: ubuntu-latest + env: + SDK_NAME: sinch-sdk-java + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Resolve Version + id: version + run: | + if [ "${{ github.event_name }}" = "release" ]; then + VERSION="${{ github.event.release.tag_name }}" + else + VERSION="${{ inputs.version }}" + fi + # Strip leading 'v' if present (e.g. v1.2.3 → 1.2.3) + VERSION="${VERSION#v}" + echo "value=${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Validate Version Format + run: | + VERSION="${{ steps.version.outputs.value }}" + SEMVER_REGEX='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((alpha|beta|preview)(\.[0-9]+)?))?$' + if [[ ! "$VERSION" =~ $SEMVER_REGEX ]]; then + echo "::error::Invalid version format: '$VERSION'. Expected semver (e.g. 1.2.3, 1.2.3-alpha, 1.2.3-beta.1, 1.2.3-preview)" + exit 1 + fi + echo "Version '$VERSION' is valid" + + - name: Setup Java SDK + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + + - name: Generate Documentation + run: mvn -B javadoc:javadoc --file pom.xml + + - name: Package Documentation + run: | + cd target/site/apidocs + zip -r "../../../${{ env.SDK_NAME }}-${{ steps.version.outputs.value }}.zip" . + + - name: Upload to GitLab Registry + run: | + echo "Uploading documentation package to GitLab Registry..." + VERSION="${{ steps.version.outputs.value }}" + curl --fail --show-error --location --header "PRIVATE-TOKEN: ${{ secrets.GITLAB_REGISTRY_UPLOAD_DOC_TOKEN }}" \ + --upload-file "./${{ env.SDK_NAME }}-${VERSION}.zip" \ + "https://gitlab.com/api/v4/projects/63164411/packages/generic/${{ env.SDK_NAME }}/${VERSION}/${{ env.SDK_NAME }}-${VERSION}.zip" + echo "Documentation package for version ${VERSION} uploaded to GitLab Registry" + + - name: Trigger Downstream GitLab Pipeline + run: | + echo "Triggering downstream GitLab pipeline to notify about new documentation package version..." + VERSION="${{ steps.version.outputs.value }}" + curl --fail --show-error --location --request POST \ + --form "token=${{ secrets.GITLAB_NOTIFY_REGISTRY_UPLOADED_DOC_TOKEN }}" \ + --form "ref=main" \ + --form "variables[UPSTREAM_PACKAGE_NAME]=${{ env.SDK_NAME }}" \ + --form "variables[UPSTREAM_PACKAGE_VERSION]=${VERSION}" \ + "https://gitlab.com/api/v4/projects/63164411/trigger/pipeline" + echo "Documentation repo notified about new package version ${VERSION}" From dcf44cc982f67c08e5f5ebb153f9539a1db25861 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Tue, 16 Jun 2026 10:42:32 +0200 Subject: [PATCH 15/37] Voice: use SvamlControlPatch for updateCall and manageCallWithCallLeg --- .../v1/svaml/action/SvamlActionPatch.java | 9 + .../api/v1/adapters/CallsServiceTest.java | 9 +- .../sdk/e2e/domains/voice/v1/CallsSteps.java | 14 +- .../domains/voice/api/v1/CallsService.java | 10 +- .../api/v1/adapters/CallsServiceImpl.java | 25 +- .../v1/calls/request/SvamlActionPatch.java | 16 + .../calls/request/SvamlActionPatchImpl.java | 362 ++++++++++++++++++ .../v1/calls/request/SvamlControlPatch.java | 78 ++++ .../calls/request/SvamlControlPatchImpl.java | 124 ++++++ .../v1/svaml/action/SvamlActionContinue.java | 3 +- .../svaml/action/SvamlActionContinueImpl.java | 4 +- .../v1/svaml/action/SvamlActionHangup.java | 3 +- .../svaml/action/SvamlActionHangupImpl.java | 4 +- .../v1/svaml/action/SvamlActionPark.java | 3 +- .../v1/svaml/action/SvamlActionParkImpl.java | 4 +- .../v1/svaml/SvamlControlPatchTest.java | 39 ++ .../voice/v1/svaml/SvamlControlPatchDto.json | 12 + 17 files changed, 685 insertions(+), 34 deletions(-) create mode 100644 client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPatch.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatch.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatchImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatch.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatchImpl.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/svaml/SvamlControlPatchTest.java create mode 100644 openapi-contracts/src/test/resources/domains/voice/v1/svaml/SvamlControlPatchDto.json diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPatch.java b/client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPatch.java new file mode 100644 index 000000000..aac08730d --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPatch.java @@ -0,0 +1,9 @@ +package com.sinch.sdk.domains.voice.models.v1.svaml.action; + +/** + * Base class related to SVAML actions available for PATCH call operations (updateCall and + * manageCallWithCallLeg). Only hangup, continue and park actions are supported. + * + * @since 2.1 + */ +public interface SvamlActionPatch {} diff --git a/client/src/test/java/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceTest.java index 3d158a320..1253b9881 100644 --- a/client/src/test/java/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceTest.java @@ -23,7 +23,7 @@ import com.sinch.sdk.domains.voice.models.v1.calls.CallInformationTest; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallLeg; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation; -import com.sinch.sdk.domains.voice.models.v1.svaml.SvamlControlTest; +import com.sinch.sdk.domains.voice.models.v1.svaml.SvamlControlPatchTest; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -38,7 +38,7 @@ public class CallsServiceTest extends BaseTest { @GivenTextResource("/domains/voice/v1/calls/GetCallInformationResponseDto.json") String getCallInformationResponseDto; - @GivenTextResource("/domains/voice/v1/svaml/SvamlControlDto.json") + @GivenTextResource("/domains/voice/v1/svaml/SvamlControlPatchDto.json") String svamlControlDto; static final Collection AUTH_NAMES = Arrays.asList("Basic", "Signed"); @@ -108,7 +108,7 @@ void update() throws ApiException { argThat(new HttpRequestMatcher(httpRequest)))) .thenReturn(httpResponse); - service.update("call/id", SvamlControlTest.expectedSvamlControl); + service.update("call/id", SvamlControlPatchTest.expectedSvamlControlPatch); } @Test @@ -132,6 +132,7 @@ void manageWithCallLeg() throws ApiException { argThat(new HttpRequestMatcher(httpRequest)))) .thenReturn(httpResponse); - service.manageWithCallLeg("call/id", CallLeg.BOTH, SvamlControlTest.expectedSvamlControl); + service.manageWithCallLeg( + "call/id", CallLeg.BOTH, SvamlControlPatchTest.expectedSvamlControlPatch); } } diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/CallsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/CallsSteps.java index 6e32a5dbf..34a1452f6 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/CallsSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/CallsSteps.java @@ -5,13 +5,13 @@ import com.sinch.sdk.domains.voice.api.v1.CallsService; import com.sinch.sdk.domains.voice.models.v1.Price; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallLeg; +import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation.DomainEnum; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation.ReasonEnum; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation.StatusEnum; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallResult; import com.sinch.sdk.domains.voice.models.v1.destination.DestinationPstn; -import com.sinch.sdk.domains.voice.models.v1.svaml.SvamlControl; import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionContinue; import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangup; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstructionPlayFiles; @@ -46,8 +46,8 @@ public void getCall() { @When("^I send a request to update a call$") public void updateCall() { - SvamlControl request = - SvamlControl.builder() + SvamlControlPatch request = + SvamlControlPatch.builder() .setInstructions( Arrays.asList( SvamlInstructionSay.builder() @@ -63,8 +63,8 @@ public void updateCall() { @When("^I send a request to update a call that doesn't exist$") public void updateCallNotExits() { - SvamlControl request = - SvamlControl.builder() + SvamlControlPatch request = + SvamlControlPatch.builder() .setInstructions( Arrays.asList( SvamlInstructionSay.builder() @@ -83,8 +83,8 @@ public void updateCallNotExits() { @When("^I send a request to manage a call with callLeg$") public void manageCallWithCallLeg() { - SvamlControl request = - SvamlControl.builder() + SvamlControlPatch request = + SvamlControlPatch.builder() .setInstructions( Arrays.asList( SvamlInstructionPlayFiles.builder() diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/CallsService.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/CallsService.java index 6aa50ceb5..28f0b209b 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/CallsService.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/CallsService.java @@ -12,8 +12,8 @@ import com.sinch.sdk.core.exceptions.ApiException; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallLeg; +import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation; -import com.sinch.sdk.domains.voice.models.v1.svaml.SvamlControl; /** Calls Service */ public interface CallsService { @@ -53,10 +53,10 @@ public interface CallsService { * `caller`.<br><Warning>The `callLeg` identifier is ignored * for calls that are part of a conference and calls initiated using the Callout * API.</Warning> (required) - * @param svamlControl (optional) + * @param svamlControlPatch (optional) * @throws ApiException if fails to make API call */ - void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControl svamlControl) + void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControlPatch svamlControlPatch) throws ApiException; /** @@ -70,8 +70,8 @@ void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControl svamlControl * * @param callId The unique identifier of the call. This value is generated by the system. * (required) - * @param svamlControl (optional) + * @param svamlControlPatch (optional) * @throws ApiException if fails to make API call */ - void update(String callId, SvamlControl svamlControl) throws ApiException; + void update(String callId, SvamlControlPatch svamlControlPatch) throws ApiException; } diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceImpl.java index afd380188..1c146f8b0 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceImpl.java @@ -24,8 +24,8 @@ import com.sinch.sdk.core.http.URLPathUtils; import com.sinch.sdk.core.models.ServerConfiguration; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallLeg; +import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation; -import com.sinch.sdk.domains.voice.models.v1.svaml.SvamlControl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -109,7 +109,7 @@ private HttpRequest getRequestBuilder(String callId) throws ApiException { } @Override - public void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControl svamlControl) + public void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControlPatch svamlControlPatch) throws ApiException { LOGGER.finest( @@ -121,10 +121,10 @@ public void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControl svaml + "callLeg: " + callLeg + ", " - + "svamlControl: " - + svamlControl); + + "svamlControlPatch: " + + svamlControlPatch); - HttpRequest httpRequest = manageWithCallLegRequestBuilder(callId, callLeg, svamlControl); + HttpRequest httpRequest = manageWithCallLegRequestBuilder(callId, callLeg, svamlControlPatch); HttpResponse response = httpClient.invokeAPI( this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); @@ -142,7 +142,7 @@ public void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControl svaml } private HttpRequest manageWithCallLegRequestBuilder( - String callId, CallLeg callLeg, SvamlControl svamlControl) throws ApiException { + String callId, CallLeg callLeg, SvamlControlPatch svamlControlPatch) throws ApiException { // verify the required parameter 'callId' is set if (callId == null) { throw new ApiException( @@ -169,7 +169,7 @@ private HttpRequest manageWithCallLegRequestBuilder( final Collection localVarContentTypes = Arrays.asList("application/json"); final Collection localVarAuthNames = Arrays.asList("Basic", "Signed"); - final String serializedBody = mapper.serialize(localVarContentTypes, svamlControl); + final String serializedBody = mapper.serialize(localVarContentTypes, svamlControlPatch); return new HttpRequest( localVarPath, @@ -183,11 +183,12 @@ private HttpRequest manageWithCallLegRequestBuilder( } @Override - public void update(String callId, SvamlControl svamlControl) throws ApiException { + public void update(String callId, SvamlControlPatch svamlControlPatch) throws ApiException { - LOGGER.finest("[update]" + " " + "callId: " + callId + ", " + "svamlControl: " + svamlControl); + LOGGER.finest( + "[update]" + " " + "callId: " + callId + ", " + "svamlControlPatch: " + svamlControlPatch); - HttpRequest httpRequest = updateRequestBuilder(callId, svamlControl); + HttpRequest httpRequest = updateRequestBuilder(callId, svamlControlPatch); HttpResponse response = httpClient.invokeAPI( this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); @@ -204,7 +205,7 @@ public void update(String callId, SvamlControl svamlControl) throws ApiException mapper.deserialize(response, new TypeReference>() {})); } - private HttpRequest updateRequestBuilder(String callId, SvamlControl svamlControl) + private HttpRequest updateRequestBuilder(String callId, SvamlControlPatch svamlControlPatch) throws ApiException { // verify the required parameter 'callId' is set if (callId == null) { @@ -225,7 +226,7 @@ private HttpRequest updateRequestBuilder(String callId, SvamlControl svamlContro final Collection localVarContentTypes = Arrays.asList("application/json"); final Collection localVarAuthNames = Arrays.asList("Basic", "Signed"); - final String serializedBody = mapper.serialize(localVarContentTypes, svamlControl); + final String serializedBody = mapper.serialize(localVarContentTypes, svamlControlPatch); return new HttpRequest( localVarPath, diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatch.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatch.java new file mode 100644 index 000000000..c618d66f4 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatch.java @@ -0,0 +1,16 @@ +/* + * Voice API | Sinch + * + * OpenAPI document version: 1.0.1 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.voice.models.v1.calls.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(using = SvamlActionPatchImpl.SvamlActionPatchImplDeserializer.class) +public interface SvamlActionPatch {} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatchImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatchImpl.java new file mode 100644 index 000000000..98f6e10bf --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatchImpl.java @@ -0,0 +1,362 @@ +package com.sinch.sdk.domains.voice.models.v1.calls.request; + +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.core.type.TypeReference; +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 com.sinch.sdk.core.models.AbstractOpenApiSchema; +import com.sinch.sdk.core.utils.databind.JSONNavigator; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionContinueImpl; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangupImpl; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionParkImpl; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +@JsonDeserialize(using = SvamlActionPatchImpl.SvamlActionPatchImplDeserializer.class) +@JsonSerialize(using = SvamlActionPatchImpl.SvamlActionPatchImplSerializer.class) +public class SvamlActionPatchImpl extends AbstractOpenApiSchema implements SvamlActionPatch { + private static final Logger log = Logger.getLogger(SvamlActionPatchImpl.class.getName()); + + public static final class SvamlActionPatchImplSerializer + extends StdSerializer { + private static final long serialVersionUID = 1L; + + public SvamlActionPatchImplSerializer(Class t) { + super(t); + } + + public SvamlActionPatchImplSerializer() { + this(null); + } + + @Override + public void serialize( + SvamlActionPatchImpl value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static final class SvamlActionPatchImplDeserializer + extends StdDeserializer { + + private static final long serialVersionUID = 1L; + + public SvamlActionPatchImplDeserializer() { + this(SvamlActionPatchImpl.class); + } + + public SvamlActionPatchImplDeserializer(Class vc) { + super(vc); + } + + @Override + public SvamlActionPatchImpl deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + Object deserialized = null; + SvamlActionPatchImpl newSvamlActionPatchImpl = new SvamlActionPatchImpl(); + Map result2 = + tree.traverse(jp.getCodec()).readValueAs(new TypeReference>() {}); + String discriminatorValue = (String) result2.get("name"); + switch (discriminatorValue) { + case "continue": + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionContinueImpl.class); + newSvamlActionPatchImpl.setActualInstance(deserialized); + return newSvamlActionPatchImpl; + case "hangup": + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionHangupImpl.class); + newSvamlActionPatchImpl.setActualInstance(deserialized); + return newSvamlActionPatchImpl; + case "park": + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionParkImpl.class); + newSvamlActionPatchImpl.setActualInstance(deserialized); + return newSvamlActionPatchImpl; + default: + log.log( + Level.WARNING, + String.format( + "Failed to lookup discriminator value `%s` for SvamlActionPatchImpl. Possible" + + " values: continue hangup park", + discriminatorValue)); + } + + boolean typeCoercion = ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS); + int match = 0; + JsonToken token = tree.traverse(jp.getCodec()).nextToken(); + // deserialize SvamlActionContinueImpl + try { + boolean attemptParsing = true; + // ensure that we respect type coercion as set on the client ObjectMapper + if (SvamlActionContinueImpl.class.equals(Integer.class) + || SvamlActionContinueImpl.class.equals(Long.class) + || SvamlActionContinueImpl.class.equals(Float.class) + || SvamlActionContinueImpl.class.equals(Double.class) + || SvamlActionContinueImpl.class.equals(Boolean.class) + || SvamlActionContinueImpl.class.equals(String.class)) { + attemptParsing = typeCoercion; + if (!attemptParsing) { + attemptParsing |= + ((SvamlActionContinueImpl.class.equals(Integer.class) + || SvamlActionContinueImpl.class.equals(Long.class)) + && token == JsonToken.VALUE_NUMBER_INT); + attemptParsing |= + ((SvamlActionContinueImpl.class.equals(Float.class) + || SvamlActionContinueImpl.class.equals(Double.class)) + && token == JsonToken.VALUE_NUMBER_FLOAT); + attemptParsing |= + (SvamlActionContinueImpl.class.equals(Boolean.class) + && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE)); + attemptParsing |= + (SvamlActionContinueImpl.class.equals(String.class) + && token == JsonToken.VALUE_STRING); + } + } + if (attemptParsing) { + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionContinueImpl.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 'SvamlActionContinueImpl'"); + } + } catch (Exception e) { + // deserialization failed, continue + log.log(Level.FINER, "Input data does not match schema 'SvamlActionContinueImpl'", e); + } + + // deserialize SvamlActionHangupImpl + try { + boolean attemptParsing = true; + // ensure that we respect type coercion as set on the client ObjectMapper + if (SvamlActionHangupImpl.class.equals(Integer.class) + || SvamlActionHangupImpl.class.equals(Long.class) + || SvamlActionHangupImpl.class.equals(Float.class) + || SvamlActionHangupImpl.class.equals(Double.class) + || SvamlActionHangupImpl.class.equals(Boolean.class) + || SvamlActionHangupImpl.class.equals(String.class)) { + attemptParsing = typeCoercion; + if (!attemptParsing) { + attemptParsing |= + ((SvamlActionHangupImpl.class.equals(Integer.class) + || SvamlActionHangupImpl.class.equals(Long.class)) + && token == JsonToken.VALUE_NUMBER_INT); + attemptParsing |= + ((SvamlActionHangupImpl.class.equals(Float.class) + || SvamlActionHangupImpl.class.equals(Double.class)) + && token == JsonToken.VALUE_NUMBER_FLOAT); + attemptParsing |= + (SvamlActionHangupImpl.class.equals(Boolean.class) + && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE)); + attemptParsing |= + (SvamlActionHangupImpl.class.equals(String.class) + && token == JsonToken.VALUE_STRING); + } + } + if (attemptParsing) { + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionHangupImpl.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 'SvamlActionHangupImpl'"); + } + } catch (Exception e) { + // deserialization failed, continue + log.log(Level.FINER, "Input data does not match schema 'SvamlActionHangupImpl'", e); + } + + // deserialize SvamlActionParkImpl + try { + boolean attemptParsing = true; + // ensure that we respect type coercion as set on the client ObjectMapper + if (SvamlActionParkImpl.class.equals(Integer.class) + || SvamlActionParkImpl.class.equals(Long.class) + || SvamlActionParkImpl.class.equals(Float.class) + || SvamlActionParkImpl.class.equals(Double.class) + || SvamlActionParkImpl.class.equals(Boolean.class) + || SvamlActionParkImpl.class.equals(String.class)) { + attemptParsing = typeCoercion; + if (!attemptParsing) { + attemptParsing |= + ((SvamlActionParkImpl.class.equals(Integer.class) + || SvamlActionParkImpl.class.equals(Long.class)) + && token == JsonToken.VALUE_NUMBER_INT); + attemptParsing |= + ((SvamlActionParkImpl.class.equals(Float.class) + || SvamlActionParkImpl.class.equals(Double.class)) + && token == JsonToken.VALUE_NUMBER_FLOAT); + attemptParsing |= + (SvamlActionParkImpl.class.equals(Boolean.class) + && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE)); + attemptParsing |= + (SvamlActionParkImpl.class.equals(String.class) && token == JsonToken.VALUE_STRING); + } + } + if (attemptParsing) { + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionParkImpl.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 'SvamlActionParkImpl'"); + } + } catch (Exception e) { + // deserialization failed, continue + log.log(Level.FINER, "Input data does not match schema 'SvamlActionParkImpl'", e); + } + + if (match == 1) { + SvamlActionPatchImpl ret = new SvamlActionPatchImpl(); + ret.setActualInstance(deserialized); + return ret; + } + throw new IOException( + String.format( + "Failed deserialization for SvamlActionPatchImpl: %d classes match result, expected" + + " 1", + match)); + } + + /** Handle deserialization of the 'null' value. */ + @Override + public SvamlActionPatchImpl getNullValue(DeserializationContext ctxt) + throws JsonMappingException { + throw new JsonMappingException(ctxt.getParser(), "SvamlActionPatchImpl cannot be null"); + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap<>(); + + public SvamlActionPatchImpl() { + super("oneOf", Boolean.FALSE); + } + + public SvamlActionPatchImpl(SvamlActionContinueImpl o) { + super("oneOf", Boolean.FALSE); + setActualInstance(o); + } + + public SvamlActionPatchImpl(SvamlActionHangupImpl o) { + super("oneOf", Boolean.FALSE); + setActualInstance(o); + } + + public SvamlActionPatchImpl(SvamlActionParkImpl o) { + super("oneOf", Boolean.FALSE); + setActualInstance(o); + } + + static { + schemas.put("SvamlActionContinueImpl", SvamlActionContinueImpl.class); + schemas.put("SvamlActionHangupImpl", SvamlActionHangupImpl.class); + schemas.put("SvamlActionParkImpl", SvamlActionParkImpl.class); + JSONNavigator.registerDescendants( + SvamlActionPatchImpl.class, Collections.unmodifiableMap(schemas)); + // Initialize and register the discriminator mappings. + Map> mappings = new HashMap>(); + mappings.put("continue", SvamlActionContinueImpl.class); + mappings.put("hangup", SvamlActionHangupImpl.class); + mappings.put("park", SvamlActionParkImpl.class); + mappings.put("svaml.action.patch", SvamlActionPatchImpl.class); + JSONNavigator.registerDiscriminator(SvamlActionPatchImpl.class, "name", mappings); + } + + @Override + public Map> getSchemas() { + return SvamlActionPatchImpl.schemas; + } + + /** + * Set the instance that matches the oneOf child schema, check the instance parameter is valid + * against the oneOf child schemas: SvamlActionContinueImpl, SvamlActionHangupImpl, + * SvamlActionParkImpl + * + *

It could be an instance of the 'oneOf' schemas. The oneOf child schemas may themselves be a + * composed schema (allOf, anyOf, oneOf). + */ + @Override + public void setActualInstance(Object instance) { + if (JSONNavigator.isInstanceOf( + SvamlActionContinueImpl.class, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + + if (JSONNavigator.isInstanceOf( + SvamlActionHangupImpl.class, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + + if (JSONNavigator.isInstanceOf(SvamlActionParkImpl.class, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + + throw new RuntimeException( + "Invalid instance type. Must be SvamlActionContinueImpl, SvamlActionHangupImpl," + + " SvamlActionParkImpl"); + } + + /** + * Get the actual instance, which can be the following: SvamlActionContinueImpl, + * SvamlActionHangupImpl, SvamlActionParkImpl + * + * @return The actual instance (SvamlActionContinueImpl, SvamlActionHangupImpl, + * SvamlActionParkImpl) + */ + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + /** + * Get the actual instance of `SvamlActionContinueImpl`. If the actual instance is not + * `SvamlActionContinueImpl`, the ClassCastException will be thrown. + * + * @return The actual instance of `SvamlActionContinueImpl` + * @throws ClassCastException if the instance is not `SvamlActionContinueImpl` + */ + public SvamlActionContinueImpl getSvamlActionContinueImpl() throws ClassCastException { + return (SvamlActionContinueImpl) super.getActualInstance(); + } + + /** + * Get the actual instance of `SvamlActionHangupImpl`. If the actual instance is not + * `SvamlActionHangupImpl`, the ClassCastException will be thrown. + * + * @return The actual instance of `SvamlActionHangupImpl` + * @throws ClassCastException if the instance is not `SvamlActionHangupImpl` + */ + public SvamlActionHangupImpl getSvamlActionHangupImpl() throws ClassCastException { + return (SvamlActionHangupImpl) super.getActualInstance(); + } + + /** + * Get the actual instance of `SvamlActionParkImpl`. If the actual instance is not + * `SvamlActionParkImpl`, the ClassCastException will be thrown. + * + * @return The actual instance of `SvamlActionParkImpl` + * @throws ClassCastException if the instance is not `SvamlActionParkImpl` + */ + public SvamlActionParkImpl getSvamlActionParkImpl() throws ClassCastException { + return (SvamlActionParkImpl) super.getActualInstance(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatch.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatch.java new file mode 100644 index 000000000..b48ca0ffb --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatch.java @@ -0,0 +1,78 @@ +/* + * Voice API | Sinch + * + * OpenAPI document version: 1.0.1 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.voice.models.v1.calls.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; +import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; +import java.util.Collection; + +/** + * SVAML is a call control markup language. When a server receives a callback event from the Sinch + * platform, it can respond with a SVAML object to control the voice call. The following is an + * example of a SVAML object type and its contents. + */ +@JsonDeserialize(builder = SvamlControlPatchImpl.Builder.class) +public interface SvamlControlPatch extends com.sinch.sdk.domains.voice.models.v1.svaml.Control { + + /** + * The collection of instructions that can perform various tasks during the call. You can include + * as many instructions as necessary. + * + * @return instructions + */ + Collection getInstructions(); + + /** + * Get action + * + * @return action + */ + SvamlActionPatch getAction(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SvamlControlPatchImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param instructions see getter + * @return Current builder + * @see #getInstructions + */ + Builder setInstructions(Collection instructions); + + /** + * see getter + * + * @param action see getter + * @return Current builder + * @see #getAction + */ + Builder setAction(SvamlActionPatch action); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + SvamlControlPatch build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatchImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatchImpl.java new file mode 100644 index 000000000..8bff22441 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatchImpl.java @@ -0,0 +1,124 @@ +package com.sinch.sdk.domains.voice.models.v1.calls.request; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; +import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; +import java.util.Collection; +import java.util.Objects; + +@JsonPropertyOrder({ + SvamlControlPatchImpl.JSON_PROPERTY_INSTRUCTIONS, + SvamlControlPatchImpl.JSON_PROPERTY_ACTION +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SvamlControlPatchImpl + implements SvamlControlPatch, com.sinch.sdk.domains.voice.models.v1.svaml.Control { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_INSTRUCTIONS = "instructions"; + + private OptionalValue> instructions; + + public static final String JSON_PROPERTY_ACTION = "action"; + + private OptionalValue action; + + public SvamlControlPatchImpl() {} + + protected SvamlControlPatchImpl( + OptionalValue> instructions, + OptionalValue action) { + this.instructions = instructions; + this.action = action; + } + + @JsonIgnore + public Collection getInstructions() { + return instructions.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_INSTRUCTIONS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> instructions() { + return instructions; + } + + @JsonIgnore + public SvamlActionPatch getAction() { + return action.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ACTION) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue action() { + return action; + } + + /** Return true if this UpdateCallSVAMLRequestBody object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SvamlControlPatchImpl updateCallSVAMLRequestBody = (SvamlControlPatchImpl) o; + return Objects.equals(this.instructions, updateCallSVAMLRequestBody.instructions) + && Objects.equals(this.action, updateCallSVAMLRequestBody.action); + } + + @Override + public int hashCode() { + return Objects.hash(instructions, action); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SvamlControlPatchImpl {\n"); + sb.append(" instructions: ").append(toIndentedString(instructions)).append("\n"); + sb.append(" action: ").append(toIndentedString(action)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements SvamlControlPatch.Builder { + OptionalValue> instructions = OptionalValue.empty(); + OptionalValue action = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_INSTRUCTIONS) + public Builder setInstructions(Collection instructions) { + this.instructions = OptionalValue.of(instructions); + return this; + } + + @JsonProperty(JSON_PROPERTY_ACTION) + public Builder setAction(SvamlActionPatch action) { + this.action = OptionalValue.of(action); + return this; + } + + public SvamlControlPatch build() { + return new SvamlControlPatchImpl(instructions, action); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinue.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinue.java index 37240e504..784c2b6e9 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinue.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinue.java @@ -23,7 +23,8 @@ */ @JsonDeserialize(builder = SvamlActionContinueImpl.Builder.class) public interface SvamlActionContinue - extends com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction { + extends com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, + com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { /** ready to use action to send a `continue` */ SvamlActionContinue SVAML_ACTION_CONTINUE = SvamlActionContinue.builder().build(); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinueImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinueImpl.java index 09c3450e7..91f182b55 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinueImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinueImpl.java @@ -13,7 +13,9 @@ @JsonFilter("uninitializedFilter") @JsonInclude(value = JsonInclude.Include.CUSTOM) public class SvamlActionContinueImpl - implements SvamlActionContinue, com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction { + implements SvamlActionContinue, + com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, + com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { private static final long serialVersionUID = 1L; public static final String JSON_PROPERTY_NAME = "name"; diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangup.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangup.java index c40d66663..419dbc3b6 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangup.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangup.java @@ -25,7 +25,8 @@ */ @JsonDeserialize(builder = SvamlActionHangupImpl.Builder.class) public interface SvamlActionHangup - extends com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction { + extends com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, + com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { /** ready to use action to send a `hangup` */ SvamlActionHangup SVAML_ACTION_HANGUP = SvamlActionHangup.builder().build(); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangupImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangupImpl.java index 9cdac58e9..3d938e358 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangupImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangupImpl.java @@ -13,7 +13,9 @@ @JsonFilter("uninitializedFilter") @JsonInclude(value = JsonInclude.Include.CUSTOM) public class SvamlActionHangupImpl - implements SvamlActionHangup, com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction { + implements SvamlActionHangup, + com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, + com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { private static final long serialVersionUID = 1L; public static final String JSON_PROPERTY_NAME = "name"; diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPark.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPark.java index db8f2bdf2..69b131dfb 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPark.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPark.java @@ -24,7 +24,8 @@ */ @JsonDeserialize(builder = SvamlActionParkImpl.Builder.class) public interface SvamlActionPark - extends com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction { + extends com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, + com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { /** The name property. Must have the value park. */ public class NameEnum extends EnumDynamic { diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionParkImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionParkImpl.java index d66651404..72fe17653 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionParkImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionParkImpl.java @@ -19,7 +19,9 @@ @JsonFilter("uninitializedFilter") @JsonInclude(value = JsonInclude.Include.CUSTOM) public class SvamlActionParkImpl - implements SvamlActionPark, com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction { + implements SvamlActionPark, + com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, + com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { private static final long serialVersionUID = 1L; public static final String JSON_PROPERTY_NAME = "name"; diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/svaml/SvamlControlPatchTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/svaml/SvamlControlPatchTest.java new file mode 100644 index 000000000..3ffb58741 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/svaml/SvamlControlPatchTest.java @@ -0,0 +1,39 @@ +package com.sinch.sdk.domains.voice.models.v1.svaml; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangup; +import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstructionSay; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class SvamlControlPatchTest extends BaseTest { + + public static SvamlControlPatch expectedSvamlControlPatch = + SvamlControlPatch.builder() + .setAction(SvamlActionHangup.SVAML_ACTION_HANGUP) + .setInstructions( + Arrays.asList( + SvamlInstructionSay.builder() + .setText("Sorry, the conference has been cancelled. The call will end now.") + .setLocale("en-US") + .build())) + .build(); + + @GivenTextResource("/domains/voice/v1/svaml/SvamlControlPatchDto.json") + String jsonSvamlControlPatchDto; + + @Test + void serializeSVAMLResponse() throws JsonProcessingException, JSONException { + + String serializedString = objectMapper.writeValueAsString(expectedSvamlControlPatch); + + JSONAssert.assertEquals(jsonSvamlControlPatchDto, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v1/svaml/SvamlControlPatchDto.json b/openapi-contracts/src/test/resources/domains/voice/v1/svaml/SvamlControlPatchDto.json new file mode 100644 index 000000000..1640c9049 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v1/svaml/SvamlControlPatchDto.json @@ -0,0 +1,12 @@ +{ + "instructions": [ + { + "name": "say", + "text": "Sorry, the conference has been cancelled. The call will end now.", + "locale": "en-US" + } + ], + "action": { + "name": "hangup" + } +} From b857f9d4042c876c509c2ab690823830717c4be4 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Tue, 16 Jun 2026 10:53:05 +0200 Subject: [PATCH 16/37] Updated references to SvamlPatch in the snippets --- .../src/main/java/voice/calls/ManageWithCallLeg.java | 10 +++++----- .../snippets/src/main/java/voice/calls/Update.java | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java b/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java index 47c74a033..13fb16d46 100644 --- a/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java +++ b/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java @@ -10,9 +10,9 @@ import com.sinch.sdk.SinchClient; import com.sinch.sdk.domains.voice.api.v1.CallsService; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallLeg; -import com.sinch.sdk.domains.voice.models.v1.svaml.SvamlControl; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction; +import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangup; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstructionSay; import com.sinch.sdk.models.Configuration; @@ -47,7 +47,7 @@ public static void main(String[] args) { LOGGER.info(String.format("Manage call with ID '%s'", callId)); - SvamlAction action = SvamlActionHangup.SVAML_ACTION_HANGUP; + SvamlActionPatch action = SvamlActionHangup.SVAML_ACTION_HANGUP; Collection instructions = Collections.singletonList( @@ -55,8 +55,8 @@ public static void main(String[] args) { .setText("Hello, the call is over, hanging up now. Goodbye") .build()); - SvamlControl request = - SvamlControl.builder().setInstructions(instructions).setAction(action).build(); + SvamlControlPatch request = + SvamlControlPatch.builder().setInstructions(instructions).setAction(action).build(); callsService.manageWithCallLeg(callId, callLeg, request); diff --git a/examples/snippets/src/main/java/voice/calls/Update.java b/examples/snippets/src/main/java/voice/calls/Update.java index 9d5cbad45..d6d84f461 100644 --- a/examples/snippets/src/main/java/voice/calls/Update.java +++ b/examples/snippets/src/main/java/voice/calls/Update.java @@ -9,9 +9,9 @@ import com.sinch.sdk.SinchClient; import com.sinch.sdk.domains.voice.api.v1.CallsService; -import com.sinch.sdk.domains.voice.models.v1.svaml.SvamlControl; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction; +import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangup; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstructionSay; import com.sinch.sdk.models.Configuration; @@ -34,7 +34,7 @@ public static void main(String[] args) { // The instruction to be performed SvamlInstruction instruction = SvamlInstructionSay.builder().setText("Goodbye").build(); // The instruction to add to the call - SvamlAction action = SvamlActionHangup.SVAML_ACTION_HANGUP; + SvamlActionPatch action = SvamlActionHangup.SVAML_ACTION_HANGUP; Configuration configuration = Configuration.builder() @@ -50,8 +50,8 @@ public static void main(String[] args) { Collection instructions = Collections.singletonList(instruction); - SvamlControl request = - SvamlControl.builder().setInstructions(instructions).setAction(action).build(); + SvamlControlPatch request = + SvamlControlPatch.builder().setInstructions(instructions).setAction(action).build(); callsService.update(callId, request); From b9bb6ae8952b04a622cffe649f83d3e0bac2689f Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Tue, 16 Jun 2026 16:29:20 +0200 Subject: [PATCH 17/37] Refactor e2e tests for list responses --- .../domains/conversation/ContactsSteps.java | 49 +++++------------- .../conversation/ConversationsSteps.java | 48 +++++------------ .../e2e/domains/conversation/EventsSteps.java | 24 +++------ .../domains/conversation/MessagesSteps.java | 51 +++++-------------- .../e2e/domains/numbers/v1/NumbersSteps.java | 6 +-- .../sdk/e2e/domains/sms/v1/BatchesSteps.java | 18 +++---- .../domains/sms/v1/DeliveryReportsSteps.java | 17 +++---- .../sdk/e2e/domains/sms/v1/GroupsSteps.java | 16 +++--- .../sdk/e2e/domains/sms/v1/InboundsSteps.java | 17 +++---- 9 files changed, 73 insertions(+), 173 deletions(-) diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ContactsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ContactsSteps.java index 6827278d8..8bb48a5a2 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ContactsSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ContactsSteps.java @@ -30,16 +30,12 @@ public class ContactsSteps { ContactsService service; Contact createResponse; ContactsListResponse listPageResponse; - ContactsListResponse listAllResponse; - ContactsListResponse listPageIterateResponse; Contact getResponse; Contact updateResponse; Contact mergeResponse; boolean deletePassed; GetChannelProfileResponse channelProfileByContactIdResponse; IdentityConflictsListResponse listIdentityConflictsResponse; - IdentityConflictsListResponse listAllIdentityConflictsResponse; - IdentityConflictsListResponse listPageIterateIdentityConflictsResponse; @Given("^the Conversation service \"Contacts\" is available$") public void serviceAvailable() { @@ -78,7 +74,7 @@ public void listAll() { ContactsListQueryParameters request = ContactsListQueryParameters.builder().setPageSize(2).build(); - listAllResponse = service.list(request); + listPageResponse = service.list(request); } @When("^I iterate manually over the contacts pages$") @@ -86,7 +82,7 @@ public void listPageIterate() { ContactsListQueryParameters request = ContactsListQueryParameters.builder().setPageSize(2).build(); - listPageIterateResponse = service.list(request); + listPageResponse = service.list(request); } @When("^I send a request to retrieve a contact$") @@ -156,7 +152,7 @@ public void listIdentityConflictsAll() { IdentityConflictsListQueryParameters request = IdentityConflictsListQueryParameters.builder().setPageSize(2).build(); - listAllIdentityConflictsResponse = service.listIdentityConflicts(request); + listIdentityConflictsResponse = service.listIdentityConflicts(request); } @When("^I iterate manually over the identity conflicts pages$") @@ -164,7 +160,7 @@ public void listIdentityConflictsPageIterate() { IdentityConflictsListQueryParameters request = IdentityConflictsListQueryParameters.builder().setPageSize(2).build(); - listPageIterateIdentityConflictsResponse = service.listIdentityConflicts(request); + listIdentityConflictsResponse = service.listIdentityConflicts(request); } @Then("the contact is created") @@ -181,15 +177,7 @@ public void listExistingResult(int size) { @Then("the contacts list contains \"{int}\" contacts") public void listAllResult(int size) { - // FIXME: to be thread-safe compliant we need to check which variables are set - Iterator iterator = null; - if (null != listAllResponse) { - iterator = listAllResponse.iterator(); - } - - if (null != listPageIterateResponse) { - iterator = listPageIterateResponse.iterator(); - } + Iterator iterator = listPageResponse.iterator(); TestHelpers.checkIteratorItems(iterator, size); } @@ -197,15 +185,13 @@ public void listAllResult(int size) { public void listPageIterateResult(int size) { int pageCount = 0; - - ContactsListResponse listPageIterateResponseThreadSafety = listPageIterateResponse; + ContactsListResponse currentPage = listPageResponse; do { pageCount++; - if (!listPageIterateResponseThreadSafety.hasNextPage()) { + if (!currentPage.hasNextPage()) { break; } - listPageIterateResponseThreadSafety = listPageIterateResponseThreadSafety.nextPage(); - + currentPage = currentPage.nextPage(); } while (true); Assertions.assertEquals(pageCount, size); @@ -281,15 +267,8 @@ public void listIdentityConflictsResults(int count) { @Then("the identity conflicts list contains \"{int}\" identity conflicts") public void listIdentityConflictsAllResults(int count) { - // FIXME: to be thread-safe compliant we need to check which variables are set - Iterator iterator = null; - if (null != listAllIdentityConflictsResponse) { - iterator = listAllIdentityConflictsResponse.iterator(); - } - - if (null != listPageIterateIdentityConflictsResponse) { - iterator = listPageIterateIdentityConflictsResponse.iterator(); - } + + Iterator iterator = listIdentityConflictsResponse.iterator(); TestHelpers.checkIteratorItems(iterator, count); } @@ -297,15 +276,13 @@ public void listIdentityConflictsAllResults(int count) { public void listAllIdentityConflictsPageIterateResults(int count) { int pageCount = 0; - IdentityConflictsListResponse listPageIterateResponseThreadSafety = - listPageIterateIdentityConflictsResponse; + IdentityConflictsListResponse currentPage = listIdentityConflictsResponse; do { pageCount++; - if (!listPageIterateResponseThreadSafety.hasNextPage()) { + if (!currentPage.hasNextPage()) { break; } - listPageIterateResponseThreadSafety = listPageIterateResponseThreadSafety.nextPage(); - + currentPage = currentPage.nextPage(); } while (true); Assertions.assertEquals(pageCount, count); diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConversationsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConversationsSteps.java index 6c0db3b92..971a8864d 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConversationsSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConversationsSteps.java @@ -38,11 +38,7 @@ public class ConversationsSteps { ConversationsService service; Conversation createResponse; ConversationsListResponse listPageResponse; - ConversationsListResponse listAllResponse; - ConversationsListResponse listPageIterateResponse; RecentConversationsListResponse listRecentPageResponse; - RecentConversationsListResponse listRecentAllResponse; - RecentConversationsListResponse listRecentPageIterateResponse; Conversation getResponse; Conversation updateResponse; boolean deletePassed; @@ -87,7 +83,7 @@ public void listAll() { ConversationsListQueryParameters request = ConversationsListQueryParameters.builder().setAppId(AppsSteps.APP_ID).build(); - listAllResponse = service.list(request); + listPageResponse = service.list(request); } @When("^I iterate manually over the conversations pages$") @@ -95,7 +91,7 @@ public void listPageIterate() { ConversationsListQueryParameters request = ConversationsListQueryParameters.builder().setAppId(AppsSteps.APP_ID).build(); - listPageIterateResponse = service.list(request); + listPageResponse = service.list(request); } @When("^I send a request to list the recent conversations$") @@ -111,7 +107,7 @@ public void listRecentAll() { RecentConversationsListQueryParameters request = RecentConversationsListQueryParameters.builder().setAppId(AppsSteps.APP_ID).build(); - listRecentAllResponse = service.listRecent(request); + listRecentPageResponse = service.listRecent(request); } @When("^I iterate manually over the recent conversations pages$") @@ -119,7 +115,7 @@ public void listRecentPageIterate() { RecentConversationsListQueryParameters request = RecentConversationsListQueryParameters.builder().setAppId(AppsSteps.APP_ID).build(); - listRecentPageIterateResponse = service.listRecent(request); + listRecentPageResponse = service.listRecent(request); } @When("^I send a request to retrieve a conversation$") @@ -205,15 +201,7 @@ public void listPageResult(int size) { @Then("the conversations list contains \"{int}\" conversations") public void listAllResult(int size) { - // FIXME: to be thread-safe compliant we need to check which variables are set - Iterator iterator = null; - if (null != listAllResponse) { - iterator = listAllResponse.iterator(); - } - - if (null != listPageIterateResponse) { - iterator = listPageIterateResponse.iterator(); - } + Iterator iterator = listPageResponse.iterator(); TestHelpers.checkIteratorItems(iterator, size); } @@ -221,15 +209,13 @@ public void listAllResult(int size) { public void listPageIterateResult(int size) { int pageCount = 0; - - ConversationsListResponse listPageIterateResponseThreadSafety = listPageIterateResponse; + ConversationsListResponse currentPage = listPageResponse; do { pageCount++; - if (!listPageIterateResponseThreadSafety.hasNextPage()) { + if (!currentPage.hasNextPage()) { break; } - listPageIterateResponseThreadSafety = listPageIterateResponseThreadSafety.nextPage(); - + currentPage = currentPage.nextPage(); } while (true); Assertions.assertEquals(pageCount, size); @@ -244,14 +230,7 @@ public void listRecentPageResult(int size) { @Then("the recent conversations list contains \"{int}\" recent conversations") public void listRecentAllResult(int size) { - Iterator iterator = null; - if (null != listRecentAllResponse) { - iterator = listRecentAllResponse.iterator(); - } - - if (null != listRecentPageIterateResponse) { - iterator = listRecentPageIterateResponse.iterator(); - } + Iterator iterator = listRecentPageResponse.iterator(); TestHelpers.checkIteratorItems(iterator, size); } @@ -259,16 +238,13 @@ public void listRecentAllResult(int size) { public void listRecentPageIterateResult(int size) { int pageCount = 0; - - RecentConversationsListResponse listPageIterateResponseThreadSafety = - listRecentPageIterateResponse; + RecentConversationsListResponse currentPage = listRecentPageResponse; do { pageCount++; - if (!listPageIterateResponseThreadSafety.hasNextPage()) { + if (!currentPage.hasNextPage()) { break; } - listPageIterateResponseThreadSafety = listPageIterateResponseThreadSafety.nextPage(); - + currentPage = currentPage.nextPage(); } while (true); Assertions.assertEquals(pageCount, size); diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/EventsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/EventsSteps.java index 482ae8c09..9cc12aa0e 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/EventsSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/EventsSteps.java @@ -23,8 +23,6 @@ public class EventsSteps { EventsService service; SendEventResponse sendResponse; EventsListResponse listPageResponse; - EventsListResponse listAllResponse; - EventsListResponse listPageIterateResponse; ConversationEvent getResponse; boolean deletePassed; @@ -57,14 +55,14 @@ public void listPage() { public void listAll() { EventsListQueryParameters request = EventsListQueryParameters.builder().setPageSize(2).build(); - listAllResponse = service.list(request); + listPageResponse = service.list(request); } @When("^I iterate manually over the conversation events pages$") public void listPageIterate() { EventsListQueryParameters request = EventsListQueryParameters.builder().setPageSize(2).build(); - listPageIterateResponse = service.list(request); + listPageResponse = service.list(request); } @When("^I send a request to retrieve a conversation event$") @@ -94,15 +92,7 @@ public void listPageResult(int size) { @Then("the conversation events list contains \"{int}\" conversation events") public void listAllResult(int size) { - // FIXME: to be thread-safe compliant we need to check which variables are set - Iterator iterator = null; - if (null != listAllResponse) { - iterator = listAllResponse.iterator(); - } - - if (null != listPageIterateResponse) { - iterator = listPageIterateResponse.iterator(); - } + Iterator iterator = listPageResponse.iterator(); TestHelpers.checkIteratorItems(iterator, size); } @@ -110,15 +100,13 @@ public void listAllResult(int size) { public void listPageIterateResult(int size) { int pageCount = 0; - - EventsListResponse listPageIterateResponseThreadSafety = listPageIterateResponse; + EventsListResponse currentPage = listPageResponse; do { pageCount++; - if (!listPageIterateResponseThreadSafety.hasNextPage()) { + if (!currentPage.hasNextPage()) { break; } - listPageIterateResponseThreadSafety = listPageIterateResponseThreadSafety.nextPage(); - + currentPage = currentPage.nextPage(); } while (true); Assertions.assertEquals(pageCount, size); diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/MessagesSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/MessagesSteps.java index d5b0bea6e..2fede9688 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/MessagesSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/MessagesSteps.java @@ -29,13 +29,9 @@ public class MessagesSteps { MessagesService service; SendMessageResponse sendResponse; MessagesListResponse listPageResponse; - MessagesListResponse listAllResponse; - MessagesListResponse listPageIterateResponse; ConversationMessage getResponse; ConversationMessage updateResponse; MessagesListResponse listLastMessagesByChannelIdentityResponse; - MessagesListResponse listAllLastMessagesByChannelIdentityResponse; - MessagesListResponse listPageIteratorLastMessagesByChannelIdentityResponse; boolean deletePassed; @@ -73,7 +69,7 @@ public void listAll() { MessagesListQueryParameters request = MessagesListQueryParameters.builder().setPageSize(2).build(); - listAllResponse = service.list(request); + listPageResponse = service.list(request); } @When("^I iterate manually over the messages pages$") @@ -81,7 +77,7 @@ public void listPageIterate() { MessagesListQueryParameters request = MessagesListQueryParameters.builder().setPageSize(2).build(); - listPageIterateResponse = service.list(request); + listPageResponse = service.list(request); } @When("^I send a request to retrieve a message$") @@ -126,8 +122,7 @@ public void listAllLastMessagesByChannelIdentity() { .setMessagesSource(MessageSource.CONVERSATION_SOURCE) .setPageSize(2) .build(); - listAllLastMessagesByChannelIdentityResponse = - service.listLastMessagesByChannelIdentity(request); + listLastMessagesByChannelIdentityResponse = service.listLastMessagesByChannelIdentity(request); } @When("^I iterate manually over the last messages sent to specified channel identities pages$") @@ -139,8 +134,7 @@ public void listLastMessagesByChannelIdentityPageIterator() { .setMessagesSource(MessageSource.CONVERSATION_SOURCE) .setPageSize(2) .build(); - listPageIteratorLastMessagesByChannelIdentityResponse = - service.listLastMessagesByChannelIdentity(request); + listLastMessagesByChannelIdentityResponse = service.listLastMessagesByChannelIdentity(request); } @Then("the response contains the id of the message") @@ -160,15 +154,7 @@ public void listPageResult(int size) { @Then("the messages list contains \"{int}\" messages") public void listAllResult(int size) { - // FIXME: to be thread-safe compliant we need to check which variables are set - Iterator iterator = null; - if (null != listAllResponse) { - iterator = listAllResponse.iterator(); - } - - if (null != listPageIterateResponse) { - iterator = listPageIterateResponse.iterator(); - } + Iterator iterator = listPageResponse.iterator(); TestHelpers.checkIteratorItems(iterator, size); } @@ -176,15 +162,13 @@ public void listAllResult(int size) { public void listPageIterateResult(int size) { int pageCount = 0; - - MessagesListResponse listPageIterateResponseThreadSafety = listPageIterateResponse; + MessagesListResponse currentPage = listPageResponse; do { pageCount++; - if (!listPageIterateResponseThreadSafety.hasNextPage()) { + if (!currentPage.hasNextPage()) { break; } - listPageIterateResponseThreadSafety = listPageIterateResponseThreadSafety.nextPage(); - + currentPage = currentPage.nextPage(); } while (true); Assertions.assertEquals(pageCount, size); @@ -215,16 +199,8 @@ public void listLastMessagesByChannelIdentity(int size) { @Then("the response list contains \"{int}\" last messages sent to specified channel identities") public void listAllLastMessagesByChannelIdentity(int size) { - Iterator iterator = null; - - if (null != listAllLastMessagesByChannelIdentityResponse) { - iterator = listAllLastMessagesByChannelIdentityResponse.iterator(); - } - - if (null != listPageIteratorLastMessagesByChannelIdentityResponse) { - iterator = listPageIteratorLastMessagesByChannelIdentityResponse.iterator(); - } + Iterator iterator = listLastMessagesByChannelIdentityResponse.iterator(); TestHelpers.checkIteratorItems(iterator, size); } @@ -234,16 +210,13 @@ public void listAllLastMessagesByChannelIdentity(int size) { public void listAllLastMessagesByChannelIdentityPageIterator(int size) { int pageCount = 0; - - MessagesListResponse listPageIterateResponseThreadSafety = - listPageIteratorLastMessagesByChannelIdentityResponse; + MessagesListResponse currentPage = listLastMessagesByChannelIdentityResponse; do { pageCount++; - if (!listPageIterateResponseThreadSafety.hasNextPage()) { + if (!currentPage.hasNextPage()) { break; } - listPageIterateResponseThreadSafety = listPageIterateResponseThreadSafety.nextPage(); - + currentPage = currentPage.nextPage(); } while (true); Assertions.assertEquals(pageCount, size); diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/NumbersSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/NumbersSteps.java index 656f58a09..83f7bf70f 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/NumbersSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/NumbersSteps.java @@ -46,8 +46,6 @@ public class NumbersSteps { ActiveNumber rentResponse; ActiveNumbersListResponse activeNumberListResponse; - ActiveNumbersListResponse activeNumberListAllResponse; - ActiveNumber updateResponse; ActiveNumber getResponse; ApiException getResponseException; @@ -156,7 +154,7 @@ public void listAll() { .setRegionCode("US") .setType(NumberType.LOCAL) .build(); - activeNumberListAllResponse = service.list(request); + activeNumberListResponse = service.list(request); } @When("I send a request to update the phone number {string}") @@ -336,7 +334,7 @@ public void listResult(int expected) { @Then("the phone numbers list contains \"{int}\" phone numbers") public void listAllResult(int expected) { - Assertions.assertEquals(expected, activeNumberListAllResponse.stream().count()); + Assertions.assertEquals(expected, activeNumberListResponse.stream().count()); } @Then("the response contains a phone number with updated parameters") diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/BatchesSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/BatchesSteps.java index c7324263e..f728614c8 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/BatchesSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/BatchesSteps.java @@ -36,8 +36,6 @@ public class BatchesSteps { BatchResponse sendTextWithParametersResponse; DryRunResponse dryRunResponse; ListBatchesResponse listOnePageResponse; - ListBatchesResponse listAllResponse; - ListBatchesResponse listAllByPageResponse; BatchResponse getBatchResponse; BatchResponse updateResponse; BatchResponse replaceResponse; @@ -144,7 +142,7 @@ public void listAll() { ListBatchesQueryParameters request = ListBatchesQueryParameters.builder().setPageSize(2).build(); - listAllResponse = service.list(request); + listOnePageResponse = service.list(request); } @When("^I iterate manually over the SMS batches pages$") @@ -152,7 +150,7 @@ public void listAllByPage() { ListBatchesQueryParameters request = ListBatchesQueryParameters.builder().setPageSize(2).build(); - listAllByPageResponse = service.list(request); + listOnePageResponse = service.list(request); } @When("^I send a request to retrieve an SMS batch$") @@ -304,11 +302,8 @@ public void onePageResult(int expected) { @Then("the SMS batches list contains \"{int}\" SMS batches") public void listAllResult(int expected) { - ListBatchesResponse response = - null != listAllResponse ? listAllResponse : listAllByPageResponse; - AtomicInteger count = new AtomicInteger(); - response.iterator().forEachRemaining(_unused -> count.getAndIncrement()); + listOnePageResponse.iterator().forEachRemaining(_unused -> count.getAndIncrement()); Assertions.assertEquals(count.get(), expected); } @@ -316,10 +311,11 @@ public void listAllResult(int expected) { @Then("the SMS batches iteration result contains the data from \"{int}\" pages") public void listAllByPageResult(int expected) { - int count = listAllByPageResponse.getContent().isEmpty() ? 0 : 1; - while (listAllByPageResponse.hasNextPage()) { + int count = listOnePageResponse.getContent().isEmpty() ? 0 : 1; + ListBatchesResponse currentPage = listOnePageResponse; + while (currentPage.hasNextPage()) { count++; - listAllByPageResponse = listAllByPageResponse.nextPage(); + currentPage = currentPage.nextPage(); } Assertions.assertEquals(count, expected); } diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/DeliveryReportsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/DeliveryReportsSteps.java index 9c53251cf..f9de95f39 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/DeliveryReportsSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/DeliveryReportsSteps.java @@ -32,8 +32,6 @@ public class DeliveryReportsSteps { BatchDeliveryReport fullReport; RecipientDeliveryReport recipientReport; ListDeliveryReportsResponse listOnePageResponse; - ListDeliveryReportsResponse listAllResponse; - ListDeliveryReportsResponse listAllByPageResponse; @Given("^the SMS service \"Delivery Reports\" is available") public void serviceAvailable() { @@ -90,7 +88,7 @@ public void listAll() { ListDeliveryReportsQueryParameters request = ListDeliveryReportsQueryParameters.builder().setPageSize(2).build(); - listAllResponse = service.list(request); + listOnePageResponse = service.list(request); } @When("^I iterate manually over the SMS delivery reports pages$") @@ -98,7 +96,7 @@ public void listAllByPage() { ListDeliveryReportsQueryParameters request = ListDeliveryReportsQueryParameters.builder().setPageSize(2).build(); - listAllByPageResponse = service.list(request); + listOnePageResponse = service.list(request); } @Then("the response contains a summary SMS delivery report") @@ -173,11 +171,9 @@ public void onePageResult(int expected) { @Then("the SMS delivery reports list contains \"{int}\" SMS delivery reports") public void listAllResult(int expected) { - ListDeliveryReportsResponse response = - null != listAllResponse ? listAllResponse : listAllByPageResponse; AtomicInteger count = new AtomicInteger(); - response.iterator().forEachRemaining(_unused -> count.getAndIncrement()); + listOnePageResponse.iterator().forEachRemaining(_unused -> count.getAndIncrement()); Assertions.assertEquals(count.get(), expected); } @@ -185,10 +181,11 @@ public void listAllResult(int expected) { @Then("the SMS delivery reports iteration result contains the data from \"{int}\" pages") public void listAllByPageResult(int expected) { - int count = listAllByPageResponse.getContent().isEmpty() ? 0 : 1; - while (listAllByPageResponse.hasNextPage()) { + int count = listOnePageResponse.getContent().isEmpty() ? 0 : 1; + ListDeliveryReportsResponse currentPage = listOnePageResponse; + while (currentPage.hasNextPage()) { count++; - listAllByPageResponse = listAllByPageResponse.nextPage(); + currentPage = currentPage.nextPage(); } Assertions.assertEquals(count, expected); } diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/GroupsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/GroupsSteps.java index 14bba1bf1..12f0433ee 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/GroupsSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/GroupsSteps.java @@ -26,8 +26,6 @@ public class GroupsSteps { Group createResponse; Group getResponse; ListGroupsResponse listOnePageResponse; - ListGroupsResponse listAllResponse; - ListGroupsResponse listAllByPageResponse; Group updateResponse; Group updateRemoveNameResponse; Group replaceResponse; @@ -76,14 +74,14 @@ public void listOnePage() { public void listAll() { ListGroupsQueryParameters request = ListGroupsQueryParameters.builder().setPageSize(2).build(); - listAllResponse = service.list(request); + listOnePageResponse = service.list(request); } @When("^I iterate manually over the SMS groups pages$") public void listAllByPage() { ListGroupsQueryParameters request = ListGroupsQueryParameters.builder().setPageSize(2).build(); - listAllByPageResponse = service.list(request); + listOnePageResponse = service.list(request); } @When("^I send a request to update an SMS group$") @@ -157,10 +155,9 @@ public void onePageResult(int expected) { @Then("the SMS groups list contains \"{int}\" SMS groups") public void listAllResult(int expected) { - ListGroupsResponse response = null != listAllResponse ? listAllResponse : listAllByPageResponse; AtomicInteger count = new AtomicInteger(); - response.iterator().forEachRemaining(_unused -> count.getAndIncrement()); + listOnePageResponse.iterator().forEachRemaining(_unused -> count.getAndIncrement()); Assertions.assertEquals(count.get(), expected); } @@ -168,10 +165,11 @@ public void listAllResult(int expected) { @Then("the SMS groups iteration result contains the data from \"{int}\" pages") public void listAllByPageResult(int expected) { - int count = listAllByPageResponse.getContent().isEmpty() ? 0 : 1; - while (listAllByPageResponse.hasNextPage()) { + int count = listOnePageResponse.getContent().isEmpty() ? 0 : 1; + ListGroupsResponse currentPage = listOnePageResponse; + while (currentPage.hasNextPage()) { count++; - listAllByPageResponse = listAllByPageResponse.nextPage(); + currentPage = currentPage.nextPage(); } Assertions.assertEquals(count, expected); } diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/InboundsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/InboundsSteps.java index 16f310e86..e36031fa8 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/InboundsSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/InboundsSteps.java @@ -20,8 +20,6 @@ public class InboundsSteps { InboundsService service; InboundMessage getResponse; ListInboundsResponse listOnePageResponse; - ListInboundsResponse listAllResponse; - ListInboundsResponse listAllByPageResponse; @Given("^the SMS service \"Inbounds\" is available") public void serviceAvailable() { @@ -62,7 +60,7 @@ public void listAll() { .setPageSize(2) .build(); - listAllResponse = service.list(request); + listOnePageResponse = service.list(request); } @When("^I iterate manually over the inbound messages pages$") @@ -73,7 +71,7 @@ public void listAllByPage() { .setPageSize(2) .build(); - listAllByPageResponse = service.list(request); + listOnePageResponse = service.list(request); } @Then("the response contains the inbound message details") @@ -99,11 +97,9 @@ public void onePageResult(int expected) { @Then("the inbound messages list contains \"{int}\" inbound messages") public void listAllResult(int expected) { - ListInboundsResponse response = - null != listAllResponse ? listAllResponse : listAllByPageResponse; AtomicInteger count = new AtomicInteger(); - response.iterator().forEachRemaining(_unused -> count.getAndIncrement()); + listOnePageResponse.iterator().forEachRemaining(_unused -> count.getAndIncrement()); Assertions.assertEquals(count.get(), expected); } @@ -111,10 +107,11 @@ public void listAllResult(int expected) { @Then("the inbound messages iteration result contains the data from \"{int}\" pages") public void listAllByPageResult(int expected) { - int count = listAllByPageResponse.getContent().isEmpty() ? 0 : 1; - while (listAllByPageResponse.hasNextPage()) { + int count = listOnePageResponse.getContent().isEmpty() ? 0 : 1; + ListInboundsResponse currentPage = listOnePageResponse; + while (currentPage.hasNextPage()) { count++; - listAllByPageResponse = listAllByPageResponse.nextPage(); + currentPage = currentPage.nextPage(); } Assertions.assertEquals(count, expected); } From 45e3ed939d306f03527e56df024a40f28f32be6d Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Wed, 17 Jun 2026 12:01:21 +0200 Subject: [PATCH 18/37] Refactor e2e tests for parallel execution with JUnit 5 Cucumber --- .../com/sinch/sdk/e2e/domains/conversation/ConversationIT.java | 2 ++ .../sinch/sdk/e2e/domains/numberlookup/v2/NumberLookupIT.java | 2 ++ .../java/com/sinch/sdk/e2e/domains/numbers/v1/NumbersIT.java | 2 ++ .../src/test/java/com/sinch/sdk/e2e/domains/sms/v1/SmsIT.java | 2 ++ .../sinch/sdk/e2e/domains/verification/v1/VerificationIT.java | 2 ++ .../test/java/com/sinch/sdk/e2e/domains/voice/v1/VoiceIT.java | 2 ++ 6 files changed, 12 insertions(+) diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConversationIT.java b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConversationIT.java index f865e2159..e5e633e6c 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConversationIT.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/conversation/ConversationIT.java @@ -1,6 +1,7 @@ package com.sinch.sdk.e2e.domains.conversation; import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.IncludeEngines; @@ -13,4 +14,5 @@ @IncludeEngines("cucumber") @SelectClasspathResource("features/conversation") @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.sinch.sdk.e2e.domains.conversation") +@ConfigurationParameter(key = PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, value = "true") public class ConversationIT {} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/NumberLookupIT.java b/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/NumberLookupIT.java index 668d5466a..89c7aaae4 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/NumberLookupIT.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/numberlookup/v2/NumberLookupIT.java @@ -1,6 +1,7 @@ package com.sinch.sdk.e2e.domains.numberlookup.v2; import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.IncludeEngines; @@ -15,4 +16,5 @@ @ConfigurationParameter( key = GLUE_PROPERTY_NAME, value = "com.sinch.sdk.e2e.domains.numberlookup.v2") +@ConfigurationParameter(key = PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, value = "true") public class NumberLookupIT {} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/NumbersIT.java b/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/NumbersIT.java index ea1a27336..3be0f0910 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/NumbersIT.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/NumbersIT.java @@ -1,6 +1,7 @@ package com.sinch.sdk.e2e.domains.numbers.v1; import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.IncludeEngines; @@ -13,4 +14,5 @@ @IncludeEngines("cucumber") @SelectClasspathResource("features/numbers") @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.sinch.sdk.e2e.domains.numbers.v1") +@ConfigurationParameter(key = PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, value = "true") public class NumbersIT {} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/SmsIT.java b/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/SmsIT.java index 42139b9be..3ab37bf7c 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/SmsIT.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/sms/v1/SmsIT.java @@ -1,6 +1,7 @@ package com.sinch.sdk.e2e.domains.sms.v1; import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.IncludeEngines; @@ -13,4 +14,5 @@ @IncludeEngines("cucumber") @SelectClasspathResource("features/sms") @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.sinch.sdk.e2e.domains.sms.v1") +@ConfigurationParameter(key = PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, value = "true") public class SmsIT {} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/verification/v1/VerificationIT.java b/client/src/test/java/com/sinch/sdk/e2e/domains/verification/v1/VerificationIT.java index 75b21605b..e0e45cb0b 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/verification/v1/VerificationIT.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/verification/v1/VerificationIT.java @@ -1,6 +1,7 @@ package com.sinch.sdk.e2e.domains.verification.v1; import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.IncludeEngines; @@ -15,4 +16,5 @@ @ConfigurationParameter( key = GLUE_PROPERTY_NAME, value = "com.sinch.sdk.e2e.domains.verification.v1") +@ConfigurationParameter(key = PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, value = "true") public class VerificationIT {} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/VoiceIT.java b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/VoiceIT.java index 421a7a053..3b04db4ef 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/VoiceIT.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/VoiceIT.java @@ -1,6 +1,7 @@ package com.sinch.sdk.e2e.domains.voice.v1; import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.IncludeEngines; @@ -13,4 +14,5 @@ @IncludeEngines("cucumber") @SelectClasspathResource("features/voice") @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.sinch.sdk.e2e.domains.voice.v1") +@ConfigurationParameter(key = PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, value = "true") public class VoiceIT {} From e449cb6f6ec6c90a7240d64577b33a7aae2e7ff4 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Wed, 17 Jun 2026 18:02:06 +0200 Subject: [PATCH 19/37] Removed dead code for parallelism into the pom.xml file --- pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pom.xml b/pom.xml index 021a0bf4d..0d3e83e8a 100644 --- a/pom.xml +++ b/pom.xml @@ -283,10 +283,6 @@ maven-failsafe-plugin ${maven.failsafe.plugin.version} - - all - true - @@ -314,7 +310,6 @@ ${maven-surefire-plugin.version} ${skipUTs} - all From 14c14f8ff2460665fb196a8471ecebcb60226825 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Thu, 18 Jun 2026 15:55:57 +0200 Subject: [PATCH 20/37] Voice: updated sources and references for tests and snippet files --- .../api/v1/adapters/CallsServiceTest.java | 14 +- .../sdk/e2e/domains/voice/v1/CallsSteps.java | 14 +- .../java/voice/calls/ManageWithCallLeg.java | 6 +- .../src/main/java/voice/calls/Update.java | 6 +- .../domains/voice/api/v1/CallsService.java | 46 ++- .../api/v1/adapters/CallsServiceImpl.java | 155 +++++++- .../v1/calls/request/CallUpdateRequest.java | 78 ++++ .../calls/request/CallUpdateRequestImpl.java | 123 ++++++ .../internal/SvamlActionPatchInternal.java | 17 + .../SvamlActionPatchInternalImpl.java | 366 ++++++++++++++++++ .../calls/request/CallUpdateRequestTest.java | 38 ++ .../calls/request/CallUpdateRequestDto.json | 12 + 12 files changed, 838 insertions(+), 37 deletions(-) create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequest.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestImpl.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/internal/SvamlActionPatchInternal.java create mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/internal/SvamlActionPatchInternalImpl.java create mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestTest.java create mode 100644 openapi-contracts/src/test/resources/domains/voice/v1/calls/request/CallUpdateRequestDto.json diff --git a/client/src/test/java/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceTest.java index 1253b9881..22e11c14d 100644 --- a/client/src/test/java/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceTest.java @@ -22,8 +22,8 @@ import com.sinch.sdk.domains.voice.api.v1.CallsService; import com.sinch.sdk.domains.voice.models.v1.calls.CallInformationTest; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallLeg; +import com.sinch.sdk.domains.voice.models.v1.calls.request.CallUpdateRequestTest; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation; -import com.sinch.sdk.domains.voice.models.v1.svaml.SvamlControlPatchTest; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -38,8 +38,8 @@ public class CallsServiceTest extends BaseTest { @GivenTextResource("/domains/voice/v1/calls/GetCallInformationResponseDto.json") String getCallInformationResponseDto; - @GivenTextResource("/domains/voice/v1/svaml/SvamlControlPatchDto.json") - String svamlControlDto; + @GivenTextResource("/domains/voice/v1/calls/request/CallUpdateRequestDto.json") + String callUpdateRequestDto; static final Collection AUTH_NAMES = Arrays.asList("Basic", "Signed"); @@ -95,7 +95,7 @@ void update() throws ApiException { "/calling/v1/calls/id/" + URLPathUtils.encodePathSegment("call/id"), HttpMethod.PATCH, Collections.emptyList(), - svamlControlDto, + callUpdateRequestDto, Collections.emptyMap(), Collections.emptyList(), Collections.singletonList(HttpContentType.APPLICATION_JSON), @@ -108,7 +108,7 @@ void update() throws ApiException { argThat(new HttpRequestMatcher(httpRequest)))) .thenReturn(httpResponse); - service.update("call/id", SvamlControlPatchTest.expectedSvamlControlPatch); + service.update("call/id", CallUpdateRequestTest.expectedCallUpdateRequest); } @Test @@ -119,7 +119,7 @@ void manageWithCallLeg() throws ApiException { "/calling/v1/calls/id/" + URLPathUtils.encodePathSegment("call/id") + "/leg/both", HttpMethod.PATCH, Collections.emptyList(), - svamlControlDto, + callUpdateRequestDto, Collections.emptyMap(), Collections.singletonList(HttpContentType.APPLICATION_JSON), Collections.singletonList(HttpContentType.APPLICATION_JSON), @@ -133,6 +133,6 @@ void manageWithCallLeg() throws ApiException { .thenReturn(httpResponse); service.manageWithCallLeg( - "call/id", CallLeg.BOTH, SvamlControlPatchTest.expectedSvamlControlPatch); + "call/id", CallLeg.BOTH, CallUpdateRequestTest.expectedCallUpdateRequest); } } diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/CallsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/CallsSteps.java index 34a1452f6..f371e2c8f 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/CallsSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v1/CallsSteps.java @@ -5,7 +5,7 @@ import com.sinch.sdk.domains.voice.api.v1.CallsService; import com.sinch.sdk.domains.voice.models.v1.Price; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallLeg; -import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; +import com.sinch.sdk.domains.voice.models.v1.calls.request.CallUpdateRequest; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation.DomainEnum; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation.ReasonEnum; @@ -46,8 +46,8 @@ public void getCall() { @When("^I send a request to update a call$") public void updateCall() { - SvamlControlPatch request = - SvamlControlPatch.builder() + CallUpdateRequest request = + CallUpdateRequest.builder() .setInstructions( Arrays.asList( SvamlInstructionSay.builder() @@ -63,8 +63,8 @@ public void updateCall() { @When("^I send a request to update a call that doesn't exist$") public void updateCallNotExits() { - SvamlControlPatch request = - SvamlControlPatch.builder() + CallUpdateRequest request = + CallUpdateRequest.builder() .setInstructions( Arrays.asList( SvamlInstructionSay.builder() @@ -83,8 +83,8 @@ public void updateCallNotExits() { @When("^I send a request to manage a call with callLeg$") public void manageCallWithCallLeg() { - SvamlControlPatch request = - SvamlControlPatch.builder() + CallUpdateRequest request = + CallUpdateRequest.builder() .setInstructions( Arrays.asList( SvamlInstructionPlayFiles.builder() diff --git a/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java b/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java index 13fb16d46..d03127cb1 100644 --- a/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java +++ b/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java @@ -10,7 +10,7 @@ import com.sinch.sdk.SinchClient; import com.sinch.sdk.domains.voice.api.v1.CallsService; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallLeg; -import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; +import com.sinch.sdk.domains.voice.models.v1.calls.request.CallUpdateRequest; import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangup; import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; @@ -55,8 +55,8 @@ public static void main(String[] args) { .setText("Hello, the call is over, hanging up now. Goodbye") .build()); - SvamlControlPatch request = - SvamlControlPatch.builder().setInstructions(instructions).setAction(action).build(); + CallUpdateRequest request = + CallUpdateRequest.builder().setInstructions(instructions).setAction(action).build(); callsService.manageWithCallLeg(callId, callLeg, request); diff --git a/examples/snippets/src/main/java/voice/calls/Update.java b/examples/snippets/src/main/java/voice/calls/Update.java index d6d84f461..a25d298c6 100644 --- a/examples/snippets/src/main/java/voice/calls/Update.java +++ b/examples/snippets/src/main/java/voice/calls/Update.java @@ -9,7 +9,7 @@ import com.sinch.sdk.SinchClient; import com.sinch.sdk.domains.voice.api.v1.CallsService; -import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; +import com.sinch.sdk.domains.voice.models.v1.calls.request.CallUpdateRequest; import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangup; import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; @@ -50,8 +50,8 @@ public static void main(String[] args) { Collection instructions = Collections.singletonList(instruction); - SvamlControlPatch request = - SvamlControlPatch.builder().setInstructions(instructions).setAction(action).build(); + CallUpdateRequest request = + CallUpdateRequest.builder().setInstructions(instructions).setAction(action).build(); callsService.update(callId, request); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/CallsService.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/CallsService.java index 28f0b209b..680128532 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/CallsService.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/CallsService.java @@ -12,8 +12,9 @@ import com.sinch.sdk.core.exceptions.ApiException; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallLeg; -import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; +import com.sinch.sdk.domains.voice.models.v1.calls.request.CallUpdateRequest; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation; +import com.sinch.sdk.domains.voice.models.v1.svaml.SvamlControl; /** Calls Service */ public interface CallsService { @@ -53,10 +54,10 @@ public interface CallsService { * `caller`.<br><Warning>The `callLeg` identifier is ignored * for calls that are part of a conference and calls initiated using the Callout * API.</Warning> (required) - * @param svamlControlPatch (optional) + * @param callUpdateRequest (optional) * @throws ApiException if fails to make API call */ - void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControlPatch svamlControlPatch) + void manageWithCallLeg(String callId, CallLeg callLeg, CallUpdateRequest callUpdateRequest) throws ApiException; /** @@ -70,8 +71,43 @@ void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControlPatch svamlCo * * @param callId The unique identifier of the call. This value is generated by the system. * (required) - * @param svamlControlPatch (optional) + * @param callUpdateRequest (optional) * @throws ApiException if fails to make API call */ - void update(String callId, SvamlControlPatch svamlControlPatch) throws ApiException; + void update(String callId, CallUpdateRequest callUpdateRequest) throws ApiException; + + /** + * Manage Call with `callLeg` + * + *

This method is deprecated replaced by {@link #manageWithCallLeg(String, CallLeg, + * CallUpdateRequest)} + * + * @param callId The unique identifier of the call. This value is generated by the system. + * (required) + * @param callLeg Specifies which part of the call will be managed. This option is used only by + * the `PlayFiles` and `Say` instructions to indicate which channel the + * sound will be played on. Valid options are `caller`, `callee` or + * `both`. If not specified, the default value is + * `caller`.<br><Warning>The `callLeg` identifier is ignored + * for calls that are part of a conference and calls initiated using the Callout + * API.</Warning> (required) + * @param svamlControl (optional) + * @throws ApiException if fails to make API call + * @deprecated + */ + void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControl svamlControl) + throws ApiException; + + /** + * Update a call in progress + * + *

This method is deprecated replaced by {@link #update(String, CallUpdateRequest)} + * + * @param callId The unique identifier of the call. This value is generated by the system. + * (required) + * @param svamlControl (optional) + * @throws ApiException if fails to make API call + * @deprecated + */ + void update(String callId, SvamlControl svamlControl) throws ApiException; } diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceImpl.java index 1c146f8b0..5eabae47c 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/CallsServiceImpl.java @@ -24,8 +24,9 @@ import com.sinch.sdk.core.http.URLPathUtils; import com.sinch.sdk.core.models.ServerConfiguration; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallLeg; -import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; +import com.sinch.sdk.domains.voice.models.v1.calls.request.CallUpdateRequest; import com.sinch.sdk.domains.voice.models.v1.calls.response.CallInformation; +import com.sinch.sdk.domains.voice.models.v1.svaml.SvamlControl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -109,7 +110,7 @@ private HttpRequest getRequestBuilder(String callId) throws ApiException { } @Override - public void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControlPatch svamlControlPatch) + public void manageWithCallLeg(String callId, CallLeg callLeg, CallUpdateRequest callUpdateRequest) throws ApiException { LOGGER.finest( @@ -121,10 +122,10 @@ public void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControlPatch + "callLeg: " + callLeg + ", " - + "svamlControlPatch: " - + svamlControlPatch); + + "callUpdateRequest: " + + callUpdateRequest); - HttpRequest httpRequest = manageWithCallLegRequestBuilder(callId, callLeg, svamlControlPatch); + HttpRequest httpRequest = manageWithCallLegRequestBuilder(callId, callLeg, callUpdateRequest); HttpResponse response = httpClient.invokeAPI( this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); @@ -142,7 +143,7 @@ public void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControlPatch } private HttpRequest manageWithCallLegRequestBuilder( - String callId, CallLeg callLeg, SvamlControlPatch svamlControlPatch) throws ApiException { + String callId, CallLeg callLeg, CallUpdateRequest callUpdateRequest) throws ApiException { // verify the required parameter 'callId' is set if (callId == null) { throw new ApiException( @@ -169,7 +170,7 @@ private HttpRequest manageWithCallLegRequestBuilder( final Collection localVarContentTypes = Arrays.asList("application/json"); final Collection localVarAuthNames = Arrays.asList("Basic", "Signed"); - final String serializedBody = mapper.serialize(localVarContentTypes, svamlControlPatch); + final String serializedBody = mapper.serialize(localVarContentTypes, callUpdateRequest); return new HttpRequest( localVarPath, @@ -183,12 +184,12 @@ private HttpRequest manageWithCallLegRequestBuilder( } @Override - public void update(String callId, SvamlControlPatch svamlControlPatch) throws ApiException { + public void update(String callId, CallUpdateRequest callUpdateRequest) throws ApiException { LOGGER.finest( - "[update]" + " " + "callId: " + callId + ", " + "svamlControlPatch: " + svamlControlPatch); + "[update]" + " " + "callId: " + callId + ", " + "callUpdateRequest: " + callUpdateRequest); - HttpRequest httpRequest = updateRequestBuilder(callId, svamlControlPatch); + HttpRequest httpRequest = updateRequestBuilder(callId, callUpdateRequest); HttpResponse response = httpClient.invokeAPI( this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); @@ -205,7 +206,7 @@ public void update(String callId, SvamlControlPatch svamlControlPatch) throws Ap mapper.deserialize(response, new TypeReference>() {})); } - private HttpRequest updateRequestBuilder(String callId, SvamlControlPatch svamlControlPatch) + private HttpRequest updateRequestBuilder(String callId, CallUpdateRequest callUpdateRequest) throws ApiException { // verify the required parameter 'callId' is set if (callId == null) { @@ -226,7 +227,137 @@ private HttpRequest updateRequestBuilder(String callId, SvamlControlPatch svamlC final Collection localVarContentTypes = Arrays.asList("application/json"); final Collection localVarAuthNames = Arrays.asList("Basic", "Signed"); - final String serializedBody = mapper.serialize(localVarContentTypes, svamlControlPatch); + final String serializedBody = mapper.serialize(localVarContentTypes, callUpdateRequest); + + return new HttpRequest( + localVarPath, + HttpMethod.PATCH, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } + + @Override + public void manageWithCallLeg(String callId, CallLeg callLeg, SvamlControl svamlControl) + throws ApiException { + + LOGGER.finest( + "[manageWithCallLeg]" + + " " + + "callId: " + + callId + + ", " + + "callLeg: " + + callLeg + + ", " + + "svamlControl: " + + svamlControl); + + HttpRequest httpRequest = manageWithCallLegRequestBuilder(callId, callLeg, svamlControl); + HttpResponse response = + httpClient.invokeAPI( + this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); + + if (HttpStatus.isSuccessfulStatus(response.getCode())) { + return; + } + // fallback to default errors handling: + // all error cases definition are not required from specs: will try some "hardcoded" content + // parsing + throw ApiExceptionBuilder.build( + response.getMessage(), + response.getCode(), + mapper.deserialize(response, new TypeReference>() {})); + } + + private HttpRequest manageWithCallLegRequestBuilder( + String callId, CallLeg callLeg, SvamlControl svamlControl) throws ApiException { + // verify the required parameter 'callId' is set + if (callId == null) { + throw new ApiException( + 400, "Missing the required parameter 'callId' when calling manageWithCallLeg"); + } + // verify the required parameter 'callLeg' is set + if (callLeg == null) { + throw new ApiException( + 400, "Missing the required parameter 'callLeg' when calling manageWithCallLeg"); + } + + String localVarPath = + "/calling/v1/calls/id/{callId}/leg/{callLeg}" + .replaceAll("\\{" + "callId" + "\\}", URLPathUtils.encodePathSegment(callId.toString())) + .replaceAll( + "\\{" + "callLeg" + "\\}", URLPathUtils.encodePathSegment(callLeg.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = Arrays.asList("application/json"); + + final Collection localVarContentTypes = Arrays.asList("application/json"); + + final Collection localVarAuthNames = Arrays.asList("Basic", "Signed"); + final String serializedBody = mapper.serialize(localVarContentTypes, svamlControl); + + return new HttpRequest( + localVarPath, + HttpMethod.PATCH, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } + + @Override + public void update(String callId, SvamlControl svamlControl) throws ApiException { + + LOGGER.finest("[update]" + " " + "callId: " + callId + ", " + "svamlControl: " + svamlControl); + + HttpRequest httpRequest = updateRequestBuilder(callId, svamlControl); + HttpResponse response = + httpClient.invokeAPI( + this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); + + if (HttpStatus.isSuccessfulStatus(response.getCode())) { + return; + } + // fallback to default errors handling: + // all error cases definition are not required from specs: will try some "hardcoded" content + // parsing + throw ApiExceptionBuilder.build( + response.getMessage(), + response.getCode(), + mapper.deserialize(response, new TypeReference>() {})); + } + + private HttpRequest updateRequestBuilder(String callId, SvamlControl svamlControl) + throws ApiException { + // verify the required parameter 'callId' is set + if (callId == null) { + throw new ApiException(400, "Missing the required parameter 'callId' when calling update"); + } + + String localVarPath = + "/calling/v1/calls/id/{callId}" + .replaceAll( + "\\{" + "callId" + "\\}", URLPathUtils.encodePathSegment(callId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = Arrays.asList(); + + final Collection localVarContentTypes = Arrays.asList("application/json"); + + final Collection localVarAuthNames = Arrays.asList("Basic", "Signed"); + final String serializedBody = mapper.serialize(localVarContentTypes, svamlControl); return new HttpRequest( localVarPath, diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequest.java new file mode 100644 index 000000000..39221bc4d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequest.java @@ -0,0 +1,78 @@ +/* + * Voice API | Sinch + * + * OpenAPI document version: 1.0.1 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.voice.models.v1.calls.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; +import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; +import java.util.Collection; + +/** + * SVAML is a call control markup language. When a server receives a callback event from the Sinch + * platform, it can respond with a SVAML object to control the voice call. The following is an + * example of a SVAML object type and its contents. + */ +@JsonDeserialize(builder = CallUpdateRequestImpl.Builder.class) +public interface CallUpdateRequest { + + /** + * The collection of instructions that can perform various tasks during the call. You can include + * as many instructions as necessary. + * + * @return instructions + */ + Collection getInstructions(); + + /** + * Get action + * + * @return action + */ + SvamlActionPatch getAction(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new CallUpdateRequestImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param instructions see getter + * @return Current builder + * @see #getInstructions + */ + Builder setInstructions(Collection instructions); + + /** + * see getter + * + * @param action see getter + * @return Current builder + * @see #getAction + */ + Builder setAction(SvamlActionPatch action); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + CallUpdateRequest build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestImpl.java new file mode 100644 index 000000000..7ba595fa6 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestImpl.java @@ -0,0 +1,123 @@ +package com.sinch.sdk.domains.voice.models.v1.calls.request; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.sinch.sdk.core.models.OptionalValue; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; +import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; +import java.util.Collection; +import java.util.Objects; + +@JsonPropertyOrder({ + CallUpdateRequestImpl.JSON_PROPERTY_INSTRUCTIONS, + CallUpdateRequestImpl.JSON_PROPERTY_ACTION +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class CallUpdateRequestImpl implements CallUpdateRequest { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_INSTRUCTIONS = "instructions"; + + private OptionalValue> instructions; + + public static final String JSON_PROPERTY_ACTION = "action"; + + private OptionalValue action; + + public CallUpdateRequestImpl() {} + + protected CallUpdateRequestImpl( + OptionalValue> instructions, + OptionalValue action) { + this.instructions = instructions; + this.action = action; + } + + @JsonIgnore + public Collection getInstructions() { + return instructions.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_INSTRUCTIONS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> instructions() { + return instructions; + } + + @JsonIgnore + public SvamlActionPatch getAction() { + return action.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ACTION) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue action() { + return action; + } + + /** Return true if this PATCHSVAMLRequestBody object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CallUpdateRequestImpl paTCHSVAMLRequestBody = (CallUpdateRequestImpl) o; + return Objects.equals(this.instructions, paTCHSVAMLRequestBody.instructions) + && Objects.equals(this.action, paTCHSVAMLRequestBody.action); + } + + @Override + public int hashCode() { + return Objects.hash(instructions, action); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class CallUpdateRequestImpl {\n"); + sb.append(" instructions: ").append(toIndentedString(instructions)).append("\n"); + sb.append(" action: ").append(toIndentedString(action)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @JsonPOJOBuilder(withPrefix = "set") + static class Builder implements CallUpdateRequest.Builder { + OptionalValue> instructions = OptionalValue.empty(); + OptionalValue action = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_INSTRUCTIONS) + public Builder setInstructions(Collection instructions) { + this.instructions = OptionalValue.of(instructions); + return this; + } + + @JsonProperty(JSON_PROPERTY_ACTION) + public Builder setAction(SvamlActionPatch action) { + this.action = OptionalValue.of(action); + return this; + } + + public CallUpdateRequest build() { + return new CallUpdateRequestImpl(instructions, action); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/internal/SvamlActionPatchInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/internal/SvamlActionPatchInternal.java new file mode 100644 index 000000000..8fcd49233 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/internal/SvamlActionPatchInternal.java @@ -0,0 +1,17 @@ +/* + * Voice API | Sinch + * + * OpenAPI document version: 1.0.1 + * Contact: support@sinch.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + */ + +package com.sinch.sdk.domains.voice.models.v1.svaml.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize( + using = SvamlActionPatchInternalImpl.SvamlActionPatchInternalImplDeserializer.class) +public interface SvamlActionPatchInternal {} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/internal/SvamlActionPatchInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/internal/SvamlActionPatchInternalImpl.java new file mode 100644 index 000000000..5fadaa722 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/internal/SvamlActionPatchInternalImpl.java @@ -0,0 +1,366 @@ +package com.sinch.sdk.domains.voice.models.v1.svaml.internal; + +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.core.type.TypeReference; +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 com.sinch.sdk.core.models.AbstractOpenApiSchema; +import com.sinch.sdk.core.utils.databind.JSONNavigator; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionContinueImpl; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangupImpl; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionParkImpl; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +@JsonDeserialize( + using = SvamlActionPatchInternalImpl.SvamlActionPatchInternalImplDeserializer.class) +@JsonSerialize(using = SvamlActionPatchInternalImpl.SvamlActionPatchInternalImplSerializer.class) +public class SvamlActionPatchInternalImpl extends AbstractOpenApiSchema + implements SvamlActionPatchInternal { + private static final Logger log = Logger.getLogger(SvamlActionPatchInternalImpl.class.getName()); + + public static final class SvamlActionPatchInternalImplSerializer + extends StdSerializer { + private static final long serialVersionUID = 1L; + + public SvamlActionPatchInternalImplSerializer(Class t) { + super(t); + } + + public SvamlActionPatchInternalImplSerializer() { + this(null); + } + + @Override + public void serialize( + SvamlActionPatchInternalImpl value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static final class SvamlActionPatchInternalImplDeserializer + extends StdDeserializer { + + private static final long serialVersionUID = 1L; + + public SvamlActionPatchInternalImplDeserializer() { + this(SvamlActionPatchInternalImpl.class); + } + + public SvamlActionPatchInternalImplDeserializer(Class vc) { + super(vc); + } + + @Override + public SvamlActionPatchInternalImpl deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + Object deserialized = null; + SvamlActionPatchInternalImpl newSvamlActionPatchInternalImpl = + new SvamlActionPatchInternalImpl(); + Map result2 = + tree.traverse(jp.getCodec()).readValueAs(new TypeReference>() {}); + String discriminatorValue = (String) result2.get("name"); + switch (discriminatorValue) { + case "continue": + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionContinueImpl.class); + newSvamlActionPatchInternalImpl.setActualInstance(deserialized); + return newSvamlActionPatchInternalImpl; + case "hangup": + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionHangupImpl.class); + newSvamlActionPatchInternalImpl.setActualInstance(deserialized); + return newSvamlActionPatchInternalImpl; + case "park": + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionParkImpl.class); + newSvamlActionPatchInternalImpl.setActualInstance(deserialized); + return newSvamlActionPatchInternalImpl; + default: + log.log( + Level.WARNING, + String.format( + "Failed to lookup discriminator value `%s` for SvamlActionPatchInternalImpl." + + " Possible values: continue hangup park", + discriminatorValue)); + } + + boolean typeCoercion = ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS); + int match = 0; + JsonToken token = tree.traverse(jp.getCodec()).nextToken(); + // deserialize SvamlActionContinueImpl + try { + boolean attemptParsing = true; + // ensure that we respect type coercion as set on the client ObjectMapper + if (SvamlActionContinueImpl.class.equals(Integer.class) + || SvamlActionContinueImpl.class.equals(Long.class) + || SvamlActionContinueImpl.class.equals(Float.class) + || SvamlActionContinueImpl.class.equals(Double.class) + || SvamlActionContinueImpl.class.equals(Boolean.class) + || SvamlActionContinueImpl.class.equals(String.class)) { + attemptParsing = typeCoercion; + if (!attemptParsing) { + attemptParsing |= + ((SvamlActionContinueImpl.class.equals(Integer.class) + || SvamlActionContinueImpl.class.equals(Long.class)) + && token == JsonToken.VALUE_NUMBER_INT); + attemptParsing |= + ((SvamlActionContinueImpl.class.equals(Float.class) + || SvamlActionContinueImpl.class.equals(Double.class)) + && token == JsonToken.VALUE_NUMBER_FLOAT); + attemptParsing |= + (SvamlActionContinueImpl.class.equals(Boolean.class) + && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE)); + attemptParsing |= + (SvamlActionContinueImpl.class.equals(String.class) + && token == JsonToken.VALUE_STRING); + } + } + if (attemptParsing) { + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionContinueImpl.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 'SvamlActionContinueImpl'"); + } + } catch (Exception e) { + // deserialization failed, continue + log.log(Level.FINER, "Input data does not match schema 'SvamlActionContinueImpl'", e); + } + + // deserialize SvamlActionHangupImpl + try { + boolean attemptParsing = true; + // ensure that we respect type coercion as set on the client ObjectMapper + if (SvamlActionHangupImpl.class.equals(Integer.class) + || SvamlActionHangupImpl.class.equals(Long.class) + || SvamlActionHangupImpl.class.equals(Float.class) + || SvamlActionHangupImpl.class.equals(Double.class) + || SvamlActionHangupImpl.class.equals(Boolean.class) + || SvamlActionHangupImpl.class.equals(String.class)) { + attemptParsing = typeCoercion; + if (!attemptParsing) { + attemptParsing |= + ((SvamlActionHangupImpl.class.equals(Integer.class) + || SvamlActionHangupImpl.class.equals(Long.class)) + && token == JsonToken.VALUE_NUMBER_INT); + attemptParsing |= + ((SvamlActionHangupImpl.class.equals(Float.class) + || SvamlActionHangupImpl.class.equals(Double.class)) + && token == JsonToken.VALUE_NUMBER_FLOAT); + attemptParsing |= + (SvamlActionHangupImpl.class.equals(Boolean.class) + && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE)); + attemptParsing |= + (SvamlActionHangupImpl.class.equals(String.class) + && token == JsonToken.VALUE_STRING); + } + } + if (attemptParsing) { + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionHangupImpl.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 'SvamlActionHangupImpl'"); + } + } catch (Exception e) { + // deserialization failed, continue + log.log(Level.FINER, "Input data does not match schema 'SvamlActionHangupImpl'", e); + } + + // deserialize SvamlActionParkImpl + try { + boolean attemptParsing = true; + // ensure that we respect type coercion as set on the client ObjectMapper + if (SvamlActionParkImpl.class.equals(Integer.class) + || SvamlActionParkImpl.class.equals(Long.class) + || SvamlActionParkImpl.class.equals(Float.class) + || SvamlActionParkImpl.class.equals(Double.class) + || SvamlActionParkImpl.class.equals(Boolean.class) + || SvamlActionParkImpl.class.equals(String.class)) { + attemptParsing = typeCoercion; + if (!attemptParsing) { + attemptParsing |= + ((SvamlActionParkImpl.class.equals(Integer.class) + || SvamlActionParkImpl.class.equals(Long.class)) + && token == JsonToken.VALUE_NUMBER_INT); + attemptParsing |= + ((SvamlActionParkImpl.class.equals(Float.class) + || SvamlActionParkImpl.class.equals(Double.class)) + && token == JsonToken.VALUE_NUMBER_FLOAT); + attemptParsing |= + (SvamlActionParkImpl.class.equals(Boolean.class) + && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE)); + attemptParsing |= + (SvamlActionParkImpl.class.equals(String.class) && token == JsonToken.VALUE_STRING); + } + } + if (attemptParsing) { + deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionParkImpl.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 'SvamlActionParkImpl'"); + } + } catch (Exception e) { + // deserialization failed, continue + log.log(Level.FINER, "Input data does not match schema 'SvamlActionParkImpl'", e); + } + + if (match == 1) { + SvamlActionPatchInternalImpl ret = new SvamlActionPatchInternalImpl(); + ret.setActualInstance(deserialized); + return ret; + } + throw new IOException( + String.format( + "Failed deserialization for SvamlActionPatchInternalImpl: %d classes match result," + + " expected 1", + match)); + } + + /** Handle deserialization of the 'null' value. */ + @Override + public SvamlActionPatchInternalImpl getNullValue(DeserializationContext ctxt) + throws JsonMappingException { + throw new JsonMappingException( + ctxt.getParser(), "SvamlActionPatchInternalImpl cannot be null"); + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap<>(); + + public SvamlActionPatchInternalImpl() { + super("oneOf", Boolean.FALSE); + } + + public SvamlActionPatchInternalImpl(SvamlActionContinueImpl o) { + super("oneOf", Boolean.FALSE); + setActualInstance(o); + } + + public SvamlActionPatchInternalImpl(SvamlActionHangupImpl o) { + super("oneOf", Boolean.FALSE); + setActualInstance(o); + } + + public SvamlActionPatchInternalImpl(SvamlActionParkImpl o) { + super("oneOf", Boolean.FALSE); + setActualInstance(o); + } + + static { + schemas.put("SvamlActionContinueImpl", SvamlActionContinueImpl.class); + schemas.put("SvamlActionHangupImpl", SvamlActionHangupImpl.class); + schemas.put("SvamlActionParkImpl", SvamlActionParkImpl.class); + JSONNavigator.registerDescendants( + SvamlActionPatchInternalImpl.class, Collections.unmodifiableMap(schemas)); + // Initialize and register the discriminator mappings. + Map> mappings = new HashMap>(); + mappings.put("continue", SvamlActionContinueImpl.class); + mappings.put("hangup", SvamlActionHangupImpl.class); + mappings.put("park", SvamlActionParkImpl.class); + mappings.put("svaml.action.patch", SvamlActionPatchInternalImpl.class); + JSONNavigator.registerDiscriminator(SvamlActionPatchInternalImpl.class, "name", mappings); + } + + @Override + public Map> getSchemas() { + return SvamlActionPatchInternalImpl.schemas; + } + + /** + * Set the instance that matches the oneOf child schema, check the instance parameter is valid + * against the oneOf child schemas: SvamlActionContinueImpl, SvamlActionHangupImpl, + * SvamlActionParkImpl + * + *

It could be an instance of the 'oneOf' schemas. The oneOf child schemas may themselves be a + * composed schema (allOf, anyOf, oneOf). + */ + @Override + public void setActualInstance(Object instance) { + if (JSONNavigator.isInstanceOf( + SvamlActionContinueImpl.class, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + + if (JSONNavigator.isInstanceOf( + SvamlActionHangupImpl.class, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + + if (JSONNavigator.isInstanceOf(SvamlActionParkImpl.class, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + + throw new RuntimeException( + "Invalid instance type. Must be SvamlActionContinueImpl, SvamlActionHangupImpl," + + " SvamlActionParkImpl"); + } + + /** + * Get the actual instance, which can be the following: SvamlActionContinueImpl, + * SvamlActionHangupImpl, SvamlActionParkImpl + * + * @return The actual instance (SvamlActionContinueImpl, SvamlActionHangupImpl, + * SvamlActionParkImpl) + */ + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + /** + * Get the actual instance of `SvamlActionContinueImpl`. If the actual instance is not + * `SvamlActionContinueImpl`, the ClassCastException will be thrown. + * + * @return The actual instance of `SvamlActionContinueImpl` + * @throws ClassCastException if the instance is not `SvamlActionContinueImpl` + */ + public SvamlActionContinueImpl getSvamlActionContinueImpl() throws ClassCastException { + return (SvamlActionContinueImpl) super.getActualInstance(); + } + + /** + * Get the actual instance of `SvamlActionHangupImpl`. If the actual instance is not + * `SvamlActionHangupImpl`, the ClassCastException will be thrown. + * + * @return The actual instance of `SvamlActionHangupImpl` + * @throws ClassCastException if the instance is not `SvamlActionHangupImpl` + */ + public SvamlActionHangupImpl getSvamlActionHangupImpl() throws ClassCastException { + return (SvamlActionHangupImpl) super.getActualInstance(); + } + + /** + * Get the actual instance of `SvamlActionParkImpl`. If the actual instance is not + * `SvamlActionParkImpl`, the ClassCastException will be thrown. + * + * @return The actual instance of `SvamlActionParkImpl` + * @throws ClassCastException if the instance is not `SvamlActionParkImpl` + */ + public SvamlActionParkImpl getSvamlActionParkImpl() throws ClassCastException { + return (SvamlActionParkImpl) super.getActualInstance(); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestTest.java new file mode 100644 index 000000000..5da9707ad --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestTest.java @@ -0,0 +1,38 @@ +package com.sinch.sdk.domains.voice.models.v1.calls.request; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangup; +import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstructionSay; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class CallUpdateRequestTest extends BaseTest { + + public static CallUpdateRequest expectedCallUpdateRequest = + CallUpdateRequest.builder() + .setAction(SvamlActionHangup.SVAML_ACTION_HANGUP) + .setInstructions( + Arrays.asList( + SvamlInstructionSay.builder() + .setText("Sorry, the conference has been cancelled. The call will end now.") + .setLocale("en-US") + .build())) + .build(); + + @GivenTextResource("/domains/voice/v1/calls/request/CallUpdateRequestDto.json") + String jsonCallUpdateRequestDto; + + @Test + void serializeCallUpdateRequest() throws JsonProcessingException, JSONException { + + String serializedString = objectMapper.writeValueAsString(expectedCallUpdateRequest); + + JSONAssert.assertEquals(jsonCallUpdateRequestDto, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v1/calls/request/CallUpdateRequestDto.json b/openapi-contracts/src/test/resources/domains/voice/v1/calls/request/CallUpdateRequestDto.json new file mode 100644 index 000000000..1640c9049 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v1/calls/request/CallUpdateRequestDto.json @@ -0,0 +1,12 @@ +{ + "instructions": [ + { + "name": "say", + "text": "Sorry, the conference has been cancelled. The call will end now.", + "locale": "en-US" + } + ], + "action": { + "name": "hangup" + } +} From 896879d7df21f40e69cc890c671467656113bfc2 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Fri, 19 Jun 2026 09:17:36 +0200 Subject: [PATCH 21/37] Voice: rename SvamlActionPatch to ManagedCallSvamlAction, generated sources and remove duplicated test --- .../svaml/action/ManagedCallSvamlAction.java | 12 + .../v1/svaml/action/SvamlActionPatch.java | 9 - .../java/voice/calls/ManageWithCallLeg.java | 4 +- .../src/main/java/voice/calls/Update.java | 4 +- .../v1/calls/request/CallUpdateRequest.java | 6 +- .../calls/request/CallUpdateRequestImpl.java | 14 +- .../v1/calls/request/SvamlActionPatch.java | 16 - .../calls/request/SvamlActionPatchImpl.java | 362 ------------------ .../v1/calls/request/SvamlControlPatch.java | 78 ---- .../calls/request/SvamlControlPatchImpl.java | 124 ------ .../v1/svaml/action/SvamlActionContinue.java | 2 +- .../svaml/action/SvamlActionContinueImpl.java | 2 +- .../v1/svaml/action/SvamlActionHangup.java | 2 +- .../svaml/action/SvamlActionHangupImpl.java | 2 +- .../v1/svaml/action/SvamlActionPark.java | 2 +- .../v1/svaml/action/SvamlActionParkImpl.java | 2 +- .../v1/svaml/SvamlControlPatchTest.java | 39 -- .../voice/v1/svaml/SvamlControlPatchDto.json | 12 - 18 files changed, 32 insertions(+), 660 deletions(-) create mode 100644 client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/ManagedCallSvamlAction.java delete mode 100644 client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPatch.java delete mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatch.java delete mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatchImpl.java delete mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatch.java delete mode 100644 openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatchImpl.java delete mode 100644 openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/svaml/SvamlControlPatchTest.java delete mode 100644 openapi-contracts/src/test/resources/domains/voice/v1/svaml/SvamlControlPatchDto.json diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/ManagedCallSvamlAction.java b/client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/ManagedCallSvamlAction.java new file mode 100644 index 000000000..dbc22d00a --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/ManagedCallSvamlAction.java @@ -0,0 +1,12 @@ +package com.sinch.sdk.domains.voice.models.v1.svaml.action; + +/** + * Base class related to SVAML actions available for managing ongoing, connected calls (updateCall + * and manageCallWithCallLeg). + * + * @see SvamlActionHangup + * @see SvamlActionContinue + * @see SvamlActionPark + * @since 2.1 + */ +public interface ManagedCallSvamlAction {} diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPatch.java b/client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPatch.java deleted file mode 100644 index aac08730d..000000000 --- a/client/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPatch.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sinch.sdk.domains.voice.models.v1.svaml.action; - -/** - * Base class related to SVAML actions available for PATCH call operations (updateCall and - * manageCallWithCallLeg). Only hangup, continue and park actions are supported. - * - * @since 2.1 - */ -public interface SvamlActionPatch {} diff --git a/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java b/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java index d03127cb1..56207a989 100644 --- a/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java +++ b/examples/snippets/src/main/java/voice/calls/ManageWithCallLeg.java @@ -11,8 +11,8 @@ import com.sinch.sdk.domains.voice.api.v1.CallsService; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallLeg; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallUpdateRequest; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.ManagedCallSvamlAction; import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangup; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstructionSay; import com.sinch.sdk.models.Configuration; @@ -47,7 +47,7 @@ public static void main(String[] args) { LOGGER.info(String.format("Manage call with ID '%s'", callId)); - SvamlActionPatch action = SvamlActionHangup.SVAML_ACTION_HANGUP; + ManagedCallSvamlAction action = SvamlActionHangup.SVAML_ACTION_HANGUP; Collection instructions = Collections.singletonList( diff --git a/examples/snippets/src/main/java/voice/calls/Update.java b/examples/snippets/src/main/java/voice/calls/Update.java index a25d298c6..bcd6c03c2 100644 --- a/examples/snippets/src/main/java/voice/calls/Update.java +++ b/examples/snippets/src/main/java/voice/calls/Update.java @@ -10,8 +10,8 @@ import com.sinch.sdk.SinchClient; import com.sinch.sdk.domains.voice.api.v1.CallsService; import com.sinch.sdk.domains.voice.models.v1.calls.request.CallUpdateRequest; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.ManagedCallSvamlAction; import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangup; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstructionSay; import com.sinch.sdk.models.Configuration; @@ -34,7 +34,7 @@ public static void main(String[] args) { // The instruction to be performed SvamlInstruction instruction = SvamlInstructionSay.builder().setText("Goodbye").build(); // The instruction to add to the call - SvamlActionPatch action = SvamlActionHangup.SVAML_ACTION_HANGUP; + ManagedCallSvamlAction action = SvamlActionHangup.SVAML_ACTION_HANGUP; Configuration configuration = Configuration.builder() diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequest.java index 39221bc4d..6d8645fc6 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequest.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequest.java @@ -11,7 +11,7 @@ package com.sinch.sdk.domains.voice.models.v1.calls.request; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.ManagedCallSvamlAction; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; import java.util.Collection; @@ -36,7 +36,7 @@ public interface CallUpdateRequest { * * @return action */ - SvamlActionPatch getAction(); + ManagedCallSvamlAction getAction(); /** * Getting builder @@ -66,7 +66,7 @@ interface Builder { * @return Current builder * @see #getAction */ - Builder setAction(SvamlActionPatch action); + Builder setAction(ManagedCallSvamlAction action); /** * Create instance diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestImpl.java index 7ba595fa6..86be341d7 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/CallUpdateRequestImpl.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.sinch.sdk.core.models.OptionalValue; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; +import com.sinch.sdk.domains.voice.models.v1.svaml.action.ManagedCallSvamlAction; import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; import java.util.Collection; import java.util.Objects; @@ -27,13 +27,13 @@ public class CallUpdateRequestImpl implements CallUpdateRequest { public static final String JSON_PROPERTY_ACTION = "action"; - private OptionalValue action; + private OptionalValue action; public CallUpdateRequestImpl() {} protected CallUpdateRequestImpl( OptionalValue> instructions, - OptionalValue action) { + OptionalValue action) { this.instructions = instructions; this.action = action; } @@ -50,13 +50,13 @@ public OptionalValue> instructions() { } @JsonIgnore - public SvamlActionPatch getAction() { + public ManagedCallSvamlAction getAction() { return action.orElse(null); } @JsonProperty(JSON_PROPERTY_ACTION) @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) - public OptionalValue action() { + public OptionalValue action() { return action; } @@ -102,7 +102,7 @@ private String toIndentedString(Object o) { @JsonPOJOBuilder(withPrefix = "set") static class Builder implements CallUpdateRequest.Builder { OptionalValue> instructions = OptionalValue.empty(); - OptionalValue action = OptionalValue.empty(); + OptionalValue action = OptionalValue.empty(); @JsonProperty(JSON_PROPERTY_INSTRUCTIONS) public Builder setInstructions(Collection instructions) { @@ -111,7 +111,7 @@ public Builder setInstructions(Collection instructions) { } @JsonProperty(JSON_PROPERTY_ACTION) - public Builder setAction(SvamlActionPatch action) { + public Builder setAction(ManagedCallSvamlAction action) { this.action = OptionalValue.of(action); return this; } diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatch.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatch.java deleted file mode 100644 index c618d66f4..000000000 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatch.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Voice API | Sinch - * - * OpenAPI document version: 1.0.1 - * Contact: support@sinch.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * Do not edit the class manually. - */ - -package com.sinch.sdk.domains.voice.models.v1.calls.request; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -@JsonDeserialize(using = SvamlActionPatchImpl.SvamlActionPatchImplDeserializer.class) -public interface SvamlActionPatch {} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatchImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatchImpl.java deleted file mode 100644 index 98f6e10bf..000000000 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlActionPatchImpl.java +++ /dev/null @@ -1,362 +0,0 @@ -package com.sinch.sdk.domains.voice.models.v1.calls.request; - -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.core.type.TypeReference; -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 com.sinch.sdk.core.models.AbstractOpenApiSchema; -import com.sinch.sdk.core.utils.databind.JSONNavigator; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionContinueImpl; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangupImpl; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionParkImpl; -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -@JsonDeserialize(using = SvamlActionPatchImpl.SvamlActionPatchImplDeserializer.class) -@JsonSerialize(using = SvamlActionPatchImpl.SvamlActionPatchImplSerializer.class) -public class SvamlActionPatchImpl extends AbstractOpenApiSchema implements SvamlActionPatch { - private static final Logger log = Logger.getLogger(SvamlActionPatchImpl.class.getName()); - - public static final class SvamlActionPatchImplSerializer - extends StdSerializer { - private static final long serialVersionUID = 1L; - - public SvamlActionPatchImplSerializer(Class t) { - super(t); - } - - public SvamlActionPatchImplSerializer() { - this(null); - } - - @Override - public void serialize( - SvamlActionPatchImpl value, JsonGenerator jgen, SerializerProvider provider) - throws IOException, JsonProcessingException { - jgen.writeObject(value.getActualInstance()); - } - } - - public static final class SvamlActionPatchImplDeserializer - extends StdDeserializer { - - private static final long serialVersionUID = 1L; - - public SvamlActionPatchImplDeserializer() { - this(SvamlActionPatchImpl.class); - } - - public SvamlActionPatchImplDeserializer(Class vc) { - super(vc); - } - - @Override - public SvamlActionPatchImpl deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException { - JsonNode tree = jp.readValueAsTree(); - Object deserialized = null; - SvamlActionPatchImpl newSvamlActionPatchImpl = new SvamlActionPatchImpl(); - Map result2 = - tree.traverse(jp.getCodec()).readValueAs(new TypeReference>() {}); - String discriminatorValue = (String) result2.get("name"); - switch (discriminatorValue) { - case "continue": - deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionContinueImpl.class); - newSvamlActionPatchImpl.setActualInstance(deserialized); - return newSvamlActionPatchImpl; - case "hangup": - deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionHangupImpl.class); - newSvamlActionPatchImpl.setActualInstance(deserialized); - return newSvamlActionPatchImpl; - case "park": - deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionParkImpl.class); - newSvamlActionPatchImpl.setActualInstance(deserialized); - return newSvamlActionPatchImpl; - default: - log.log( - Level.WARNING, - String.format( - "Failed to lookup discriminator value `%s` for SvamlActionPatchImpl. Possible" - + " values: continue hangup park", - discriminatorValue)); - } - - boolean typeCoercion = ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS); - int match = 0; - JsonToken token = tree.traverse(jp.getCodec()).nextToken(); - // deserialize SvamlActionContinueImpl - try { - boolean attemptParsing = true; - // ensure that we respect type coercion as set on the client ObjectMapper - if (SvamlActionContinueImpl.class.equals(Integer.class) - || SvamlActionContinueImpl.class.equals(Long.class) - || SvamlActionContinueImpl.class.equals(Float.class) - || SvamlActionContinueImpl.class.equals(Double.class) - || SvamlActionContinueImpl.class.equals(Boolean.class) - || SvamlActionContinueImpl.class.equals(String.class)) { - attemptParsing = typeCoercion; - if (!attemptParsing) { - attemptParsing |= - ((SvamlActionContinueImpl.class.equals(Integer.class) - || SvamlActionContinueImpl.class.equals(Long.class)) - && token == JsonToken.VALUE_NUMBER_INT); - attemptParsing |= - ((SvamlActionContinueImpl.class.equals(Float.class) - || SvamlActionContinueImpl.class.equals(Double.class)) - && token == JsonToken.VALUE_NUMBER_FLOAT); - attemptParsing |= - (SvamlActionContinueImpl.class.equals(Boolean.class) - && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE)); - attemptParsing |= - (SvamlActionContinueImpl.class.equals(String.class) - && token == JsonToken.VALUE_STRING); - } - } - if (attemptParsing) { - deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionContinueImpl.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 'SvamlActionContinueImpl'"); - } - } catch (Exception e) { - // deserialization failed, continue - log.log(Level.FINER, "Input data does not match schema 'SvamlActionContinueImpl'", e); - } - - // deserialize SvamlActionHangupImpl - try { - boolean attemptParsing = true; - // ensure that we respect type coercion as set on the client ObjectMapper - if (SvamlActionHangupImpl.class.equals(Integer.class) - || SvamlActionHangupImpl.class.equals(Long.class) - || SvamlActionHangupImpl.class.equals(Float.class) - || SvamlActionHangupImpl.class.equals(Double.class) - || SvamlActionHangupImpl.class.equals(Boolean.class) - || SvamlActionHangupImpl.class.equals(String.class)) { - attemptParsing = typeCoercion; - if (!attemptParsing) { - attemptParsing |= - ((SvamlActionHangupImpl.class.equals(Integer.class) - || SvamlActionHangupImpl.class.equals(Long.class)) - && token == JsonToken.VALUE_NUMBER_INT); - attemptParsing |= - ((SvamlActionHangupImpl.class.equals(Float.class) - || SvamlActionHangupImpl.class.equals(Double.class)) - && token == JsonToken.VALUE_NUMBER_FLOAT); - attemptParsing |= - (SvamlActionHangupImpl.class.equals(Boolean.class) - && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE)); - attemptParsing |= - (SvamlActionHangupImpl.class.equals(String.class) - && token == JsonToken.VALUE_STRING); - } - } - if (attemptParsing) { - deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionHangupImpl.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 'SvamlActionHangupImpl'"); - } - } catch (Exception e) { - // deserialization failed, continue - log.log(Level.FINER, "Input data does not match schema 'SvamlActionHangupImpl'", e); - } - - // deserialize SvamlActionParkImpl - try { - boolean attemptParsing = true; - // ensure that we respect type coercion as set on the client ObjectMapper - if (SvamlActionParkImpl.class.equals(Integer.class) - || SvamlActionParkImpl.class.equals(Long.class) - || SvamlActionParkImpl.class.equals(Float.class) - || SvamlActionParkImpl.class.equals(Double.class) - || SvamlActionParkImpl.class.equals(Boolean.class) - || SvamlActionParkImpl.class.equals(String.class)) { - attemptParsing = typeCoercion; - if (!attemptParsing) { - attemptParsing |= - ((SvamlActionParkImpl.class.equals(Integer.class) - || SvamlActionParkImpl.class.equals(Long.class)) - && token == JsonToken.VALUE_NUMBER_INT); - attemptParsing |= - ((SvamlActionParkImpl.class.equals(Float.class) - || SvamlActionParkImpl.class.equals(Double.class)) - && token == JsonToken.VALUE_NUMBER_FLOAT); - attemptParsing |= - (SvamlActionParkImpl.class.equals(Boolean.class) - && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE)); - attemptParsing |= - (SvamlActionParkImpl.class.equals(String.class) && token == JsonToken.VALUE_STRING); - } - } - if (attemptParsing) { - deserialized = tree.traverse(jp.getCodec()).readValueAs(SvamlActionParkImpl.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 'SvamlActionParkImpl'"); - } - } catch (Exception e) { - // deserialization failed, continue - log.log(Level.FINER, "Input data does not match schema 'SvamlActionParkImpl'", e); - } - - if (match == 1) { - SvamlActionPatchImpl ret = new SvamlActionPatchImpl(); - ret.setActualInstance(deserialized); - return ret; - } - throw new IOException( - String.format( - "Failed deserialization for SvamlActionPatchImpl: %d classes match result, expected" - + " 1", - match)); - } - - /** Handle deserialization of the 'null' value. */ - @Override - public SvamlActionPatchImpl getNullValue(DeserializationContext ctxt) - throws JsonMappingException { - throw new JsonMappingException(ctxt.getParser(), "SvamlActionPatchImpl cannot be null"); - } - } - - // store a list of schema names defined in oneOf - public static final Map> schemas = new HashMap<>(); - - public SvamlActionPatchImpl() { - super("oneOf", Boolean.FALSE); - } - - public SvamlActionPatchImpl(SvamlActionContinueImpl o) { - super("oneOf", Boolean.FALSE); - setActualInstance(o); - } - - public SvamlActionPatchImpl(SvamlActionHangupImpl o) { - super("oneOf", Boolean.FALSE); - setActualInstance(o); - } - - public SvamlActionPatchImpl(SvamlActionParkImpl o) { - super("oneOf", Boolean.FALSE); - setActualInstance(o); - } - - static { - schemas.put("SvamlActionContinueImpl", SvamlActionContinueImpl.class); - schemas.put("SvamlActionHangupImpl", SvamlActionHangupImpl.class); - schemas.put("SvamlActionParkImpl", SvamlActionParkImpl.class); - JSONNavigator.registerDescendants( - SvamlActionPatchImpl.class, Collections.unmodifiableMap(schemas)); - // Initialize and register the discriminator mappings. - Map> mappings = new HashMap>(); - mappings.put("continue", SvamlActionContinueImpl.class); - mappings.put("hangup", SvamlActionHangupImpl.class); - mappings.put("park", SvamlActionParkImpl.class); - mappings.put("svaml.action.patch", SvamlActionPatchImpl.class); - JSONNavigator.registerDiscriminator(SvamlActionPatchImpl.class, "name", mappings); - } - - @Override - public Map> getSchemas() { - return SvamlActionPatchImpl.schemas; - } - - /** - * Set the instance that matches the oneOf child schema, check the instance parameter is valid - * against the oneOf child schemas: SvamlActionContinueImpl, SvamlActionHangupImpl, - * SvamlActionParkImpl - * - *

It could be an instance of the 'oneOf' schemas. The oneOf child schemas may themselves be a - * composed schema (allOf, anyOf, oneOf). - */ - @Override - public void setActualInstance(Object instance) { - if (JSONNavigator.isInstanceOf( - SvamlActionContinueImpl.class, instance, new HashSet>())) { - super.setActualInstance(instance); - return; - } - - if (JSONNavigator.isInstanceOf( - SvamlActionHangupImpl.class, instance, new HashSet>())) { - super.setActualInstance(instance); - return; - } - - if (JSONNavigator.isInstanceOf(SvamlActionParkImpl.class, instance, new HashSet>())) { - super.setActualInstance(instance); - return; - } - - throw new RuntimeException( - "Invalid instance type. Must be SvamlActionContinueImpl, SvamlActionHangupImpl," - + " SvamlActionParkImpl"); - } - - /** - * Get the actual instance, which can be the following: SvamlActionContinueImpl, - * SvamlActionHangupImpl, SvamlActionParkImpl - * - * @return The actual instance (SvamlActionContinueImpl, SvamlActionHangupImpl, - * SvamlActionParkImpl) - */ - @Override - public Object getActualInstance() { - return super.getActualInstance(); - } - - /** - * Get the actual instance of `SvamlActionContinueImpl`. If the actual instance is not - * `SvamlActionContinueImpl`, the ClassCastException will be thrown. - * - * @return The actual instance of `SvamlActionContinueImpl` - * @throws ClassCastException if the instance is not `SvamlActionContinueImpl` - */ - public SvamlActionContinueImpl getSvamlActionContinueImpl() throws ClassCastException { - return (SvamlActionContinueImpl) super.getActualInstance(); - } - - /** - * Get the actual instance of `SvamlActionHangupImpl`. If the actual instance is not - * `SvamlActionHangupImpl`, the ClassCastException will be thrown. - * - * @return The actual instance of `SvamlActionHangupImpl` - * @throws ClassCastException if the instance is not `SvamlActionHangupImpl` - */ - public SvamlActionHangupImpl getSvamlActionHangupImpl() throws ClassCastException { - return (SvamlActionHangupImpl) super.getActualInstance(); - } - - /** - * Get the actual instance of `SvamlActionParkImpl`. If the actual instance is not - * `SvamlActionParkImpl`, the ClassCastException will be thrown. - * - * @return The actual instance of `SvamlActionParkImpl` - * @throws ClassCastException if the instance is not `SvamlActionParkImpl` - */ - public SvamlActionParkImpl getSvamlActionParkImpl() throws ClassCastException { - return (SvamlActionParkImpl) super.getActualInstance(); - } -} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatch.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatch.java deleted file mode 100644 index b48ca0ffb..000000000 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatch.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Voice API | Sinch - * - * OpenAPI document version: 1.0.1 - * Contact: support@sinch.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * Do not edit the class manually. - */ - -package com.sinch.sdk.domains.voice.models.v1.calls.request; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; -import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; -import java.util.Collection; - -/** - * SVAML is a call control markup language. When a server receives a callback event from the Sinch - * platform, it can respond with a SVAML object to control the voice call. The following is an - * example of a SVAML object type and its contents. - */ -@JsonDeserialize(builder = SvamlControlPatchImpl.Builder.class) -public interface SvamlControlPatch extends com.sinch.sdk.domains.voice.models.v1.svaml.Control { - - /** - * The collection of instructions that can perform various tasks during the call. You can include - * as many instructions as necessary. - * - * @return instructions - */ - Collection getInstructions(); - - /** - * Get action - * - * @return action - */ - SvamlActionPatch getAction(); - - /** - * Getting builder - * - * @return New Builder instance - */ - static Builder builder() { - return new SvamlControlPatchImpl.Builder(); - } - - /** Dedicated Builder */ - interface Builder { - - /** - * see getter - * - * @param instructions see getter - * @return Current builder - * @see #getInstructions - */ - Builder setInstructions(Collection instructions); - - /** - * see getter - * - * @param action see getter - * @return Current builder - * @see #getAction - */ - Builder setAction(SvamlActionPatch action); - - /** - * Create instance - * - * @return The instance build with current builder values - */ - SvamlControlPatch build(); - } -} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatchImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatchImpl.java deleted file mode 100644 index 8bff22441..000000000 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/calls/request/SvamlControlPatchImpl.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.sinch.sdk.domains.voice.models.v1.calls.request; - -import com.fasterxml.jackson.annotation.JsonFilter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import com.sinch.sdk.core.models.OptionalValue; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch; -import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstruction; -import java.util.Collection; -import java.util.Objects; - -@JsonPropertyOrder({ - SvamlControlPatchImpl.JSON_PROPERTY_INSTRUCTIONS, - SvamlControlPatchImpl.JSON_PROPERTY_ACTION -}) -@JsonFilter("uninitializedFilter") -@JsonInclude(value = JsonInclude.Include.CUSTOM) -public class SvamlControlPatchImpl - implements SvamlControlPatch, com.sinch.sdk.domains.voice.models.v1.svaml.Control { - private static final long serialVersionUID = 1L; - - public static final String JSON_PROPERTY_INSTRUCTIONS = "instructions"; - - private OptionalValue> instructions; - - public static final String JSON_PROPERTY_ACTION = "action"; - - private OptionalValue action; - - public SvamlControlPatchImpl() {} - - protected SvamlControlPatchImpl( - OptionalValue> instructions, - OptionalValue action) { - this.instructions = instructions; - this.action = action; - } - - @JsonIgnore - public Collection getInstructions() { - return instructions.orElse(null); - } - - @JsonProperty(JSON_PROPERTY_INSTRUCTIONS) - @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) - public OptionalValue> instructions() { - return instructions; - } - - @JsonIgnore - public SvamlActionPatch getAction() { - return action.orElse(null); - } - - @JsonProperty(JSON_PROPERTY_ACTION) - @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) - public OptionalValue action() { - return action; - } - - /** Return true if this UpdateCallSVAMLRequestBody object is equal to o. */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - SvamlControlPatchImpl updateCallSVAMLRequestBody = (SvamlControlPatchImpl) o; - return Objects.equals(this.instructions, updateCallSVAMLRequestBody.instructions) - && Objects.equals(this.action, updateCallSVAMLRequestBody.action); - } - - @Override - public int hashCode() { - return Objects.hash(instructions, action); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("class SvamlControlPatchImpl {\n"); - sb.append(" instructions: ").append(toIndentedString(instructions)).append("\n"); - sb.append(" action: ").append(toIndentedString(action)).append("\n"); - sb.append("}"); - return sb.toString(); - } - - /** - * Convert the given object to string with each line indented by 4 spaces (except the first line). - */ - private String toIndentedString(Object o) { - if (o == null) { - return "null"; - } - return o.toString().replace("\n", "\n "); - } - - @JsonPOJOBuilder(withPrefix = "set") - static class Builder implements SvamlControlPatch.Builder { - OptionalValue> instructions = OptionalValue.empty(); - OptionalValue action = OptionalValue.empty(); - - @JsonProperty(JSON_PROPERTY_INSTRUCTIONS) - public Builder setInstructions(Collection instructions) { - this.instructions = OptionalValue.of(instructions); - return this; - } - - @JsonProperty(JSON_PROPERTY_ACTION) - public Builder setAction(SvamlActionPatch action) { - this.action = OptionalValue.of(action); - return this; - } - - public SvamlControlPatch build() { - return new SvamlControlPatchImpl(instructions, action); - } - } -} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinue.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinue.java index 784c2b6e9..8c9ed6c40 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinue.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinue.java @@ -24,7 +24,7 @@ @JsonDeserialize(builder = SvamlActionContinueImpl.Builder.class) public interface SvamlActionContinue extends com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, - com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { + com.sinch.sdk.domains.voice.models.v1.svaml.action.ManagedCallSvamlAction { /** ready to use action to send a `continue` */ SvamlActionContinue SVAML_ACTION_CONTINUE = SvamlActionContinue.builder().build(); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinueImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinueImpl.java index 91f182b55..54b1c9b2c 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinueImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionContinueImpl.java @@ -15,7 +15,7 @@ public class SvamlActionContinueImpl implements SvamlActionContinue, com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, - com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { + com.sinch.sdk.domains.voice.models.v1.svaml.action.ManagedCallSvamlAction { private static final long serialVersionUID = 1L; public static final String JSON_PROPERTY_NAME = "name"; diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangup.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangup.java index 419dbc3b6..9433b1099 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangup.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangup.java @@ -26,7 +26,7 @@ @JsonDeserialize(builder = SvamlActionHangupImpl.Builder.class) public interface SvamlActionHangup extends com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, - com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { + com.sinch.sdk.domains.voice.models.v1.svaml.action.ManagedCallSvamlAction { /** ready to use action to send a `hangup` */ SvamlActionHangup SVAML_ACTION_HANGUP = SvamlActionHangup.builder().build(); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangupImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangupImpl.java index 3d938e358..abdf61e9b 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangupImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionHangupImpl.java @@ -15,7 +15,7 @@ public class SvamlActionHangupImpl implements SvamlActionHangup, com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, - com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { + com.sinch.sdk.domains.voice.models.v1.svaml.action.ManagedCallSvamlAction { private static final long serialVersionUID = 1L; public static final String JSON_PROPERTY_NAME = "name"; diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPark.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPark.java index 69b131dfb..74161aa2a 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPark.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionPark.java @@ -25,7 +25,7 @@ @JsonDeserialize(builder = SvamlActionParkImpl.Builder.class) public interface SvamlActionPark extends com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, - com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { + com.sinch.sdk.domains.voice.models.v1.svaml.action.ManagedCallSvamlAction { /** The name property. Must have the value park. */ public class NameEnum extends EnumDynamic { diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionParkImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionParkImpl.java index 72fe17653..73577fdc3 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionParkImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v1/svaml/action/SvamlActionParkImpl.java @@ -21,7 +21,7 @@ public class SvamlActionParkImpl implements SvamlActionPark, com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlAction, - com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionPatch { + com.sinch.sdk.domains.voice.models.v1.svaml.action.ManagedCallSvamlAction { private static final long serialVersionUID = 1L; public static final String JSON_PROPERTY_NAME = "name"; diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/svaml/SvamlControlPatchTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/svaml/SvamlControlPatchTest.java deleted file mode 100644 index 3ffb58741..000000000 --- a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v1/svaml/SvamlControlPatchTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.sinch.sdk.domains.voice.models.v1.svaml; - -import com.adelean.inject.resources.junit.jupiter.GivenTextResource; -import com.adelean.inject.resources.junit.jupiter.TestWithResources; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.sinch.sdk.BaseTest; -import com.sinch.sdk.domains.voice.models.v1.calls.request.SvamlControlPatch; -import com.sinch.sdk.domains.voice.models.v1.svaml.action.SvamlActionHangup; -import com.sinch.sdk.domains.voice.models.v1.svaml.instruction.SvamlInstructionSay; -import java.util.Arrays; -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; - -@TestWithResources -public class SvamlControlPatchTest extends BaseTest { - - public static SvamlControlPatch expectedSvamlControlPatch = - SvamlControlPatch.builder() - .setAction(SvamlActionHangup.SVAML_ACTION_HANGUP) - .setInstructions( - Arrays.asList( - SvamlInstructionSay.builder() - .setText("Sorry, the conference has been cancelled. The call will end now.") - .setLocale("en-US") - .build())) - .build(); - - @GivenTextResource("/domains/voice/v1/svaml/SvamlControlPatchDto.json") - String jsonSvamlControlPatchDto; - - @Test - void serializeSVAMLResponse() throws JsonProcessingException, JSONException { - - String serializedString = objectMapper.writeValueAsString(expectedSvamlControlPatch); - - JSONAssert.assertEquals(jsonSvamlControlPatchDto, serializedString, true); - } -} diff --git a/openapi-contracts/src/test/resources/domains/voice/v1/svaml/SvamlControlPatchDto.json b/openapi-contracts/src/test/resources/domains/voice/v1/svaml/SvamlControlPatchDto.json deleted file mode 100644 index 1640c9049..000000000 --- a/openapi-contracts/src/test/resources/domains/voice/v1/svaml/SvamlControlPatchDto.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "instructions": [ - { - "name": "say", - "text": "Sorry, the conference has been cancelled. The call will end now.", - "locale": "en-US" - } - ], - "action": { - "name": "hangup" - } -} From 538b4479ae414492aa05e9fe77ac480754afb836 Mon Sep 17 00:00:00 2001 From: Marcos Lozano Romero Date: Wed, 24 Jun 2026 16:00:08 +0200 Subject: [PATCH 22/37] Feature/merge v2.1 next with main (#354) * chore: update readme content and structure * Update changelog with README update --- CHANGELOG.md | 2 + README.md | 374 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 325 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a648f00c4..e519254ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ All notable changes to the **Sinch Java SDK** are documented in this file. - **[fix]** `HttpClientApache`: wrap response-body `Scanner` in a try-with-resources block to prevent resource leaks; gracefully handle empty (`null`) response entities - **[fix]** `SinchClient`: guard against a `NullPointerException` when `java.vendor` system property is absent while building the `User-Agent` auxiliary flag - **[fix]** `Configuration`: correct copy-paste error in `toString()` and Javadoc — `conversationContext` label was incorrectly attributed to the Voice domain +- **[doc]** Improve README structure and content. + ### Examples / Snippets - **[doc]** Fix typos in `conversation/conversations/Create` and `voice/applications/GetEventDestinations` snippets diff --git a/README.md b/README.md index 4967d9b74..7f0ad701a 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,39 @@ # Sinch Java SDK +[![Java](https://img.shields.io/badge/Java-8+-orange.svg)](https://www.java.com) +[![Latest Release](https://img.shields.io/maven-central/v/com.sinch.sdk/sinch-sdk-java?label=sinch&labelColor=FFC658)](https://central.sonatype.com/artifact/com.sinch.sdk/sinch-sdk-java) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/sinch/sinch-sdk-java/blob/main/LICENSE) + Here you'll find documentation related to the Sinch Java SDK, including how to install it, initialize it, and start developing Java code using Sinch services. To use Sinch services, you'll need a Sinch account and access keys. You can sign up for an account and create access keys at [dashboard.sinch.com](https://dashboard.sinch.com). -For more information on the SDK, refer to the dedicated [Java SDK documentation section](https://developers.sinch.com/docs/sdks/java) and for the Sinch APIs on which this SDK is based, refer to the official [developer documentation portal](https://developers.sinch.com). +For more information on the SDK, refer to the dedicated [Java SDK documentation section](https://developers.sinch.com/docs/sdks/java) and the [Javadoc](https://www.javadoc.io/doc/com.sinch.sdk/sinch-sdk-java/latest/index.html). For the Sinch APIs on which this SDK is based, refer to the official [developer documentation portal](https://developers.sinch.com). + ## Table of contents: + - [Prerequisites](#prerequisites) - [Installation](#installation) -- [Getting started](#getting-started) - - [Client initialization](#client-initialization) - - [Client lifecycle](#client-lifecycle) - [Supported APIs](#supported-apis) +- [Getting started](#getting-started) - [Logging](#logging) -- [Onboarding](#onboarding) +- [Handling Exceptions](#handling-exceptions) - [Third-party dependencies](#third-party-dependencies) -- [Changelog and Migration guides](#changelog--migration) +- [Examples](#examples) +- [Changelog and Migration](#changelog--migration) +- [License](#license) +- [Contact](#contact) ## Prerequisites -- JDK 8 or later +- [JDK 8 or later](https://www.java.com) - [Maven](https://maven.apache.org/) -- [Maven Repository for this SDK](https://central.sonatype.com/artifact/com.sinch.sdk/sinch-sdk-java) -- [Sinch account](https://dashboard.sinch.com) +- [Sinch account](https://dashboard.sinch.com/) + +> **Warning**: +> This SDK is intended for server-side (backend) use only. Do not use it in front-end or client-side applications (web, mobile, or desktop), regardless of language or framework. Doing so can expose your Sinch credentials to end-users. + ## Installation @@ -40,61 +50,292 @@ Add the SDK dependency to your `pom.xml` file: ``` Note: The `${sdk.version}` needs to be set according to the released version to be used (see available versions from [Maven Repository](https://central.sonatype.com/artifact/com.sinch.sdk/sinch-sdk-java)) -## Getting started -Once the SDK is installed, you must start by initializing the main client class. +## Supported APIs + + +| API Category | API Name | +|-----------------|---------------------------------------------------------------------| +| Messaging | [Conversation API](https://developers.sinch.com/docs/conversation/) | +| | [SMS API](https://developers.sinch.com/docs/sms/) | +| Voice and Video | [Voice API](https://developers.sinch.com/docs/voice/) | +| Numbers | [Numbers API](https://developers.sinch.com/docs/numbers/) | +| Verification | [Verification API](https://developers.sinch.com/docs/verification/) | +| | [Number Lookup API](https://developers.sinch.com/docs/number-lookup/) | + +> **Note:** The SMS API is end-of-sale. New integrations should use the [Conversation API](https://developers.sinch.com/docs/conversation/) instead, which supports SMS and many other channels. + + +## Getting started ### Client initialization -To initialize communication with the Sinch servers, credentials obtained from the Sinch dashboard must be provided to the main client class of this SDK. +To start using the SDK, initialize the main client class. This client gives you access to all the SDK services: -It's highly recommended to not hardcode these credentials and to load them from environment variables instead. +```java +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.models.Configuration; -Sample: +// Warning: not all APIs support project authentication. Check the section for each API before using this snippet. + +Configuration configuration = + Configuration.builder() + .setProjectId(PROJECT_ID) + .setKeyId(KEY_ID) + .setKeySecret(KEY_SECRET) + .build(); + +SinchClient client = new SinchClient(configuration); +``` + +Get `projectId`, `keyId` and `keySecret` from the [Access keys](https://dashboard.sinch.com/settings/access-keys) page in your Sinch dashboard (`keySecret` is shown only once, at creation time). It's highly recommended to not hardcode these credentials: load them from environment variables for local development, and from a secret manager in production. + +This snippet is the common starting point for project-based API. Some APIs need a different initialization or extra parameters (for example, a region or application credentials), see the section for each API below. + + +### Conversation API + +The Conversation API is regionalized. To use this API, the `conversationRegion` parameter is required: ```java import com.sinch.sdk.SinchClient; import com.sinch.sdk.models.Configuration; +import com.sinch.sdk.models.ConversationRegion; + +Configuration configuration = + Configuration.builder() + .setProjectId(PROJECT_ID) + .setKeyId(KEY_ID) + .setKeySecret(KEY_SECRET) + .setConversationRegion(ConversationRegion.EU) + .build(); -... -Configuration configuration = Configuration.builder() - .setKeyId(PARAM_KEY_ID) - .setKeySecret(PARAM_KEY_SECRET) - .setProjectId(PARAM_PROJECT_ID) - .build(); -... SinchClient client = new SinchClient(configuration); ``` -### Client lifecycle -`SinchClient` exposes a `close()` method to shut down the underlying HTTP connection pool and release all associated resources. +#### Sinch Events + +The Conversation API delivers asynchronous Sinch Events to the Event Destination URL you configure for your app in the [Conversation dashboard](https://dashboard.sinch.com/convapi/apps). `validateAuthenticationHeader` confirms a request comes from Sinch and `parseEvent` turns its payload into an event object. `headers` and `body` are the incoming request's headers and raw body: + +```java +import com.sinch.sdk.domains.conversation.api.v1.SinchEventsService; +import com.sinch.sdk.domains.conversation.models.v1.sinchevents.ConversationSinchEvent; + +SinchEventsService sinchEvents = sinchClient.conversation().v1().sinchEvents(); + +boolean validAuth = sinchEvents.validateAuthenticationHeader(sinchEventsSecret, headers, body); + +ConversationSinchEvent event = sinchEvents.parseEvent(body); +``` + +`sinchEventsSecret` is set per app in the [Conversation dashboard](https://dashboard.sinch.com/convapi/apps). `parseEvent` works without validating the request, but then its origin can't be verified, so validating is recommended in production. + +You can find a complete example in [examples/sinch-events](examples/sinch-events). + +### SMS API -**Explicit close at shutdown:** +> **Warning:** the SMS API is end-of-sale. For new integrations, prefer the [Conversation API](#conversation-api). + +The SMS API is regionalized: set `smsRegion` to the region where your SMS account is hosted. The accepted values are `US`, `EU`, `AU`, `BR` and `CA`, and the region also determines which credentials you can use: + +- **Project access keys** — available only in the `US` and `EU` regions. Use the same `projectId`, `keyId` and `keySecret` as the common client, plus `smsRegion`: ```java -// Create once at startup (e.g. as a Spring bean) +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.models.Configuration; +import com.sinch.sdk.models.SMSRegion; + +Configuration configuration = + Configuration.builder() + .setProjectId(PROJECT_ID) + .setKeyId(KEY_ID) + .setKeySecret(KEY_SECRET) + .setSmsRegion(SMSRegion.US) + .build(); + SinchClient client = new SinchClient(configuration); +``` + +> **SMS authentication for new projects** +> +> Projects created after the SMS API end-of-sale (`15/04/26`) cannot use +> project access keys — the SMS API requests return `401 Unauthorized`. +> +> If you encounter this issue, consider the following options: +> +> 1. Use service plan credentials (`smsServicePlanId` + `smsApiToken`) +> 2. Use the Conversation API, which works with project access keys. +> 3. Contact your account manager + +- **Service plan** — available in all regions (`US`, `EU`, `AU`, `BR`, `CA`). Use a `smsServicePlanId` and `smsApiToken`, both available on the [Service APIs dashboard](https://dashboard.sinch.com/sms/api/services): + +```java +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.models.Configuration; +import com.sinch.sdk.models.SMSRegion; -// ... use throughout the application lifetime ... +Configuration configuration = + Configuration.builder() + .setSmsServicePlanId(SERVICE_PLAN_ID) + .setSmsApiToken(API_TOKEN) + .setSmsRegion(SMSRegion.US) + .build(); -// Release resources at shutdown -client.close(); +SinchClient client = new SinchClient(configuration); ``` -Failing to close a `SinchClient` that has made at least one request will leave the HTTP connection pool open until the object is garbage-collected, which may delay JVM shutdown and exhaust file descriptors in long-running processes. +#### Sinch Events -## Supported APIs +The SMS API delivers asynchronous Sinch Events to an Event Destination, whose URL is set per batch with the `eventDestinationTarget` parameter on the send, update and replace operations. `validateAuthenticationHeader` confirms a request comes from Sinch and `parseEvent` turns its payload into an event object. `headers` and `body` are the incoming request's headers and raw body: + +```java +import com.sinch.sdk.domains.sms.api.v1.SinchEventsService; +import com.sinch.sdk.domains.sms.models.v1.sinchevents.SmsSinchEvent; + +SinchEventsService sinchEvents = client.sms().v1().sinchEvents(); + +boolean validAuth = sinchEvents.validateAuthenticationHeader(sinchEventsSecret, headers, body); + +SmsSinchEvent event = sinchEvents.parseEvent(body); +``` + +Signature authentication for SMS events must be enabled for your account by your account manager. Until it is activated, signature headers will not be present and `parseEvent` can be called directly without signature validation. See the [SMS events documentation](https://developers.sinch.com/docs/sms/api-reference/sms/tag/Webhooks/#tag/Webhooks/section/Callbacks). -Here is the list of the Sinch products and their level of support by the Java SDK: +You can find a complete example in [examples/sinch-events](examples/sinch-events). -| API Category | API Name | Status | -|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|:------:| -| Messaging | Conversation API [(javadoc)](https://www.javadoc.io/doc/com.sinch.sdk/sinch-sdk-java/latest/com/sinch/sdk/domains/conversation/package-summary.html) | ✅ | -| | SMS API [(javadoc)](https://www.javadoc.io/doc/com.sinch.sdk/sinch-sdk-java/latest/com/sinch/sdk/domains/sms/package-summary.html) | ✅ | -| Numbers & Connectivity | Numbers API [(javadoc)](https://www.javadoc.io/doc/com.sinch.sdk/sinch-sdk-java/latest/com/sinch/sdk/domains/numbers/package-summary.html) | ✅ | -| Verification | Verification API [(javadoc)](https://www.javadoc.io/doc/com.sinch.sdk/sinch-sdk-java/latest/com/sinch/sdk/domains/verification/package-summary.html) | ✅ | -| Voice and Video | Voice API [(javadoc)](https://www.javadoc.io/doc/com.sinch.sdk/sinch-sdk-java/latest/com/sinch/sdk/domains/voice/package-summary.html) | ✅ | +### Numbers API + +The Numbers API needs no extra parameters, use the [common client](#client-initialization) shown above. + +#### Sinch Events + +The Numbers API delivers asynchronous Sinch Events to the Event Destination URL you configure for your project through `sinchClient.numbers().v1().eventDestinations()`. `validateAuthenticationHeader` confirms a request comes from Sinch and `parseEvent` turns its payload into an event object. `headers` and `body` are the incoming request's headers and raw body: + +```java +import com.sinch.sdk.domains.numbers.api.v1.SinchEventsService; +import com.sinch.sdk.domains.numbers.models.v1.sinchevents.NumberSinchEvent; + +SinchEventsService sinchEvents = client.numbers().v1().sinchEvents(); + +boolean validAuth = sinchEvents.validateAuthenticationHeader(sinchEventsSecret, headers, body); + +NumberSinchEvent event = sinchEvents.parseEvent(body); +``` + +`sinchEventsSecret` is the `HmacSecret` value configured on the Event Destination. `parseEvent` works without validating the request, but then its origin can't be verified, so validating is recommended in production. + +You can find a complete example in [examples/sinch-events](examples/sinch-events). + +### Number Lookup API + +The Number Lookup API needs no extra parameters, use the [common client](#client-initialization) shown above. + +### Verification API + +The Verification API uses application credentials. Set `applicationKey` and `applicationSecret`, both available on the [Apps dashboard](https://dashboard.sinch.com/verification/apps): + +```java +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.models.Configuration; + +Configuration configuration = + Configuration.builder() + .setApplicationKey(APPLICATION_KEY) + .setApplicationSecret(APPLICATION_SECRET) + .build(); + +SinchClient client = new SinchClient(configuration); +``` + +#### Sinch Events + +The Verification API delivers synchronous Sinch Events to the Event Destination URL configured for your app. Requests are signed with your application credentials, so validation requires the HTTP verb and URI of the controller handling the request, in addition to the `headers` and raw `body`: + +```java +import com.sinch.sdk.domains.verification.api.v1.SinchEventsService; + +SinchEventsService sinchEvents = client.verification().v1().sinchEvents(); + +boolean validAuth = sinchEvents.validateAuthenticationHeader("POST", "/VerificationEvent", headers, body); + +var event = sinchEvents.parseEvent(body); +``` + +Some events expect a response: build it from the business layer and serialize it with `sinchEvents.serializeResponse(response)` before returning it to Sinch. + +You can find a complete example in [examples/sinch-events](examples/sinch-events). + +### Voice API + +The Voice API uses application credentials. Set `applicationKey` and `applicationSecret` (both available on the [Apps dashboard](https://dashboard.sinch.com/voice/apps)); `voiceRegion` is optional and defaults to a global region: + +```java +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.models.Configuration; +import com.sinch.sdk.models.VoiceRegion; + +Configuration configuration = + Configuration.builder() + .setApplicationKey(APPLICATION_KEY) + .setApplicationSecret(APPLICATION_SECRET) + .setVoiceRegion(VoiceRegion.GLOBAL) + .build(); + +SinchClient client = new SinchClient(configuration); +``` + +#### Sinch Events + +The Voice API delivers synchronous Sinch Events to the Event Destination URL configured for your app. Requests are signed with your application credentials, so validation requires the HTTP verb and URI of the controller handling the request, in addition to the `headers` and raw `body`: + +```java +import com.sinch.sdk.domains.voice.api.v1.SinchEventsService; + +SinchEventsService sinchEvents = client.voice().v1().sinchEvents(); + +boolean validAuth = sinchEvents.validateAuthenticationHeader("POST", "/VoiceEvent", headers, body); + +var event = sinchEvents.parseEvent(body); +``` + +Some events (for example an incoming call) expect a SVAML response: build it from the business layer and serialize it with `sinchEvents.serializeResponse(response)` before returning it to Sinch. + +You can find a complete example in [examples/sinch-events](examples/sinch-events). + + +### Your first request + +Once your client is configured, you can send your first message. The example below uses the Conversation API to send a simple text message over SMS. Replace `CONVERSATION_APP_ID` with your app ID, `SINCH_VIRTUAL_PHONE_NUMBER` with your Sinch number and `RECIPIENT_PHONE_NUMBER` with the recipient's phone number: + +```java +import com.sinch.sdk.domains.conversation.models.v1.ChannelRecipientIdentities; +import com.sinch.sdk.domains.conversation.models.v1.ChannelRecipientIdentity; +import com.sinch.sdk.domains.conversation.models.v1.ConversationChannel; +import com.sinch.sdk.domains.conversation.models.v1.messages.AppMessage; +import com.sinch.sdk.domains.conversation.models.v1.messages.request.SendMessageRequest; +import com.sinch.sdk.domains.conversation.models.v1.messages.response.SendMessageResponse; +import com.sinch.sdk.domains.conversation.models.v1.messages.types.text.TextMessage; +import java.util.Collections; + +SendMessageRequest request = + SendMessageRequest.builder() + .setAppId("CONVERSATION_APP_ID") + .setRecipient( + ChannelRecipientIdentities.of( + ChannelRecipientIdentity.builder() + .setChannel(ConversationChannel.SMS) + .setIdentity("RECIPIENT_PHONE_NUMBER") + .build())) + .setMessage( + AppMessage.builder() + .setBody(TextMessage.builder().setText("Hello from the Sinch Java SDK!").build()) + .build()) + .setChannelProperties(Collections.singletonMap("SMS_SENDER", "SINCH_VIRTUAL_PHONE_NUMBER")) + .build(); + +SendMessageResponse response = client.conversation().messages().sendMessage(request); +``` ## Logging @@ -127,17 +368,40 @@ java.util.logging.ConsoleHandler.level = FINEST ``` If you are using a different logging framework (such as SLF4J or Spring), please refer to the documentation for your specific framework. -## Onboarding -To enhance the onboarding experience, the following resources are available: -- Sinch Online Developers Documentation: https://developers.sinch.com -- Java SDK Online Javadoc: https://www.javadoc.io/doc/com.sinch.sdk/sinch-sdk-java -- Java SDK examples: A collection of basic code snippets, tutorials and getting started for reference: [examples](examples/README.md) +## Handling exceptions + +When a backend call is unsuccessful, the SDK throws an `ApiException` (from `com.sinch.sdk.core.exceptions`). It is the base class for all Sinch API exceptions and exposes the HTTP status code through `getCode()`. + + +```java +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.domains.numbers.api.v1.NumbersService; + +NumbersService numbersService = sinchClient.numbers().v1(); + +try { + AvailableNumbersListResponse response = + numbersService.searchForAvailableNumbers( + AvailableNumbersListQueryParameters.builder() + .setRegionCode("US") + .setType(NumberType.LOCAL) + .build()); +} catch (ApiException e) { + // e.getCode() holds the HTTP status code, e.getMessage() the error detail +} +``` + +`ApiException` is an unchecked exception, so wrapping calls in a `try/catch` is optional but recommended. Catching `ApiException` handles every error raised by an API call, including its more specific subclasses: + +- `ApiAuthException`: authentication/authorization failures. +- `ApiMappingException`: the response payload could not be deserialized into the expected type. + ## Third-party dependencies The SDK relies on the following third-party dependencies: -- [Jackson's library](https://github.com/FasterXML/jackson-jakarta-rs-providers): Provides JSON serialization/deserialization functionality. -- [Apache HTTP client](https://hc.apache.org/httpcomponents-client-ga/): Manages communication with Sinch products' REST APIs +- [Jackson](https://github.com/FasterXML/jackson-jakarta-rs-providers): provides JSON serialization/deserialization functionality. +- [Apache HTTP client](https://hc.apache.org/httpcomponents-client-ga/): manages communication with Sinch products' REST APIs. ### Jackson/Java EE/jaxrs/Jakarta Compatibility Consideration The transition from `javax` to `jakarta` namespaces with the Java EE to Jakarta EE migration may cause compatibility issues. Refer to [Oracle's note about the transition](https://docslib.org/doc/1185955/transition-from-java-ee-to-jakarta-ee-ee-conclusion-what-happened-and-what-you-need-to-know) for additional details. @@ -173,16 +437,24 @@ Note: Replace `VERSION-YOU-WANT-TO-BE-USED` with a Jackson version of at least ` ... ``` +## Examples + +You can find: + - self-contained code snippets covering every API operation in the [examples/snippets](./examples/snippets) folder. + - getting started guides for specific use cases in the [examples/getting-started](./examples/getting-started) folder. + - a Spring Boot server template to process received Sinch Events in the [examples/sinch-events](./examples/sinch-events) folder. + - an SDK client template application in the [examples/client](./examples/client) folder. + - step-by-step tutorials combining multiple SDK features in the [examples/tutorials](./examples/tutorials) folder. + + ## Changelog & Migration For information about the latest changes in the SDK, please refer to the [CHANGELOG](CHANGELOG.md) file and the [MIGRATION-GUIDE](MIGRATION-GUIDE.md) for instructions on how to update your code when upgrading to a new major version of the SDK. -## Contact - -Developer Experience engineering team: [team-developer-experience@sinch.com](mailto:team-developer-experience@sinch.com) - ## License -This project is licensed under the Apache License. +This project is licensed under the Apache License. See the [LICENSE](LICENSE) file for the license text. + +## Contact -See the [LICENSE](LICENSE) file for the license text. +Developer Experience engineering team: [team-developer-experience@sinch.com](mailto:team-developer-experience@sinch.com) From 231d3063a934e57abfa7a55c34383ad54dc38e16 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Thu, 25 Jun 2026 17:53:06 +0200 Subject: [PATCH 23/37] New generated Java Number sources and test coverage for internalFailureCode field --- .../numbers/models/v1/errors/BadRequest.java | 1 + .../models/v1/errors/InternalError.java | 4 + .../models/v1/errors/InvalidArgument.java | 2 + .../numbers/models/v1/errors/NotFound.java | 2 + .../errors/internal/InternalErrorError.java | 4 + .../errors/internal/InvalidArgumentError.java | 2 + .../v1/errors/internal/NotFoundError.java | 2 + .../EventDestinationUpdateRequest.java | 4 +- .../v1/sinchevents/NumberSinchEvent.java | 95 ++++++++++++++++++- .../v1/sinchevents/NumberSinchEventImpl.java | 55 ++++++++++- .../numbers/models/v1/SinchEventsDtoTest.java | 1 + .../v1/sinchevents/number-sinch-event.json | 3 +- 12 files changed, 165 insertions(+), 10 deletions(-) diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/BadRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/BadRequest.java index d852e6610..243f28b26 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/BadRequest.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/BadRequest.java @@ -23,6 +23,7 @@ public interface BadRequest { /** Gets or Sets type */ public class TypeEnum extends EnumDynamic { + /** The request was malformed in some way. */ public static final TypeEnum BAD_REQUEST = new TypeEnum("BadRequest"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/InternalError.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/InternalError.java index cf929c17b..4c77289a8 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/InternalError.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/InternalError.java @@ -23,6 +23,7 @@ public interface InternalError { /** Gets or Sets code */ public class CodeEnum extends EnumDynamic { + /** The HTTP status code for an internal server error. */ public static final CodeEnum NUMBER_500 = new CodeEnum(500); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -61,7 +62,10 @@ public static Integer valueOf(CodeEnum e) { /** Gets or Sets status */ public class StatusEnum extends EnumDynamic { + /** An internal server error. */ public static final StatusEnum INTERNAL = new StatusEnum("INTERNAL"); + + /** Some unknown error. */ public static final StatusEnum UNKNOWN = new StatusEnum("UNKNOWN"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/InvalidArgument.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/InvalidArgument.java index 002733b2d..5e0e5d41e 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/InvalidArgument.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/InvalidArgument.java @@ -23,6 +23,7 @@ public interface InvalidArgument { /** Gets or Sets code */ public class CodeEnum extends EnumDynamic { + /** The HTTP status code for invalid arguments. */ public static final CodeEnum NUMBER_400 = new CodeEnum(400); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -61,6 +62,7 @@ public static Integer valueOf(CodeEnum e) { /** Gets or Sets status */ public class StatusEnum extends EnumDynamic { + /** The request contained invalid arguments. */ public static final StatusEnum INVALID_ARGUMENT = new StatusEnum("INVALID_ARGUMENT"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/NotFound.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/NotFound.java index 43351bc95..1d6bb1416 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/NotFound.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/NotFound.java @@ -23,6 +23,7 @@ public interface NotFound { /** Gets or Sets code */ public class CodeEnum extends EnumDynamic { + /** The HTTP status code for a resource not found. */ public static final CodeEnum NUMBER_404 = new CodeEnum(404); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -61,6 +62,7 @@ public static Integer valueOf(CodeEnum e) { /** Gets or Sets status */ public class StatusEnum extends EnumDynamic { + /** The request resource could not be found. */ public static final StatusEnum NOT_FOUND = new StatusEnum("NOT_FOUND"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/InternalErrorError.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/InternalErrorError.java index a60ecf0b4..b013639f2 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/InternalErrorError.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/InternalErrorError.java @@ -23,6 +23,7 @@ public interface InternalErrorError { /** Gets or Sets code */ public class CodeEnum extends EnumDynamic { + /** The HTTP status code for an internal server error. */ public static final CodeEnum NUMBER_500 = new CodeEnum(500); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -61,7 +62,10 @@ public static Integer valueOf(CodeEnum e) { /** Gets or Sets status */ public class StatusEnum extends EnumDynamic { + /** An internal server error. */ public static final StatusEnum INTERNAL = new StatusEnum("INTERNAL"); + + /** Some unknown error. */ public static final StatusEnum UNKNOWN = new StatusEnum("UNKNOWN"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/InvalidArgumentError.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/InvalidArgumentError.java index 6c1ef6f4d..85e04a439 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/InvalidArgumentError.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/InvalidArgumentError.java @@ -24,6 +24,7 @@ public interface InvalidArgumentError { /** Gets or Sets code */ public class CodeEnum extends EnumDynamic { + /** The HTTP status code for invalid arguments. */ public static final CodeEnum NUMBER_400 = new CodeEnum(400); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -62,6 +63,7 @@ public static Integer valueOf(CodeEnum e) { /** Gets or Sets status */ public class StatusEnum extends EnumDynamic { + /** The request contained invalid arguments. */ public static final StatusEnum INVALID_ARGUMENT = new StatusEnum("INVALID_ARGUMENT"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/NotFoundError.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/NotFoundError.java index 3813ceb5a..7e4f032e6 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/NotFoundError.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/errors/internal/NotFoundError.java @@ -24,6 +24,7 @@ public interface NotFoundError { /** Gets or Sets code */ public class CodeEnum extends EnumDynamic { + /** The HTTP status code for a resource not found. */ public static final CodeEnum NUMBER_404 = new CodeEnum(404); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -62,6 +63,7 @@ public static Integer valueOf(CodeEnum e) { /** Gets or Sets status */ public class StatusEnum extends EnumDynamic { + /** The request resource could not be found. */ public static final StatusEnum NOT_FOUND = new StatusEnum("NOT_FOUND"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/eventdestinations/request/EventDestinationUpdateRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/eventdestinations/request/EventDestinationUpdateRequest.java index aeca3cb64..15c163234 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/eventdestinations/request/EventDestinationUpdateRequest.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/eventdestinations/request/EventDestinationUpdateRequest.java @@ -17,7 +17,9 @@ public interface EventDestinationUpdateRequest { /** - * The HMAC secret to be updated for the specified project + * The HMAC secret to be updated for the specified project. It must be between 32 and 64 + * characters, alphanumeric (A-Z, a-z, 0-9) and hyphens (-) only. Regex pattern: + * ^[a-zA-Z0-9-]{32,64}$ * * @return hmacSecret */ diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/sinchevents/NumberSinchEvent.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/sinchevents/NumberSinchEvent.java index 3492004d6..f66eb20d4 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/sinchevents/NumberSinchEvent.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/sinchevents/NumberSinchEvent.java @@ -59,16 +59,35 @@ public interface NumberSinchEvent { /** The type of the event. */ public class EventTypeEnum extends EnumDynamic { + /** An event that occurs when a number is linked to a Service Plan ID. */ public static final EventTypeEnum PROVISIONING_TO_SMS_PLATFORM = new EventTypeEnum("PROVISIONING_TO_SMS_PLATFORM"); + + /** An event that occurs when a number is unlinked from a Service Plan ID. */ public static final EventTypeEnum DEPROVISIONING_FROM_SMS_PLATFORM = new EventTypeEnum("DEPROVISIONING_FROM_SMS_PLATFORM"); + + /** An event that occurs when a number is linked to a Campaign. */ public static final EventTypeEnum PROVISIONING_TO_CAMPAIGN = new EventTypeEnum("PROVISIONING_TO_CAMPAIGN"); + + /** An event that occurs when a number is unlinked from a Campaign. */ public static final EventTypeEnum DEPROVISIONING_FROM_CAMPAIGN = new EventTypeEnum("DEPROVISIONING_FROM_CAMPAIGN"); + + /** An event that occurs when a number is enabled for Voice operations. */ public static final EventTypeEnum PROVISIONING_TO_VOICE_PLATFORM = new EventTypeEnum("PROVISIONING_TO_VOICE_PLATFORM"); + + /** An event that occurs when a number is disabled for Voice operations. */ + public static final EventTypeEnum DEPROVISIONING_FROM_VOICE_PLATFORM = + new EventTypeEnum("DEPROVISIONING_FROM_VOICE_PLATFORM"); + + /** An event that occurs when a Number Order transitions to a new state. */ + public static final EventTypeEnum NUMBER_ORDER_PROCESSING = + new EventTypeEnum("NUMBER_ORDER_PROCESSING"); + + /** Deprecated. Replaced by DEPROVISIONING_FROM_VOICE_PLATFORM. */ public static final EventTypeEnum DEPROVISIONING_TO_VOICE_PLATFORM = new EventTypeEnum("DEPROVISIONING_TO_VOICE_PLATFORM"); @@ -82,6 +101,8 @@ public class EventTypeEnum extends EnumDynamic { PROVISIONING_TO_CAMPAIGN, DEPROVISIONING_FROM_CAMPAIGN, PROVISIONING_TO_VOICE_PLATFORM, + DEPROVISIONING_FROM_VOICE_PLATFORM, + NUMBER_ORDER_PROCESSING, DEPROVISIONING_TO_VOICE_PLATFORM)); private EventTypeEnum(String value) { @@ -108,14 +129,34 @@ public static String valueOf(EventTypeEnum e) { */ EventTypeEnum getEventType(); - /** The status of the event. For example, SUCCEEDED or FAILED. */ + /** The status of the event or the state transition it represents. */ public class StatusEnum extends EnumDynamic { + /** The event has succeeded. */ public static final StatusEnum SUCCEEDED = new StatusEnum("SUCCEEDED"); + + /** The event has failed. */ public static final StatusEnum FAILED = new StatusEnum("FAILED"); + /** The Number Order transitioned to the IN_REVIEW state. */ + public static final StatusEnum IN_REVIEW = new StatusEnum("IN_REVIEW"); + + /** The Number Order transitioned to the BLOCKED state. */ + public static final StatusEnum BLOCKED = new StatusEnum("BLOCKED"); + + /** The Number Order transitioned to the COMPLETED state. */ + public static final StatusEnum COMPLETED = new StatusEnum("COMPLETED"); + + /** The Number Order transitioned to the REJECTED state. */ + public static final StatusEnum REJECTED = new StatusEnum("REJECTED"); + + /** The Number Order transitioned to the EXPIRED state. */ + public static final StatusEnum EXPIRED = new StatusEnum("EXPIRED"); + private static final EnumSupportDynamic ENUM_SUPPORT = new EnumSupportDynamic<>( - StatusEnum.class, StatusEnum::new, Arrays.asList(SUCCEEDED, FAILED)); + StatusEnum.class, + StatusEnum::new, + Arrays.asList(SUCCEEDED, FAILED, IN_REVIEW, BLOCKED, COMPLETED, REJECTED, EXPIRED)); private StatusEnum(String value) { super(value); @@ -135,7 +176,7 @@ public static String valueOf(StatusEnum e) { } /** - * The status of the event. For example, SUCCEEDED or FAILED. + * The status of the event or the state transition it represents. * * @return status */ @@ -147,32 +188,61 @@ public static String valueOf(StatusEnum e) { * campaign provisioning-related failures, refer to the list for the possible values. */ public class FailureCodeEnum extends EnumDynamic { + /** The specified campaign is not available. */ public static final FailureCodeEnum CAMPAIGN_NOT_AVAILABLE = new FailureCodeEnum("CAMPAIGN_NOT_AVAILABLE"); + + /** Exceeded the limit for 10DLC. */ public static final FailureCodeEnum EXCEEDED_10_DLC_LIMIT = new FailureCodeEnum("EXCEEDED_10DLC_LIMIT"); + + /** Provisioning the number failed. */ public static final FailureCodeEnum NUMBER_PROVISIONING_FAILED = new FailureCodeEnum("NUMBER_PROVISIONING_FAILED"); + + /** The third party service is unavailable. */ public static final FailureCodeEnum PARTNER_SERVICE_UNAVAILABLE = new FailureCodeEnum("PARTNER_SERVICE_UNAVAILABLE"); + + /** The campaign is not yet accepted. */ public static final FailureCodeEnum CAMPAIGN_PENDING_ACCEPTANCE = new FailureCodeEnum("CAMPAIGN_PENDING_ACCEPTANCE"); + + /** Error with MNO. */ public static final FailureCodeEnum MNO_SHARING_ERROR = new FailureCodeEnum("MNO_SHARING_ERROR"); + + /** The campaign failed to provision. */ public static final FailureCodeEnum CAMPAIGN_PROVISIONING_FAILED = new FailureCodeEnum("CAMPAIGN_PROVISIONING_FAILED"); + + /** The campaign expired. */ public static final FailureCodeEnum CAMPAIGN_EXPIRED = new FailureCodeEnum("CAMPAIGN_EXPIRED"); + + /** The campaign MNO was rejected. */ public static final FailureCodeEnum CAMPAIGN_MNO_REJECTED = new FailureCodeEnum("CAMPAIGN_MNO_REJECTED"); + + /** The campaign MNO was suspended. */ public static final FailureCodeEnum CAMPAIGN_MNO_SUSPENDED = new FailureCodeEnum("CAMPAIGN_MNO_SUSPENDED"); + + /** The campaign MNO is under review. */ public static final FailureCodeEnum CAMPAIGN_MNO_REVIEW = new FailureCodeEnum("CAMPAIGN_MNO_REVIEW"); + + /** Not enough credit in the account. */ public static final FailureCodeEnum INSUFFICIENT_BALANCE = new FailureCodeEnum("INSUFFICIENT_BALANCE"); + + /** Provisioning not allowed for mock campaigns. */ public static final FailureCodeEnum MOCK_CAMPAIGN_NOT_ALLOWED = new FailureCodeEnum("MOCK_CAMPAIGN_NOT_ALLOWED"); + + /** Toll free numbers not allowed. */ public static final FailureCodeEnum TFN_NOT_ALLOWED = new FailureCodeEnum("TFN_NOT_ALLOWED"); + + /** Invalid NNID. */ public static final FailureCodeEnum INVALID_NNID = new FailureCodeEnum("INVALID_NNID"); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -222,6 +292,16 @@ public static String valueOf(FailureCodeEnum e) { */ FailureCodeEnum getFailureCode(); + /** + * If the status is FAILED, certain processes (eg. number to campaign provisioning) will have an + * internalFailureCode in the payload. The details of these codes can be found in our dedicated Provisioning + * errors documentation. + * + * @return internalFailureCode + */ + String getInternalFailureCode(); + /** * Getting builder * @@ -306,6 +386,15 @@ interface Builder { */ Builder setFailureCode(FailureCodeEnum failureCode); + /** + * see getter + * + * @param internalFailureCode see getter + * @return Current builder + * @see #getInternalFailureCode + */ + Builder setInternalFailureCode(String internalFailureCode); + /** * Create instance * diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/sinchevents/NumberSinchEventImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/sinchevents/NumberSinchEventImpl.java index fb7003b65..122fd950a 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/sinchevents/NumberSinchEventImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/numbers/models/v1/sinchevents/NumberSinchEventImpl.java @@ -18,7 +18,8 @@ NumberSinchEventImpl.JSON_PROPERTY_RESOURCE_TYPE, NumberSinchEventImpl.JSON_PROPERTY_EVENT_TYPE, NumberSinchEventImpl.JSON_PROPERTY_STATUS, - NumberSinchEventImpl.JSON_PROPERTY_FAILURE_CODE + NumberSinchEventImpl.JSON_PROPERTY_FAILURE_CODE, + NumberSinchEventImpl.JSON_PROPERTY_INTERNAL_FAILURE_CODE }) @JsonFilter("uninitializedFilter") @JsonInclude(value = JsonInclude.Include.CUSTOM) @@ -57,6 +58,10 @@ public class NumberSinchEventImpl implements NumberSinchEvent { private OptionalValue failureCode; + public static final String JSON_PROPERTY_INTERNAL_FAILURE_CODE = "internalFailureCode"; + + private OptionalValue internalFailureCode; + public NumberSinchEventImpl() {} protected NumberSinchEventImpl( @@ -67,7 +72,8 @@ protected NumberSinchEventImpl( OptionalValue resourceType, OptionalValue eventType, OptionalValue status, - OptionalValue failureCode) { + OptionalValue failureCode, + OptionalValue internalFailureCode) { this.eventId = eventId; this.timestamp = timestamp; this.projectId = projectId; @@ -76,6 +82,7 @@ protected NumberSinchEventImpl( this.eventType = eventType; this.status = status; this.failureCode = failureCode; + this.internalFailureCode = internalFailureCode; } @JsonIgnore @@ -166,6 +173,17 @@ public OptionalValue failureCode() { return failureCode; } + @JsonIgnore + public String getInternalFailureCode() { + return internalFailureCode.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_INTERNAL_FAILURE_CODE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue internalFailureCode() { + return internalFailureCode; + } + /** Return true if this CallbackPayload object is equal to o. */ @Override public boolean equals(Object o) { @@ -183,13 +201,22 @@ public boolean equals(Object o) { && Objects.equals(this.resourceType, callbackPayload.resourceType) && Objects.equals(this.eventType, callbackPayload.eventType) && Objects.equals(this.status, callbackPayload.status) - && Objects.equals(this.failureCode, callbackPayload.failureCode); + && Objects.equals(this.failureCode, callbackPayload.failureCode) + && Objects.equals(this.internalFailureCode, callbackPayload.internalFailureCode); } @Override public int hashCode() { return Objects.hash( - eventId, timestamp, projectId, resourceId, resourceType, eventType, status, failureCode); + eventId, + timestamp, + projectId, + resourceId, + resourceType, + eventType, + status, + failureCode, + internalFailureCode); } @Override @@ -204,6 +231,9 @@ public String toString() { sb.append(" eventType: ").append(toIndentedString(eventType)).append("\n"); sb.append(" status: ").append(toIndentedString(status)).append("\n"); sb.append(" failureCode: ").append(toIndentedString(failureCode)).append("\n"); + sb.append(" internalFailureCode: ") + .append(toIndentedString(internalFailureCode)) + .append("\n"); sb.append("}"); return sb.toString(); } @@ -228,6 +258,7 @@ static class Builder implements NumberSinchEvent.Builder { OptionalValue eventType = OptionalValue.empty(); OptionalValue status = OptionalValue.empty(); OptionalValue failureCode = OptionalValue.empty(); + OptionalValue internalFailureCode = OptionalValue.empty(); @JsonProperty(JSON_PROPERTY_EVENT_ID) public Builder setEventId(String eventId) { @@ -277,9 +308,23 @@ public Builder setFailureCode(FailureCodeEnum failureCode) { return this; } + @JsonProperty(JSON_PROPERTY_INTERNAL_FAILURE_CODE) + public Builder setInternalFailureCode(String internalFailureCode) { + this.internalFailureCode = OptionalValue.of(internalFailureCode); + return this; + } + public NumberSinchEvent build() { return new NumberSinchEventImpl( - eventId, timestamp, projectId, resourceId, resourceType, eventType, status, failureCode); + eventId, + timestamp, + projectId, + resourceId, + resourceType, + eventType, + status, + failureCode, + internalFailureCode); } } } diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numbers/models/v1/SinchEventsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numbers/models/v1/SinchEventsDtoTest.java index 19cbcc587..915656ca6 100644 --- a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numbers/models/v1/SinchEventsDtoTest.java +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numbers/models/v1/SinchEventsDtoTest.java @@ -29,6 +29,7 @@ public class SinchEventsDtoTest extends NumbersBaseTest { .setEventType(EventTypeEnum.PROVISIONING_TO_CAMPAIGN) .setStatus(StatusEnum.FAILED) .setFailureCode(FailureCodeEnum.CAMPAIGN_NOT_AVAILABLE) + .setInternalFailureCode("CRS0018") .build(); @Test diff --git a/openapi-contracts/src/test/resources/domains/numbers/v1/sinchevents/number-sinch-event.json b/openapi-contracts/src/test/resources/domains/numbers/v1/sinchevents/number-sinch-event.json index 55ce33569..3716b90f2 100644 --- a/openapi-contracts/src/test/resources/domains/numbers/v1/sinchevents/number-sinch-event.json +++ b/openapi-contracts/src/test/resources/domains/numbers/v1/sinchevents/number-sinch-event.json @@ -6,5 +6,6 @@ "resourceType": "ACTIVE_NUMBER", "eventType": "PROVISIONING_TO_CAMPAIGN", "status": "FAILED", - "failureCode": "CAMPAIGN_NOT_AVAILABLE" + "failureCode": "CAMPAIGN_NOT_AVAILABLE", + "internalFailureCode": "CRS0018" } \ No newline at end of file From d2bb03b5487dd6a6e025c82235a6d96603ee8ddc Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Fri, 26 Jun 2026 17:40:49 +0200 Subject: [PATCH 24/37] New generated Java Conversation sources --- .../models/v1/apps/DeliveryReportBasedFallback.java | 9 ++++++--- .../models/v1/consents/response/AuditRecord.java | 8 ++++++++ .../RecentConversationsListQueryParameters.java | 3 +++ .../credentials/ConversationChannelCredentials.java | 4 +++- .../v1/eventdestinations/ClientCredentials.java | 9 +++++++++ .../models/v1/events/types/CommentEvent.java | 3 +++ .../events/types/internal/CommentEventInternal.java | 3 +++ .../internal/ChannelSpecificMessageInternal.java | 11 +++++++++++ .../ChannelSpecificContactMessage.java | 1 + .../kakaotalk/commerce/DiscountFixedCommerce.java | 13 +++++++------ .../kakaotalk/commerce/DiscountRateCommerce.java | 13 +++++++------ .../kakaotalk/commerce/RegularPriceCommerce.java | 5 +++-- .../kakaotalk/coupons/DiscountFixedCoupon.java | 4 ++-- .../kakaotalk/coupons/DiscountRateCoupon.java | 4 ++-- .../whatsapp/flows/FlowChannelSpecificMessage.java | 6 ++++++ .../flows/WhatsAppInteractiveHeaderDocument.java | 1 + .../flows/WhatsAppInteractiveHeaderImage.java | 1 + .../flows/WhatsAppInteractiveHeaderText.java | 1 + .../flows/WhatsAppInteractiveHeaderVideo.java | 1 + .../nfmreply/WhatsAppInteractiveNfmReply.java | 3 +++ ...activeNfmReplyChannelSpecificContactMessage.java | 1 + .../whatsapp/payment/OrderDetailsPayment.java | 6 ++++++ .../payment/OrderDetailsPaymentButtonBoleto.java | 1 + .../OrderDetailsPaymentButtonDynamicPix.java | 10 ++++++++++ .../OrderDetailsPaymentButtonPaymentLink.java | 1 + .../whatsapp/payment/OrderStatusPaymentDetails.java | 11 +++++++++++ ...hannelSpecificContactMessageMessageInternal.java | 1 + .../capability/CapabilityEventNotification.java | 10 ++++++++++ .../models/v1/sinchevents/inbound/Event.java | 1 + .../message/MessageInboundEventMessage.java | 1 + .../sinchevents/opting/OptInEventNotification.java | 5 +++++ .../sinchevents/opting/OptOutEventNotification.java | 5 +++++ .../record/RecordNotificationEventNotification.java | 3 +++ .../smartconversations/OffensiveAnalysis.java | 3 +++ .../templates/api/v2/TemplatesV2Service.java | 2 +- .../api/v2/adapters/TemplatesV2ServiceImpl.java | 2 +- .../templates/models/TemplateVariable.java | 2 +- .../models/v2/ChannelTemplateOverride.java | 2 +- .../models/v2/TemplateTranslationBase.java | 6 +++++- .../models/v2/TemplateTranslationBaseImpl.java | 6 ++---- .../templates/models/v2/TemplateV2.java | 2 +- .../v2/internal/ListTemplatesResponseInternal.java | 2 +- .../internal/ListTranslationsResponseInternal.java | 2 +- .../v2/request/ListTranslationsQueryParameters.java | 2 +- .../models/v2/response/TemplatesV2ListResponse.java | 2 +- .../v2/response/TranslationsV2ListResponse.java | 2 +- 46 files changed, 157 insertions(+), 37 deletions(-) diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/apps/DeliveryReportBasedFallback.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/apps/DeliveryReportBasedFallback.java index 50c26ddda..ed11f8056 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/apps/DeliveryReportBasedFallback.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/apps/DeliveryReportBasedFallback.java @@ -15,8 +15,7 @@ /** * This object contains additional settings related to delivery - * report based fallback. Note that this paid functionality is available for - * open beta testing. + * report based fallback. Note that this is paid functionality. */ @JsonDeserialize(builder = DeliveryReportBasedFallbackImpl.Builder.class) public interface DeliveryReportBasedFallback { @@ -34,7 +33,11 @@ public interface DeliveryReportBasedFallback { /** * Optional. The time, in seconds, after which a message without a positive delivery report will - * fallback to the next channel. The valid values for this field are [60 - 259200]. + * fallback to the next channel. + * + *

Minimum: 10 + * + *

Maximum: 259200 * * @return deliveryReportWaitingTime */ diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecord.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecord.java index a4fb5c34f..a48de1a32 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecord.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/consents/response/AuditRecord.java @@ -27,7 +27,10 @@ public interface AuditRecord { * an end-user MO). */ public class OriginEnum extends EnumDynamic { + /** The opt-in or opt-out came from the API. */ public static final OriginEnum ORIGIN_API = new OriginEnum("ORIGIN_API"); + + /** The opt-in or optout came from an MO. */ public static final OriginEnum ORIGIN_MO = new OriginEnum("ORIGIN_MO"); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -63,8 +66,13 @@ public static String valueOf(OriginEnum e) { /** The operation that the audit record refers to. Can be INSERT, UPDATE or DELETE. */ public class OperationEnum extends EnumDynamic { + /** The Insert operation */ public static final OperationEnum OPERATION_INSERT = new OperationEnum("OPERATION_INSERT"); + + /** The Update operation */ public static final OperationEnum OPERATION_UPDATE = new OperationEnum("OPERATION_UPDATE"); + + /** The Delete operation */ public static final OperationEnum OPERATION_DELETE = new OperationEnum("OPERATION_DELETE"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/conversations/request/RecentConversationsListQueryParameters.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/conversations/request/RecentConversationsListQueryParameters.java index 799523df9..2337f5e3e 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/conversations/request/RecentConversationsListQueryParameters.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/conversations/request/RecentConversationsListQueryParameters.java @@ -49,7 +49,10 @@ public interface RecentConversationsListQueryParameters { /** Gets or Sets order */ public class OrderEnum extends EnumDynamic { + /** Ascending order. Oldest first. */ public static final OrderEnum ASC = new OrderEnum("ASC"); + + /** Descending order. Newest first. */ public static final OrderEnum DESC = new OrderEnum("DESC"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/credentials/ConversationChannelCredentials.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/credentials/ConversationChannelCredentials.java index 72fcaa573..7303cd727 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/credentials/ConversationChannelCredentials.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/credentials/ConversationChannelCredentials.java @@ -74,7 +74,9 @@ public interface ConversationChannelCredentials { * case in which there are multiple credential integrations per channel on a single app, this * field must have a unique value for each multi-credential channel entry. * - *

minimum: 0 maximum: 255 + *

Minimum: 0 + * + *

Maximum: 255 * * @return credentialOrdinalNumber */ diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/eventdestinations/ClientCredentials.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/eventdestinations/ClientCredentials.java index b9496e1e8..43cc43c0e 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/eventdestinations/ClientCredentials.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/eventdestinations/ClientCredentials.java @@ -72,7 +72,16 @@ public interface ClientCredentials { * BASIC is used by default. */ public class TokenRequestTypeEnum extends EnumDynamic { + /** + * Sent using the HTTP Authorization header (Basic auth). In this case, client_id + * and client_secret are NOT included in the form body. + */ public static final TokenRequestTypeEnum BASIC = new TokenRequestTypeEnum("BASIC"); + + /** + * Sent in the form body as client_id and client_secret. No + * Authorization header is added. + */ public static final TokenRequestTypeEnum FORM = new TokenRequestTypeEnum("FORM"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/events/types/CommentEvent.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/events/types/CommentEvent.java index 704e651b1..f230e6926 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/events/types/CommentEvent.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/events/types/CommentEvent.java @@ -39,7 +39,10 @@ public interface CommentEvent /** Either LIVE or FEED. Indicates the type of media on which the comment was made. */ public class CommentTypeEnum extends EnumDynamic { + /** A comment was made on a Feed post. */ public static final CommentTypeEnum FEED = new CommentTypeEnum("FEED"); + + /** A comment was made during a Live. */ public static final CommentTypeEnum LIVE = new CommentTypeEnum("LIVE"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/events/types/internal/CommentEventInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/events/types/internal/CommentEventInternal.java index 86080d9c4..890450cbf 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/events/types/internal/CommentEventInternal.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/events/types/internal/CommentEventInternal.java @@ -42,7 +42,10 @@ public interface CommentEventInternal { /** Either LIVE or FEED. Indicates the type of media on which the comment was made. */ public class CommentTypeEnum extends EnumDynamic { + /** A comment was made on a Feed post. */ public static final CommentTypeEnum FEED = new CommentTypeEnum("FEED"); + + /** A comment was made during a Live. */ public static final CommentTypeEnum LIVE = new CommentTypeEnum("LIVE"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/internal/ChannelSpecificMessageInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/internal/ChannelSpecificMessageInternal.java index cdbbc2829..50e9138f5 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/internal/ChannelSpecificMessageInternal.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/internal/ChannelSpecificMessageInternal.java @@ -22,12 +22,23 @@ public interface ChannelSpecificMessageInternal { /** The type of the channel specific message. */ public class MessageTypeEnum extends EnumDynamic { + /** The WhatsApp Flows message type. */ public static final MessageTypeEnum FLOWS = new MessageTypeEnum("FLOWS"); + + /** The WhatsApp order details message type. */ public static final MessageTypeEnum ORDER_DETAILS = new MessageTypeEnum("ORDER_DETAILS"); + + /** The WhatsApp order status message type. */ public static final MessageTypeEnum ORDER_STATUS = new MessageTypeEnum("ORDER_STATUS"); + + /** KakaoTalk commerce channel specific message type */ public static final MessageTypeEnum COMMERCE = new MessageTypeEnum("COMMERCE"); + + /** KakaoTalk carousel commerce channel specific message type */ public static final MessageTypeEnum CAROUSEL_COMMERCE = new MessageTypeEnum("CAROUSEL_COMMERCE"); + + /** LINE notification message template type */ public static final MessageTypeEnum NOTIFICATION_MESSAGE_TEMPLATE = new MessageTypeEnum("NOTIFICATION_MESSAGE_TEMPLATE"); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/ChannelSpecificContactMessage.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/ChannelSpecificContactMessage.java index 2885f34a2..aa37551f1 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/ChannelSpecificContactMessage.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/ChannelSpecificContactMessage.java @@ -23,6 +23,7 @@ public interface ChannelSpecificContactMessage /** The message type. */ public class MessageTypeEnum extends EnumDynamic { + /** The nfm_reply message type. */ public static final MessageTypeEnum NFM_REPLY = new MessageTypeEnum("nfm_reply"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/DiscountFixedCommerce.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/DiscountFixedCommerce.java index 58c5ad31c..8e82ed997 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/DiscountFixedCommerce.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/DiscountFixedCommerce.java @@ -24,6 +24,7 @@ public interface DiscountFixedCommerce /** Gets or Sets type */ public class TypeEnum extends EnumDynamic { + /** Commerce with fixed discount */ public static final TypeEnum FIXED_DISCOUNT_COMMERCE = new TypeEnum("FIXED_DISCOUNT_COMMERCE"); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -59,9 +60,9 @@ public static String valueOf(TypeEnum e) { /** * Regular price of the product * - *

minimum: 0 maximum: 99999999 + *

Minimum: 0 * - *

Field is required + *

Maximum: 99999999 Field is required * * @return regularPrice */ @@ -70,9 +71,9 @@ public static String valueOf(TypeEnum e) { /** * Discounted price of the product * - *

minimum: 0 maximum: 99999999 + *

Minimum: 0 * - *

Field is required + *

Maximum: 99999999 Field is required * * @return discountPrice */ @@ -81,9 +82,9 @@ public static String valueOf(TypeEnum e) { /** * Fixed discount * - *

minimum: 0 maximum: 999999 + *

Minimum: 0 * - *

Field is required + *

Maximum: 999999 Field is required * * @return discountFixed */ diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/DiscountRateCommerce.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/DiscountRateCommerce.java index dcb5fe64e..6f26d736b 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/DiscountRateCommerce.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/DiscountRateCommerce.java @@ -24,6 +24,7 @@ public interface DiscountRateCommerce /** Gets or Sets type */ public class TypeEnum extends EnumDynamic { + /** Commerce with percentage discount */ public static final TypeEnum PERCENTAGE_DISCOUNT_COMMERCE = new TypeEnum("PERCENTAGE_DISCOUNT_COMMERCE"); @@ -60,9 +61,9 @@ public static String valueOf(TypeEnum e) { /** * Regular price of the product * - *

minimum: 0 maximum: 99999999 + *

Minimum: 0 * - *

Field is required + *

Maximum: 99999999 Field is required * * @return regularPrice */ @@ -71,9 +72,9 @@ public static String valueOf(TypeEnum e) { /** * Discounted price of the product * - *

minimum: 0 maximum: 99999999 + *

Minimum: 0 * - *

Field is required + *

Maximum: 99999999 Field is required * * @return discountPrice */ @@ -82,9 +83,9 @@ public static String valueOf(TypeEnum e) { /** * Discount rate (%) * - *

minimum: 0 maximum: 100 + *

Minimum: 0 * - *

Field is required + *

Maximum: 100 Field is required * * @return discountRate */ diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/RegularPriceCommerce.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/RegularPriceCommerce.java index 88505d33d..859340207 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/RegularPriceCommerce.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/commerce/RegularPriceCommerce.java @@ -24,6 +24,7 @@ public interface RegularPriceCommerce /** Gets or Sets type */ public class TypeEnum extends EnumDynamic { + /** Commerce with regular price */ public static final TypeEnum REGULAR_PRICE_COMMERCE = new TypeEnum("REGULAR_PRICE_COMMERCE"); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -59,9 +60,9 @@ public static String valueOf(TypeEnum e) { /** * Regular price of the product * - *

minimum: 0 maximum: 99999999 + *

Minimum: 0 * - *

Field is required + *

Maximum: 99999999 Field is required * * @return regularPrice */ diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/coupons/DiscountFixedCoupon.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/coupons/DiscountFixedCoupon.java index c77b07546..369fd11e6 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/coupons/DiscountFixedCoupon.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/coupons/DiscountFixedCoupon.java @@ -91,9 +91,9 @@ public static String valueOf(TypeEnum e) { /** * Fixed discount * - *

minimum: 0 maximum: 999999 + *

Minimum: 0 * - *

Field is required + *

Maximum: 999999 Field is required * * @return discountFixed */ diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/coupons/DiscountRateCoupon.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/coupons/DiscountRateCoupon.java index 1623ca6ac..563ebed74 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/coupons/DiscountRateCoupon.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/kakaotalk/coupons/DiscountRateCoupon.java @@ -92,9 +92,9 @@ public static String valueOf(TypeEnum e) { /** * Discount rate (%) * - *

minimum: 0 maximum: 100 + *

Minimum: 0 * - *

Field is required + *

Maximum: 100 Field is required * * @return discountRate */ diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/FlowChannelSpecificMessage.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/FlowChannelSpecificMessage.java index d3e4e7b8f..512033b35 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/FlowChannelSpecificMessage.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/FlowChannelSpecificMessage.java @@ -61,7 +61,10 @@ public interface FlowChannelSpecificMessage /** The mode in which the flow is. */ public class FlowModeEnum extends EnumDynamic { + /** The flow is in draft mode. */ public static final FlowModeEnum DRAFT = new FlowModeEnum("draft"); + + /** The flow is published. */ public static final FlowModeEnum PUBLISHED = new FlowModeEnum("published"); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -104,7 +107,10 @@ public static String valueOf(FlowModeEnum e) { /** Gets or Sets flowAction */ public class FlowActionEnum extends EnumDynamic { + /** The navigate flow action. */ public static final FlowActionEnum NAVIGATE = new FlowActionEnum("navigate"); + + /** The data exchange flow action. */ public static final FlowActionEnum DATA_EXCHANGE = new FlowActionEnum("data_exchange"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderDocument.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderDocument.java index f16aa24b2..88107c002 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderDocument.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderDocument.java @@ -24,6 +24,7 @@ public interface WhatsAppInteractiveHeaderDocument /** Gets or Sets type */ public class TypeEnum extends EnumDynamic { + /** The document associated with the header. */ public static final TypeEnum DOCUMENT = new TypeEnum("document"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderImage.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderImage.java index 7d32b04de..5ce5ecdbe 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderImage.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderImage.java @@ -24,6 +24,7 @@ public interface WhatsAppInteractiveHeaderImage /** Gets or Sets type */ public class TypeEnum extends EnumDynamic { + /** The image associated with the header. */ public static final TypeEnum IMAGE = new TypeEnum("image"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderText.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderText.java index fd8cd253d..40f3365b9 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderText.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderText.java @@ -24,6 +24,7 @@ public interface WhatsAppInteractiveHeaderText /** Gets or Sets type */ public class TypeEnum extends EnumDynamic { + /** The text of the header. */ public static final TypeEnum TEXT = new TypeEnum("text"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderVideo.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderVideo.java index 146efe8f0..0e22dc03e 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderVideo.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/flows/WhatsAppInteractiveHeaderVideo.java @@ -24,6 +24,7 @@ public interface WhatsAppInteractiveHeaderVideo /** Gets or Sets type */ public class TypeEnum extends EnumDynamic { + /** The video associated with the header. */ public static final TypeEnum VIDEO = new TypeEnum("video"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/nfmreply/WhatsAppInteractiveNfmReply.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/nfmreply/WhatsAppInteractiveNfmReply.java index a0d51d844..147e2a8ec 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/nfmreply/WhatsAppInteractiveNfmReply.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/nfmreply/WhatsAppInteractiveNfmReply.java @@ -22,7 +22,10 @@ public interface WhatsAppInteractiveNfmReply { /** The nfm reply message type. */ public class NameEnum extends EnumDynamic { + /** The WhatsApp flow nfm reply message type. */ public static final NameEnum FLOW = new NameEnum("flow"); + + /** The address nfm reply message type. */ public static final NameEnum ADDRESS_MESSAGE = new NameEnum("address_message"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/nfmreply/WhatsAppInteractiveNfmReplyChannelSpecificContactMessage.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/nfmreply/WhatsAppInteractiveNfmReplyChannelSpecificContactMessage.java index 000df7a5c..68826b94a 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/nfmreply/WhatsAppInteractiveNfmReplyChannelSpecificContactMessage.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/nfmreply/WhatsAppInteractiveNfmReplyChannelSpecificContactMessage.java @@ -25,6 +25,7 @@ public interface WhatsAppInteractiveNfmReplyChannelSpecificContactMessage /** The interactive message type. */ public class TypeEnum extends EnumDynamic { + /** The interactive nfm reply type. */ public static final TypeEnum NFM_REPLY = new TypeEnum("nfm_reply"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPayment.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPayment.java index eeabfc043..56158b778 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPayment.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPayment.java @@ -23,7 +23,10 @@ public interface OrderDetailsPayment { /** The country/currency associated with the payment message. */ public class TypeEnum extends EnumDynamic { + /** Brazil */ public static final TypeEnum BR = new TypeEnum("br"); + + /** Singapore */ public static final TypeEnum SG = new TypeEnum("sg"); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -66,7 +69,10 @@ public static String valueOf(TypeEnum e) { /** The type of good associated with this order. */ public class TypeOfGoodsEnum extends EnumDynamic { + /** Digital goods. */ public static final TypeOfGoodsEnum DIGITAL_GOODS = new TypeOfGoodsEnum("digital-goods"); + + /** Physical goods. */ public static final TypeOfGoodsEnum PHYSICAL_GOODS = new TypeOfGoodsEnum("physical-goods"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonBoleto.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonBoleto.java index ac2e3a51e..16bc54de6 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonBoleto.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonBoleto.java @@ -24,6 +24,7 @@ public interface OrderDetailsPaymentButtonBoleto /** Gets or Sets type */ public class TypeEnum extends EnumDynamic { + /** The Boleto button identifier */ public static final TypeEnum BOLETO = new TypeEnum("boleto"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonDynamicPix.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonDynamicPix.java index fc7f0779b..f1ddb5519 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonDynamicPix.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonDynamicPix.java @@ -24,6 +24,7 @@ public interface OrderDetailsPaymentButtonDynamicPix /** Gets or Sets type */ public class TypeEnum extends EnumDynamic { + /** The dynamic Pix code button identifier */ public static final TypeEnum PIX_DYNAMIC_CODE = new TypeEnum("pix_dynamic_code"); private static final EnumSupportDynamic ENUM_SUPPORT = @@ -75,10 +76,19 @@ public static String valueOf(TypeEnum e) { /** Pix key type. */ public class KeyTypeEnum extends EnumDynamic { + /** The CPF key type. */ public static final KeyTypeEnum CPF = new KeyTypeEnum("CPF"); + + /** The CNPJ key type. */ public static final KeyTypeEnum CNPJ = new KeyTypeEnum("CNPJ"); + + /** The EMAIL key type. */ public static final KeyTypeEnum EMAIL = new KeyTypeEnum("EMAIL"); + + /** The PHONE key type. */ public static final KeyTypeEnum PHONE = new KeyTypeEnum("PHONE"); + + /** The EVP key type. */ public static final KeyTypeEnum EVP = new KeyTypeEnum("EVP"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonPaymentLink.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonPaymentLink.java index 7715388b9..1d44bde44 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonPaymentLink.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderDetailsPaymentButtonPaymentLink.java @@ -24,6 +24,7 @@ public interface OrderDetailsPaymentButtonPaymentLink /** Gets or Sets type */ public class TypeEnum extends EnumDynamic { + /** The payment link button identifier */ public static final TypeEnum PAYMENT_LINK = new TypeEnum("payment_link"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderStatusPaymentDetails.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderStatusPaymentDetails.java index 89f11d943..24184fc3b 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderStatusPaymentDetails.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/channelspecific/whatsapp/payment/OrderStatusPaymentDetails.java @@ -22,11 +22,22 @@ public interface OrderStatusPaymentDetails { /** The new payment message status. */ public class StatusEnum extends EnumDynamic { + /** The order is pending. */ public static final StatusEnum PENDING = new StatusEnum("pending"); + + /** The order is processing. */ public static final StatusEnum PROCESSING = new StatusEnum("processing"); + + /** A portion of the order has been shipped. */ public static final StatusEnum PARTIALLY_SHIPPED = new StatusEnum("partially-shipped"); + + /** The order has been shipped. */ public static final StatusEnum SHIPPED = new StatusEnum("shipped"); + + /** The order is complete. */ public static final StatusEnum COMPLETED = new StatusEnum("completed"); + + /** The order was canceled. */ public static final StatusEnum CANCELED = new StatusEnum("canceled"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/internal/ChannelSpecificContactMessageMessageInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/internal/ChannelSpecificContactMessageMessageInternal.java index a69f9a605..96c86d4a0 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/internal/ChannelSpecificContactMessageMessageInternal.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/messages/types/internal/ChannelSpecificContactMessageMessageInternal.java @@ -23,6 +23,7 @@ public interface ChannelSpecificContactMessageMessageInternal { /** The message type. */ public class MessageTypeEnum extends EnumDynamic { + /** The nfm_reply message type. */ public static final MessageTypeEnum NFM_REPLY = new MessageTypeEnum("nfm_reply"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/capability/CapabilityEventNotification.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/capability/CapabilityEventNotification.java index 776831862..fc7586868 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/capability/CapabilityEventNotification.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/capability/CapabilityEventNotification.java @@ -53,12 +53,22 @@ public interface CapabilityEventNotification { /** Status indicating the recipient's capability on the channel. */ public class CapabilityStatusEnum extends EnumDynamic { + /** + * The channel capability for the contact is unknown due to the underlying channel not making + * this information available. + */ public static final CapabilityStatusEnum CAPABILITY_UNKNOWN = new CapabilityStatusEnum("CAPABILITY_UNKNOWN"); + + /** The specified contact supports all the features of the channel. */ public static final CapabilityStatusEnum CAPABILITY_FULL = new CapabilityStatusEnum("CAPABILITY_FULL"); + + /** The specified contact supports a subset of the channel features. */ public static final CapabilityStatusEnum CAPABILITY_PARTIAL = new CapabilityStatusEnum("CAPABILITY_PARTIAL"); + + /** The specified contact has no capability on the channel. */ public static final CapabilityStatusEnum NO_CAPABILITY = new CapabilityStatusEnum("NO_CAPABILITY"); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/inbound/Event.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/inbound/Event.java index 3b18fbeaa..8cbcfa488 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/inbound/Event.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/inbound/Event.java @@ -34,6 +34,7 @@ public interface Event { /** The direction of the event. It's always TO_APP for contact events. */ public class DirectionEnum extends EnumDynamic { + /** An event sent to a Conversation API app. */ public static final DirectionEnum TO_APP = new DirectionEnum("TO_APP"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/message/MessageInboundEventMessage.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/message/MessageInboundEventMessage.java index dbde95e7a..b77e79ffc 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/message/MessageInboundEventMessage.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/message/MessageInboundEventMessage.java @@ -33,6 +33,7 @@ public interface MessageInboundEventMessage { /** The direction of the message, it's always TO_APP for contact messages. */ public class DirectionEnum extends EnumDynamic { + /** A message sent to a Conversation API app. */ public static final DirectionEnum TO_APP = new DirectionEnum("TO_APP"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/opting/OptInEventNotification.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/opting/OptInEventNotification.java index a481ea38e..75ef533ce 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/opting/OptInEventNotification.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/opting/OptInEventNotification.java @@ -53,8 +53,13 @@ public interface OptInEventNotification { /** Status of the opt-in registration. */ public class StatusEnum extends EnumDynamic { + /** The opt-in registration succeeded. */ public static final StatusEnum OPT_IN_SUCCEEDED = new StatusEnum("OPT_IN_SUCCEEDED"); + + /** The opt-in registration failed. */ public static final StatusEnum OPT_IN_FAILED = new StatusEnum("OPT_IN_FAILED"); + + /** The status of the opt-in registration is unknown. */ public static final StatusEnum OPT_IN_STATUS_UNSPECIFIED = new StatusEnum("OPT_IN_STATUS_UNSPECIFIED"); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/opting/OptOutEventNotification.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/opting/OptOutEventNotification.java index 79bc483b7..ba9d7a6ec 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/opting/OptOutEventNotification.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/opting/OptOutEventNotification.java @@ -53,8 +53,13 @@ public interface OptOutEventNotification { /** Status of the opt-out registration. */ public class StatusEnum extends EnumDynamic { + /** The opt-out registration succeeded. */ public static final StatusEnum OPT_OUT_SUCCEEDED = new StatusEnum("OPT_OUT_SUCCEEDED"); + + /** The opt-out registration failed. */ public static final StatusEnum OPT_OUT_FAILED = new StatusEnum("OPT_OUT_FAILED"); + + /** The status of the opt-out registration is unknown. */ public static final StatusEnum OPT_OUT_STATUS_UNSPECIFIED = new StatusEnum("OPT_OUT_STATUS_UNSPECIFIED"); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/record/RecordNotificationEventNotification.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/record/RecordNotificationEventNotification.java index 77082f361..1431af2bb 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/record/RecordNotificationEventNotification.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/record/RecordNotificationEventNotification.java @@ -32,8 +32,11 @@ public interface RecordNotificationEventNotification { /** Record status for a specific entity */ public class RecordStatusEnum extends EnumDynamic { + /** The record status is unknown. */ public static final RecordStatusEnum RECORD_STATUS_UNSPECIFIED = new RecordStatusEnum("RECORD_STATUS_UNSPECIFIED"); + + /** The record for the entity has been stored. */ public static final RecordStatusEnum EVENT_RECORD_STORED = new RecordStatusEnum("EVENT_RECORD_STORED"); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/smartconversations/OffensiveAnalysis.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/smartconversations/OffensiveAnalysis.java index b61eaebbf..4296ee165 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/smartconversations/OffensiveAnalysis.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/models/v1/sinchevents/smartconversations/OffensiveAnalysis.java @@ -36,7 +36,10 @@ public interface OffensiveAnalysis { /** A label, either SAFE or UNSAFE, that classifies the analyzed content. */ public class EvaluationEnum extends EnumDynamic { + /** The analysis of the content indicates that it is most likely safe. */ public static final EvaluationEnum SAFE = new EvaluationEnum("SAFE"); + + /** The analysis of the content indicates that it is most likely unsafe. */ public static final EvaluationEnum UNSAFE = new EvaluationEnum("UNSAFE"); private static final EnumSupportDynamic ENUM_SUPPORT = diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/TemplatesV2Service.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/TemplatesV2Service.java index ca3d93423..517af4e12 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/TemplatesV2Service.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/TemplatesV2Service.java @@ -1,7 +1,7 @@ /* * Template Management API * - * OpenAPI document version: 1.0 + * OpenAPI document version: 2.0 * Contact: support@sinch.com * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/adapters/TemplatesV2ServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/adapters/TemplatesV2ServiceImpl.java index ca1abf239..edbced9c9 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/adapters/TemplatesV2ServiceImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/api/v2/adapters/TemplatesV2ServiceImpl.java @@ -1,7 +1,7 @@ /* * Template Management API * - * OpenAPI document version: 1.0 + * OpenAPI document version: 2.0 * Contact: support@sinch.com * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/TemplateVariable.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/TemplateVariable.java index 246c856c7..dc2e53366 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/TemplateVariable.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/TemplateVariable.java @@ -1,7 +1,7 @@ /* * Template Management API * - * OpenAPI document version: 1.0 + * OpenAPI document version: 2.0 * Contact: support@sinch.com * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/ChannelTemplateOverride.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/ChannelTemplateOverride.java index bbe5d4159..32ee28835 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/ChannelTemplateOverride.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/ChannelTemplateOverride.java @@ -1,7 +1,7 @@ /* * Template Management API * - * OpenAPI document version: 1.0 + * OpenAPI document version: 2.0 * Contact: support@sinch.com * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationBase.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationBase.java index fef52a013..e2f9894bb 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationBase.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationBase.java @@ -1,7 +1,7 @@ /* * Template Management API * - * OpenAPI document version: 1.0 + * OpenAPI document version: 2.0 * Contact: support@sinch.com * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -58,6 +58,7 @@ public interface TemplateTranslationBase { * Timestamp when the translation was created. * * @return createTime + * @readOnly This field is returned by the server and cannot be modified */ Instant getCreateTime(); @@ -65,6 +66,7 @@ public interface TemplateTranslationBase { * Timestamp of when the translation was updated. * * @return updateTime + * @readOnly This field is returned by the server and cannot be modified */ Instant getUpdateTime(); @@ -114,6 +116,7 @@ B setChannelTemplateOverrides( * @param createTime see getter * @return Current builder * @see #getCreateTime + * @readOnly This field is returned by the server and cannot be modified */ B setCreateTime(Instant createTime); @@ -123,6 +126,7 @@ B setChannelTemplateOverrides( * @param updateTime see getter * @return Current builder * @see #getUpdateTime + * @readOnly This field is returned by the server and cannot be modified */ B setUpdateTime(Instant updateTime); diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationBaseImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationBaseImpl.java index 66be0479c..0414e2b14 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationBaseImpl.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationBaseImpl.java @@ -191,8 +191,7 @@ public Instant getCreateTime() { return createTime.orElse(null); } - @JsonProperty(JSON_PROPERTY_CREATE_TIME) - @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + @JsonIgnore public OptionalValue createTime() { return createTime; } @@ -202,8 +201,7 @@ public Instant getUpdateTime() { return updateTime.orElse(null); } - @JsonProperty(JSON_PROPERTY_UPDATE_TIME) - @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + @JsonIgnore public OptionalValue updateTime() { return updateTime; } diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2.java index 12fa3721b..a69a20e70 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2.java @@ -1,7 +1,7 @@ /* * Template Management API * - * OpenAPI document version: 1.0 + * OpenAPI document version: 2.0 * Contact: support@sinch.com * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/internal/ListTemplatesResponseInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/internal/ListTemplatesResponseInternal.java index dc718b171..e1f5bbcac 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/internal/ListTemplatesResponseInternal.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/internal/ListTemplatesResponseInternal.java @@ -1,7 +1,7 @@ /* * Template Management API * - * OpenAPI document version: 1.0 + * OpenAPI document version: 2.0 * Contact: support@sinch.com * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/internal/ListTranslationsResponseInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/internal/ListTranslationsResponseInternal.java index e8cbab1c2..0e7697ca9 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/internal/ListTranslationsResponseInternal.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/internal/ListTranslationsResponseInternal.java @@ -1,7 +1,7 @@ /* * Template Management API * - * OpenAPI document version: 1.0 + * OpenAPI document version: 2.0 * Contact: support@sinch.com * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/request/ListTranslationsQueryParameters.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/request/ListTranslationsQueryParameters.java index 6d387dda1..3e8e59b26 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/request/ListTranslationsQueryParameters.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/request/ListTranslationsQueryParameters.java @@ -1,7 +1,7 @@ /* * Template Management API * - * OpenAPI document version: 1.0 + * OpenAPI document version: 2.0 * Contact: support@sinch.com * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/response/TemplatesV2ListResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/response/TemplatesV2ListResponse.java index 035f9badc..47727daa6 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/response/TemplatesV2ListResponse.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/response/TemplatesV2ListResponse.java @@ -1,7 +1,7 @@ /* * Template Management API * - * OpenAPI document version: 1.0 + * OpenAPI document version: 2.0 * Contact: support@sinch.com * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/response/TranslationsV2ListResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/response/TranslationsV2ListResponse.java index b037b030b..e7bf1f618 100644 --- a/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/response/TranslationsV2ListResponse.java +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/conversation/templates/models/v2/response/TranslationsV2ListResponse.java @@ -1,7 +1,7 @@ /* * Template Management API * - * OpenAPI document version: 1.0 + * OpenAPI document version: 2.0 * Contact: support@sinch.com * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). From 8c73ec10e3a873443590583c9868abcb1a5fc737 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Mon, 29 Jun 2026 12:05:18 +0200 Subject: [PATCH 25/37] Removed create time and update time fields from tests --- .../templates/models/v2/TemplateTranslationDtoTest.java | 3 --- .../conversation/templates/models/v2/TemplateV2DtoTest.java | 3 --- .../conversation/templates/v2/TemplateTranslationDto.json | 2 -- .../domains/conversation/templates/v2/TemplateV2Dto.json | 6 +----- .../templates/v2/request/TemplateV2RequestDto.json | 2 -- .../templates/v2/response/TemplatesV2ListResponseDto.json | 6 +----- .../v2/response/TranslationsV2ListResponseDto.json | 2 -- 7 files changed, 2 insertions(+), 22 deletions(-) diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationDtoTest.java index b993ba5d5..3edd3b8d7 100644 --- a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationDtoTest.java +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationDtoTest.java @@ -8,7 +8,6 @@ import com.sinch.sdk.domains.conversation.models.v1.messages.types.template.TemplateMessageDtoTest; import com.sinch.sdk.domains.conversation.templates.api.adapters.TemplatesBaseTest; import com.sinch.sdk.domains.conversation.templates.models.TemplateVariableDtoTest; -import java.time.Instant; import java.util.Arrays; import java.util.Collections; import org.json.JSONException; @@ -28,8 +27,6 @@ public class TemplateTranslationDtoTest extends TemplatesBaseTest { ConversationChannel.KAKAOTALK, ChannelTemplateOverrideDtoTest.expectedWithVersionDto)) .setVariables(Arrays.asList(TemplateVariableDtoTest.expectedDto)) - .setCreateTime(Instant.parse("2024-07-07T02:59:59Z")) - .setUpdateTime(Instant.parse("2024-07-07T06:07:44Z")) .build(); @GivenTextResource("/domains/conversation/templates/v2/TemplateTranslationDto.json") diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2DtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2DtoTest.java index 88fb8800e..43dcdf31e 100644 --- a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2DtoTest.java +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2DtoTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.sinch.sdk.core.TestHelpers; import com.sinch.sdk.domains.conversation.templates.api.adapters.TemplatesBaseTest; -import java.time.Instant; import java.util.Arrays; import org.json.JSONException; import org.junit.jupiter.api.Test; @@ -27,8 +26,6 @@ public class TemplateV2DtoTest extends TemplatesBaseTest { .setDefaultTranslation("fr-FR") .setDescription("template description value") .setVersion(1) - .setCreateTime(Instant.parse("2024-08-26T06:00:00Z")) - .setUpdateTime(Instant.parse("2024-08-26T06:07:44Z")) .build(); @Test diff --git a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateTranslationDto.json b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateTranslationDto.json index cac806cd8..fdf376264 100644 --- a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateTranslationDto.json +++ b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateTranslationDto.json @@ -22,8 +22,6 @@ "preview_value": "preview value" } ], - "create_time": "2024-07-07T02:59:59Z", - "update_time": "2024-07-07T06:07:44Z", "template_message": { "channel_template": { "KAKAOTALK": { diff --git a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateV2Dto.json b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateV2Dto.json index a61b1af2f..a0d268849 100644 --- a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateV2Dto.json +++ b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateV2Dto.json @@ -28,8 +28,6 @@ "preview_value": "preview value" } ], - "create_time": "2024-07-07T02:59:59Z", - "update_time": "2024-07-07T06:07:44Z", "template_message": { "channel_template": { "KAKAOTALK": { @@ -48,7 +46,5 @@ } } } - ], - "create_time": "2024-08-26T06:00:00Z", - "update_time": "2024-08-26T06:07:44Z" + ] } diff --git a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/request/TemplateV2RequestDto.json b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/request/TemplateV2RequestDto.json index f7628b229..a0d268849 100644 --- a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/request/TemplateV2RequestDto.json +++ b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/request/TemplateV2RequestDto.json @@ -28,8 +28,6 @@ "preview_value": "preview value" } ], - "create_time": "2024-07-07T02:59:59Z", - "update_time": "2024-07-07T06:07:44Z", "template_message": { "channel_template": { "KAKAOTALK": { diff --git a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplatesV2ListResponseDto.json b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplatesV2ListResponseDto.json index 035e34d34..56ab0f9a6 100644 --- a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplatesV2ListResponseDto.json +++ b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplatesV2ListResponseDto.json @@ -30,8 +30,6 @@ "preview_value": "preview value" } ], - "create_time": "2024-07-07T02:59:59Z", - "update_time": "2024-07-07T06:07:44Z", "template_message": { "channel_template": { "KAKAOTALK": { @@ -50,9 +48,7 @@ } } } - ], - "create_time": "2024-08-26T06:00:00Z", - "update_time": "2024-08-26T06:07:44Z" + ] } ] } diff --git a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TranslationsV2ListResponseDto.json b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TranslationsV2ListResponseDto.json index 1036ee5f1..d96b796fa 100644 --- a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TranslationsV2ListResponseDto.json +++ b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TranslationsV2ListResponseDto.json @@ -24,8 +24,6 @@ "preview_value": "preview value" } ], - "create_time": "2024-07-07T02:59:59Z", - "update_time": "2024-07-07T06:07:44Z", "template_message": { "channel_template": { "KAKAOTALK": { From 76f63286e2b7260817ff5429463c4955163bc6dc Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Tue, 30 Jun 2026 13:24:41 +0200 Subject: [PATCH 26/37] Added CHANGELOG for new field internalFailurecode and added field into SinchEventSteps tests --- CHANGELOG.md | 3 +++ .../com/sinch/sdk/e2e/domains/numbers/v1/SinchEventsSteps.java | 2 ++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e519254ac..c4ea3e85c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ All notable changes to the **Sinch Java SDK** are documented in this file. ## v2.1 – unreleased +### Numbers +- **[feature]** `internalFailureCode` field added + ### Conversation - **[feature]** Support `Consents` API: `listIdentities` and `listAuditRecords` endpoints diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/SinchEventsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/SinchEventsSteps.java index a8b39cae9..8adcd341c 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/SinchEventsSteps.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/numbers/v1/SinchEventsSteps.java @@ -81,6 +81,7 @@ public void validateResult(String status, String trigger) { .setEventType(EventTypeEnum.PROVISIONING_TO_VOICE_PLATFORM) .setStatus(StatusEnum.SUCCEEDED) .setFailureCode(null) + .setInternalFailureCode(null) .build(); NumberSinchEvent expectedFailure = @@ -93,6 +94,7 @@ public void validateResult(String status, String trigger) { .setEventType(EventTypeEnum.PROVISIONING_TO_VOICE_PLATFORM) .setStatus(StatusEnum.FAILED) .setFailureCode(FailureCodeEnum.from("PROVISIONING_TO_VOICE_PLATFORM_FAILED")) + .setInternalFailureCode(null) .build(); NumberSinchEvent expected = From 75abf97a2bd4255501e4fdbe44de8165662a2262 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Tue, 30 Jun 2026 14:38:12 +0200 Subject: [PATCH 27/37] Improved CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4ea3e85c..812fed52b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,11 @@ All notable changes to the **Sinch Java SDK** are documented in this file. ## v2.1 – unreleased ### Numbers -- **[feature]** `internalFailureCode` field added +- **[feature]** Extend `NumberSinchEvents` class. + - **[fix]** `EventTypeEnum`: `DEPROVISIONING_FROM_VOICE_PLATFORM` is deprecated and has to be replaced by `VOICE_PLATFORM_DEPROVISIONING`. + - **[feature]** Support new `internalFailureCode` field. + - **[feature]** Support new `StatusEnum` values: `IN_REVIEW`, `BLOCKED`, `COMPLETED`, `REJECTED`, `EXPIRED`. + - **[feature]** Support new `EventTypeEnum` value: `NUMBER_ORDER_PROCESSING`. ### Conversation - **[feature]** Support `Consents` API: `listIdentities` and `listAuditRecords` endpoints From 6a1ebb3338daf5c6d97a512453a7c104dea17eea Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Tue, 30 Jun 2026 14:40:31 +0200 Subject: [PATCH 28/37] Fixed CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 812fed52b..667a31037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ All notable changes to the **Sinch Java SDK** are documented in this file. ## v2.1 – unreleased ### Numbers -- **[feature]** Extend `NumberSinchEvents` class. +- Extend `NumberSinchEvents` class. - **[fix]** `EventTypeEnum`: `DEPROVISIONING_FROM_VOICE_PLATFORM` is deprecated and has to be replaced by `VOICE_PLATFORM_DEPROVISIONING`. - **[feature]** Support new `internalFailureCode` field. - **[feature]** Support new `StatusEnum` values: `IN_REVIEW`, `BLOCKED`, `COMPLETED`, `REJECTED`, `EXPIRED`. From 7390338bf01a7807cee8f6d246ac6e56ff1888b1 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Tue, 30 Jun 2026 15:31:10 +0200 Subject: [PATCH 29/37] Added request and response dedicated Dto tests --- .../adapters/v2/TemplatesV2ServiceTest.java | 31 ++++++------- .../models/v2/TemplateTranslationDtoTest.java | 34 +++++++++++--- .../models/v2/TemplateV2DtoTest.java | 26 ++++++++--- .../TemplatesV2ListResponseDtoTest.java | 2 +- .../TranslationsV2ListResponseDtoTest.java | 11 +---- .../TemplateTranslationV2RequestDto.json} | 0 .../TemplateTranslationV2ResponseDto.json | 44 +++++++++++++++++++ .../TemplateV2ResponseDto.json} | 6 ++- .../response/TemplatesV2ListResponseDto.json | 4 ++ .../TranslationsV2ListResponseDto.json | 2 + 10 files changed, 119 insertions(+), 41 deletions(-) rename openapi-contracts/src/test/resources/domains/conversation/templates/v2/{TemplateTranslationDto.json => request/TemplateTranslationV2RequestDto.json} (100%) create mode 100644 openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplateTranslationV2ResponseDto.json rename openapi-contracts/src/test/resources/domains/conversation/templates/v2/{TemplateV2Dto.json => response/TemplateV2ResponseDto.json} (88%) diff --git a/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/v2/TemplatesV2ServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/v2/TemplatesV2ServiceTest.java index 47e1d1f6c..945897100 100644 --- a/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/v2/TemplatesV2ServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/v2/TemplatesV2ServiceTest.java @@ -57,7 +57,7 @@ public class TemplatesV2ServiceTest extends TemplatesBaseTest { @GivenTextResource("/domains/conversation/templates/v2/request/TemplateV2RequestDto.json") String jsonTemplateV2RequestDto; - @GivenTextResource("/domains/conversation/templates/v2/TemplateV2Dto.json") + @GivenTextResource("/domains/conversation/templates/v2/response/TemplateV2ResponseDto.json") String jsonTemplateV2Dto; @GivenTextResource("/domains/conversation/templates/v2/response/TemplatesV2ListResponseDto.json") @@ -96,9 +96,9 @@ void create() throws ApiException { argThat(new HttpRequestMatcher(httpRequest)))) .thenReturn(httpResponse); - TemplateV2 response = service.create(TemplateV2DtoTest.expectedDto); + TemplateV2 response = service.create(TemplateV2DtoTest.expectedRequestDto); - TestHelpers.recursiveEquals(response, TemplateV2DtoTest.expectedDto); + TestHelpers.recursiveEquals(response, TemplateV2DtoTest.expectedResponseDto); } @Test @@ -109,7 +109,7 @@ void get() throws ApiException { String.format( "/v2/projects/%s/templates/%s", URLPathUtils.encodePathSegment(uriPartID), - URLPathUtils.encodePathSegment(TemplateV2DtoTest.expectedDto.getId())), + URLPathUtils.encodePathSegment(TemplateV2DtoTest.expectedRequestDto.getId())), HttpMethod.GET, Collections.emptyList(), (String) null, @@ -126,9 +126,9 @@ void get() throws ApiException { argThat(new HttpRequestMatcher(httpRequest)))) .thenReturn(httpResponse); - TemplateV2 response = service.get(TemplateV2DtoTest.expectedDto.getId()); + TemplateV2 response = service.get(TemplateV2DtoTest.expectedRequestDto.getId()); - TestHelpers.recursiveEquals(response, TemplateV2DtoTest.expectedDto); + TestHelpers.recursiveEquals(response, TemplateV2DtoTest.expectedResponseDto); } @Test @@ -178,7 +178,7 @@ void listTranslations() throws ApiException { String.format( "/v2/projects/%s/templates/%s/translations", URLPathUtils.encodePathSegment(uriPartID), - URLPathUtils.encodePathSegment(TemplateV2DtoTest.expectedDto.getId())), + URLPathUtils.encodePathSegment(TemplateV2DtoTest.expectedRequestDto.getId())), HttpMethod.GET, Collections.emptyList(), (String) null, @@ -197,7 +197,7 @@ void listTranslations() throws ApiException { .thenReturn(httpResponse); TranslationsV2ListResponse response = - service.listTranslations(TemplateV2DtoTest.expectedDto.getId()); + service.listTranslations(TemplateV2DtoTest.expectedRequestDto.getId()); Iterator iterator = response.iterator(); TranslationsV2ListResponse expectedListResponse = @@ -221,7 +221,7 @@ void listTranslationsWithParameters() throws ApiException { String.format( "/v2/projects/%s/templates/%s/translations", URLPathUtils.encodePathSegment(uriPartID), - URLPathUtils.encodePathSegment(TemplateV2DtoTest.expectedDto.getId())), + URLPathUtils.encodePathSegment(TemplateV2DtoTest.expectedRequestDto.getId())), HttpMethod.GET, Arrays.asList( new URLParameter("language_code", "language code", STYLE.FORM, true), @@ -243,7 +243,7 @@ void listTranslationsWithParameters() throws ApiException { TranslationsV2ListResponse response = service.listTranslations( - TemplateV2DtoTest.expectedDto.getId(), + TemplateV2DtoTest.expectedRequestDto.getId(), ListTranslationsQueryParameters.builder() .setLanguageCode("language code") .setTranslationVersion("translation version") @@ -271,7 +271,7 @@ void update() throws ApiException { String.format( "/v2/projects/%s/templates/%s", URLPathUtils.encodePathSegment(uriPartID), - URLPathUtils.encodePathSegment(TemplateV2DtoTest.expectedDto.getId())), + URLPathUtils.encodePathSegment(TemplateV2DtoTest.expectedRequestDto.getId())), HttpMethod.PUT, Collections.emptyList(), jsonTemplateV2RequestDto, @@ -289,9 +289,10 @@ void update() throws ApiException { .thenReturn(httpResponse); TemplateV2 response = - service.update(TemplateV2DtoTest.expectedDto.getId(), TemplateV2DtoTest.expectedDto); + service.update( + TemplateV2DtoTest.expectedRequestDto.getId(), TemplateV2DtoTest.expectedRequestDto); - TestHelpers.recursiveEquals(response, TemplateV2DtoTest.expectedDto); + TestHelpers.recursiveEquals(response, TemplateV2DtoTest.expectedResponseDto); } @Test @@ -302,7 +303,7 @@ void delete() throws ApiException { String.format( "/v2/projects/%s/templates/%s", URLPathUtils.encodePathSegment(uriPartID), - URLPathUtils.encodePathSegment(TemplateV2DtoTest.expectedDto.getId())), + URLPathUtils.encodePathSegment(TemplateV2DtoTest.expectedRequestDto.getId())), HttpMethod.DELETE, Collections.emptyList(), (String) null, @@ -318,6 +319,6 @@ void delete() throws ApiException { argThat(new HttpRequestMatcher(httpRequest)))) .thenReturn(httpResponse); - service.delete(TemplateV2DtoTest.expectedDto.getId()); + service.delete(TemplateV2DtoTest.expectedResponseDto.getId()); } } diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationDtoTest.java index 3edd3b8d7..eefd2c69b 100644 --- a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationDtoTest.java +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateTranslationDtoTest.java @@ -8,6 +8,7 @@ import com.sinch.sdk.domains.conversation.models.v1.messages.types.template.TemplateMessageDtoTest; import com.sinch.sdk.domains.conversation.templates.api.adapters.TemplatesBaseTest; import com.sinch.sdk.domains.conversation.templates.models.TemplateVariableDtoTest; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import org.json.JSONException; @@ -17,7 +18,7 @@ @TestWithResources public class TemplateTranslationDtoTest extends TemplatesBaseTest { - public static TemplateTranslation expectedDto = + public static TemplateTranslation expectedRequestDto = TemplateTranslation.builder() .setMessage(TemplateMessageDtoTest.templateMessageDto) .setLanguageCode("fr-FR") @@ -29,20 +30,39 @@ public class TemplateTranslationDtoTest extends TemplatesBaseTest { .setVariables(Arrays.asList(TemplateVariableDtoTest.expectedDto)) .build(); - @GivenTextResource("/domains/conversation/templates/v2/TemplateTranslationDto.json") - String json; + public static TemplateTranslation expectedResponseDto = + TemplateTranslation.builder() + .setMessage(TemplateMessageDtoTest.templateMessageDto) + .setLanguageCode("fr-FR") + .setVersion("1") + .setChannelTemplateOverrides( + Collections.singletonMap( + ConversationChannel.KAKAOTALK, + ChannelTemplateOverrideDtoTest.expectedWithVersionDto)) + .setVariables(Arrays.asList(TemplateVariableDtoTest.expectedDto)) + .setCreateTime(Instant.parse("2024-07-07T02:59:59Z")) + .setUpdateTime(Instant.parse("2024-07-07T06:07:44Z")) + .build(); + + @GivenTextResource( + "/domains/conversation/templates/v2/request/TemplateTranslationV2RequestDto.json") + String jsonRequest; + + @GivenTextResource( + "/domains/conversation/templates/v2/response/TemplateTranslationV2ResponseDto.json") + String jsonResponse; @Test void serialize() throws JsonProcessingException, JSONException { - String serializedString = objectMapper.writeValueAsString(expectedDto); + String serializedString = objectMapper.writeValueAsString(expectedRequestDto); - JSONAssert.assertEquals(json, serializedString, true); + JSONAssert.assertEquals(jsonRequest, serializedString, true); } @Test void deserialize() throws JsonProcessingException { - Object deserialized = objectMapper.readValue(json, TemplateTranslation.class); + Object deserialized = objectMapper.readValue(jsonResponse, TemplateTranslation.class); - TestHelpers.recursiveEquals(deserialized, expectedDto); + TestHelpers.recursiveEquals(deserialized, expectedResponseDto); } } diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2DtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2DtoTest.java index 43dcdf31e..e552014c3 100644 --- a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2DtoTest.java +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/TemplateV2DtoTest.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.sinch.sdk.core.TestHelpers; import com.sinch.sdk.domains.conversation.templates.api.adapters.TemplatesBaseTest; +import java.time.Instant; import java.util.Arrays; import org.json.JSONException; import org.junit.jupiter.api.Test; @@ -16,29 +17,40 @@ public class TemplateV2DtoTest extends TemplatesBaseTest { @GivenTextResource("/domains/conversation/templates/v2/request/TemplateV2RequestDto.json") String requestJSON; - @GivenTextResource("/domains/conversation/templates/v2/TemplateV2Dto.json") - String responseSON; + @GivenTextResource("/domains/conversation/templates/v2/response/TemplateV2ResponseDto.json") + String responseJSON; - public static TemplateV2 expectedDto = + public static TemplateV2 expectedRequestDto = TemplateV2.builder() .setId("template ID") - .setTranslations(Arrays.asList(TemplateTranslationDtoTest.expectedDto)) + .setTranslations(Arrays.asList(TemplateTranslationDtoTest.expectedRequestDto)) .setDefaultTranslation("fr-FR") .setDescription("template description value") .setVersion(1) .build(); + public static TemplateV2 expectedResponseDto = + TemplateV2.builder() + .setId("template ID") + .setTranslations(Arrays.asList(TemplateTranslationDtoTest.expectedResponseDto)) + .setDefaultTranslation("fr-FR") + .setDescription("template description value") + .setVersion(1) + .setCreateTime(Instant.parse("2024-08-26T06:00:00Z")) + .setUpdateTime(Instant.parse("2024-08-26T06:07:44Z")) + .build(); + @Test void serialize() throws JsonProcessingException, JSONException { - String serializedString = objectMapper.writeValueAsString(expectedDto); + String serializedString = objectMapper.writeValueAsString(expectedRequestDto); JSONAssert.assertEquals(requestJSON, serializedString, true); } @Test void deserialize() throws JsonProcessingException { - Object deserialized = objectMapper.readValue(responseSON, TemplateV2.class); + Object deserialized = objectMapper.readValue(responseJSON, TemplateV2.class); - TestHelpers.recursiveEquals(deserialized, expectedDto); + TestHelpers.recursiveEquals(deserialized, expectedResponseDto); } } diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/response/TemplatesV2ListResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/response/TemplatesV2ListResponseDtoTest.java index 13f169c23..0f8b93aa9 100644 --- a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/response/TemplatesV2ListResponseDtoTest.java +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/response/TemplatesV2ListResponseDtoTest.java @@ -15,7 +15,7 @@ public class TemplatesV2ListResponseDtoTest extends TemplatesBaseTest { public static ListTemplatesResponseInternal expectedDto = ListTemplatesResponseInternal.builder() - .setTemplates(Arrays.asList(TemplateV2DtoTest.expectedDto)) + .setTemplates(Arrays.asList(TemplateV2DtoTest.expectedResponseDto)) .build(); @GivenTextResource("/domains/conversation/templates/v2/response/TemplatesV2ListResponseDto.json") diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/response/TranslationsV2ListResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/response/TranslationsV2ListResponseDtoTest.java index d9d17bf74..5890dc13a 100644 --- a/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/response/TranslationsV2ListResponseDtoTest.java +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/conversation/templates/models/v2/response/TranslationsV2ListResponseDtoTest.java @@ -8,29 +8,20 @@ import com.sinch.sdk.domains.conversation.templates.models.v2.TemplateTranslationDtoTest; import com.sinch.sdk.domains.conversation.templates.models.v2.internal.ListTranslationsResponseInternal; import java.util.Arrays; -import org.json.JSONException; import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; @TestWithResources public class TranslationsV2ListResponseDtoTest extends TemplatesBaseTest { public static ListTranslationsResponseInternal expectedDto = ListTranslationsResponseInternal.builder() - .setTranslations(Arrays.asList(TemplateTranslationDtoTest.expectedDto)) + .setTranslations(Arrays.asList(TemplateTranslationDtoTest.expectedResponseDto)) .build(); @GivenTextResource( "/domains/conversation/templates/v2/response/TranslationsV2ListResponseDto.json") String json; - @Test - void serialize() throws JsonProcessingException, JSONException { - String serializedString = objectMapper.writeValueAsString(expectedDto); - - JSONAssert.assertEquals(json, serializedString, true); - } - @Test void deserialize() throws JsonProcessingException { Object deserialized = objectMapper.readValue(json, ListTranslationsResponseInternal.class); diff --git a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateTranslationDto.json b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/request/TemplateTranslationV2RequestDto.json similarity index 100% rename from openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateTranslationDto.json rename to openapi-contracts/src/test/resources/domains/conversation/templates/v2/request/TemplateTranslationV2RequestDto.json diff --git a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplateTranslationV2ResponseDto.json b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplateTranslationV2ResponseDto.json new file mode 100644 index 000000000..cac806cd8 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplateTranslationV2ResponseDto.json @@ -0,0 +1,44 @@ +{ + "language_code": "fr-FR", + "version": "1", + "channel_template_overrides": { + "KAKAOTALK": { + "template_reference": { + "language_code": "another language", + "parameters": { + "name": "Value for the name parameter used in the version 1 and language \"en-US\" of the template" + }, + "template_id": "another template ID", + "version": "another version" + }, + "parameter_mappings": { + "a key": "a value" + } + } + }, + "variables": [ + { + "key": "key value", + "preview_value": "preview value" + } + ], + "create_time": "2024-07-07T02:59:59Z", + "update_time": "2024-07-07T06:07:44Z", + "template_message": { + "channel_template": { + "KAKAOTALK": { + "language_code": "en-US", + "template_id": "my template ID value", + "version": "a version" + } + }, + "omni_template": { + "language_code": "another language", + "parameters": { + "name": "Value for the name parameter used in the version 1 and language \"en-US\" of the template" + }, + "template_id": "another template ID", + "version": "another version" + } + } +} diff --git a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateV2Dto.json b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplateV2ResponseDto.json similarity index 88% rename from openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateV2Dto.json rename to openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplateV2ResponseDto.json index a0d268849..a61b1af2f 100644 --- a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/TemplateV2Dto.json +++ b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplateV2ResponseDto.json @@ -28,6 +28,8 @@ "preview_value": "preview value" } ], + "create_time": "2024-07-07T02:59:59Z", + "update_time": "2024-07-07T06:07:44Z", "template_message": { "channel_template": { "KAKAOTALK": { @@ -46,5 +48,7 @@ } } } - ] + ], + "create_time": "2024-08-26T06:00:00Z", + "update_time": "2024-08-26T06:07:44Z" } diff --git a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplatesV2ListResponseDto.json b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplatesV2ListResponseDto.json index 56ab0f9a6..36ee0d6cd 100644 --- a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplatesV2ListResponseDto.json +++ b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TemplatesV2ListResponseDto.json @@ -5,6 +5,8 @@ "description": "template description value", "version": 1, "default_translation": "fr-FR", + "create_time": "2024-08-26T06:00:00Z", + "update_time": "2024-08-26T06:07:44Z", "translations": [ { "language_code": "fr-FR", @@ -30,6 +32,8 @@ "preview_value": "preview value" } ], + "create_time": "2024-07-07T02:59:59Z", + "update_time": "2024-07-07T06:07:44Z", "template_message": { "channel_template": { "KAKAOTALK": { diff --git a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TranslationsV2ListResponseDto.json b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TranslationsV2ListResponseDto.json index d96b796fa..1036ee5f1 100644 --- a/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TranslationsV2ListResponseDto.json +++ b/openapi-contracts/src/test/resources/domains/conversation/templates/v2/response/TranslationsV2ListResponseDto.json @@ -24,6 +24,8 @@ "preview_value": "preview value" } ], + "create_time": "2024-07-07T02:59:59Z", + "update_time": "2024-07-07T06:07:44Z", "template_message": { "channel_template": { "KAKAOTALK": { From c8b0e2af0baa1a61db3db6b9f982625be02330d7 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier <141755467+JPPortier@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:22:59 +0200 Subject: [PATCH 30/37] DEVEXP-1378: feat (HttpClient): Support proxy usage (#357) --- CHANGELOG.md | 3 +- README.md | 49 ++++ .../src/main/com/sinch/sdk/SinchClient.java | 2 +- .../com/sinch/sdk/http/HttpClientApache.java | 109 +++++++- .../com/sinch/sdk/models/Configuration.java | 31 +++ .../sdk/models/HttpProxyConfiguration.java | 255 ++++++++++++++++++ .../java/com/sinch/sdk/SinchClientTest.java | 59 ++++ .../test/java/com/sinch/sdk/e2e/Config.java | 53 ++++ .../proxy/HttpClientApacheProxyIT.java | 77 ++++++ .../sinch/sdk/e2e/domains/proxy/ProxyIT.java | 18 ++ .../sdk/e2e/domains/proxy/ProxySteps.java | 69 +++++ .../http/HttpClientApacheLifecycleTest.java | 33 +++ .../sdk/http/HttpClientApacheProxyTest.java | 184 +++++++++++++ .../sinch/sdk/http/HttpClientApacheTest.java | 83 +++++- .../sdk/models/ConfigurationBuilderTest.java | 63 +++++ .../models/HttpProxyConfigurationTest.java | 204 ++++++++++++++ pom.xml | 2 + 17 files changed, 1278 insertions(+), 16 deletions(-) create mode 100644 client/src/main/com/sinch/sdk/models/HttpProxyConfiguration.java create mode 100644 client/src/test/java/com/sinch/sdk/e2e/domains/proxy/HttpClientApacheProxyIT.java create mode 100644 client/src/test/java/com/sinch/sdk/e2e/domains/proxy/ProxyIT.java create mode 100644 client/src/test/java/com/sinch/sdk/e2e/domains/proxy/ProxySteps.java create mode 100644 client/src/test/java/com/sinch/sdk/http/HttpClientApacheProxyTest.java create mode 100644 client/src/test/java/com/sinch/sdk/models/HttpProxyConfigurationTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 667a31037..a72b52967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ All notable changes to the **Sinch Java SDK** are documented in this file. ### Numbers - Extend `NumberSinchEvents` class. - - **[fix]** `EventTypeEnum`: `DEPROVISIONING_FROM_VOICE_PLATFORM` is deprecated and has to be replaced by `VOICE_PLATFORM_DEPROVISIONING`. + - **[fix]** `EventTypeEnum`: `DEPROVISIONING_TO_VOICE_PLATFORM` is deprecated and has to be replaced by `DEPROVISIONING_FROM_VOICE_PLATFORM`. - **[feature]** Support new `internalFailureCode` field. - **[feature]** Support new `StatusEnum` values: `IN_REVIEW`, `BLOCKED`, `COMPLETED`, `REJECTED`, `EXPIRED`. - **[feature]** Support new `EventTypeEnum` value: `NUMBER_ORDER_PROCESSING`. @@ -29,6 +29,7 @@ All notable changes to the **Sinch Java SDK** are documented in this file. - **[feature]** Support `Consents` API: `listIdentities` and `listAuditRecords` endpoints ### SDK +- **[feature]** HTTP proxy support: configure an unauthenticated or authenticated (Basic) proxy via `HttpProxyConfiguration` - **[feature]** `SinchClient` exposes a `close()` method to shut down the underlying HTTP connection pool and release all associated resources deterministically - **[fix]** `HttpClientApache`: declare now `headersToBeAdded` as `volatile` to guarantee visibility across threads in concurrent usage - **[fix]** `HttpClientApache`: wrap response-body `Scanner` in a try-with-resources block to prevent resource leaks; gracefully handle empty (`null`) response entities diff --git a/README.md b/README.md index 7f0ad701a..04f7344ab 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ For more information on the SDK, refer to the dedicated [Java SDK documentation - [Getting started](#getting-started) - [Logging](#logging) - [Handling Exceptions](#handling-exceptions) +- [Proxy configuration](#proxy-configuration) - [Third-party dependencies](#third-party-dependencies) - [Examples](#examples) - [Changelog and Migration](#changelog--migration) @@ -397,6 +398,54 @@ try { - `ApiAuthException`: authentication/authorization failures. - `ApiMappingException`: the response payload could not be deserialized into the expected type. +## Proxy configuration + +If your network environment routes outbound traffic through an HTTP proxy, provide proxy +configuration via [HttpProxyConfiguration](client/src/main/com/sinch/sdk/models/HttpProxyConfiguration.java) on the [Configuration](client/src/main/com/sinch/sdk/models/Configuration.java) builder. + +When used, all connections will go through the proxy (including OAuth). + +**Unauthenticated proxy:** + +```java +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.models.Configuration; +import com.sinch.sdk.models.HttpProxyConfiguration; + +... +Configuration configuration = Configuration.builder() + .setKeyId(PARAM_KEY_ID) + .setKeySecret(PARAM_KEY_SECRET) + .setProjectId(PARAM_PROJECT_ID) + .setHttpProxyConfiguration( + HttpProxyConfiguration.builder() + .setHostname(PARAM_PROXY_HOSTNAME) + .setPort(PARAM_PROXY_PORT) + .build()) + .build(); +SinchClient client = new SinchClient(configuration); +``` + +**Authenticated proxy:** + + +```java +Configuration configuration = Configuration.builder() + .setKeyId(PARAM_KEY_ID) + .setKeySecret(PARAM_KEY_SECRET) + .setProjectId(PARAM_PROJECT_ID) + .setHttpProxyConfiguration( + HttpProxyConfiguration.builder() + .setHostname(PARAM_PROXY_HOSTNAME) + .setPort(PARAM_PROXY_PORT) + .setUsername(PARAM_PROXY_USERNAME) + // prefer char[] over String to reduce password exposure in heap memory + .setPassword(PARAM_PROXY_PASSWORD_AS_CHAR_ARRAY) + .build()) + .build(); +SinchClient client = new SinchClient(configuration); +``` + ## Third-party dependencies The SDK relies on the following third-party dependencies: diff --git a/client/src/main/com/sinch/sdk/SinchClient.java b/client/src/main/com/sinch/sdk/SinchClient.java index 2fe0adc7f..36a6d6b1b 100644 --- a/client/src/main/com/sinch/sdk/SinchClient.java +++ b/client/src/main/com/sinch/sdk/SinchClient.java @@ -449,7 +449,7 @@ private HttpClientApache getHttpClient() { synchronized (this) { local = httpClient; if (null == local || local.isClosed()) { - local = new HttpClientApache(); + local = new HttpClientApache(configuration.getHttpProxyConfiguration().orElse(null)); // set SDK User-Agent String userAgent = formatSdkUserAgentHeader(); diff --git a/client/src/main/com/sinch/sdk/http/HttpClientApache.java b/client/src/main/com/sinch/sdk/http/HttpClientApache.java index e5882a487..bf3f1dad2 100644 --- a/client/src/main/com/sinch/sdk/http/HttpClientApache.java +++ b/client/src/main/com/sinch/sdk/http/HttpClientApache.java @@ -16,6 +16,7 @@ import com.sinch.sdk.core.http.URLParameter; import com.sinch.sdk.core.models.ServerConfiguration; import com.sinch.sdk.core.utils.Pair; +import com.sinch.sdk.models.HttpProxyConfiguration; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; @@ -31,14 +32,22 @@ import java.util.Scanner; import java.util.logging.Logger; import java.util.stream.Collectors; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.HttpResponseException; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.support.AbstractMessageBuilder; @@ -47,12 +56,48 @@ public class HttpClientApache implements com.sinch.sdk.core.http.HttpClient { private static final Logger LOGGER = Logger.getLogger(HttpClientApache.class.getName()); + /** + * HTTP 407 Proxy Authentication Required. Kept as a local constant to make the guard in {@link + * #invokeAPI} self-documenting and independent of any external enum. + */ + private static final int HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + private volatile Map headersToBeAdded; private volatile CloseableHttpClient client; public HttpClientApache() { - this.client = HttpClients.createDefault(); + this(null); + } + + public HttpClientApache(HttpProxyConfiguration proxyConfiguration) { + this.client = buildHttpClient(proxyConfiguration); + } + + private static CloseableHttpClient buildHttpClient(HttpProxyConfiguration proxyConfiguration) { + if (proxyConfiguration == null) { + return HttpClients.createDefault(); + } + + HttpHost proxyHost = + new HttpHost(proxyConfiguration.getHostname(), proxyConfiguration.getPort()); + HttpClientBuilder builder = + HttpClients.custom().setRoutePlanner(new DefaultProxyRoutePlanner(proxyHost)); + + if (proxyConfiguration.getUsername().isPresent()) { + // getPassword() returns a defensive copy of the internal array; HC5 receives that copy and + // owns it for the lifetime of the client. The caller does not need to keep the original + // alive. + CredentialsProvider credentialsProvider = + CredentialsProviderBuilder.create() + .add( + new AuthScope(proxyHost), + proxyConfiguration.getUsername().orElse(""), + proxyConfiguration.getPassword().orElse(new char[0])) + .build(); + builder.setDefaultCredentialsProvider(credentialsProvider); + } + return builder.build(); } public void setRequestHeaders(Map headers) { @@ -156,6 +201,20 @@ public HttpResponse invokeAPI( HttpResponse response = processRequest(activeClient, request); LOGGER.finest("connection response: " + response); + // HTTP 407 (Proxy Authentication Required) is normally handled transparently by Apache + // HttpClient via DefaultProxyRoutePlanner + CredentialsProvider (the 407→retry cycle + // happens inside processRequest and is invisible to this method). + // If 407 surfaces here it means proxy credentials are absent, wrong, or the proxy uses an + // unsupported auth scheme. Guard explicitly so that the OAuth-refresh block below does NOT + // misfire: some enterprise proxies include a `www-authenticate: Bearer error="expired"` + // header on 407 responses, which would incorrectly trigger an OAuth token reset. + if (response.getCode() == HTTP_PROXY_AUTHENTICATION_REQUIRED) { + LOGGER.warning( + "Proxy authentication required (HTTP 407). " + + "Verify HttpProxyConfiguration hostname, port and credentials."); + return response; + } + // UNAUTHORIZED (HTTP 401) error code could imply refreshing the OAuth token if (response.getCode() == HttpStatus.UNAUTHORIZED) { boolean couldRetryRequest = @@ -169,6 +228,15 @@ public HttpResponse invokeAPI( } } return response; + } catch (ClientProtocolException cpe) { + int code = extractHttpStatusCode(cpe); + if (code > 0) { + LOGGER.severe("HTTP protocol error with status code " + code + ": " + cpe.getMessage()); + throw new ApiException( + "HTTP protocol error (status code " + code + "): " + cpe.getMessage(), cpe, code); + } + LOGGER.severe("HTTP protocol error: " + cpe.getMessage()); + throw new ApiException("HTTP protocol error: " + cpe.getMessage(), cpe); } catch (Exception e) { LOGGER.severe("Error:" + e); throw new ApiException(e); @@ -180,6 +248,9 @@ private boolean processUnauthorizedResponse( HttpResponse response, Map authManagersByOasSecuritySchemes) { + if (null == authManagersByOasSecuritySchemes) { + return false; + } Map authManagersByAuthSchemes = authManagersByOasSecuritySchemes.values().stream() .map(authManager -> new AbstractMap.SimpleEntry<>(authManager.getSchema(), authManager)) @@ -348,6 +419,42 @@ HttpResponse processRequest(CloseableHttpClient client, ClassicHttpRequest reque return client.execute(request, HttpClientApache::processResponse); } + /** + * Extracts the HTTP status code from a {@link ClientProtocolException}. + * + *

Handles two cases: + * + *

    + *
  • {@link HttpResponseException} — carries the code directly via {@code getStatusCode()}. + *
  • Plain {@link ClientProtocolException} — Apache embeds the status line in the message + * (e.g. {@code "CONNECT refused by proxy: HTTP/1.1 407 Proxy Authentication Required"}). + *
+ * + * @return the HTTP status code, or {@code -1} if it cannot be determined + */ + private static int extractHttpStatusCode(ClientProtocolException e) { + if (e instanceof HttpResponseException) { + return ((HttpResponseException) e).getStatusCode(); + } + String message = e.getMessage(); + if (message == null) { + return -1; + } + int idx = message.indexOf("HTTP/"); + if (idx < 0) { + return -1; + } + String[] parts = message.substring(idx).split(" ", 3); + if (parts.length < 2) { + return -1; + } + try { + return Integer.parseInt(parts[1]); + } catch (NumberFormatException ignored) { + return -1; + } + } + private Optional extractCharset(AbstractMessageBuilder messageBuilder) { Header[] headers = messageBuilder.getHeaders(CONTENT_TYPE_HEADER); diff --git a/client/src/main/com/sinch/sdk/models/Configuration.java b/client/src/main/com/sinch/sdk/models/Configuration.java index 962f03190..05d26aef1 100644 --- a/client/src/main/com/sinch/sdk/models/Configuration.java +++ b/client/src/main/com/sinch/sdk/models/Configuration.java @@ -15,6 +15,7 @@ public class Configuration { private final VerificationContext verificationContext; private final VoiceContext voiceContext; private final ConversationContext conversationContext; + private final HttpProxyConfiguration httpProxyConfiguration; private final NumberLookupContext numberLookupContext; private Configuration( @@ -22,6 +23,7 @@ private Configuration( ApplicationCredentials applicationCredentials, SmsServicePlanCredentials smsServicePlanCredentials, String oauthUrl, + HttpProxyConfiguration httpProxyConfiguration, NumbersContext numbersContext, SmsContext smsContext, VerificationContext verificationContext, @@ -38,6 +40,7 @@ private Configuration( this.verificationContext = verificationContext; this.conversationContext = conversationContext; this.numberLookupContext = numberLookupContext; + this.httpProxyConfiguration = httpProxyConfiguration; } @Override @@ -58,6 +61,8 @@ public String toString() { + conversationContext + ", numberLookupContext=" + numberLookupContext + + ", httpProxyConfiguration=" + + httpProxyConfiguration + "}"; } @@ -175,6 +180,16 @@ public Optional getNumberLookupContext() { return Optional.ofNullable(numberLookupContext); } + /** + * Get HTTP proxy configuration + * + * @return HTTP proxy configuration + * @since 2.1 + */ + public Optional getHttpProxyConfiguration() { + return Optional.ofNullable(httpProxyConfiguration); + } + /** * Getting Builder * @@ -213,6 +228,7 @@ public static class Builder { VoiceContext.Builder voiceContext; ConversationContext.Builder conversationContext; NumberLookupContext.Builder numberLookupContext; + HttpProxyConfiguration httpProxyConfiguration; protected Builder() {} @@ -246,6 +262,7 @@ protected Builder(Configuration configuration) { configuration.getConversationContext().map(ConversationContext::builder).orElse(null); this.numberLookupContext = configuration.getNumberLookupContext().map(NumberLookupContext::builder).orElse(null); + this.httpProxyConfiguration = configuration.getHttpProxyConfiguration().orElse(null); } /** @@ -531,6 +548,19 @@ public Builder setNumberLookupContext(NumberLookupContext context) { return this; } + /** + * Set HTTP proxy configuration + * + * @param httpProxyConfiguration proxy configuration, or {@code null} to disable proxy + * @return Current builder + * @see Configuration#getHttpProxyConfiguration() getter + * @since 2.1 + */ + public Builder setHttpProxyConfiguration(HttpProxyConfiguration httpProxyConfiguration) { + this.httpProxyConfiguration = httpProxyConfiguration; + return this; + } + /** * Build a Configuration instance from builder current state * @@ -544,6 +574,7 @@ public Configuration build() { null != applicationCredentials ? applicationCredentials.build() : null, null != smsServicePlanCredentials ? smsServicePlanCredentials.build() : null, oauthUrl, + httpProxyConfiguration, null != numbersContext ? numbersContext.build() : null, null != smsContext ? smsContext.build() : null, null != verificationContext ? verificationContext.build() : null, diff --git a/client/src/main/com/sinch/sdk/models/HttpProxyConfiguration.java b/client/src/main/com/sinch/sdk/models/HttpProxyConfiguration.java new file mode 100644 index 000000000..519767e9c --- /dev/null +++ b/client/src/main/com/sinch/sdk/models/HttpProxyConfiguration.java @@ -0,0 +1,255 @@ +package com.sinch.sdk.models; + +import com.sinch.sdk.core.utils.StringUtil; +import java.util.Arrays; +import java.util.Optional; + +/** + * HTTP proxy configuration for the Sinch SDK HTTP client. + * + *

Provides a transport-agnostic proxy abstraction. Use {@link Builder} to construct an instance, + * optionally providing credentials for authenticated proxies. + * + *

Scheme: Only plain HTTP proxies are supported. HTTPS-terminating (SSL + * intercepting) proxies are not. All outbound connections — including OAuth token exchange — are + * routed through the configured proxy. + * + *

{@code
+ * // Unauthenticated proxy
+ * HttpProxyConfiguration proxy = HttpProxyConfiguration.builder()
+ *     .setHostname("proxy.corp.example.com")
+ *     .setPort(3128)
+ *     .build();
+ *
+ * // Authenticated proxy
+ * HttpProxyConfiguration proxy = HttpProxyConfiguration.builder()
+ *     .setHostname("proxy.corp.example.com")
+ *     .setPort(3128)
+ *     .setUsername("user")
+ *     .setPassword("secret")
+ *     .build();
+ * }
+ * + * @since 2.1 + */ +public class HttpProxyConfiguration { + private final String hostname; + private final int port; + private final String username; + private final char[] password; + + private HttpProxyConfiguration(String hostname, int port, String username, char[] password) { + this.hostname = hostname; + this.port = port; + this.username = username; + this.password = password; + } + + /** + * Proxy host name or IP address. + * + * @return hostname + * @since 2.1 + */ + public String getHostname() { + return hostname; + } + + /** + * Proxy port number. + * + * @return port + * @since 2.1 + */ + public int getPort() { + return port; + } + + /** + * Proxy username, present only when the proxy requires authentication. + * + * @return username, or empty if not configured + * @since 2.1 + */ + public Optional getUsername() { + return Optional.ofNullable(username); + } + + /** + * Proxy password, present only when the proxy requires authentication. + * + *

Returns a defensive copy of the internal array. Callers are encouraged to zero the array + * with {@code Arrays.fill(pwd, '\0')} once they have finished using it. + * + * @return password as a char array, or empty if not configured + * @since 2.1 + */ + public Optional getPassword() { + return password == null + ? Optional.empty() + : Optional.of(Arrays.copyOf(password, password.length)); + } + + @Override + public String toString() { + return "HttpProxyConfiguration{" + + "hostname='" + + hostname + + '\'' + + ", port=" + + port + + ", username=" + + (username != null ? "'***'" : "null") + + ", password=" + + (password != null ? "'***'" : "null") + + '}'; + } + + /** + * Getting Builder + * + * @return New Builder instance + * @since 2.1 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Getting Builder pre-populated from an existing instance + * + * @param configuration source configuration + * @return New Builder instance + * @since 2.1 + */ + public static Builder builder(HttpProxyConfiguration configuration) { + return new Builder(configuration); + } + + /** + * Dedicated Builder + * + * @since 2.1 + */ + public static class Builder { + + private String hostname; + private int port; + private String username; + private char[] password; + + protected Builder() {} + + protected Builder(HttpProxyConfiguration configuration) { + if (null == configuration) { + return; + } + this.hostname = configuration.hostname; + this.port = configuration.port; + this.username = configuration.username; + this.password = + configuration.password == null + ? null + : Arrays.copyOf(configuration.password, configuration.password.length); + } + + /** + * Set proxy hostname or IP address + * + * @param hostname proxy host + * @return Current builder + * @since 2.1 + */ + public Builder setHostname(String hostname) { + this.hostname = hostname; + return this; + } + + /** + * Set proxy port + * + * @param port proxy port + * @return Current builder + * @since 2.1 + */ + public Builder setPort(int port) { + this.port = port; + return this; + } + + /** + * Set proxy username (for authenticated proxies) + * + * @param username proxy username + * @return Current builder + * @since 2.1 + */ + public Builder setUsername(String username) { + this.username = username; + return this; + } + + /** + * Set proxy password (for authenticated proxies). + * + *

The {@code String} argument is converted to a {@code char[]} immediately and the reference + * is not retained. Prefer {@link #setPassword(char[])} when the password is already available + * as a {@code char[]} (e.g. from {@code Console.readPassword()}) to avoid creating an + * intermediate {@code String} on the heap. + * + * @param password proxy password + * @return Current builder + * @since 2.1 + */ + public Builder setPassword(String password) { + this.password = password == null ? null : password.toCharArray(); + return this; + } + + /** + * Set proxy password as a char array (for authenticated proxies). + * + *

A defensive copy of the provided array is stored. The caller may zero the original array + * with {@code Arrays.fill(password, '\0')} immediately after this call. + * + * @param password proxy password as a char array + * @return Current builder + * @since 2.1 + */ + public Builder setPassword(char[] password) { + this.password = password == null ? null : Arrays.copyOf(password, password.length); + return this; + } + + /** + * Build an {@link HttpProxyConfiguration} instance + * + * @return HttpProxyConfiguration instance + * @since 2.1 + */ + public HttpProxyConfiguration build() { + String trimmedHostname = hostname == null ? null : hostname.trim(); + if (StringUtil.isEmpty(trimmedHostname)) { + throw new IllegalArgumentException( + "HttpProxyConfiguration: hostname must not be null or empty"); + } + if (port <= 0 || port > 65535) { + throw new IllegalArgumentException( + "HttpProxyConfiguration: port must be in range 1–65535, got: " + port); + } + if (password != null && username == null) { + throw new IllegalArgumentException( + "HttpProxyConfiguration: a password was provided without a username"); + } + if (username != null && password == null) { + throw new IllegalArgumentException( + "HttpProxyConfiguration: a username was provided without a password"); + } + return new HttpProxyConfiguration( + trimmedHostname, + port, + username, + password == null ? null : Arrays.copyOf(password, password.length)); + } + } +} diff --git a/client/src/test/java/com/sinch/sdk/SinchClientTest.java b/client/src/test/java/com/sinch/sdk/SinchClientTest.java index 5ec89d15a..8baee2d6a 100644 --- a/client/src/test/java/com/sinch/sdk/SinchClientTest.java +++ b/client/src/test/java/com/sinch/sdk/SinchClientTest.java @@ -3,11 +3,15 @@ import static org.junit.jupiter.api.Assertions.*; import com.sinch.sdk.core.utils.StringUtil; +import com.sinch.sdk.http.HttpClientApache; import com.sinch.sdk.models.Configuration; import com.sinch.sdk.models.ConversationRegion; +import com.sinch.sdk.models.HttpProxyConfiguration; import com.sinch.sdk.models.SMSRegion; import com.sinch.sdk.models.VoiceContext; import com.sinch.sdk.models.VoiceRegion; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import org.junit.jupiter.api.Test; class SinchClientTest { @@ -278,4 +282,59 @@ void doubleCloseBeforeAnyCall() { client.close(); assertDoesNotThrow(client::close); } + + /** + * Verifies that a proxy configuration set on {@link Configuration} is preserved in the {@link + * SinchClient}'s internal configuration after construction. + */ + @Test + void proxyConfigurationPreservedInConfiguration() { + HttpProxyConfiguration proxy = + HttpProxyConfiguration.builder() + .setHostname("proxy.corp.example.com") + .setPort(3128) + .build(); + Configuration configuration = Configuration.builder().setHttpProxyConfiguration(proxy).build(); + SinchClient client = new SinchClient(configuration); + assertTrue( + client.getConfiguration().getHttpProxyConfiguration().isPresent(), + "Proxy configuration must be present in the SinchClient's stored configuration"); + assertEquals( + "proxy.corp.example.com", + client.getConfiguration().getHttpProxyConfiguration().get().getHostname(), + "Proxy hostname must survive SinchClient construction"); + assertEquals( + 3128, + client.getConfiguration().getHttpProxyConfiguration().get().getPort(), + "Proxy port must survive SinchClient construction"); + } + + @Test + void proxyConfigurationWiredIntoHttpClient() throws Exception { + HttpProxyConfiguration proxy = + HttpProxyConfiguration.builder() + .setHostname("proxy.corp.example.com") + .setPort(3128) + .build(); + Configuration configuration = Configuration.builder().setHttpProxyConfiguration(proxy).build(); + SinchClient sinchClient = new SinchClient(configuration); + + Method getHttpClient = SinchClient.class.getDeclaredMethod("getHttpClient"); + getHttpClient.setAccessible(true); + Object httpClientApache = getHttpClient.invoke(sinchClient); + assertInstanceOf(HttpClientApache.class, httpClientApache); + + Field clientField = HttpClientApache.class.getDeclaredField("client"); + clientField.setAccessible(true); + Object apacheClient = clientField.get(httpClientApache); + + Field routePlannerField = apacheClient.getClass().getDeclaredField("routePlanner"); + routePlannerField.setAccessible(true); + Object routePlanner = routePlannerField.get(apacheClient); + + assertInstanceOf( + org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner.class, + routePlanner, + "HttpClient must use a DefaultProxyRoutePlanner when proxy is configured"); + } } diff --git a/client/src/test/java/com/sinch/sdk/e2e/Config.java b/client/src/test/java/com/sinch/sdk/e2e/Config.java index badb5846a..ec636f6d4 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/Config.java +++ b/client/src/test/java/com/sinch/sdk/e2e/Config.java @@ -4,6 +4,7 @@ import com.sinch.sdk.models.Configuration; import com.sinch.sdk.models.ConversationContext; import com.sinch.sdk.models.ConversationRegion; +import com.sinch.sdk.models.HttpProxyConfiguration; import com.sinch.sdk.models.NumberLookupContext; import com.sinch.sdk.models.NumbersContext; import com.sinch.sdk.models.SMSRegion; @@ -37,8 +38,20 @@ public class Config { public static final String NUMBER_LOOKUP_HOST_NAME = "http://localhost:3022"; + public static final int PROXY_UNAUTHENTICATED_PORT = 3128; + public static final int PROXY_AUTHENTICATED_PORT = 3129; + public static final String PROXY_HOST = "localhost"; + public static final String PROXY_USERNAME = "user"; + public static final String PROXY_PASSWORD = "password"; + + public static final String PROXY_VISIBLE_AUTH_URL = + "http://authentication-server:1080/oauth2/token"; + public static final String PROXY_VISIBLE_NUMBER_LOOKUP_URL = "http://proxy-reachable-server:1080"; + private final SinchClient client; private final SinchClient clientServicePlanId; + private final SinchClient clientProxyUnauthenticated; + private final SinchClient clientProxyAuthenticated; private Config() { @@ -81,6 +94,24 @@ private Config() { .build(); clientServicePlanId = new SinchClient(configurationServicePlanId); + + clientProxyUnauthenticated = + new SinchClient( + createConfigurationWithProxyUsage( + HttpProxyConfiguration.builder() + .setHostname(PROXY_HOST) + .setPort(Config.PROXY_UNAUTHENTICATED_PORT) + .build())); + + clientProxyAuthenticated = + new SinchClient( + createConfigurationWithProxyUsage( + HttpProxyConfiguration.builder() + .setHostname(PROXY_HOST) + .setPort(Config.PROXY_AUTHENTICATED_PORT) + .setUsername(PROXY_USERNAME) + .setPassword(PROXY_PASSWORD) + .build())); } private static class LazyHolder { @@ -94,4 +125,26 @@ public static SinchClient getSinchClient() { public static SinchClient getSinchClientServicePlanId() { return LazyHolder.INSTANCE.clientServicePlanId; } + + public static SinchClient getSinchClientProxyUnauthenticated() { + return LazyHolder.INSTANCE.clientProxyUnauthenticated; + } + + public static SinchClient getSinchClientProxyAuthenticated() { + return LazyHolder.INSTANCE.clientProxyAuthenticated; + } + + private static Configuration createConfigurationWithProxyUsage(HttpProxyConfiguration proxy) { + return Configuration.builder() + .setOAuthUrl(PROXY_VISIBLE_AUTH_URL) + .setProjectId(Config.PROJECT_ID) + .setKeyId(Config.KEY_ID) + .setKeySecret(Config.KEY_SECRET) + .setNumberLookupContext( + NumberLookupContext.builder() + .setNumberLookupUrl(PROXY_VISIBLE_NUMBER_LOOKUP_URL) + .build()) + .setHttpProxyConfiguration(proxy) + .build(); + } } diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/proxy/HttpClientApacheProxyIT.java b/client/src/test/java/com/sinch/sdk/e2e/domains/proxy/HttpClientApacheProxyIT.java new file mode 100644 index 000000000..3064112ed --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/proxy/HttpClientApacheProxyIT.java @@ -0,0 +1,77 @@ +package com.sinch.sdk.e2e.domains.proxy; + +import static org.junit.jupiter.api.Assertions.*; + +import com.sinch.sdk.core.http.HttpMethod; +import com.sinch.sdk.core.http.HttpRequest; +import com.sinch.sdk.core.http.HttpResponse; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.e2e.Config; +import com.sinch.sdk.http.HttpClientApache; +import com.sinch.sdk.models.HttpProxyConfiguration; +import org.junit.jupiter.api.Test; + +class HttpClientApacheProxyIT { + + private static final String PROXY_HOST = "localhost"; + private static final String TARGET_URL = "http://proxy-reachable-server:1080/"; + + private static HttpRequest simpleGetRequest() { + return new HttpRequest("health", HttpMethod.GET, null, (String) null, null, null, null, null); + } + + @Test + void unauthenticatedProxyRoutesTraffic() throws Exception { + HttpProxyConfiguration proxyConfig = + HttpProxyConfiguration.builder() + .setHostname(PROXY_HOST) + .setPort(Config.PROXY_UNAUTHENTICATED_PORT) + .build(); + + try (HttpClientApache client = new HttpClientApache(proxyConfig)) { + HttpResponse response = + client.invokeAPI(new ServerConfiguration(TARGET_URL), null, simpleGetRequest()); + + assertEquals(200, response.getCode(), "Request through unauthenticated proxy must succeed"); + } + } + + @Test + void authenticatedProxyRoutesTraffic() throws Exception { + HttpProxyConfiguration proxyConfig = + HttpProxyConfiguration.builder() + .setHostname(PROXY_HOST) + .setPort(Config.PROXY_AUTHENTICATED_PORT) + .setUsername("user") + .setPassword("password") + .build(); + + try (HttpClientApache client = new HttpClientApache(proxyConfig)) { + HttpResponse response = + client.invokeAPI(new ServerConfiguration(TARGET_URL), null, simpleGetRequest()); + + assertEquals(200, response.getCode(), "Request through authenticated proxy must succeed"); + } + } + + @Test + void authenticatedProxyRejectsWrongCredentials() throws Exception { + HttpProxyConfiguration proxyConfig = + HttpProxyConfiguration.builder() + .setHostname(PROXY_HOST) + .setPort(Config.PROXY_AUTHENTICATED_PORT) + .setUsername("wrong-user") + .setPassword("wrong-pass") + .build(); + + try (HttpClientApache client = new HttpClientApache(proxyConfig)) { + HttpResponse response = + client.invokeAPI(new ServerConfiguration(TARGET_URL), null, simpleGetRequest()); + + assertEquals( + 407, + response.getCode(), + "Wrong credentials must result in 407 Proxy Authentication Required"); + } + } +} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/proxy/ProxyIT.java b/client/src/test/java/com/sinch/sdk/e2e/domains/proxy/ProxyIT.java new file mode 100644 index 000000000..80db3d35c --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/proxy/ProxyIT.java @@ -0,0 +1,18 @@ +package com.sinch.sdk.e2e.domains.proxy; + +import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; + +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.api.SuiteDisplayName; + +@Suite +@SuiteDisplayName("Proxy") +@IncludeEngines("cucumber") +@SelectClasspathResource("features/proxy") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.sinch.sdk.e2e.domains.proxy") +@ConfigurationParameter(key = PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, value = "true") +public class ProxyIT {} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/proxy/ProxySteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/proxy/ProxySteps.java new file mode 100644 index 000000000..d498e65ea --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/proxy/ProxySteps.java @@ -0,0 +1,69 @@ +package com.sinch.sdk.e2e.domains.proxy; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.domains.numberlookup.models.v2.request.NumberLookupRequest; +import com.sinch.sdk.domains.numberlookup.models.v2.response.Line; +import com.sinch.sdk.domains.numberlookup.models.v2.response.LineType; +import com.sinch.sdk.domains.numberlookup.models.v2.response.NumberLookupResponse; +import com.sinch.sdk.e2e.Config; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import org.junit.jupiter.api.Assertions; + +public class ProxySteps { + + NumberLookupResponse response; + + @Given("the proxy server is available") + public void proxyServerAvailable() { + Assertions.assertNotNull( + Config.getSinchClientProxyUnauthenticated(), "Unauthenticated proxy client"); + Assertions.assertNotNull( + Config.getSinchClientProxyAuthenticated(), "Authenticated proxy client"); + } + + @When("I send a Number Lookup request through the unauthenticated proxy") + public void lookupThroughUnauthenticatedProxy() { + response = lookup(Config.getSinchClientProxyUnauthenticated()); + } + + @When("I send a Number Lookup request through the authenticated proxy") + public void lookupThroughAuthenticatedProxy() { + response = lookup(Config.getSinchClientProxyAuthenticated()); + } + + @Then("the response is successfully returned through the unauthenticated proxy") + public void responseReturnedThroughUnauthenticatedProxy() { + assertResponse("a11a11a11a11a11a11a11a11a11unauth"); + } + + @Then("the response is successfully returned through the authenticated proxy") + public void responseReturnedThroughAuthenticatedProxy() { + assertResponse("b22b22b22b22b22b22b22b22b22b2auth"); + } + + private void assertResponse(String expectedTraceId) { + NumberLookupResponse expected = + NumberLookupResponse.builder() + .setNumber("+12016666666") + .setCountryCode("US") + .setTraceId(expectedTraceId) + .setLine( + Line.builder() + .setCarrier("T-Mobile USA") + .setType(LineType.MOBILE) + .setMobileCountryCode("310") + .setMobileNetworkCode("260") + .build()) + .build(); + + TestHelpers.recursiveEquals(response, expected); + } + + private static NumberLookupResponse lookup(SinchClient client) { + NumberLookupRequest request = NumberLookupRequest.builder().setNumber("+12016666666").build(); + return client.lookup().v2().lookup(request); + } +} diff --git a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheLifecycleTest.java b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheLifecycleTest.java index 2c637f5dd..68f21426b 100644 --- a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheLifecycleTest.java +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheLifecycleTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.*; +import com.sinch.sdk.models.HttpProxyConfiguration; import org.junit.jupiter.api.Test; class HttpClientApacheLifecycleTest { @@ -36,4 +37,36 @@ void closeViaAutoCloseable() { } }); } + + @Test + void proxyConstructorCreatesOpenInstance() throws Exception { + HttpProxyConfiguration proxy = + HttpProxyConfiguration.builder().setHostname("proxy.example.com").setPort(3128).build(); + try (HttpClientApache client = new HttpClientApache(proxy)) { + assertFalse(client.isClosed(), "HttpClientApache created with proxy config must be open"); + } + } + + @Test + void proxyConstructorWithNullBehavesAsNoArgConstructor() throws Exception { + try (HttpClientApache client = new HttpClientApache(null)) { + assertFalse(client.isClosed(), "HttpClientApache(null) must behave like no-arg constructor"); + } + } + + @Test + void proxyConstructorWithAuthCreatesOpenInstance() throws Exception { + HttpProxyConfiguration proxy = + HttpProxyConfiguration.builder() + .setHostname("proxy.example.com") + .setPort(3128) + .setUsername("user") + .setPassword("pass") + .build(); + try (HttpClientApache client = new HttpClientApache(proxy)) { + assertFalse( + client.isClosed(), + "HttpClientApache created with authenticated proxy config must be open"); + } + } } diff --git a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheProxyTest.java b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheProxyTest.java new file mode 100644 index 000000000..01e40bcc5 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheProxyTest.java @@ -0,0 +1,184 @@ +package com.sinch.sdk.http; + +import static org.junit.jupiter.api.Assertions.*; + +import com.sinch.sdk.core.http.HttpMethod; +import com.sinch.sdk.core.http.HttpRequest; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.models.HttpProxyConfiguration; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.concurrent.TimeUnit; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class HttpClientApacheProxyTest { + + MockWebServer mockProxy; + + @BeforeEach + void setUp() throws IOException { + mockProxy = new MockWebServer(); + mockProxy.start(); + } + + @AfterEach + void tearDown() throws IOException { + mockProxy.shutdown(); + } + + @Test + void unauthenticatedProxyRequestRoutedThroughProxy() throws Exception { + HttpProxyConfiguration proxySettings = + HttpProxyConfiguration.builder() + .setHostname("localhost") + .setPort(mockProxy.getPort()) + .build(); + + mockProxy.enqueue( + new MockResponse().setBody("{}").addHeader("Content-Type", "application/json")); + + try (HttpClientApache proxyClient = new HttpClientApache(proxySettings)) { + proxyClient.invokeAPI( + new ServerConfiguration("http://foo.com"), + null, + new HttpRequest( + "/api/path", HttpMethod.GET, null, (String) null, null, null, null, null)); + } + + RecordedRequest request = mockProxy.takeRequest(5, TimeUnit.SECONDS); + assertNotNull(request, "Proxy should have received the request"); + assertTrue( + request.getRequestLine().startsWith("GET http://foo.com/api/path"), + "Initial request should be a full URL as per RFC 7230 when sent to a proxy; actual: " + + request.getRequestLine()); + assertNotNull(request.getRequestUrl(), "Recorded request must have an URL"); + assertEquals( + "localhost", + request.getRequestUrl().host(), + "Proxy request should be sent to the proxy host"); + assertEquals( + mockProxy.getPort(), + request.getRequestUrl().port(), + "Proxy request should be sent to the proxy port"); + assertEquals( + "/", request.getRequestUrl().encodedPath(), "Initial request should have the proxy path"); + + RecordedRequest noMoreRequest = mockProxy.takeRequest(5, TimeUnit.SECONDS); + assertNull(noMoreRequest, "Proxy should not receive more than 1 request for the same call"); + } + + @Test + void authenticatedProxyCredentialsSentAfterChallenge() throws Exception { + HttpProxyConfiguration proxySettings = + HttpProxyConfiguration.builder() + .setHostname("localhost") + .setPort(mockProxy.getPort()) + .setUsername("proxy-user") + .setPassword("proxy-pass") + .build(); + + mockProxy.enqueue( + new MockResponse() + .setResponseCode(407) + .addHeader("Proxy-Authenticate", "Basic realm=\"proxy\"")); + mockProxy.enqueue( + new MockResponse().setBody("{}").addHeader("Content-Type", "application/json")); + + try (HttpClientApache proxyClient = new HttpClientApache(proxySettings)) { + proxyClient.invokeAPI( + new ServerConfiguration("http://foo.com"), + null, + new HttpRequest( + "/api/path", HttpMethod.GET, null, (String) null, null, null, null, null)); + } + + RecordedRequest initial = mockProxy.takeRequest(5, TimeUnit.SECONDS); + assertNotNull(initial, "Proxy should have received the request"); + assertTrue( + initial.getRequestLine().startsWith("GET http://foo.com/api/path"), + "Initial request should be a full URL as per RFC 7230 when sent to a proxy; actual: " + + initial.getRequestLine()); + assertNotNull(initial.getRequestUrl(), "Recorded request must have an URL"); + assertEquals( + "localhost", + initial.getRequestUrl().host(), + "Proxy request should be sent to the proxy host"); + assertEquals( + mockProxy.getPort(), + initial.getRequestUrl().port(), + "Proxy request should be sent to the proxy port"); + assertEquals( + "/", initial.getRequestUrl().encodedPath(), "Initial request should have the proxy path"); + + RecordedRequest retry = mockProxy.takeRequest(5, TimeUnit.SECONDS); + assertNotNull(retry, "Proxy should receive the retry request after 407 challenge"); + + String proxyAuthHeader = retry.getHeader("Proxy-Authorization"); + assertNotNull(proxyAuthHeader, "Proxy-Authorization must be present on the retry"); + assertTrue( + proxyAuthHeader.startsWith("Basic "), + "Proxy-Authorization must use the Basic scheme; actual: " + proxyAuthHeader); + String decoded = + new String( + Base64.getDecoder().decode(proxyAuthHeader.substring("Basic ".length())), + StandardCharsets.UTF_8); + assertEquals( + "proxy-user:proxy-pass", + decoded, + "Proxy-Authorization must encode the exact configured credentials"); + + RecordedRequest noMoreRequest = mockProxy.takeRequest(5, TimeUnit.SECONDS); + assertNull( + noMoreRequest, + "Proxy should not receive more than 2 requests for the same call (initial + retry)"); + } + + @Test + void authenticatedProxyNoChallengeSucceedsDirectly() throws Exception { + HttpProxyConfiguration proxySettings = + HttpProxyConfiguration.builder() + .setHostname("localhost") + .setPort(mockProxy.getPort()) + .setUsername("user") + .setPassword("pass") + .build(); + + mockProxy.enqueue( + new MockResponse().setBody("{}").addHeader("Content-Type", "application/json")); + + try (HttpClientApache proxyClient = new HttpClientApache(proxySettings)) { + proxyClient.invokeAPI( + new ServerConfiguration("http://foo.com"), + null, + new HttpRequest( + "/api/path", HttpMethod.GET, null, (String) null, null, null, null, null)); + } + + RecordedRequest request = mockProxy.takeRequest(5, TimeUnit.SECONDS); + assertNotNull(request, "Proxy should have received the request"); + assertTrue( + request.getRequestLine().startsWith("GET http://foo.com/api/path"), + "Initial request should be a full URL as per RFC 7230 when sent to a proxy; actual: " + + request.getRequestLine()); + assertNotNull(request.getRequestUrl(), "Recorded request must have an URL"); + assertEquals( + "localhost", + request.getRequestUrl().host(), + "Proxy request should be sent to the proxy host"); + assertEquals( + mockProxy.getPort(), + request.getRequestUrl().port(), + "Proxy request should be sent to the proxy port"); + assertEquals( + "/", request.getRequestUrl().encodedPath(), "Initial request should have the proxy path"); + + RecordedRequest noMoreRequest = mockProxy.takeRequest(5, TimeUnit.SECONDS); + assertNull(noMoreRequest, "Proxy should not receive more than 1 request for the same call"); + } +} diff --git a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTest.java b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTest.java index 329580abd..f2835cbac 100644 --- a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTest.java +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTest.java @@ -53,19 +53,18 @@ void testInvokeApiHandles401WithEmptyHeaders() throws Exception { doReturn(unauthorizedResponse).when(client).processRequest(any(), any()); // Mock ServerConfiguration and HttpRequest - ServerConfiguration serverConfig = mock(ServerConfiguration.class); - when(serverConfig.getUrl()).thenReturn("https://api.example.com"); - - HttpRequest request = mock(HttpRequest.class); - when(request.getFullUrl()).thenReturn(Optional.of("https://api.example.com/v1/test")); - when(request.getMethod()).thenReturn(HttpMethod.GET); - when(request.getQueryParameters()).thenReturn(Collections.emptyList()); - when(request.getBody()).thenReturn(null); - when(request.getFormParams()).thenReturn(Collections.emptyMap()); - when(request.getHeaderParams()).thenReturn(Collections.emptyMap()); - when(request.getAccept()).thenReturn(Collections.emptyList()); - when(request.getContentType()).thenReturn(Collections.emptyList()); - when(request.getAuthNames()).thenReturn(Collections.singletonList("Bearer")); + ServerConfiguration serverConfig = new ServerConfiguration("https://api.example.com"); + + HttpRequest request = + new HttpRequest( + "https://api.example.com/v1/test", + HttpMethod.GET, + Collections.emptyList(), + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyList(), + Collections.emptyList(), + Collections.singletonList("Bearer")); // Mock AuthManagers map (bearer) Map authManagers = new HashMap<>(); @@ -110,4 +109,62 @@ void processResponseDecodesBodyAsUtf8() throws Exception { assertEquals(nonAscii, decoded, "Response body must round-trip through UTF-8 correctly"); } } + + /** + * Verifies that a 407 Proxy Authentication Required response returned by {@code processRequest} + * (i.e. Apache's internal 407 handling failed) is returned immediately to the caller without + * triggering the OAuth token-refresh logic. + * + *

Some enterprise proxies include a {@code www-authenticate: Bearer error="expired"} header on + * 407 responses. Without the explicit 407 guard in {@link + * HttpClientApache#invokeAPI(com.sinch.sdk.core.models.ServerConfiguration, Map, + * com.sinch.sdk.core.http.HttpRequest)}, that header would cause the OAuthManager to reset its + * token and retry the request — incorrectly treating a proxy auth failure as an expired API + * token. + */ + @Test + void testInvokeApi407DoesNotTriggerOAuthRefresh() throws Exception { + // GIVEN: a 407 response that also carries www-authenticate: Bearer error="expired" + // (non-standard but seen on some corporate proxies) + Map> proxyHeaders = new HashMap<>(); + proxyHeaders.put( + OAuthManager.BEARER_AUTHENTICATE_RESPONSE_HEADER_KEYWORD, + Collections.singletonList("Bearer realm=\"proxy\", error=\"expired\"")); + + HttpResponse proxyAuthResponse = + new HttpResponse(407, "Proxy Authentication Required", proxyHeaders, null); + + doReturn(proxyAuthResponse).when(client).processRequest(any(), any()); + + ServerConfiguration serverConfig = new ServerConfiguration("https://api.example.com"); + + HttpRequest request = + new HttpRequest( + "https://api.example.com/v1/test", + HttpMethod.GET, + Collections.emptyList(), + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyList(), + Collections.emptyList(), + Collections.singletonList("Bearer")); + + Map authManagers = new HashMap<>(); + authManagers.put(OAuthManager.SCHEMA_KEYWORD_BEARER, mockAuthManager); + when(mockAuthManager.getSchema()).thenReturn(OAuthManager.SCHEMA_KEYWORD_BEARER); + when(mockAuthManager.getAuthorizationHeaders(any(), any(), any(), any())) + .thenReturn(Collections.emptyList()); + + // WHEN: invokeAPI is called + HttpResponse response = client.invokeAPI(serverConfig, authManagers, request); + + // THEN: the 407 is returned as-is + assertEquals(407, response.getCode()); + + // AND: OAuth token reset must NOT be triggered by the proxy 407 + verify(mockAuthManager, never()).resetToken(); + + // AND: processRequest is called exactly once — no OAuth retry + verify(client, times(1)).processRequest(any(), any()); + } } diff --git a/client/src/test/java/com/sinch/sdk/models/ConfigurationBuilderTest.java b/client/src/test/java/com/sinch/sdk/models/ConfigurationBuilderTest.java index 772f49812..69c116765 100644 --- a/client/src/test/java/com/sinch/sdk/models/ConfigurationBuilderTest.java +++ b/client/src/test/java/com/sinch/sdk/models/ConfigurationBuilderTest.java @@ -14,6 +14,10 @@ class ConfigurationBuilderTest { static final ConversationRegion CONVERSATION_REGION = ConversationRegion.BR; static final String CONVERSATION_SERVER = "%sfooCONVERSATION_SERVER"; static final String CONVERSATION_TEMPLATE_SERVER = "%sfooCONVERSATION_TEMPLATE_SERVER"; + static final String PROXY_HOST = "proxy.corp.example.com"; + static final int PROXY_PORT = 3128; + static final String PROXY_USER = "proxyUser"; + static final String PROXY_PASS = "proxyPass"; @Test void build() { @@ -32,6 +36,13 @@ void build() { .setUrl(CONVERSATION_SERVER) .setTemplateManagementUrl(CONVERSATION_TEMPLATE_SERVER) .build()) + .setHttpProxyConfiguration( + HttpProxyConfiguration.builder() + .setHostname(PROXY_HOST) + .setPort(PROXY_PORT) + .setUsername(PROXY_USER) + .setPassword(PROXY_PASS) + .build()) .build(); Assertions.assertEquals(OAUTH_URL, builder.getOAuthServer().getUrl()); Assertions.assertEquals( @@ -67,5 +78,57 @@ void build() { .getUrl() .contains("fooCONVERSATION_TEMPLATE_SERVER"), "Conversation template server present within conversation template server URL"); + + Assertions.assertTrue( + builder.getHttpProxyConfiguration().isPresent(), "Proxy configuration should be present"); + Assertions.assertEquals( + PROXY_HOST, builder.getHttpProxyConfiguration().get().getHostname(), "Proxy hostname"); + Assertions.assertEquals( + PROXY_PORT, builder.getHttpProxyConfiguration().get().getPort(), "Proxy port"); + Assertions.assertEquals( + PROXY_USER, + builder.getHttpProxyConfiguration().get().getUsername().orElse(null), + "Proxy username"); + Assertions.assertArrayEquals( + PROXY_PASS.toCharArray(), + builder.getHttpProxyConfiguration().get().getPassword().orElse(null), + "Proxy password"); + } + + @Test + void buildWithoutProxy() { + Configuration config = + new Configuration.Builder() + .setKeyId(KEY) + .setKeySecret(SECRET) + .setProjectId(PROJECT) + .setOAuthUrl(OAUTH_URL) + .build(); + Assertions.assertFalse( + config.getHttpProxyConfiguration().isPresent(), + "Proxy configuration should be absent when not configured"); + } + + @Test + void builderCopyPreservesProxyConfiguration() { + HttpProxyConfiguration proxy = + HttpProxyConfiguration.builder().setHostname(PROXY_HOST).setPort(PROXY_PORT).build(); + Configuration original = + new Configuration.Builder() + .setKeyId(KEY) + .setKeySecret(SECRET) + .setProjectId(PROJECT) + .setOAuthUrl(OAUTH_URL) + .setHttpProxyConfiguration(proxy) + .build(); + + Configuration copy = Configuration.builder(original).build(); + Assertions.assertTrue(copy.getHttpProxyConfiguration().isPresent()); + Assertions.assertEquals( + PROXY_HOST, + copy.getHttpProxyConfiguration().get().getHostname(), + "Hostname preserved in copy"); + Assertions.assertEquals( + PROXY_PORT, copy.getHttpProxyConfiguration().get().getPort(), "Port preserved in copy"); } } diff --git a/client/src/test/java/com/sinch/sdk/models/HttpProxyConfigurationTest.java b/client/src/test/java/com/sinch/sdk/models/HttpProxyConfigurationTest.java new file mode 100644 index 000000000..1d17f5409 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/models/HttpProxyConfigurationTest.java @@ -0,0 +1,204 @@ +package com.sinch.sdk.models; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class HttpProxyConfigurationTest { + + static final String HOST = "proxy.corp.example.com"; + static final int PORT = 3128; + static final String USERNAME = "proxyUser"; + static final String PASSWORD = "proxyPass"; + + static final HttpProxyConfiguration fullSettings = + HttpProxyConfiguration.builder() + .setHostname(HOST) + .setPort(PORT) + .setUsername(USERNAME) + .setPassword(PASSWORD) + .build(); + + static final HttpProxyConfiguration minimalSettings = + HttpProxyConfiguration.builder().setHostname(HOST).setPort(PORT).build(); + + @Test + void getHostname() { + assertEquals(HOST, fullSettings.getHostname()); + } + + @Test + void getPort() { + assertEquals(PORT, fullSettings.getPort()); + } + + @Test + void getUsernameWhenConfigured() { + assertTrue(fullSettings.getUsername().isPresent()); + assertEquals(USERNAME, fullSettings.getUsername().get()); + } + + @Test + void getUsernameAbsentWhenNotConfigured() { + assertFalse(minimalSettings.getUsername().isPresent()); + } + + @Test + void getPasswordWhenConfigured() { + assertTrue(fullSettings.getPassword().isPresent()); + assertArrayEquals(PASSWORD.toCharArray(), fullSettings.getPassword().get()); + } + + @Test + void getPasswordAbsentWhenNotConfigured() { + assertFalse(minimalSettings.getPassword().isPresent()); + } + + @Test + void setPasswordCharArrayOverload() { + char[] pwd = PASSWORD.toCharArray(); + HttpProxyConfiguration config = + HttpProxyConfiguration.builder() + .setHostname(HOST) + .setPort(PORT) + .setUsername(USERNAME) + .setPassword(pwd) + .build(); + assertTrue(config.getPassword().isPresent()); + assertArrayEquals(PASSWORD.toCharArray(), config.getPassword().get()); + } + + @Test + void getPasswordReturnsDefensiveCopy() { + char[] first = fullSettings.getPassword().get(); + first[0] = 'X'; + char[] second = fullSettings.getPassword().get(); + assertEquals( + PASSWORD.charAt(0), + second[0], + "Mutating the returned array must not affect internal state"); + } + + @Test + void setPasswordCharArrayInputMutationDoesNotAffectConfig() { + char[] pwd = PASSWORD.toCharArray(); + HttpProxyConfiguration config = + HttpProxyConfiguration.builder() + .setHostname(HOST) + .setPort(PORT) + .setUsername(USERNAME) + .setPassword(pwd) + .build(); + pwd[0] = 'X'; + assertArrayEquals( + PASSWORD.toCharArray(), + config.getPassword().get(), + "Mutating the original char[] after build must not affect stored password"); + } + + @Test + void toStringSensitiveDataMasked() { + String value = fullSettings.toString(); + assertFalse(value.contains(USERNAME), "username must not appear in toString output"); + assertFalse(value.contains(PASSWORD), "password must not appear in toString output"); + assertTrue(value.contains(HOST), "hostname should be visible in toString output"); + } + + @Test + void builderCopyPreservesAllFields() { + HttpProxyConfiguration copy = HttpProxyConfiguration.builder(fullSettings).build(); + assertEquals(fullSettings.getHostname(), copy.getHostname()); + assertEquals(fullSettings.getPort(), copy.getPort()); + assertEquals(fullSettings.getUsername(), copy.getUsername()); + assertArrayEquals(fullSettings.getPassword().orElse(null), copy.getPassword().orElse(null)); + } + + @Test + void buildNullHostnameThrows() { + assertThrows( + IllegalArgumentException.class, + () -> HttpProxyConfiguration.builder().setPort(PORT).build()); + } + + @Test + void buildEmptyHostnameThrows() { + assertThrows( + IllegalArgumentException.class, + () -> HttpProxyConfiguration.builder().setHostname("").setPort(PORT).build()); + } + + @Test + void buildPortZeroThrows() { + assertThrows( + IllegalArgumentException.class, + () -> HttpProxyConfiguration.builder().setHostname(HOST).setPort(0).build()); + } + + @Test + void buildPortNegativeThrows() { + assertThrows( + IllegalArgumentException.class, + () -> HttpProxyConfiguration.builder().setHostname(HOST).setPort(-1).build()); + } + + @Test + void buildPortAboveMaxThrows() { + assertThrows( + IllegalArgumentException.class, + () -> HttpProxyConfiguration.builder().setHostname(HOST).setPort(65536).build()); + } + + @Test + void buildPortAtBoundaryAccepted() { + assertDoesNotThrow(() -> HttpProxyConfiguration.builder().setHostname(HOST).setPort(1).build()); + assertDoesNotThrow( + () -> HttpProxyConfiguration.builder().setHostname(HOST).setPort(65535).build()); + } + + @Test + void buildPasswordWithoutUsernameThrows() { + assertThrows( + IllegalArgumentException.class, + () -> + HttpProxyConfiguration.builder() + .setHostname(HOST) + .setPort(PORT) + .setPassword(PASSWORD) + .build()); + } + + @Test + void buildUsernameWithoutPasswordThrows() { + assertThrows( + IllegalArgumentException.class, + () -> + HttpProxyConfiguration.builder() + .setHostname(HOST) + .setPort(PORT) + .setUsername(USERNAME) + .build()); + } + + @Test + void buildHostnameWithWhitespaceOnlyThrows() { + assertThrows( + IllegalArgumentException.class, + () -> HttpProxyConfiguration.builder().setHostname(" ").setPort(PORT).build()); + } + + @Test + void buildHostnameWithLeadingTrailingSpacesIsTrimmed() { + HttpProxyConfiguration config = + HttpProxyConfiguration.builder().setHostname(" " + HOST + " ").setPort(PORT).build(); + assertEquals(HOST, config.getHostname(), "Hostname should be trimmed"); + } + + @Test + void builderCopyAllowsOverride() { + HttpProxyConfiguration modified = + HttpProxyConfiguration.builder(fullSettings).setHostname("other-proxy.example.com").build(); + assertEquals("other-proxy.example.com", modified.getHostname()); + assertEquals(PORT, modified.getPort()); + assertEquals(fullSettings.getUsername(), modified.getUsername()); + } +} diff --git a/pom.xml b/pom.xml index 0d3e83e8a..84273c1ca 100644 --- a/pom.xml +++ b/pom.xml @@ -297,6 +297,8 @@ com.sinch.sdk.e2e.domains.voice.v1.VoiceIT com.sinch.sdk.e2e.domains.verification.v1.VerificationIT com.sinch.sdk.e2e.domains.numberlookup.v2.NumberLookupIT + com.sinch.sdk.e2e.domains.proxy.ProxyIT + com.sinch.sdk.e2e.domains.proxy.HttpClientApacheProxyIT From cb538a5cbff5ad62dd39f5e2f232281f926008a3 Mon Sep 17 00:00:00 2001 From: Eduardo San Segundo Date: Thu, 2 Jul 2026 09:14:29 +0200 Subject: [PATCH 31/37] Added Number Lookup to CHANGELOG for Java release v2.1 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a72b52967..11fa76aab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ All notable changes to the **Sinch Java SDK** are documented in this file. ### Conversation - **[feature]** Support `Consents` API: `listIdentities` and `listAuditRecords` endpoints +### Number Lookup +- **[feature]** Support `Number Lookup` API V2 + ### SDK - **[feature]** HTTP proxy support: configure an unauthenticated or authenticated (Basic) proxy via `HttpProxyConfiguration` - **[feature]** `SinchClient` exposes a `close()` method to shut down the underlying HTTP connection pool and release all associated resources deterministically From c009bba1dbd6aa704587c61ec88ec8b88dfac5b4 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier Date: Thu, 2 Jul 2026 16:01:16 +0200 Subject: [PATCH 32/37] chore: Bump sonatype plugin version to '0.11.0' --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 84273c1ca..3f472621f 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ 3.6.0 3.0.1 1.6 - 0.7.0 + 0.11.0 From b8d58bc3cd6ca137f3a0bb1aa9dbad32421b683d Mon Sep 17 00:00:00 2001 From: git Date: Thu, 2 Jul 2026 14:11:31 +0000 Subject: [PATCH 33/37] build (release): Bump version to 2.1.0 for sources --- client/src/main/com/sinch/sdk/SDK.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/com/sinch/sdk/SDK.java b/client/src/main/com/sinch/sdk/SDK.java index 98d6ab17c..333f1b97d 100644 --- a/client/src/main/com/sinch/sdk/SDK.java +++ b/client/src/main/com/sinch/sdk/SDK.java @@ -3,6 +3,6 @@ public class SDK { public static final String NAME = "Sinch Java SDK"; - public static final String VERSION = "2.1.0-dev"; + public static final String VERSION = "2.1.0"; public static final String AUXILIARY_FLAG = ""; } From 9693456d5dd61fd7bb70bd3f609fc0912e209a81 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 2 Jul 2026 14:13:52 +0000 Subject: [PATCH 34/37] [release] Set release & tag: 2.1.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3f472621f..285388f7f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.sinch.sdk sinch-sdk-java - 2.1.0-SNAPSHOT + 2.1.0 Sinch Java SDK @@ -37,7 +37,7 @@ https://github.com/sinch/sinch-sdk-java.git scm:git:${project.scm.url} scm:git:${project.scm.url} - HEAD + v2.1.0 From cf32bc905fa1e51bf54de199dc1c75748596f770 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 2 Jul 2026 14:13:54 +0000 Subject: [PATCH 35/37] [release] Set next version: 2.2.0-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 285388f7f..44e1f524e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.sinch.sdk sinch-sdk-java - 2.1.0 + 2.2.0-SNAPSHOT Sinch Java SDK @@ -37,7 +37,7 @@ https://github.com/sinch/sinch-sdk-java.git scm:git:${project.scm.url} scm:git:${project.scm.url} - v2.1.0 + HEAD From ebe3e056f0913d6a77daec3e90b178698076aa2f Mon Sep 17 00:00:00 2001 From: git Date: Thu, 2 Jul 2026 14:15:56 +0000 Subject: [PATCH 36/37] build (release): Set next version to 2.2.0-dev for sources --- client/src/main/com/sinch/sdk/SDK.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/com/sinch/sdk/SDK.java b/client/src/main/com/sinch/sdk/SDK.java index 333f1b97d..d01900177 100644 --- a/client/src/main/com/sinch/sdk/SDK.java +++ b/client/src/main/com/sinch/sdk/SDK.java @@ -3,6 +3,6 @@ public class SDK { public static final String NAME = "Sinch Java SDK"; - public static final String VERSION = "2.1.0"; + public static final String VERSION = "2.2.0-dev"; public static final String AUXILIARY_FLAG = ""; } From 8b8fc8fe781475970cdab6c34e3b75a28ebee10e Mon Sep 17 00:00:00 2001 From: Jean-Pierre Portier <141755467+JPPortier@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:32:26 +0200 Subject: [PATCH 37/37] Update CHANGELOG for v2.1 release Update version to 2.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11fa76aab..4bd0d8b31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ All notable changes to the **Sinch Java SDK** are documented in this file. --- -## v2.1 – unreleased +## v2.1 – 2026-07-02 ### Numbers - Extend `NumberSinchEvents` class.