diff --git a/CHANGELOG.md b/CHANGELOG.md index a735ee66e..064399fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,32 @@ All notable changes to the **Sinch Java SDK** are documented in this file. > - `[tech]` — technical improvement --- +## v2.3.0 - 2026-10-15 + +### Voice +- **[feature]** Support [Voice API v2](https://developers.sinch.com/docs/voice-2.0), exposed under `voice().v2()` +- **[feature]** Support V2 `Calls` API: `start`, `get`, `list`, `interactByCallId` and `interactByCallName` endpoints +- **[feature]** Support V2 `Batches` API: `start`, `get`, `getDetails` and `stop` endpoints +- **[feature]** Support V2 `Sessions` API: `get` endpoint +- **[feature]** Support V2 `Services` API: `create`, `get`, `list`, `update` and `delete` endpoints +- **[feature]** Support V2 `SVAML` API: `describe` and `validate` endpoints +- **[feature]** Support V2 Sinch Events: `validateAuthenticationHeader`, `parseEvent` and `serializeResponse` +- **[doc]** Snippets for every Voice V2 endpoint, under `voice/v2/` +- **[doc]** `sinch-events` example: new Voice V2 Sinch Events controller on `/VoiceV2Event` +- **[test]** Extend V1 `SinchEventsService.validateAuthenticationHeader` coverage + +### SDK +- **[feature]** `SinchClient` transport level settings: `TransportSettings` (`timeoutSeconds`, `useSinchAuth`) +- **[feature]** `Idempotency-Key` support: for the operations that accept the header, the SDK generates a key when none is set and sends the same key on the `HTTP 429` retries of a call +- **[test]** Extend `ApplicationAuthManager` signed request validation coverage + +### Verification +- **[test]** Extend `SinchEventsService.validateAuthenticationHeader` coverage + +### Build +- **[dependency]** Bump `jackson.version` to `2.21.7` to solve denial-of-service and unsafe deserialization vulnerabilities in `jackson-databind` (CVE-2026-19032, CVE-2026-68497, CVE-2026-83557, CVE-2026-91776 and CVE-2026-91777). +- **[releasing]** Bump `central-publishing-maven-plugin` to `0.12.0`: Maven Central rejected the release bundle built with Apache Maven 3.10.0 + ## v2.2.0 - 2026-09-17 ### SDK diff --git a/README.md b/README.md index f32bb8132..2397f8ffe 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ To use Sinch services, you'll need a Sinch account and access keys. You can sign - [Handling Exceptions](#handling-exceptions) - [Proxy configuration](#proxy-configuration) - [Retry configuration](#retry-configuration) +- [Transport settings](#transport-settings) - [Third-party dependencies](#third-party-dependencies) - [Examples](#examples) - [Changelog and Migration](#changelog--migration) @@ -107,6 +108,7 @@ Note: The `${sdk.version}` needs to be set according to the released version to | 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/) | +| | [Voice API v2](https://developers.sinch.com/docs/voice-2.0) | | 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-api-v2/) | @@ -316,7 +318,17 @@ You can find a complete example in [examples/sinch-events](https://github.com/si ### 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: +**Note:** For all new projects, please use Voice API v2. This is especially important for projects involving: + +- AI capabilities like Streams and Voice Relay +- Voice campaigns like Batch calling + +You should only use the older Voice API v1 if one of the following is a strict requirement: + +- The project must be in General Availability (GA). +- The project requires In-App voice or Conferencing (until these features are available in v2). + +The Voice API v1 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; @@ -335,7 +347,7 @@ 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`: +The Voice API v1 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; @@ -351,6 +363,12 @@ Some events (for example an incoming call) expect a SVAML response: build it fro You can find a complete example in [examples/sinch-events](https://github.com/sinch/sinch-sdk-java/tree/main/examples/sinch-events). +#### Voice API v2 + +> **Note:** Support for the Voice API v2 is currently in preview. + +The Voice API v2 needs no extra parameters, use the [common client](#client-initialization) shown above. + ### Your first request @@ -542,6 +560,46 @@ Configuration configuration = Configuration.builder() SinchClient client = new SinchClient(configuration); ``` +## Transport settings + +`SinchClient` accepts optional transport-level settings alongside credentials. They apply to product API calls and to OAuth token fetches. + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `timeoutSeconds` | `int` | `60` | HTTP I/O timeout in seconds. Pass `0` to disable the timeout. Negative values are rejected. | +| `useSinchAuth` | `boolean` | `true` | When `true`, OAuth-capable APIs fetch a Sinch access token. Set `false` to disable Sinch authentication: no token is fetched and no `Authorization` header is added. With unified credentials only `projectId` is then required. Has no effect with SMS service plan credentials. | + +### Timeout + +```java +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.models.Configuration; +import com.sinch.sdk.models.TransportSettings; + +... +Configuration configuration = Configuration.builder() + ... + .setTransportSettings( + TransportSettings.builder() + .setTimeoutSeconds(30) + .build()) + .build(); +SinchClient client = new SinchClient(configuration); +``` + +### Disable Sinch OAuth + +```java +Configuration configuration = Configuration.builder() + ... + .setTransportSettings( + TransportSettings.builder() + .setUseSinchAuth(false) + .build()) + .build(); +SinchClient client = new SinchClient(configuration); +``` + ## Third-party dependencies The SDK relies on the following third-party dependencies: - [Jackson](https://github.com/FasterXML/jackson-jakarta-rs-providers): provides JSON serialization/deserialization functionality. diff --git a/client/resources/config-default.properties b/client/resources/config-default.properties index 102be6245..233852680 100644 --- a/client/resources/config-default.properties +++ b/client/resources/config-default.properties @@ -18,6 +18,14 @@ voice-server-south-america=https://calling-sae1.api.sinch.com voice-server-south-east-asia1=https://calling-apse1.api.sinch.com voice-server-south-east-asia2=https://calling-apse2.api.sinch.com +voice-v2-region=global +voice-v2-server-global=https://voice.api.sinch.com +voice-v2-server-us1=https://us1.voice.api.sinch.com +voice-v2-server-br1=https://br1.voice.api.sinch.com +voice-v2-server-eu1=https://eu1.voice.api.sinch.com +voice-v2-server-sg1=https://sg1.voice.api.sinch.com +voice-v2-server-au1=https://au1.voice.api.sinch.com + conversation-region= conversation-server=https://%s.conversation.api.sinch.com template-management-conversation-server=https://%s.template.api.sinch.com diff --git a/client/src/main/com/sinch/sdk/SDK.java b/client/src/main/com/sinch/sdk/SDK.java index 68682caf9..88ad4d526 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.3.0-dev"; + public static final String VERSION = "2.4.0-dev"; public static final String AUXILIARY_FLAG = ""; } diff --git a/client/src/main/com/sinch/sdk/SinchClient.java b/client/src/main/com/sinch/sdk/SinchClient.java index a4e392f5c..f0a0bfe3c 100644 --- a/client/src/main/com/sinch/sdk/SinchClient.java +++ b/client/src/main/com/sinch/sdk/SinchClient.java @@ -17,6 +17,7 @@ import com.sinch.sdk.models.NumbersContext; import com.sinch.sdk.models.SMSRegion; import com.sinch.sdk.models.SmsContext; +import com.sinch.sdk.models.TransportSettings; import com.sinch.sdk.models.VerificationContext; import com.sinch.sdk.models.VoiceContext; import com.sinch.sdk.models.VoiceRegion; @@ -46,6 +47,7 @@ public class SinchClient { private static final String VOICE_REGION_KEY = "voice-region"; private static final String VOICE_APPLICATION_MANAGEMENT_SERVER_KEY = "voice-application-management-server"; + private static final String VOICE_V2_REGION_KEY = "voice-v2-region"; private static final String VERIFICATION_SERVER_KEY = "verification-server"; @@ -94,6 +96,7 @@ public SinchClient(Configuration configuration) { handleDefaultSmsSettings(configurationGuard, props, builder); handleDefaultVerificationSettings(configurationGuard, props, builder); handleDefaultVoiceSettings(configurationGuard, props, builder); + handleDefaultVoiceV2Settings(configurationGuard, props, builder); handleDefaultConversationSettings(configurationGuard, props, builder); handleDefaultNumberLookupSettings(configurationGuard, props, builder); @@ -210,6 +213,45 @@ private void handleDefaultVoiceSettings( } } + private void handleDefaultVoiceV2Settings( + Configuration configuration, Properties props, Configuration.Builder builder) { + + com.sinch.sdk.domains.voice.models.v2.VoiceRegion region = + configuration + .getVoiceV2Context() + .map(com.sinch.sdk.domains.voice.models.v2.VoiceContext::getVoiceRegion) + .orElse(null); + + String voiceUrl = + configuration + .getVoiceV2Context() + .map(com.sinch.sdk.domains.voice.models.v2.VoiceContext::getVoiceUrl) + .orElse(null); + + // default region to be used ? + if (null == region && props.containsKey(VOICE_V2_REGION_KEY)) { + region = + com.sinch.sdk.domains.voice.models.v2.VoiceRegion.from( + props.getProperty(VOICE_V2_REGION_KEY)); + } + + // server is not defined: use the region to set to an existing one and use "global" as a default + // fallback + if (StringUtil.isEmpty(voiceUrl)) { + com.sinch.sdk.domains.voice.models.v2.VoiceRegion regionForFormat = + null == region ? com.sinch.sdk.domains.voice.models.v2.VoiceRegion.GLOBAL : region; + voiceUrl = props.getProperty(String.format("voice-v2-server-%s", regionForFormat.value())); + } + + if (null != region || null != voiceUrl) { + builder.setVoiceV2Context( + com.sinch.sdk.domains.voice.models.v2.VoiceContext.builder() + .setVoiceRegion(region) + .setVoiceUrl(voiceUrl) + .build()); + } + } + private void handleDefaultConversationSettings( Configuration configuration, Properties props, Configuration.Builder builder) { @@ -379,12 +421,21 @@ private void checkConfiguration(Configuration configuration) throws NullPointerE Objects.requireNonNull(configuration.getOAuthUrl(), "'oauthUrl' cannot be null"); } + private TransportSettings transportSettings() { + return configuration.getTransportSettings().orElse(TransportSettings.DEFAULTS); + } + + private boolean useSinchAuth() { + return transportSettings().getUseSinchAuth(); + } + private NumbersService numbersInit() { return new com.sinch.sdk.domains.numbers.adapters.NumbersService( getConfiguration().getUnifiedCredentials().orElse(null), configuration.getNumbersContext().orElse(null), configuration.getOAuthServer(), - this::getHttpClient); + this::getHttpClient, + useSinchAuth()); } private SMSService smsInit() { @@ -401,7 +452,8 @@ f, getConfiguration().getSmsContext().orElse(null), this::getHttpClient)) getConfiguration().getUnifiedCredentials().orElse(null), getConfiguration().getSmsContext().orElse(null), configuration.getOAuthServer(), - this::getHttpClient)); + this::getHttpClient, + useSinchAuth())); } private VerificationService verificationInit() { @@ -414,7 +466,10 @@ private VerificationService verificationInit() { private VoiceService voiceInit() { return new com.sinch.sdk.domains.voice.adapters.VoiceService( getConfiguration().getApplicationCredentials().orElse(null), + getConfiguration().getUnifiedCredentials().orElse(null), getConfiguration().getVoiceContext().orElse(null), + getConfiguration().getVoiceV2Context().orElse(null), + getConfiguration().getOAuthServer(), this::getHttpClient); } @@ -423,7 +478,8 @@ private ConversationService conversationInit() { getConfiguration().getUnifiedCredentials().orElse(null), getConfiguration().getConversationContext().orElse(null), getConfiguration().getOAuthServer(), - this::getHttpClient); + this::getHttpClient, + useSinchAuth()); } private NumberLookupService lookupInit() { @@ -431,7 +487,8 @@ private NumberLookupService lookupInit() { getConfiguration().getUnifiedCredentials().orElse(null), getConfiguration().getNumberLookupContext().orElse(null), getConfiguration().getOAuthServer(), - this::getHttpClient); + this::getHttpClient, + useSinchAuth()); } private Properties handlePropertiesFile(String fileName) { @@ -454,7 +511,8 @@ private HttpClient getHttpClient() { local = new HttpClientApache( configuration.getHttpProxyConfiguration().orElse(null), - new DefaultRetryManager(configuration.getRetryConfiguration().orElse(null))); + new DefaultRetryManager(configuration.getRetryConfiguration().orElse(null)), + transportSettings().getTimeoutSeconds()); // set SDK User-Agent String userAgent = formatSdkUserAgentHeader(); diff --git a/client/src/main/com/sinch/sdk/domains/conversation/adapters/ConversationService.java b/client/src/main/com/sinch/sdk/domains/conversation/adapters/ConversationService.java index 5061d481b..67e23c3d7 100644 --- a/client/src/main/com/sinch/sdk/domains/conversation/adapters/ConversationService.java +++ b/client/src/main/com/sinch/sdk/domains/conversation/adapters/ConversationService.java @@ -13,6 +13,7 @@ public class ConversationService implements com.sinch.sdk.domains.conversation.C private final ConversationContext context; private final ServerConfiguration oAuthServer; private final Supplier httpClientSupplier; + private final boolean useSinchAuth; private volatile com.sinch.sdk.domains.conversation.api.v1.ConversationService conversationV1; private volatile TemplatesService templates; @@ -21,11 +22,13 @@ public ConversationService( UnifiedCredentials credentials, ConversationContext context, ServerConfiguration oAuthServer, - Supplier httpClientSupplier) { + Supplier httpClientSupplier, + boolean useSinchAuth) { this.credentials = credentials; this.context = context; this.oAuthServer = oAuthServer; this.httpClientSupplier = httpClientSupplier; + this.useSinchAuth = useSinchAuth; } public com.sinch.sdk.domains.conversation.api.v1.ConversationService v1() { @@ -34,7 +37,7 @@ public com.sinch.sdk.domains.conversation.api.v1.ConversationService v1() { if (null == this.conversationV1) { this.conversationV1 = new com.sinch.sdk.domains.conversation.api.v1.adapters.ConversationService( - credentials, context, oAuthServer, httpClientSupplier); + credentials, context, oAuthServer, httpClientSupplier, useSinchAuth); } } } @@ -46,7 +49,8 @@ public TemplatesService templates() { synchronized (this) { if (null == this.templates) { this.templates = - new TemplatesService(credentials, context, oAuthServer, httpClientSupplier); + new TemplatesService( + credentials, context, oAuthServer, httpClientSupplier, useSinchAuth); } } } 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 aa6ad3230..29e95f819 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 @@ -46,6 +46,7 @@ import com.sinch.sdk.models.ConversationContext; import com.sinch.sdk.models.UnifiedCredentials; import java.util.AbstractMap; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; @@ -64,6 +65,7 @@ public class ConversationService private final ServerConfiguration oAuthServer; private final ConversationContext context; private final Supplier httpClientSupplier; + private final boolean useSinchAuth; private volatile Map authManagers; private volatile AppsService apps; @@ -87,13 +89,15 @@ public ConversationService( UnifiedCredentials credentials, ConversationContext context, ServerConfiguration oAuthServer, - Supplier httpClientSupplier) { + Supplier httpClientSupplier, + boolean useSinchAuth) { this.uriUUID = null != credentials ? credentials.getProjectId() : null; this.credentials = credentials; this.context = context; this.httpClientSupplier = httpClientSupplier; this.oAuthServer = oAuthServer; + this.useSinchAuth = useSinchAuth; } public AppsService apps() { @@ -245,7 +249,8 @@ public TemplatesService templates() { synchronized (this) { if (null == this.templates) { this.templates = - new TemplatesService(credentials, context, oAuthServer, httpClientSupplier); + new TemplatesService( + credentials, context, oAuthServer, httpClientSupplier, useSinchAuth); } } } @@ -308,10 +313,13 @@ private void instanceLazyInit() { Objects.requireNonNull( credentials, "Conversation service requires credentials to be defined"); Objects.requireNonNull(context, "Conversation service requires context to be defined"); - StringUtil.requireNonEmpty( - credentials.getKeyId(), "Conversation service requires 'keyId' to be defined"); - StringUtil.requireNonEmpty( - credentials.getKeySecret(), "Conversation service requires 'keySecret' to be defined"); + if (useSinchAuth) { + StringUtil.requireNonEmpty( + credentials.getKeyId(), "Conversation service requires 'keyId' to be defined"); + StringUtil.requireNonEmpty( + credentials.getKeySecret(), + "Conversation service requires 'keySecret' to be defined"); + } StringUtil.requireNonEmpty( credentials.getProjectId(), "Conversation service requires 'projectId' to be defined"); Objects.requireNonNull( @@ -321,12 +329,16 @@ private void instanceLazyInit() { StringUtil.requireNonEmpty( context.getUrl(), "Conversation service requires 'url' to be defined"); - OAuthManager authManager = - new OAuthManager( - credentials, oAuthServer, HttpMapper.getInstance(), httpClientSupplier); - authManagers = - Stream.of(new AbstractMap.SimpleEntry<>(SECURITY_SCHEME_KEYWORD_, authManager)) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + if (useSinchAuth) { + OAuthManager authManager = + new OAuthManager( + credentials, oAuthServer, HttpMapper.getInstance(), httpClientSupplier); + authManagers = + Stream.of(new AbstractMap.SimpleEntry<>(SECURITY_SCHEME_KEYWORD_, authManager)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } else { + authManagers = Collections.emptyMap(); + } LOGGER.fine( String.format( diff --git a/client/src/main/com/sinch/sdk/domains/conversation/templates/api/adapters/TemplatesService.java b/client/src/main/com/sinch/sdk/domains/conversation/templates/api/adapters/TemplatesService.java index 021cef5dd..ab6972c07 100644 --- a/client/src/main/com/sinch/sdk/domains/conversation/templates/api/adapters/TemplatesService.java +++ b/client/src/main/com/sinch/sdk/domains/conversation/templates/api/adapters/TemplatesService.java @@ -14,6 +14,7 @@ import com.sinch.sdk.models.ConversationContext; import com.sinch.sdk.models.UnifiedCredentials; import java.util.AbstractMap; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; @@ -33,6 +34,7 @@ public class TemplatesService private final ConversationContext context; private final Supplier httpClientSupplier; + private final boolean useSinchAuth; private volatile Map authManagers; private volatile TemplatesV2Service v2; @@ -45,13 +47,15 @@ public TemplatesService( UnifiedCredentials credentials, ConversationContext context, ServerConfiguration oAuthServer, - Supplier httpClientSupplier) { + Supplier httpClientSupplier, + boolean useSinchAuth) { this.uriUUID = null != credentials ? credentials.getProjectId() : null; this.context = context; this.credentials = credentials; this.oAuthServer = oAuthServer; this.httpClientSupplier = httpClientSupplier; + this.useSinchAuth = useSinchAuth; } public TemplatesV2Service v2() { @@ -80,10 +84,12 @@ private void instanceLazyInit() { if (null == this.authManagers) { Objects.requireNonNull(credentials, "Templates service requires credentials to be defined"); Objects.requireNonNull(context, "Templates service requires context to be defined"); - StringUtil.requireNonEmpty( - credentials.getKeyId(), "Templates service requires 'keyId' to be defined"); - StringUtil.requireNonEmpty( - credentials.getKeySecret(), "Templates service requires 'keySecret' to be defined"); + if (useSinchAuth) { + StringUtil.requireNonEmpty( + credentials.getKeyId(), "Templates service requires 'keyId' to be defined"); + StringUtil.requireNonEmpty( + credentials.getKeySecret(), "Templates service requires 'keySecret' to be defined"); + } StringUtil.requireNonEmpty( credentials.getProjectId(), "Templates service requires 'projectId' to be defined"); Objects.requireNonNull( @@ -94,12 +100,16 @@ private void instanceLazyInit() { context.getTemplateManagementUrl(), "Templates service requires 'templateManagementUrl' to be defined"); - OAuthManager authManager = - new OAuthManager( - credentials, oAuthServer, HttpMapper.getInstance(), httpClientSupplier); - authManagers = - Stream.of(new AbstractMap.SimpleEntry<>(SECURITY_SCHEME_KEYWORD_, authManager)) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + if (useSinchAuth) { + OAuthManager authManager = + new OAuthManager( + credentials, oAuthServer, HttpMapper.getInstance(), httpClientSupplier); + authManagers = + Stream.of(new AbstractMap.SimpleEntry<>(SECURITY_SCHEME_KEYWORD_, authManager)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } else { + authManagers = Collections.emptyMap(); + } LOGGER.fine( String.format( 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 index 4e21d9ac8..5f9dcd989 100644 --- a/client/src/main/com/sinch/sdk/domains/numberlookup/adapters/NumberLookupService.java +++ b/client/src/main/com/sinch/sdk/domains/numberlookup/adapters/NumberLookupService.java @@ -12,6 +12,7 @@ public class NumberLookupService implements com.sinch.sdk.domains.numberlookup.N private final NumberLookupContext context; private final ServerConfiguration oAuthServer; private final Supplier httpClientSupplier; + private final boolean useSinchAuth; private volatile com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupService v2; @@ -19,11 +20,13 @@ public NumberLookupService( UnifiedCredentials credentials, NumberLookupContext context, ServerConfiguration oAuthServer, - Supplier httpClientSupplier) { + Supplier httpClientSupplier, + boolean useSinchAuth) { this.credentials = credentials; this.context = context; this.oAuthServer = oAuthServer; this.httpClientSupplier = httpClientSupplier; + this.useSinchAuth = useSinchAuth; } @Override @@ -33,7 +36,7 @@ public com.sinch.sdk.domains.numberlookup.api.v2.NumberLookupService v2() { if (null == this.v2) { this.v2 = new com.sinch.sdk.domains.numberlookup.api.v2.adapters.NumberLookupService( - credentials, context, oAuthServer, httpClientSupplier); + credentials, context, oAuthServer, httpClientSupplier, useSinchAuth); } } } 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 5fdb4dcf8..8bd854871 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 @@ -12,6 +12,7 @@ import com.sinch.sdk.models.NumberLookupContext; import com.sinch.sdk.models.UnifiedCredentials; import java.util.AbstractMap; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; @@ -29,6 +30,7 @@ public class NumberLookupService private final NumberLookupContext context; private final ServerConfiguration oAuthServer; private final Supplier httpClientSupplier; + private final boolean useSinchAuth; private volatile String uriUUID; private volatile Map authManagers; @@ -39,11 +41,13 @@ public NumberLookupService( UnifiedCredentials credentials, NumberLookupContext context, ServerConfiguration oAuthServer, - Supplier httpClientSupplier) { + Supplier httpClientSupplier, + boolean useSinchAuth) { this.credentials = credentials; this.context = context; this.oAuthServer = oAuthServer; this.httpClientSupplier = httpClientSupplier; + this.useSinchAuth = useSinchAuth; } NumberLookupServiceFacade getLookupFacade() { @@ -73,10 +77,13 @@ private void instanceLazyInit() { 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"); + if (useSinchAuth) { + 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( @@ -88,16 +95,21 @@ private void instanceLazyInit() { + 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)); + + if (useSinchAuth) { + AuthManager authManager = + new OAuthManager( + credentials, oAuthServer, HttpMapper.getInstance(), httpClientSupplier); + + authManagers = + Stream.of( + new AbstractMap.SimpleEntry<>( + SECURITY_SCHEME_KEYWORD_NUMBER_LOOKUP, authManager)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } else { + authManagers = Collections.emptyMap(); + } } } } diff --git a/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java b/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java index eff4ba206..11e6275e8 100644 --- a/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java +++ b/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java @@ -12,6 +12,7 @@ public class NumbersService implements com.sinch.sdk.domains.numbers.NumbersServ private final NumbersContext context; private final ServerConfiguration oAuthServer; private final Supplier httpClientSupplier; + private final boolean useSinchAuth; private volatile com.sinch.sdk.domains.numbers.api.v1.NumbersService v1; @@ -19,11 +20,13 @@ public NumbersService( UnifiedCredentials credentials, NumbersContext context, ServerConfiguration oAuthServer, - Supplier httpClientSupplier) { + Supplier httpClientSupplier, + boolean useSinchAuth) { this.credentials = credentials; this.context = context; this.oAuthServer = oAuthServer; this.httpClientSupplier = httpClientSupplier; + this.useSinchAuth = useSinchAuth; } public com.sinch.sdk.domains.numbers.api.v1.NumbersService v1() { @@ -32,7 +35,7 @@ public com.sinch.sdk.domains.numbers.api.v1.NumbersService v1() { if (null == this.v1) { this.v1 = new com.sinch.sdk.domains.numbers.api.v1.adapters.NumbersService( - credentials, context, oAuthServer, httpClientSupplier); + credentials, context, oAuthServer, httpClientSupplier, useSinchAuth); } } } diff --git a/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersService.java b/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersService.java index fbab255b7..88cc62985 100644 --- a/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersService.java +++ b/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersService.java @@ -24,6 +24,7 @@ import com.sinch.sdk.models.NumbersContext; import com.sinch.sdk.models.UnifiedCredentials; import java.util.AbstractMap; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; @@ -40,6 +41,7 @@ public class NumbersService implements com.sinch.sdk.domains.numbers.api.v1.Numb private final NumbersContext context; private final ServerConfiguration oAuthServer; private final Supplier httpClientSupplier; + private final boolean useSinchAuth; private volatile String uriUUID; private volatile Map authManagers; @@ -57,11 +59,13 @@ public NumbersService( UnifiedCredentials credentials, NumbersContext context, ServerConfiguration oAuthServer, - Supplier httpClientSupplier) { + Supplier httpClientSupplier, + boolean useSinchAuth) { this.credentials = credentials; this.context = context; this.oAuthServer = oAuthServer; this.httpClientSupplier = httpClientSupplier; + this.useSinchAuth = useSinchAuth; } AvailableNumberServiceFacade available() { @@ -223,10 +227,12 @@ private void instanceLazyInit() { Objects.requireNonNull( credentials, "Numbers service require unified credentials to be defined"); Objects.requireNonNull(context, "Numbers service requires context to be defined"); - StringUtil.requireNonEmpty( - credentials.getKeyId(), "Numbers service requires 'keyId' to be defined"); - StringUtil.requireNonEmpty( - credentials.getKeySecret(), "Numbers service requires 'keySecret' to be defined"); + if (useSinchAuth) { + StringUtil.requireNonEmpty( + credentials.getKeyId(), "Numbers service requires 'keyId' to be defined"); + StringUtil.requireNonEmpty( + credentials.getKeySecret(), "Numbers service requires 'keySecret' to be defined"); + } StringUtil.requireNonEmpty( credentials.getProjectId(), "Numbers service requires 'projectId' to be defined"); StringUtil.requireNonEmpty( @@ -235,14 +241,19 @@ private void instanceLazyInit() { LOGGER.fine( "Activate numbers API with server='" + context.getNumbersServer().getUrl() + "'"); - AuthManager authManager = - new OAuthManager( - credentials, oAuthServer, HttpMapper.getInstance(), httpClientSupplier); - uriUUID = credentials.getProjectId(); - authManagers = - Stream.of(new AbstractMap.SimpleEntry<>(SECURITY_SCHEME_KEYWORD_NUMBERS, authManager)) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + if (useSinchAuth) { + AuthManager authManager = + new OAuthManager( + credentials, oAuthServer, HttpMapper.getInstance(), httpClientSupplier); + + authManagers = + Stream.of(new AbstractMap.SimpleEntry<>(SECURITY_SCHEME_KEYWORD_NUMBERS, authManager)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } else { + authManagers = Collections.emptyMap(); + } } } } diff --git a/client/src/main/com/sinch/sdk/domains/sms/adapters/SMSService.java b/client/src/main/com/sinch/sdk/domains/sms/adapters/SMSService.java index c178e9df8..93cb797eb 100644 --- a/client/src/main/com/sinch/sdk/domains/sms/adapters/SMSService.java +++ b/client/src/main/com/sinch/sdk/domains/sms/adapters/SMSService.java @@ -17,11 +17,12 @@ public SMSService( UnifiedCredentials credentials, SmsContext context, ServerConfiguration oAuthServer, - Supplier httpClientSupplier) { + Supplier httpClientSupplier, + boolean useSinchAuth) { this.v1Initializer = () -> new com.sinch.sdk.domains.sms.api.v1.adapters.SMSService( - credentials, context, oAuthServer, httpClientSupplier); + credentials, context, oAuthServer, httpClientSupplier, useSinchAuth); } public SMSService( diff --git a/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SMSService.java b/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SMSService.java index 3ab3ebba5..24f1cbdac 100644 --- a/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SMSService.java +++ b/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SMSService.java @@ -17,6 +17,7 @@ import com.sinch.sdk.models.SmsServicePlanCredentials; import com.sinch.sdk.models.UnifiedCredentials; import java.util.AbstractMap; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; @@ -33,6 +34,7 @@ public class SMSService implements com.sinch.sdk.domains.sms.api.v1.SMSService { private final SmsContext context; private final ServerConfiguration oAuthServer; private final Supplier httpClientSupplier; + private final boolean useSinchAuth; private volatile String uriUUID; private volatile Map authManagers; @@ -46,11 +48,13 @@ public SMSService( UnifiedCredentials credentials, SmsContext context, ServerConfiguration oAuthServer, - Supplier httpClientSupplier) { + Supplier httpClientSupplier, + boolean useSinchAuth) { this.credentials = credentials; this.oAuthServer = oAuthServer; this.context = context; this.httpClientSupplier = httpClientSupplier; + this.useSinchAuth = useSinchAuth; } public SMSService( @@ -61,6 +65,8 @@ public SMSService( this.context = context; this.httpClientSupplier = httpClientSupplier; this.oAuthServer = null; + // Sinch OAuth does not apply to service plan credentials + this.useSinchAuth = false; } @Override @@ -166,17 +172,21 @@ private void instanceLazyInit() { if (credentials instanceof UnifiedCredentials) { UnifiedCredentials unifiedCredentials = (UnifiedCredentials) credentials; - StringUtil.requireNonEmpty(unifiedCredentials.getKeyId(), "'keyId' must be defined"); - StringUtil.requireNonEmpty( - unifiedCredentials.getKeySecret(), "'keySecret' must be defined"); + if (useSinchAuth) { + StringUtil.requireNonEmpty(unifiedCredentials.getKeyId(), "'keyId' must be defined"); + StringUtil.requireNonEmpty( + unifiedCredentials.getKeySecret(), "'keySecret' must be defined"); + } StringUtil.requireNonEmpty( unifiedCredentials.getProjectId(), "'projectId' must be defined"); LOGGER.fine("Activate SMS API with server='" + context.getSmsServer().getUrl() + "'"); uriUUID = unifiedCredentials.getProjectId(); - oAuthManager = - new OAuthManager( - unifiedCredentials, oAuthServer, HttpMapper.getInstance(), httpClientSupplier); + if (useSinchAuth) { + oAuthManager = + new OAuthManager( + unifiedCredentials, oAuthServer, HttpMapper.getInstance(), httpClientSupplier); + } } else if (credentials instanceof SmsServicePlanCredentials) { SmsServicePlanCredentials servicePlanCredentials = @@ -194,8 +204,11 @@ private void instanceLazyInit() { } this.authManagers = - Stream.of(new AbstractMap.SimpleEntry<>(SECURITY_SCHEME_KEYWORD_SMS, oAuthManager)) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + null == oAuthManager + ? Collections.emptyMap() + : Stream.of( + new AbstractMap.SimpleEntry<>(SECURITY_SCHEME_KEYWORD_SMS, oAuthManager)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } } } diff --git a/client/src/main/com/sinch/sdk/domains/voice/VoiceService.java b/client/src/main/com/sinch/sdk/domains/voice/VoiceService.java index 3f386380f..8811fdc04 100644 --- a/client/src/main/com/sinch/sdk/domains/voice/VoiceService.java +++ b/client/src/main/com/sinch/sdk/domains/voice/VoiceService.java @@ -17,4 +17,13 @@ public interface VoiceService { * @since 1.1 */ com.sinch.sdk.domains.voice.api.v1.VoiceService v1(); + + /** + * Voice Service V2 + * + * @return V2 service instance for project + * @see Documentation + * @since 2.3 + */ + com.sinch.sdk.domains.voice.api.v2.VoiceService v2(); } diff --git a/client/src/main/com/sinch/sdk/domains/voice/adapters/VoiceService.java b/client/src/main/com/sinch/sdk/domains/voice/adapters/VoiceService.java index 8037bf697..372777fee 100644 --- a/client/src/main/com/sinch/sdk/domains/voice/adapters/VoiceService.java +++ b/client/src/main/com/sinch/sdk/domains/voice/adapters/VoiceService.java @@ -1,24 +1,36 @@ package com.sinch.sdk.domains.voice.adapters; import com.sinch.sdk.core.http.HttpClient; +import com.sinch.sdk.core.models.ServerConfiguration; import com.sinch.sdk.models.ApplicationCredentials; +import com.sinch.sdk.models.UnifiedCredentials; import com.sinch.sdk.models.VoiceContext; import java.util.function.Supplier; public class VoiceService implements com.sinch.sdk.domains.voice.VoiceService { private final ApplicationCredentials credentials; + private final UnifiedCredentials unifiedCredentials; private final VoiceContext context; + private final com.sinch.sdk.domains.voice.models.v2.VoiceContext v2Context; + private final ServerConfiguration oAuthServer; private final Supplier httpClientSupplier; private volatile com.sinch.sdk.domains.voice.api.v1.VoiceService v1; + private volatile com.sinch.sdk.domains.voice.api.v2.VoiceService v2; public VoiceService( ApplicationCredentials credentials, + UnifiedCredentials unifiedCredentials, VoiceContext context, + com.sinch.sdk.domains.voice.models.v2.VoiceContext v2Context, + ServerConfiguration oAuthServer, Supplier httpClientSupplier) { this.credentials = credentials; + this.unifiedCredentials = unifiedCredentials; this.context = context; + this.v2Context = v2Context; + this.oAuthServer = oAuthServer; this.httpClientSupplier = httpClientSupplier; } @@ -34,4 +46,17 @@ public com.sinch.sdk.domains.voice.api.v1.VoiceService v1() { } return this.v1; } + + public com.sinch.sdk.domains.voice.api.v2.VoiceService v2() { + if (null == this.v2) { + synchronized (this) { + if (null == this.v2) { + this.v2 = + new com.sinch.sdk.domains.voice.api.v2.adapters.VoiceService( + unifiedCredentials, v2Context, oAuthServer, httpClientSupplier); + } + } + } + return this.v2; + } } diff --git a/client/src/main/com/sinch/sdk/domains/voice/api/v2/SinchEventsService.java b/client/src/main/com/sinch/sdk/domains/voice/api/v2/SinchEventsService.java new file mode 100644 index 000000000..02cebb547 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/api/v2/SinchEventsService.java @@ -0,0 +1,90 @@ +package com.sinch.sdk.domains.voice.api.v2; + +import com.sinch.sdk.core.exceptions.ApiMappingException; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEvent; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEventResponse; +import java.util.Map; + +/** + * Sinch Events service + * + *

Helpers to handle, from your backend, the Sinch Events sent by the Voice Platform: validate + * their signature, parse their payload, and serialize the SVAML document to respond with. + * + *

This service requires no credentials from the client configuration: the service credentials + * used to validate a Sinch Event are given on each call. + * + * @see https://developers.sinch.com/docs/voice-2.0 + * @since 2.3 + */ +public interface SinchEventsService { + + /** + * The Voice Platform signs every Sinch Event request with the secret of the service the event + * belongs to. The signature is carried in the Authorization header, using the + * service scheme: Authorization: service <serviceId>:<signature> + * + *

By using following function, you can ensure authentication according to received payload + * from your backend. The request is rejected when: + * + *

    + *
  • the Authorization or the x-timestamp header is missing + *
  • the scheme is not service + *
  • the service ID in the header differs from serviceId + *
  • the signature does not match the one computed from the received request + *
+ * + *

Replay protection is the caller's responsibility: this function does not check the + * x-timestamp header against the current time. Reject requests whose + * x-timestamp lies outside the clock-skew window your application accepts. + * + * @param serviceId The ID of the service the Sinch Event belongs to + * @param serviceSecret The secret of the service, as issued (Base64 encoded) + * @param method The HTTP method used to handle the Sinch Event (always POST for + * Voice Platform requests) + * @param path The path to your backend endpoint used for the Sinch Event (scheme, host, query + * string and fragment are not part of the signature) + * @param headers Received headers + * @param jsonPayload Received payload, exactly as delivered (before any parsing or + * re-serialization) + * @return Is authentication validated (true) or not (false) + * @throws IllegalArgumentException if serviceId or serviceSecret is + * empty, or if serviceSecret is not valid Base64 + * @since 2.3 + */ + boolean validateAuthenticationHeader( + String serviceId, + String serviceSecret, + String method, + String path, + Map headers, + String jsonPayload); + + /** + * This function can be called to deserialize received payload onto Voice Sinch Event class + * + *

CloudEvents headers (ce-*) are not part of the returned event. + * + *

Custom events, triggered by the {@link + * com.sinch.sdk.domains.voice.models.v2.svaml.customevents.CustomEventCommand}, are sent by the + * API as call.webhook.<name> and returned as + * call.customEvent.<name>. + * + * @param jsonPayload Received payload to be deserialized + * @return The Voice Sinch Event + * @throws ApiMappingException if the payload cannot be deserialized + * @since 2.3 + */ + VoiceSinchEvent parseEvent(String jsonPayload) throws ApiMappingException; + + /** + * This function can be called to serialize the response to a Sinch Event + * + * @param response The response to be serialized + * @return The JSON string to be sent + * @throws ApiMappingException if the response cannot be serialized + * @since 2.3 + */ + String serializeResponse(VoiceSinchEventResponse response) throws ApiMappingException; +} diff --git a/client/src/main/com/sinch/sdk/domains/voice/api/v2/VoiceService.java b/client/src/main/com/sinch/sdk/domains/voice/api/v2/VoiceService.java new file mode 100644 index 000000000..4f9c1f46e --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/api/v2/VoiceService.java @@ -0,0 +1,61 @@ +package com.sinch.sdk.domains.voice.api.v2; + +/** + * Voice Service V2 + * + * @see https://developers.sinch.com/docs/voice-2.0 + * @since 2.3 + */ +public interface VoiceService { + + /** + * Calls Service instance + * + * @return service instance for project + * @since 2.3 + */ + CallsService calls(); + + /** + * Batches Service instance + * + * @return service instance for project + * @since 2.3 + */ + BatchesService batches(); + + /** + * Sessions Service instance + * + * @return service instance for project + * @since 2.3 + */ + SessionsService sessions(); + + /** + * Services Service instance + * + * @return service instance for project + * @since 2.3 + */ + ServicesService services(); + + /** + * Svaml Service instance + * + * @return service instance for project + * @since 2.3 + */ + SvamlService svaml(); + + /** + * Sinch Events Service instance + * + *

Requires no credentials from the client configuration. + * + * @return service instance + * @since 2.3 + */ + SinchEventsService sinchEvents(); +} diff --git a/client/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/SinchEventsService.java b/client/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/SinchEventsService.java new file mode 100644 index 000000000..6101aa712 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/SinchEventsService.java @@ -0,0 +1,126 @@ +package com.sinch.sdk.domains.voice.api.v2.adapters; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.sinch.sdk.auth.adapters.ApplicationAuthManager; +import com.sinch.sdk.core.exceptions.ApiMappingException; +import com.sinch.sdk.core.utils.MapUtils; +import com.sinch.sdk.core.utils.Pair; +import com.sinch.sdk.core.utils.StringUtil; +import com.sinch.sdk.core.utils.databind.Mapper; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEvent; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEventResponse; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Locale; +import java.util.Map; + +public class SinchEventsService implements com.sinch.sdk.domains.voice.api.v2.SinchEventsService { + + private static final String AUTH_KEYWORD = "service"; + private static final String XTIMESTAMP_HEADER = "x-timestamp"; + + // The API sends custom events as "call.webhook.": the SDK names them after the + // customEvent SVAML command, like the Python SDK does + private static final String WIRE_CUSTOM_EVENT_PREFIX = "call.webhook."; + private static final String CUSTOM_EVENT_PREFIX = "call.customEvent."; + + public boolean validateAuthenticationHeader( + String serviceId, + String serviceSecret, + String method, + String path, + Map headers, + String jsonPayload) { + + StringUtil.requireNonEmpty(serviceId, "'serviceId' must be defined"); + StringUtil.requireNonEmpty(serviceSecret, "'serviceSecret' must be defined"); + + // throws IllegalArgumentException on an invalid Base64 secret + ApplicationAuthManager signer = new ApplicationAuthManager(serviceId, serviceSecret); + + if (null == headers) { + return false; + } + + // convert header keys to use case-insensitive map keys + Map caseInsensitiveHeaders = MapUtils.getCaseInsensitiveMap(headers); + + String authorizationHeader = caseInsensitiveHeaders.get("Authorization"); + if (StringUtil.isEmpty(authorizationHeader)) { + return false; + } + + String[] split = authorizationHeader.split(" ", 2); + if (split.length != 2 || !AUTH_KEYWORD.equalsIgnoreCase(split[0])) { + return false; + } + + // signature is Base64 and contains no colon: split on the last one + String authorizationValue = split[1]; + int separator = authorizationValue.lastIndexOf(':'); + if (separator < 0) { + return false; + } + if (!serviceId.equals(authorizationValue.substring(0, separator))) { + return false; + } + String receivedSignature = authorizationValue.substring(separator + 1); + + String timestamp = caseInsensitiveHeaders.get(XTIMESTAMP_HEADER); + if (StringUtil.isEmpty(timestamp)) { + return false; + } + String contentType = caseInsensitiveHeaders.getOrDefault("content-type", ""); + + // Voice V2 "service" scheme shares the signing algorithm of the "Application" scheme + String computedSignature = + signer + .getAuthorizationHeaders( + timestamp, + null == method ? null : method.toUpperCase(Locale.ROOT), + contentType, + path, + jsonPayload) + .stream() + .filter(f -> f.getLeft().equals("Authorization")) + .findFirst() + .map(Pair::getRight) + .map(value -> value.substring(value.lastIndexOf(':') + 1)) + .orElse(""); + + return MessageDigest.isEqual( + computedSignature.getBytes(StandardCharsets.UTF_8), + receivedSignature.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public VoiceSinchEvent parseEvent(String jsonPayload) throws ApiMappingException { + try { + JsonNode node = Mapper.getInstance().readTree(jsonPayload); + JsonNode event = null != node ? node.get("event") : null; + if (node instanceof ObjectNode + && null != event + && event.isTextual() + && event.asText().startsWith(WIRE_CUSTOM_EVENT_PREFIX)) { + ((ObjectNode) node) + .put( + "event", + CUSTOM_EVENT_PREFIX + event.asText().substring(WIRE_CUSTOM_EVENT_PREFIX.length())); + } + return Mapper.getInstance().treeToValue(node, VoiceSinchEvent.class); + } catch (JsonProcessingException | IllegalArgumentException e) { + throw new ApiMappingException(jsonPayload, e); + } + } + + @Override + public String serializeResponse(VoiceSinchEventResponse response) throws ApiMappingException { + try { + return Mapper.getInstance().writeValueAsString(response); + } catch (JsonProcessingException e) { + throw new ApiMappingException(String.valueOf(response), e); + } + } +} diff --git a/client/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/VoiceService.java b/client/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/VoiceService.java new file mode 100644 index 000000000..3dbaed8e0 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/VoiceService.java @@ -0,0 +1,189 @@ +package com.sinch.sdk.domains.voice.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.voice.api.v2.BatchesService; +import com.sinch.sdk.domains.voice.api.v2.CallsService; +import com.sinch.sdk.domains.voice.api.v2.ServicesService; +import com.sinch.sdk.domains.voice.api.v2.SessionsService; +import com.sinch.sdk.domains.voice.api.v2.SvamlService; +import com.sinch.sdk.domains.voice.models.v2.VoiceContext; +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 VoiceService implements com.sinch.sdk.domains.voice.api.v2.VoiceService { + + private static final Logger LOGGER = Logger.getLogger(VoiceService.class.getName()); + private static final String SECURITY_SCHEME_KEYWORD_VOICE = "SinchOAuth2"; + + private final UnifiedCredentials credentials; + private final VoiceContext context; + private final ServerConfiguration oAuthServer; + private final Supplier httpClientSupplier; + + private volatile String uriUUID; + private volatile Map authManagers; + + private volatile CallsService calls; + private volatile BatchesService batches; + private volatile SessionsService sessions; + private volatile ServicesService services; + private volatile SvamlService svaml; + private volatile SinchEventsService sinchEvents; + + public VoiceService( + UnifiedCredentials credentials, + VoiceContext context, + ServerConfiguration oAuthServer, + Supplier httpClientSupplier) { + this.credentials = credentials; + this.context = context; + this.oAuthServer = oAuthServer; + this.httpClientSupplier = httpClientSupplier; + } + + public CallsService calls() { + if (null == this.calls) { + synchronized (this) { + if (null == this.calls) { + instanceLazyInit(); + this.calls = + new CallsServiceImpl( + httpClientSupplier.get(), + context.getVoiceServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } + } + return this.calls; + } + + public BatchesService batches() { + if (null == this.batches) { + synchronized (this) { + if (null == this.batches) { + instanceLazyInit(); + this.batches = + new BatchesServiceImpl( + httpClientSupplier.get(), + context.getVoiceServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } + } + return this.batches; + } + + public SessionsService sessions() { + if (null == this.sessions) { + synchronized (this) { + if (null == this.sessions) { + instanceLazyInit(); + this.sessions = + new SessionsServiceImpl( + httpClientSupplier.get(), + context.getVoiceServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } + } + return this.sessions; + } + + public ServicesService services() { + if (null == this.services) { + synchronized (this) { + if (null == this.services) { + instanceLazyInit(); + this.services = + new ServicesServiceImpl( + httpClientSupplier.get(), + context.getVoiceServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } + } + return this.services; + } + + public SvamlService svaml() { + if (null == this.svaml) { + synchronized (this) { + if (null == this.svaml) { + instanceLazyInit(); + this.svaml = + new SvamlServiceImpl( + httpClientSupplier.get(), + context.getVoiceServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } + } + return this.svaml; + } + + public SinchEventsService sinchEvents() { + // no lazy init: validating a Sinch Event takes service credentials on each call + if (null == this.sinchEvents) { + synchronized (this) { + if (null == this.sinchEvents) { + this.sinchEvents = new SinchEventsService(); + } + } + } + return this.sinchEvents; + } + + private void instanceLazyInit() { + if (null != this.authManagers) { + return; + } + synchronized (this) { + if (null == this.authManagers) { + Objects.requireNonNull( + credentials, "Voice V2 service requires unified credentials to be defined"); + Objects.requireNonNull(context, "Voice V2 service requires context to be defined"); + StringUtil.requireNonEmpty( + credentials.getKeyId(), "Voice V2 service requires 'keyId' to be defined"); + StringUtil.requireNonEmpty( + credentials.getKeySecret(), "Voice V2 service requires 'keySecret' to be defined"); + StringUtil.requireNonEmpty( + credentials.getProjectId(), "Voice V2 service requires 'projectId' to be defined"); + StringUtil.requireNonEmpty( + context.getVoiceUrl(), "Voice V2 service requires 'voiceUrl' to be defined"); + + LOGGER.fine( + "Activate Voice V2 API with server='" + context.getVoiceServer().getUrl() + "'"); + + AuthManager authManager = + new OAuthManager( + credentials, oAuthServer, HttpMapper.getInstance(), httpClientSupplier); + + uriUUID = credentials.getProjectId(); + authManagers = + Stream.of(new AbstractMap.SimpleEntry<>(SECURITY_SCHEME_KEYWORD_VOICE, authManager)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + } + } +} diff --git a/client/src/main/com/sinch/sdk/domains/voice/api/v2/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/api/v2/package-info.java new file mode 100644 index 000000000..bb56059c0 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/api/v2/package-info.java @@ -0,0 +1,6 @@ +/** + * Voice API interface V2 + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.api.v2; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/VoiceContext.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/VoiceContext.java new file mode 100644 index 000000000..ddb0bbf6f --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/VoiceContext.java @@ -0,0 +1,135 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.sinch.sdk.core.models.ServerConfiguration; + +/** + * Execution context related to Voice V2. It holds Voice V2 settings only: Voice V1 settings live in + * {@link com.sinch.sdk.models.VoiceContext}. + * + * @since 2.3 + */ +public class VoiceContext { + + private final VoiceRegion voiceRegion; + private final String voiceUrl; + + public VoiceContext(VoiceRegion voiceRegion, String voiceUrl) { + this.voiceRegion = voiceRegion; + this.voiceUrl = voiceUrl; + } + + /** + * Voice V2 region + * + * @return Selected Voice V2 region + * @since 2.3 + */ + public VoiceRegion getVoiceRegion() { + return voiceRegion; + } + + /** + * Voice V2 URL + * + * @return Voice V2 server URL + * @since 2.3 + */ + public String getVoiceUrl() { + return voiceUrl; + } + + /** + * Voice V2 server configuration + * + * @return Voice V2 server configuration to be used + * @since 2.3 + */ + public ServerConfiguration getVoiceServer() { + return new ServerConfiguration(getVoiceUrl()); + } + + @Override + public String toString() { + return "VoiceContext{" + "voiceRegion=" + voiceRegion + ", voiceUrl='" + voiceUrl + '\'' + '}'; + } + + /** + * Getting Builder + * + * @return New Builder instance + * @since 2.3 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Getting Builder + * + * @param context Source context to fill initial builder state + * @return New Builder instance + * @since 2.3 + */ + public static Builder builder(VoiceContext context) { + return new Builder(context); + } + + /** + * Dedicated Builder + * + * @since 2.3 + */ + public static class Builder { + + VoiceRegion voiceRegion; + String voiceUrl; + + protected Builder() {} + + /** + * Initialize a builder with existing context + * + * @param context Context to be used as initial builder state + * @since 2.3 + */ + protected Builder(VoiceContext context) { + this.voiceRegion = null != context ? context.getVoiceRegion() : null; + this.voiceUrl = null != context ? context.getVoiceUrl() : null; + } + + /** + * Set region to be used for Voice V2 service. Ignored when a URL is set with {@link + * #setVoiceUrl(String)}. + * + * @param voiceRegion The region + * @return Current builder + * @since 2.3 + */ + public Builder setVoiceRegion(VoiceRegion voiceRegion) { + this.voiceRegion = voiceRegion; + return this; + } + + /** + * Set Voice V2 URL to be used. Takes precedence over the region. + * + * @param voiceUrl Voice V2 URL + * @return Current builder + * @since 2.3 + */ + public Builder setVoiceUrl(String voiceUrl) { + this.voiceUrl = voiceUrl; + return this; + } + + /** + * Create instance + * + * @return The instance build with current builder values + * @since 2.3 + */ + public VoiceContext build() { + return new VoiceContext(voiceRegion, voiceUrl); + } + } +} diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/VoiceRegion.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/VoiceRegion.java new file mode 100644 index 000000000..a957f56d6 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/VoiceRegion.java @@ -0,0 +1,74 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * Voice V2 region. Each region is served by its own Voice V2 server. + * + *

Voice V2 regions are not the same as the Voice V1 ones. + * + * @see Voice V2 documentation + * @since 2.3 + */ +public class VoiceRegion extends EnumDynamic { + + /** Global - redirected by Sinch to the closest region */ + public static final VoiceRegion GLOBAL = new VoiceRegion("global"); + + /** North America 1 - East */ + public static final VoiceRegion NORTH_AMERICA = new VoiceRegion("us1"); + + /** South America 1 - East */ + public static final VoiceRegion SOUTH_AMERICA = new VoiceRegion("br1"); + + /** Europe 1 - Central */ + public static final VoiceRegion EUROPE = new VoiceRegion("eu1"); + + /** Asia Pacific 1 - Southeast */ + public static final VoiceRegion ASIA_PACIFIC = new VoiceRegion("sg1"); + + /** Australia and Oceania 1 - Southeast */ + public static final VoiceRegion AUSTRALIA = new VoiceRegion("au1"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + VoiceRegion.class, + VoiceRegion::new, + Arrays.asList(GLOBAL, NORTH_AMERICA, SOUTH_AMERICA, EUROPE, ASIA_PACIFIC, AUSTRALIA)); + + private VoiceRegion(String value) { + super(value); + } + + /** + * Get declared values + * + * @return Stream of values + */ + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + /** + * Get value from a string + * + * @param value String identifier + * @return Dynamic enum from value. A new enum is created if value is not yet registered + */ + public static VoiceRegion from(String value) { + return ENUM_SUPPORT.from(value); + } + + /** + * Value of the enum as String + * + * @param value The enum identifier + * @return String identifier of the enum value + */ + public static String valueOf(VoiceRegion value) { + return ENUM_SUPPORT.valueOf(value); + } +} diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/package-info.java new file mode 100644 index 000000000..b020947ca --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 batches requests + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.batches.request; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/package-info.java new file mode 100644 index 000000000..b516d52c6 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 batches responses + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.batches.response; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/package-info.java new file mode 100644 index 000000000..6a0febb56 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 calls requests + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.calls.request; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/package-info.java new file mode 100644 index 000000000..b47de6553 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 calls responses + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.calls.response; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallDestination.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallDestination.java new file mode 100644 index 000000000..bfb2126c9 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallDestination.java @@ -0,0 +1,13 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.destination.internal.CallDestinationInternalImpl; + +/** + * Base interface for the destination of a call (the to of a dial + * command). + * + * @since 2.3 + */ +@JsonDeserialize(using = CallDestinationInternalImpl.Deserializer.class) +public interface CallDestination {} diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallOrigin.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallOrigin.java new file mode 100644 index 000000000..e9fb4cc3f --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallOrigin.java @@ -0,0 +1,12 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.destination.internal.CallOriginInternalImpl; + +/** + * Base interface for the origin of a call (the from of a dial command). + * + * @since 2.3 + */ +@JsonDeserialize(using = CallOriginInternalImpl.Deserializer.class) +public interface CallOrigin {} diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/destination/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/destination/package-info.java new file mode 100644 index 000000000..e32917b41 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/destination/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 call origins and destinations + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.destination; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/package-info.java new file mode 100644 index 000000000..fee906b41 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/package-info.java @@ -0,0 +1,6 @@ +/** + * Voice API interface V2 models + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/CallBehavior.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/CallBehavior.java new file mode 100644 index 000000000..c764074e4 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/CallBehavior.java @@ -0,0 +1,13 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.services.internal.CallBehaviorInternalImpl; + +/** + * Base interface for the call behavior of a service: defines how calls are handled for this + * service. + * + * @since 2.3 + */ +@JsonDeserialize(using = CallBehaviorInternalImpl.Deserializer.class) +public interface CallBehavior {} diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/package-info.java new file mode 100644 index 000000000..f90081bcd --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 services + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.services; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/package-info.java new file mode 100644 index 000000000..87ffe534e --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 services requests + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.services.request; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/package-info.java new file mode 100644 index 000000000..271d6985a --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 services responses + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.services.response; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/sessions/response/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/sessions/response/package-info.java new file mode 100644 index 000000000..52444885a --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/sessions/response/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 sessions responses + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.sessions.response; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/package-info.java new file mode 100644 index 000000000..3bc2d4f2e --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 Sinch Events + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.sinchevents; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlCommand.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlCommand.java new file mode 100644 index 000000000..abcafbe0c --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlCommand.java @@ -0,0 +1,12 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.internal.SvamlCommandInternalImpl; + +/** + * Base interface for SVAML v2 commands. + * + * @since 2.3 + */ +@JsonDeserialize(using = SvamlCommandInternalImpl.Deserializer.class) +public interface SvamlCommand {} diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/package-info.java new file mode 100644 index 000000000..0548aaf8b --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 SVAML amd commands + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.svaml.amd; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/package-info.java new file mode 100644 index 000000000..b7b00709c --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 SVAML calls commands + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/package-info.java new file mode 100644 index 000000000..38729cc32 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 SVAML customevents commands + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.svaml.customevents; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/package-info.java new file mode 100644 index 000000000..c227fa6c0 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 SVAML menu commands + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/package-info.java new file mode 100644 index 000000000..d64f8200e --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 SVAML commands + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.svaml; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/Message.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/Message.java new file mode 100644 index 000000000..c1948b1f4 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/Message.java @@ -0,0 +1,12 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.internal.MessageInternalImpl; + +/** + * Base interface for messages played by SVAML v2 commands. + * + * @since 2.3 + */ +@JsonDeserialize(using = MessageInternalImpl.Deserializer.class) +public interface Message {} diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/package-info.java new file mode 100644 index 000000000..9662cb1da --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 SVAML playback commands + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/package-info.java new file mode 100644 index 000000000..12f9a51ba --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 SVAML recording commands + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/package-info.java new file mode 100644 index 000000000..f768ad546 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 SVAML requests + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.svaml.request; diff --git a/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/package-info.java b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/package-info.java new file mode 100644 index 000000000..fc57b066a --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/package-info.java @@ -0,0 +1,6 @@ +/** + * Models related to Voice V2 SVAML responses + * + * @since 2.3 + */ +package com.sinch.sdk.domains.voice.models.v2.svaml.response; diff --git a/client/src/main/com/sinch/sdk/http/HttpClientApache.java b/client/src/main/com/sinch/sdk/http/HttpClientApache.java index bc3d7dc73..d5afd4b55 100644 --- a/client/src/main/com/sinch/sdk/http/HttpClientApache.java +++ b/client/src/main/com/sinch/sdk/http/HttpClientApache.java @@ -13,6 +13,7 @@ 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.IdempotencyKey; import com.sinch.sdk.core.http.URLParameter; import com.sinch.sdk.core.models.ServerConfiguration; import com.sinch.sdk.core.utils.Pair; @@ -37,11 +38,14 @@ 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.config.ConnectionConfig; +import org.apache.hc.client5.http.config.RequestConfig; 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.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; @@ -52,6 +56,7 @@ 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; +import org.apache.hc.core5.util.Timeout; public class HttpClientApache implements com.sinch.sdk.core.http.HttpClient, RetryCapable { @@ -69,21 +74,15 @@ public class HttpClientApache implements com.sinch.sdk.core.http.HttpClient, Ret private final RetryManager retryManager; - public HttpClientApache() { - this(null); - } - - public HttpClientApache(HttpProxyConfiguration proxyConfiguration) { - this(proxyConfiguration, null); - } - /** * @param proxyConfiguration Proxy to route through, or {@code null} for a direct connection * @param retryManager Manager applied to rate-limited responses, or {@code null} to retry nothing - * @since 2.2 + * @param timeoutSeconds Timeout applied to I/O operations, zero to disable it + * @since 2.3 */ - public HttpClientApache(HttpProxyConfiguration proxyConfiguration, RetryManager retryManager) { - this.client = buildHttpClient(proxyConfiguration); + public HttpClientApache( + HttpProxyConfiguration proxyConfiguration, RetryManager retryManager, int timeoutSeconds) { + this.client = buildHttpClient(proxyConfiguration, timeoutSeconds); this.retryManager = null != retryManager ? retryManager : RetryManager.NO_RETRY; } @@ -92,17 +91,35 @@ public Optional getRetryManager() { return Optional.ofNullable(retryManager); } - private static CloseableHttpClient buildHttpClient(HttpProxyConfiguration proxyConfiguration) { + private static CloseableHttpClient buildHttpClient( + HttpProxyConfiguration proxyConfiguration, int timeoutSeconds) { + + Timeout timeout = Timeout.ofSeconds(timeoutSeconds); + + HttpClientBuilder builder = + HttpClients.custom() + .disableAutomaticRetries() + .setDefaultRequestConfig( + RequestConfig.custom() + .setConnectionRequestTimeout(timeout) + .setResponseTimeout(timeout) + .build()) + .setConnectionManager( + PoolingHttpClientConnectionManagerBuilder.create() + .setDefaultConnectionConfig( + ConnectionConfig.custom() + .setConnectTimeout(timeout) + .setSocketTimeout(timeout) + .build()) + .build()); + if (proxyConfiguration == null) { - return HttpClients.custom().disableAutomaticRetries().build(); + return builder.build(); } HttpHost proxyHost = new HttpHost(proxyConfiguration.getHostname(), proxyConfiguration.getPort()); - HttpClientBuilder builder = - HttpClients.custom() - .disableAutomaticRetries() - .setRoutePlanner(new DefaultProxyRoutePlanner(proxyHost)); + builder.setRoutePlanner(new DefaultProxyRoutePlanner(proxyHost)); if (proxyConfiguration.getUsername().isPresent()) { // getPassword() returns a defensive copy of the internal array; HC5 receives that copy and @@ -184,7 +201,7 @@ public HttpResponse invokeAPI( String body = httpRequest.getBody(); Map formParams = httpRequest.getFormParams(); - Map headerParams = httpRequest.getHeaderParams(); + Map headerParams = withIdempotencyKey(httpRequest); Collection accept = httpRequest.getAccept(); Collection contentType = httpRequest.getContentType(); Collection authNames = httpRequest.getAuthNames(); @@ -388,6 +405,20 @@ private void addCollectionHeader( } } + // The key is added once, before the request goes through the retry policy, so that every attempt + // of the request sends the same key + private static Map withIdempotencyKey(HttpRequest httpRequest) { + Map headers = httpRequest.getHeaderParams(); + if (!httpRequest.isIdempotent() + || (null != headers + && headers.keySet().stream().anyMatch(IdempotencyKey.HEADER::equalsIgnoreCase))) { + return headers; + } + Map withKey = null == headers ? new HashMap<>() : new HashMap<>(headers); + withKey.put(IdempotencyKey.HEADER, IdempotencyKey.generate()); + return withKey; + } + private void addHeaders(ClassicRequestBuilder requestBuilder, Map headers) { if (null == headers) { diff --git a/client/src/main/com/sinch/sdk/models/Configuration.java b/client/src/main/com/sinch/sdk/models/Configuration.java index e7043e2c1..59ce5a1ba 100644 --- a/client/src/main/com/sinch/sdk/models/Configuration.java +++ b/client/src/main/com/sinch/sdk/models/Configuration.java @@ -14,10 +14,12 @@ public class Configuration { private final SmsContext smsContext; private final VerificationContext verificationContext; private final VoiceContext voiceContext; + private final com.sinch.sdk.domains.voice.models.v2.VoiceContext voiceV2Context; private final ConversationContext conversationContext; private final HttpProxyConfiguration httpProxyConfiguration; private final NumberLookupContext numberLookupContext; private final RetryConfiguration retryConfiguration; + private final TransportSettings transportSettings; private Configuration( UnifiedCredentials unifiedCredentials, @@ -29,9 +31,11 @@ private Configuration( SmsContext smsContext, VerificationContext verificationContext, VoiceContext voiceContext, + com.sinch.sdk.domains.voice.models.v2.VoiceContext voiceV2Context, ConversationContext conversationContext, NumberLookupContext numberLookupContext, - RetryConfiguration retryConfiguration) { + RetryConfiguration retryConfiguration, + TransportSettings transportSettings) { this.unifiedCredentials = unifiedCredentials; this.applicationCredentials = applicationCredentials; this.smsServicePlanCredentials = smsServicePlanCredentials; @@ -39,11 +43,13 @@ private Configuration( this.numbersContext = numbersContext; this.smsContext = smsContext; this.voiceContext = voiceContext; + this.voiceV2Context = voiceV2Context; this.verificationContext = verificationContext; this.conversationContext = conversationContext; this.numberLookupContext = numberLookupContext; this.httpProxyConfiguration = httpProxyConfiguration; this.retryConfiguration = retryConfiguration; + this.transportSettings = transportSettings; } @Override @@ -60,6 +66,8 @@ public String toString() { + verificationContext + ", voiceContext=" + voiceContext + + ", voiceV2Context=" + + voiceV2Context + ", conversationContext=" + conversationContext + ", numberLookupContext=" @@ -68,6 +76,8 @@ public String toString() { + httpProxyConfiguration + ", retryConfiguration=" + retryConfiguration + + ", transportSettings=" + + transportSettings + "}"; } @@ -152,6 +162,16 @@ public Optional getVoiceContext() { return Optional.ofNullable(voiceContext); } + /** + * Get Voice V2 domain related execution context + * + * @return Current Voice V2 context + * @since 2.3 + */ + public Optional getVoiceV2Context() { + return Optional.ofNullable(voiceV2Context); + } + /** * Credentials to be used for Verification and Voice services * @@ -206,6 +226,17 @@ public Optional getRetryConfiguration() { return Optional.ofNullable(retryConfiguration); } + /** + * Get the transport level settings + * + * @return Transport settings, or empty when none was set and {@link TransportSettings#DEFAULTS} + * apply + * @since 2.3 + */ + public Optional getTransportSettings() { + return Optional.ofNullable(transportSettings); + } + /** * Getting Builder * @@ -242,10 +273,12 @@ public static class Builder { SmsContext.Builder smsContext; VerificationContext.Builder verificationContext; VoiceContext.Builder voiceContext; + com.sinch.sdk.domains.voice.models.v2.VoiceContext.Builder voiceV2Context; ConversationContext.Builder conversationContext; NumberLookupContext.Builder numberLookupContext; HttpProxyConfiguration httpProxyConfiguration; RetryConfiguration retryConfiguration; + TransportSettings transportSettings; protected Builder() {} @@ -275,12 +308,18 @@ protected Builder(Configuration configuration) { this.verificationContext = configuration.getVerificationContext().map(VerificationContext::builder).orElse(null); this.voiceContext = configuration.getVoiceContext().map(VoiceContext::builder).orElse(null); + this.voiceV2Context = + configuration + .getVoiceV2Context() + .map(com.sinch.sdk.domains.voice.models.v2.VoiceContext::builder) + .orElse(null); this.conversationContext = configuration.getConversationContext().map(ConversationContext::builder).orElse(null); this.numberLookupContext = configuration.getNumberLookupContext().map(NumberLookupContext::builder).orElse(null); this.httpProxyConfiguration = configuration.getHttpProxyConfiguration().orElse(null); this.retryConfiguration = configuration.getRetryConfiguration().orElse(null); + this.transportSettings = configuration.getTransportSettings().orElse(null); } /** @@ -483,6 +522,53 @@ public Builder setVoiceContext(VoiceContext context) { return this; } + /** + * Set Voice V2 related region + * + * @param region {@link com.sinch.sdk.domains.voice.models.v2.VoiceContext#getVoiceRegion() + * getter} + * @return Current builder + * @since 2.3 + */ + public Builder setVoiceV2Region(com.sinch.sdk.domains.voice.models.v2.VoiceRegion region) { + if (null == this.voiceV2Context) { + this.voiceV2Context = com.sinch.sdk.domains.voice.models.v2.VoiceContext.builder(); + } + this.voiceV2Context.setVoiceRegion(region); + return this; + } + + /** + * Set Voice V2 API URL + * + * @param voiceUrl {@link com.sinch.sdk.domains.voice.models.v2.VoiceContext#getVoiceUrl() + * getter} + * @return Current builder + * @since 2.3 + */ + public Builder setVoiceV2Url(String voiceUrl) { + if (null == this.voiceV2Context) { + this.voiceV2Context = com.sinch.sdk.domains.voice.models.v2.VoiceContext.builder(); + } + this.voiceV2Context.setVoiceUrl(voiceUrl); + return this; + } + + /** + * Set Voice V2 related context + * + * @param context {@link #getVoiceV2Context() getter} + * @return Current builder + * @since 2.3 + */ + public Builder setVoiceV2Context(com.sinch.sdk.domains.voice.models.v2.VoiceContext context) { + this.voiceV2Context = + null != context + ? com.sinch.sdk.domains.voice.models.v2.VoiceContext.builder(context) + : null; + return this; + } + /** * Set Conversation Region to be used * @@ -585,6 +671,19 @@ public Builder setRetryConfiguration(RetryConfiguration retryConfiguration) { return this; } + /** + * Set the transport level settings + * + * @param transportSettings Transport settings, or {@code null} to restore the defaults + * @return Current builder + * @see Configuration#getTransportSettings() getter + * @since 2.3 + */ + public Builder setTransportSettings(TransportSettings transportSettings) { + this.transportSettings = transportSettings; + return this; + } + /** * Build a Configuration instance from builder current state * @@ -603,9 +702,11 @@ public Configuration build() { null != smsContext ? smsContext.build() : null, null != verificationContext ? verificationContext.build() : null, null != voiceContext ? voiceContext.build() : null, + null != voiceV2Context ? voiceV2Context.build() : null, null != conversationContext ? conversationContext.build() : null, null != numberLookupContext ? numberLookupContext.build() : null, - retryConfiguration); + retryConfiguration, + transportSettings); } } } diff --git a/client/src/main/com/sinch/sdk/models/TransportSettings.java b/client/src/main/com/sinch/sdk/models/TransportSettings.java new file mode 100644 index 000000000..8a21c0439 --- /dev/null +++ b/client/src/main/com/sinch/sdk/models/TransportSettings.java @@ -0,0 +1,150 @@ +package com.sinch.sdk.models; + +/** + * Transport level settings applied to every request the SDK sends. + * + * @since 2.3 + */ +public class TransportSettings { + + /** Timeout applied to HTTP I/O operations when none is configured. */ + public static final int DEFAULT_TIMEOUT_SECONDS = 60; + + /** Sinch authentication is performed for the endpoints supporting it when none is configured. */ + public static final boolean DEFAULT_USE_SINCH_AUTH = true; + + /** + * Settings holding the default of every field: {@link #DEFAULT_TIMEOUT_SECONDS} and {@link + * #DEFAULT_USE_SINCH_AUTH}. + * + * @since 2.3 + */ + public static final TransportSettings DEFAULTS = + builder() + .setTimeoutSeconds(DEFAULT_TIMEOUT_SECONDS) + .setUseSinchAuth(DEFAULT_USE_SINCH_AUTH) + .build(); + + private final int timeoutSeconds; + private final boolean useSinchAuth; + + private TransportSettings(int timeoutSeconds, boolean useSinchAuth) { + this.timeoutSeconds = timeoutSeconds; + this.useSinchAuth = useSinchAuth; + } + + /** + * Timeout applied to HTTP I/O operations. Zero disables it. + * + * @return Timeout in seconds + * @since 2.3 + */ + public int getTimeoutSeconds() { + return timeoutSeconds; + } + + /** + * Whether the endpoints supporting it authenticate against the Sinch auth server. + * + * @return {@code true} when Sinch authentication is enabled, {@code false} when it is disabled + * @since 2.3 + */ + public boolean getUseSinchAuth() { + return useSinchAuth; + } + + @Override + public String toString() { + return "TransportSettings{" + + "timeoutSeconds=" + + timeoutSeconds + + ", useSinchAuth=" + + useSinchAuth + + '}'; + } + + /** + * Getting Builder + * + * @return New Builder instance + * @since 2.3 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Getting Builder pre-populated from an existing instance + * + * @param settings Source settings to fill initial builder state + * @return New Builder instance + * @since 2.3 + */ + public static Builder builder(TransportSettings settings) { + return new Builder(settings); + } + + /** + * Dedicated Builder + * + * @since 2.3 + */ + public static class Builder { + + private Integer timeoutSeconds; + private Boolean useSinchAuth; + + protected Builder() {} + + protected Builder(TransportSettings settings) { + if (null == settings) { + return; + } + this.timeoutSeconds = settings.timeoutSeconds; + this.useSinchAuth = settings.useSinchAuth; + } + + /** + * Set the timeout applied to HTTP I/O operations. Zero disables it. + * + * @param timeoutSeconds Timeout in seconds, zero to disable it + * @return Current builder + * @see TransportSettings#getTimeoutSeconds() getter + * @since 2.3 + */ + public Builder setTimeoutSeconds(Integer timeoutSeconds) { + this.timeoutSeconds = timeoutSeconds; + return this; + } + + /** + * Set whether the endpoints supporting it authenticate against the Sinch auth server + * + * @param useSinchAuth {@code true} (default) to authenticate with a Sinch access token, {@code + * false} to disable Sinch authentication + * @return Current builder + * @see TransportSettings#getUseSinchAuth() getter + * @since 2.3 + */ + public Builder setUseSinchAuth(Boolean useSinchAuth) { + this.useSinchAuth = useSinchAuth; + return this; + } + + /** + * Build a {@link TransportSettings} instance from builder current state + * + * @return TransportSettings instance built from current builder state + * @since 2.3 + */ + public TransportSettings build() { + if (null != timeoutSeconds && timeoutSeconds < 0) { + throw new IllegalArgumentException( + "TransportSettings: timeoutSeconds must be zero or greater, got: " + timeoutSeconds); + } + return new TransportSettings( + null != timeoutSeconds ? timeoutSeconds : DEFAULT_TIMEOUT_SECONDS, + null != useSinchAuth ? useSinchAuth : DEFAULT_USE_SINCH_AUTH); + } + } +} diff --git a/client/src/test/java/com/sinch/sdk/SinchClientTest.java b/client/src/test/java/com/sinch/sdk/SinchClientTest.java index c06f2ec2b..297fb256f 100644 --- a/client/src/test/java/com/sinch/sdk/SinchClientTest.java +++ b/client/src/test/java/com/sinch/sdk/SinchClientTest.java @@ -217,6 +217,98 @@ void voiceUrlFromUrl() { assertEquals("my foo url", client.getConfiguration().getVoiceContext().get().getVoiceUrl()); } + @Test + void defaultVoiceV2Region() { + Configuration configuration = Configuration.builder().build(); + SinchClient client = new SinchClient(configuration); + assertEquals( + com.sinch.sdk.domains.voice.models.v2.VoiceRegion.GLOBAL, + client.getConfiguration().getVoiceV2Context().get().getVoiceRegion()); + } + + @Test + void defaultVoiceV2Url() { + Configuration configuration = Configuration.builder().build(); + SinchClient client = new SinchClient(configuration); + assertEquals( + "https://voice.api.sinch.com", + client.getConfiguration().getVoiceV2Context().get().getVoiceUrl()); + } + + @Test + void voiceV2UrlFromRegion() { + Configuration configuration = + Configuration.builder() + .setVoiceV2Context( + com.sinch.sdk.domains.voice.models.v2.VoiceContext.builder() + .setVoiceRegion(com.sinch.sdk.domains.voice.models.v2.VoiceRegion.EUROPE) + .build()) + .build(); + SinchClient client = new SinchClient(configuration); + assertEquals( + "https://eu1.voice.api.sinch.com", + client.getConfiguration().getVoiceV2Context().get().getVoiceUrl()); + } + + @Test + void voiceV2UrlFromUrl() { + Configuration configuration = + Configuration.builder() + .setVoiceV2Context( + com.sinch.sdk.domains.voice.models.v2.VoiceContext.builder() + .setVoiceRegion(com.sinch.sdk.domains.voice.models.v2.VoiceRegion.EUROPE) + .setVoiceUrl("my foo url") + .build()) + .build(); + SinchClient client = new SinchClient(configuration); + assertEquals( + com.sinch.sdk.domains.voice.models.v2.VoiceRegion.EUROPE, + client.getConfiguration().getVoiceV2Context().get().getVoiceRegion()); + assertEquals("my foo url", client.getConfiguration().getVoiceV2Context().get().getVoiceUrl()); + } + + @Test + void voiceV2UrlFromRegionShortcut() { + Configuration configuration = + Configuration.builder() + .setVoiceV2Region(com.sinch.sdk.domains.voice.models.v2.VoiceRegion.AUSTRALIA) + .build(); + SinchClient client = new SinchClient(configuration); + assertEquals( + "https://au1.voice.api.sinch.com", + client.getConfiguration().getVoiceV2Context().get().getVoiceUrl()); + } + + @Test + void voiceV2UrlFromUrlShortcut() { + Configuration configuration = + Configuration.builder() + .setVoiceV2Region(com.sinch.sdk.domains.voice.models.v2.VoiceRegion.EUROPE) + .setVoiceV2Url("my foo url") + .build(); + SinchClient client = new SinchClient(configuration); + assertEquals( + com.sinch.sdk.domains.voice.models.v2.VoiceRegion.EUROPE, + client.getConfiguration().getVoiceV2Context().get().getVoiceRegion()); + assertEquals("my foo url", client.getConfiguration().getVoiceV2Context().get().getVoiceUrl()); + } + + // The V1 and V2 regions are separate settings: one must not leak into the other. + @Test + void voiceV1RegionDoesNotChangeVoiceV2Url() { + Configuration configuration = + Configuration.builder() + .setVoiceContext(VoiceContext.builder().setVoiceRegion(VoiceRegion.EUROPE).build()) + .build(); + SinchClient client = new SinchClient(configuration); + assertEquals( + "https://calling-euc1.api.sinch.com", + client.getConfiguration().getVoiceContext().get().getVoiceUrl()); + assertEquals( + "https://voice.api.sinch.com", + client.getConfiguration().getVoiceV2Context().get().getVoiceUrl()); + } + @Test void voiceApplicationManagementUrlFromUrl() { Configuration configuration = diff --git a/client/src/test/java/com/sinch/sdk/SinchClientUseSinchAuthTest.java b/client/src/test/java/com/sinch/sdk/SinchClientUseSinchAuthTest.java new file mode 100644 index 000000000..d8e325e66 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/SinchClientUseSinchAuthTest.java @@ -0,0 +1,128 @@ +package com.sinch.sdk; + +import static org.junit.jupiter.api.Assertions.*; + +import com.sinch.sdk.domains.numberlookup.models.v2.request.NumberLookupRequest; +import com.sinch.sdk.models.Configuration; +import com.sinch.sdk.models.NumberLookupContext; +import com.sinch.sdk.models.TransportSettings; +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import okhttp3.mockwebserver.Dispatcher; +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 SinchClientUseSinchAuthTest extends BaseTest { + + private MockWebServer server; + private final AtomicInteger authRequests = new AtomicInteger(); + private volatile Optional lastLookupAuthorization = Optional.empty(); + + @BeforeEach + void setUp() throws IOException { + server = new MockWebServer(); + authRequests.set(0); + server.setDispatcher( + new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + String path = null != request.getPath() ? request.getPath() : ""; + if (path.startsWith("/auth")) { + authRequests.incrementAndGet(); + return new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/json") + .setBody( + "{\"access_token\":\"t\",\"expires_in\":3600,\"token_type\":\"Bearer\"}"); + } + lastLookupAuthorization = Optional.ofNullable(request.getHeader("Authorization")); + return new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/json") + .setBody("{}"); + } + }); + server.start(); + } + + @AfterEach + void tearDown() throws IOException { + server.shutdown(); + } + + private Configuration.Builder base() { + return Configuration.builder() + .setProjectId("project") + .setOAuthUrl(server.url("/auth").toString()) + .setNumberLookupContext( + NumberLookupContext.builder() + .setNumberLookupUrl(server.url("/lookup").toString()) + .build()); + } + + private void lookup(SinchClient client) { + client.lookup().v2().lookup(NumberLookupRequest.builder().setNumber("+46700000000").build()); + } + + @Test + void disabledSkipsTheTokenFetchAndSendsNoAuthorization() { + SinchClient client = + new SinchClient( + base() + .setTransportSettings(TransportSettings.builder().setUseSinchAuth(false).build()) + .build()); + + lookup(client); + + assertEquals(0, authRequests.get(), "no token must be fetched when Sinch auth is disabled"); + assertFalse(lastLookupAuthorization.isPresent(), "no Authorization header must be sent"); + client.close(); + } + + @Test + void disabledDoesNotRequireKeyIdNorKeySecret() { + SinchClient client = + new SinchClient( + base() + .setTransportSettings(TransportSettings.builder().setUseSinchAuth(false).build()) + .build()); + + assertDoesNotThrow(() -> lookup(client)); + client.close(); + } + + @Test + void enabledStillFetchesATokenAndAuthorizes() { + SinchClient client = + new SinchClient( + base() + .setKeyId("key") + .setKeySecret("secret") + .setTransportSettings(TransportSettings.builder().setUseSinchAuth(true).build()) + .build()); + + lookup(client); + + assertEquals(1, authRequests.get(), "the token exchange must still happen by default"); + assertTrue(lastLookupAuthorization.isPresent()); + assertTrue(lastLookupAuthorization.get().startsWith("Bearer ")); + client.close(); + } + + @Test + void enabledStillRequiresCredentials() { + SinchClient client = + new SinchClient( + base() + .setTransportSettings(TransportSettings.builder().setUseSinchAuth(true).build()) + .build()); + + assertThrows(RuntimeException.class, () -> lookup(client)); + client.close(); + } +} diff --git a/client/src/test/java/com/sinch/sdk/auth/SinchEventsApplicationAuthValidationTest.java b/client/src/test/java/com/sinch/sdk/auth/SinchEventsApplicationAuthValidationTest.java new file mode 100644 index 000000000..d22768890 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/auth/SinchEventsApplicationAuthValidationTest.java @@ -0,0 +1,145 @@ +package com.sinch.sdk.auth; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +public abstract class SinchEventsApplicationAuthValidationTest { + + protected static final String AUTHORIZATION_HEADER = "authorization"; + protected static final String CONTENT_TYPE_HEADER = "content-type"; + protected static final String TIMESTAMP_HEADER = "x-timestamp"; + + protected static final String METHOD = "POST"; + + protected abstract boolean validateAuthenticationHeader( + String method, String path, Map headers, String jsonPayload); + + protected abstract String applicationKey(); + + protected abstract String path(); + + protected abstract String contentType(); + + protected abstract String timestamp(); + + protected abstract String payload(); + + protected abstract String signature(); + + @Test + void checkValidRequest() { + + Assertions.assertThat(validateAuthenticationHeader(METHOD, path(), validHeaders(), payload())) + .isTrue(); + } + + @Test + void checkValidRequestWithHeaderNamesCaseInsensitivity() { + + Map headers = + headers( + entry("Authorization", authorizationHeaderValue()), + entry("Content-Type", contentType()), + entry("X-Timestamp", timestamp())); + + Assertions.assertThat(validateAuthenticationHeader(METHOD, path(), headers, payload())) + .isTrue(); + } + + @Test + void checkValidRequestWithAuthorizationKeywordCaseInsensitivity() { + + Map headers = + validHeadersWith( + AUTHORIZATION_HEADER, "APPLICATION " + applicationKey() + ":" + signature()); + + Assertions.assertThat(validateAuthenticationHeader(METHOD, path(), headers, payload())) + .isTrue(); + } + + @Test + void checkFailureOnMissingAuthorizationHeader() { + + Map headers = validHeadersWithout(AUTHORIZATION_HEADER); + + Assertions.assertThat(validateAuthenticationHeader(METHOD, path(), headers, payload())) + .isFalse(); + } + + @Test + void checkFailureOnEmptyAuthorizationHeader() { + + Map headers = validHeadersWith(AUTHORIZATION_HEADER, ""); + + Assertions.assertThat(validateAuthenticationHeader(METHOD, path(), headers, payload())) + .isFalse(); + } + + @Test + void checkFailureOnNullAuthorizationHeader() { + + Map headers = validHeadersWith(AUTHORIZATION_HEADER, null); + + Assertions.assertThat(validateAuthenticationHeader(METHOD, path(), headers, payload())) + .isFalse(); + } + + @Test + void checkFailureOnUnknownAuthorizationKeyword() { + + Map headers = + validHeadersWith(AUTHORIZATION_HEADER, "bearer " + applicationKey() + ":" + signature()); + + Assertions.assertThat(validateAuthenticationHeader(METHOD, path(), headers, payload())) + .isFalse(); + } + + @Test + void checkFailureOnBasicAuthorizationKeyword() { + + Map headers = + validHeadersWith(AUTHORIZATION_HEADER, "basic " + applicationKey() + ":" + signature()); + + Assertions.assertThat(validateAuthenticationHeader(METHOD, path(), headers, payload())) + .isFalse(); + } + + private String authorizationHeaderValue() { + return "application " + applicationKey() + ":" + signature(); + } + + private Map validHeaders() { + return headers( + entry(AUTHORIZATION_HEADER, authorizationHeaderValue()), + entry(CONTENT_TYPE_HEADER, contentType()), + entry(TIMESTAMP_HEADER, timestamp())); + } + + private Map validHeadersWith(String header, String value) { + Map headers = validHeaders(); + headers.put(header, value); + return headers; + } + + private Map validHeadersWithout(String header) { + Map headers = validHeaders(); + headers.remove(header); + return headers; + } + + @SafeVarargs + private static Map headers(Map.Entry... entries) { + // not relying on Collectors.toMap: null values are part of the scenarios + Map headers = new LinkedHashMap<>(); + for (Map.Entry entry : entries) { + headers.put(entry.getKey(), entry.getValue()); + } + return headers; + } + + private static Map.Entry entry(String key, String value) { + return new java.util.AbstractMap.SimpleEntry<>(key, value); + } +} diff --git a/client/src/test/java/com/sinch/sdk/auth/adapters/ApplicationAuthManagerTest.java b/client/src/test/java/com/sinch/sdk/auth/adapters/ApplicationAuthManagerTest.java index 6034d753f..a0264363d 100644 --- a/client/src/test/java/com/sinch/sdk/auth/adapters/ApplicationAuthManagerTest.java +++ b/client/src/test/java/com/sinch/sdk/auth/adapters/ApplicationAuthManagerTest.java @@ -100,6 +100,151 @@ void checkApplicationAuthenticationFailureOnHash() throws ApiException { Assertions.assertThat(authenticationResult).isEqualTo(false); } + @Test + void checkValidateAuthenticatedRequestWithQueryParametersInPath() { + + // query parameters are not part of the signed data + boolean authenticationResult = + authManager.validateAuthenticatedRequest( + METHOD, PATH + "?foo=bar&baz=42", validHeaders(), PAYLOAD); + + Assertions.assertThat(authenticationResult).isEqualTo(true); + } + + @Test + void checkValidateAuthenticatedRequestWithAbsoluteUrlAsPath() { + + // only the path part of an absolute URL is signed + boolean authenticationResult = + authManager.validateAuthenticatedRequest( + METHOD, "https://callbacks.yourdomain.com" + PATH, validHeaders(), PAYLOAD); + + Assertions.assertThat(authenticationResult).isEqualTo(true); + } + + @Test + void checkApplicationAuthenticationFailureOnKeywordOnly() { + + boolean authenticationResult = + authManager.validateAuthenticatedRequest( + METHOD, PATH, validHeadersWith("authorization", "application"), PAYLOAD); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + @Test + void checkApplicationAuthenticationFailureOnMissingHash() { + + boolean authenticationResult = + authManager.validateAuthenticatedRequest( + METHOD, PATH, getHeaders("application", KEY, ""), PAYLOAD); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + @Test + void checkApplicationAuthenticationFailureOnMissingTimestamp() { + + boolean authenticationResult = + authManager.validateAuthenticatedRequest( + METHOD, PATH, validHeadersWithout("x-timestamp"), PAYLOAD); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + @Test + void checkApplicationAuthenticationFailureOnTimestamp() { + + boolean authenticationResult = + authManager.validateAuthenticatedRequest( + METHOD, PATH, validHeadersWith("x-timestamp", "2019-11-03T10:59:41Z"), PAYLOAD); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + @Test + void checkApplicationAuthenticationFailureOnMissingContentType() { + + boolean authenticationResult = + authManager.validateAuthenticatedRequest( + METHOD, PATH, validHeadersWithout("content-type"), PAYLOAD); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + @Test + void checkApplicationAuthenticationFailureOnContentType() { + + boolean authenticationResult = + authManager.validateAuthenticatedRequest( + METHOD, PATH, validHeadersWith("content-type", "text/plain"), PAYLOAD); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + @Test + void checkApplicationAuthenticationFailureOnMethod() { + + boolean authenticationResult = + authManager.validateAuthenticatedRequest( + HttpMethod.GET.name(), PATH, validHeaders(), PAYLOAD); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + @Test + void checkApplicationAuthenticationFailureOnPath() { + + boolean authenticationResult = + authManager.validateAuthenticatedRequest(METHOD, "/not/that/path", validHeaders(), PAYLOAD); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + @Test + void checkApplicationAuthenticationFailureOnPayload() { + + boolean authenticationResult = + authManager.validateAuthenticatedRequest( + METHOD, PATH, validHeaders(), "{\"hello\":\"world\"}"); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + @Test + void checkApplicationAuthenticationFailureOnEmptyPayload() { + + boolean authenticationResult = + authManager.validateAuthenticatedRequest(METHOD, PATH, validHeaders(), ""); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + @Test + void checkApplicationAuthenticationFailureOnNullPayload() { + + boolean authenticationResult = + authManager.validateAuthenticatedRequest(METHOD, PATH, validHeaders(), null); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + private static Map validHeaders() { + return getHeaders("application", KEY, HASH); + } + + private static Map validHeadersWith(String header, String value) { + Map headers = validHeaders(); + headers.put(header, value); + return headers; + } + + private static Map validHeadersWithout(String header) { + Map headers = validHeaders(); + headers.remove(header); + return headers; + } + private static Map getHeaders(String keyword, String key, String hash) { return Stream.of( new AbstractMap.SimpleEntry<>("authorization", keyword + " " + key + ":" + hash), diff --git a/client/src/test/java/com/sinch/sdk/core/adapters/apache/HttpClientTest.java b/client/src/test/java/com/sinch/sdk/core/adapters/apache/HttpClientTest.java index 60594fe35..5334fc360 100644 --- a/client/src/test/java/com/sinch/sdk/core/adapters/apache/HttpClientTest.java +++ b/client/src/test/java/com/sinch/sdk/core/adapters/apache/HttpClientTest.java @@ -15,6 +15,7 @@ import com.sinch.sdk.core.http.HttpRequest; import com.sinch.sdk.core.models.ServerConfiguration; import com.sinch.sdk.http.HttpClientApache; +import com.sinch.sdk.models.TransportSettings; import com.sinch.sdk.models.UnifiedCredentials; import java.io.BufferedWriter; import java.io.File; @@ -54,7 +55,8 @@ class HttpClientTest extends BaseTest { String serverUrl = String.format("http://localhost:%s", mockBackEnd.getPort()); UnifiedCredentials credentials = UnifiedCredentials.builder().build(); ServerConfiguration server = new ServerConfiguration(String.format("%s/auth", serverUrl)); - HttpClientApache httpClient = new HttpClientApache(); + HttpClientApache httpClient = + new HttpClientApache(null, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS); AuthManager basicAuthManager = new BasicAuthManager(credentials); OAuthManager oAuthManager = diff --git a/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/CredentialsValidationHelper.java b/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/CredentialsValidationHelper.java index ad883f9ac..177c45f52 100644 --- a/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/CredentialsValidationHelper.java +++ b/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/CredentialsValidationHelper.java @@ -41,7 +41,7 @@ private static void doNotAcceptNullKeyTemplates( IllegalArgumentException.class, () -> service.accept( - new TemplatesService(credentials, context, server, httpClientSupplier))); + new TemplatesService(credentials, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("keyId")); } @@ -56,7 +56,7 @@ private static void doNotAcceptNullKeySecretTemplates( IllegalArgumentException.class, () -> service.accept( - new TemplatesService(credentials, context, server, httpClientSupplier))); + new TemplatesService(credentials, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("keySecret")); } @@ -72,7 +72,7 @@ private static void doNotAcceptNullProjectTemplates( IllegalArgumentException.class, () -> service.accept( - new TemplatesService(credentials, context, server, httpClientSupplier))); + new TemplatesService(credentials, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("projectId")); } @@ -83,7 +83,9 @@ private static void doNotAcceptNullCredentialsTemplates( Exception exception = assertThrows( NullPointerException.class, - () -> service.accept(new TemplatesService(null, context, server, httpClientSupplier))); + () -> + service.accept( + new TemplatesService(null, context, server, httpClientSupplier, true))); assertTrue( exception.getMessage().contains("Templates service requires credentials to be defined")); } @@ -102,7 +104,7 @@ private static void doNotAcceptNullContextTemplates( NullPointerException.class, () -> service.accept( - new TemplatesService(credentials, null, server, httpClientSupplier))); + new TemplatesService(credentials, null, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("Templates service requires context to be defined")); } @@ -122,7 +124,7 @@ private static void doNotAcceptEmptyURLTemplates( IllegalArgumentException.class, () -> service.accept( - new TemplatesService(credentials, context, server, httpClientSupplier))); + new TemplatesService(credentials, context, server, httpClientSupplier, true))); assertTrue( exception .getMessage() @@ -145,7 +147,7 @@ private static void doNotAcceptNullURLTemplates( IllegalArgumentException.class, () -> service.accept( - new TemplatesService(credentials, context, server, httpClientSupplier))); + new TemplatesService(credentials, context, server, httpClientSupplier, true))); assertTrue( exception .getMessage() @@ -167,7 +169,7 @@ private static void doNotAcceptNullRegionTemplates( NullPointerException.class, () -> service.accept( - new TemplatesService(credentials, context, server, httpClientSupplier))); + new TemplatesService(credentials, context, server, httpClientSupplier, true))); assertTrue( exception.getMessage().contains("Templates service requires 'region' to be defined")); } @@ -188,7 +190,7 @@ private static void doNotAcceptEmptyRegionTemplates( IllegalArgumentException.class, () -> service.accept( - new TemplatesService(credentials, context, server, httpClientSupplier))); + new TemplatesService(credentials, context, server, httpClientSupplier, true))); assertTrue( exception.getMessage().contains("Templates service requires 'region' to be defined")); } @@ -209,7 +211,8 @@ private static void passInitTemplates( ServerConfiguration server = new ServerConfiguration("foo"); assertDoesNotThrow( () -> - service.accept(new TemplatesService(credentials, context, server, httpClientSupplier)), + service.accept( + new TemplatesService(credentials, context, server, httpClientSupplier, true)), "Init passed"); } } diff --git a/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/TemplateServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/TemplateServiceTest.java index 423f3a9ea..d01c155c8 100644 --- a/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/TemplateServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/conversation/api/templates/adapters/TemplateServiceTest.java @@ -18,6 +18,6 @@ void checkCredentialsV2() { @Test void passInitWithoutSettings() { - assertDoesNotThrow(() -> new TemplatesService(null, null, null, null), "Init passed"); + assertDoesNotThrow(() -> new TemplatesService(null, null, null, null, true), "Init passed"); } } diff --git a/client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationServiceTest.java index 0897fc301..3456a67f7 100644 --- a/client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationServiceTest.java @@ -56,11 +56,11 @@ void checkCredentialsTranscoding() { @Test void checkCredentialsSinchEvents() { assertDoesNotThrow( - () -> new ConversationService(null, null, null, null).sinchEvents(), "Init passed"); + () -> new ConversationService(null, null, null, null, true).sinchEvents(), "Init passed"); } @Test void passInitWithoutSettings() { - assertDoesNotThrow(() -> new ConversationService(null, null, null, null), "Init passed"); + assertDoesNotThrow(() -> new ConversationService(null, null, null, null, true), "Init passed"); } } diff --git a/client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/CredentialsValidationHelper.java b/client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/CredentialsValidationHelper.java index ad9c6fd30..81de4435b 100644 --- a/client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/CredentialsValidationHelper.java +++ b/client/src/test/java/com/sinch/sdk/domains/conversation/api/v1/adapters/CredentialsValidationHelper.java @@ -39,7 +39,8 @@ private static void doNotAcceptNullKey( IllegalArgumentException.class, () -> service.accept( - new ConversationService(credentials, context, server, httpClientSupplier))); + new ConversationService( + credentials, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("keyId")); } @@ -54,7 +55,8 @@ private static void doNotAcceptNullKeySecret( IllegalArgumentException.class, () -> service.accept( - new ConversationService(credentials, context, server, httpClientSupplier))); + new ConversationService( + credentials, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("keySecret")); } @@ -70,7 +72,8 @@ private static void doNotAcceptNullProject( IllegalArgumentException.class, () -> service.accept( - new ConversationService(credentials, context, server, httpClientSupplier))); + new ConversationService( + credentials, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("projectId")); } @@ -82,7 +85,8 @@ private static void doNotAcceptNullCredentials( assertThrows( NullPointerException.class, () -> - service.accept(new ConversationService(null, context, server, httpClientSupplier))); + service.accept( + new ConversationService(null, context, server, httpClientSupplier, true))); assertTrue( exception.getMessage().contains("Conversation service requires credentials to be defined")); } @@ -101,7 +105,7 @@ private static void doNotAcceptNullContext( NullPointerException.class, () -> service.accept( - new ConversationService(credentials, null, server, httpClientSupplier))); + new ConversationService(credentials, null, server, httpClientSupplier, true))); assertTrue( exception.getMessage().contains("Conversation service requires context to be defined")); } @@ -122,7 +126,8 @@ private static void doNotAcceptEmptyURL( IllegalArgumentException.class, () -> service.accept( - new ConversationService(credentials, context, server, httpClientSupplier))); + new ConversationService( + credentials, context, server, httpClientSupplier, true))); assertTrue( exception.getMessage().contains("Conversation service requires 'url' to be defined")); } @@ -143,7 +148,8 @@ private static void doNotAcceptNullURL( IllegalArgumentException.class, () -> service.accept( - new ConversationService(credentials, context, server, httpClientSupplier))); + new ConversationService( + credentials, context, server, httpClientSupplier, true))); assertTrue( exception.getMessage().contains("Conversation service requires 'url' to be defined")); } @@ -163,7 +169,8 @@ private static void doNotAcceptNullRegion( NullPointerException.class, () -> service.accept( - new ConversationService(credentials, context, server, httpClientSupplier))); + new ConversationService( + credentials, context, server, httpClientSupplier, true))); assertTrue( exception.getMessage().contains("Conversation service requires 'region' to be defined")); } @@ -187,7 +194,8 @@ private static void doNotAcceptEmptyRegion( IllegalArgumentException.class, () -> service.accept( - new ConversationService(credentials, context, server, httpClientSupplier))); + new ConversationService( + credentials, context, server, httpClientSupplier, true))); assertTrue( exception.getMessage().contains("Conversation service requires 'region' to be defined")); } @@ -209,7 +217,7 @@ private static void passInit( assertDoesNotThrow( () -> service.accept( - new ConversationService(credentials, context, server, httpClientSupplier)), + new ConversationService(credentials, context, server, httpClientSupplier, true)), "Init passed"); } } 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 index 4df84f691..1ff3c8d77 100644 --- 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 @@ -36,7 +36,7 @@ static void doNotAcceptNullCredentials( NullPointerException.class, () -> service.accept( - new NumberLookupService(null, context, oAuthServer, httpClientSupplier))); + new NumberLookupService(null, context, oAuthServer, httpClientSupplier, true))); assertTrue( exception .getMessage() @@ -56,7 +56,7 @@ static void doNotAcceptNullKey( () -> service.accept( new NumberLookupService( - credentials, context, oAuthServer, httpClientSupplier))); + credentials, context, oAuthServer, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("keyId")); } @@ -73,7 +73,7 @@ static void doNotAcceptNullKeySecret( () -> service.accept( new NumberLookupService( - credentials, context, oAuthServer, httpClientSupplier))); + credentials, context, oAuthServer, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("keySecret")); } @@ -90,7 +90,7 @@ static void doNotAcceptNullProject( () -> service.accept( new NumberLookupService( - credentials, context, oAuthServer, httpClientSupplier))); + credentials, context, oAuthServer, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("projectId")); } @@ -108,7 +108,8 @@ static void doNotAcceptNullContext( NullPointerException.class, () -> service.accept( - new NumberLookupService(credentials, null, oAuthServer, httpClientSupplier))); + new NumberLookupService( + credentials, null, oAuthServer, httpClientSupplier, true))); assertTrue( exception.getMessage().contains("Number Lookup service requires context to be defined")); } @@ -129,7 +130,7 @@ static void doNotAcceptNullNumberLookupUrl( () -> service.accept( new NumberLookupService( - credentials, context, oAuthServer, httpClientSupplier))); + credentials, context, oAuthServer, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("numberLookupUrl")); } @@ -147,7 +148,8 @@ static void initPassed( assertDoesNotThrow( () -> service.accept( - new NumberLookupService(credentials, context, oAuthServer, httpClientSupplier)), + new NumberLookupService( + credentials, context, oAuthServer, httpClientSupplier, true)), "Init passed"); } } diff --git a/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/ActiveNumberServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/ActiveNumberServiceTest.java index df0917c8a..d1392b2d1 100644 --- a/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/ActiveNumberServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/ActiveNumberServiceTest.java @@ -109,7 +109,7 @@ class ActiveNumberServiceTest extends NumbersBaseTest { public void initMocks() { service = new com.sinch.sdk.domains.numbers.api.v1.adapters.NumbersService( - credentials, context, oAuthServer, httpClientSupplier); + credentials, context, oAuthServer, httpClientSupplier, true); } @Test diff --git a/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableNumberServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableNumberServiceTest.java index 04d6d202d..26f6811e2 100644 --- a/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableNumberServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/AvailableNumberServiceTest.java @@ -95,7 +95,7 @@ class AvailableNumberServiceTest extends BaseTest { public void initMocks() { service = new com.sinch.sdk.domains.numbers.api.v1.adapters.NumbersService( - credentials, context, oAuthServer, httpClientSupplier); + credentials, context, oAuthServer, httpClientSupplier, true); } @Test diff --git a/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/CredentialsValidationHelper.java b/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/CredentialsValidationHelper.java index 3d845c733..76b35d83a 100644 --- a/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/CredentialsValidationHelper.java +++ b/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/CredentialsValidationHelper.java @@ -36,7 +36,8 @@ static void doNotAcceptNullKey( IllegalArgumentException.class, () -> service.accept( - new NumbersService(credentials, context, oAuthServer, httpClientSupplier))); + new NumbersService( + credentials, context, oAuthServer, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("keyId")); } @@ -51,7 +52,8 @@ static void doNotAcceptNullKeySecret( IllegalArgumentException.class, () -> service.accept( - new NumbersService(credentials, context, oAuthServer, httpClientSupplier))); + new NumbersService( + credentials, context, oAuthServer, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("keySecret")); } @@ -66,7 +68,8 @@ static void doNotAcceptNullProject( IllegalArgumentException.class, () -> service.accept( - new NumbersService(credentials, context, oAuthServer, httpClientSupplier))); + new NumbersService( + credentials, context, oAuthServer, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("projectId")); } @@ -84,7 +87,7 @@ static void doNotAcceptNullContext( NullPointerException.class, () -> service.accept( - new NumbersService(credentials, null, oAuthServer, httpClientSupplier))); + new NumbersService(credentials, null, oAuthServer, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("Numbers service requires context to be defined")); } @@ -103,7 +106,8 @@ static void doNotAcceptNullNumbersUrl( IllegalArgumentException.class, () -> service.accept( - new NumbersService(credentials, context, oAuthServer, httpClientSupplier))); + new NumbersService( + credentials, context, oAuthServer, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("numbersUrl")); } @@ -120,7 +124,7 @@ static void initPassed( assertDoesNotThrow( () -> service.accept( - new NumbersService(credentials, context, oAuthServer, httpClientSupplier)), + new NumbersService(credentials, context, oAuthServer, httpClientSupplier, true)), "Init passed"); } } diff --git a/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersServiceTest.java index e11fb9bd4..cd6600b67 100644 --- a/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersServiceTest.java @@ -34,6 +34,6 @@ void checkCredentialsEventDestinations() { @Test void checkCredentialsSinchEvents() { assertDoesNotThrow( - () -> new NumbersService(null, null, null, null).sinchEvents(), "Init passed"); + () -> new NumbersService(null, null, null, null, true).sinchEvents(), "Init passed"); } } diff --git a/client/src/test/java/com/sinch/sdk/domains/sms/api/v1/adapters/CredentialsValidationHelper.java b/client/src/test/java/com/sinch/sdk/domains/sms/api/v1/adapters/CredentialsValidationHelper.java index 5f53f6034..bd81eb897 100644 --- a/client/src/test/java/com/sinch/sdk/domains/sms/api/v1/adapters/CredentialsValidationHelper.java +++ b/client/src/test/java/com/sinch/sdk/domains/sms/api/v1/adapters/CredentialsValidationHelper.java @@ -46,7 +46,9 @@ static void projectIdDoNotAcceptNullKey( Exception exception = assertThrows( IllegalArgumentException.class, - () -> service.accept(new SMSService(credentials, context, server, httpClientSupplier))); + () -> + service.accept( + new SMSService(credentials, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("keyId")); } @@ -60,7 +62,9 @@ static void projectIdDoNotAcceptNullKeySecret( Exception exception = assertThrows( IllegalArgumentException.class, - () -> service.accept(new SMSService(credentials, context, server, httpClientSupplier))); + () -> + service.accept( + new SMSService(credentials, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("keySecret")); } @@ -75,7 +79,9 @@ static void projectIdDoNotAcceptNullProject( Exception exception = assertThrows( IllegalArgumentException.class, - () -> service.accept(new SMSService(credentials, context, server, httpClientSupplier))); + () -> + service.accept( + new SMSService(credentials, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("projectId")); } @@ -88,7 +94,7 @@ static void projectIdDoNotAcceptNullCredentials( Exception exception = assertThrows( NullPointerException.class, - () -> service.accept(new SMSService(null, context, server, httpClientSupplier))); + () -> service.accept(new SMSService(null, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("Credentials must be defined")); } @@ -104,7 +110,9 @@ static void projectIdDoNotAcceptNullContext( Exception exception = assertThrows( NullPointerException.class, - () -> service.accept(new SMSService(credentials, null, server, httpClientSupplier))); + () -> + service.accept( + new SMSService(credentials, null, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("Context must be defined")); } @@ -121,7 +129,9 @@ static void projectIdDoNotAcceptNullSmsUrl( Exception exception = assertThrows( IllegalArgumentException.class, - () -> service.accept(new SMSService(credentials, context, server, httpClientSupplier))); + () -> + service.accept( + new SMSService(credentials, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("SMS Url")); } @@ -138,7 +148,9 @@ static void projectIdDoNotAcceptNullSmsRegion( Exception exception = assertThrows( NullPointerException.class, - () -> service.accept(new SMSService(credentials, context, server, httpClientSupplier))); + () -> + service.accept( + new SMSService(credentials, context, server, httpClientSupplier, true))); assertTrue(exception.getMessage().contains("SMS Region")); } @@ -156,7 +168,8 @@ static void projectIdUsagePassed( ServerConfiguration server = new ServerConfiguration("https://oauth.foo.url"); assertDoesNotThrow( - () -> service.accept(new SMSService(credentials, context, server, httpClientSupplier)), + () -> + service.accept(new SMSService(credentials, context, server, httpClientSupplier, true)), "Init passed"); } diff --git a/client/src/test/java/com/sinch/sdk/domains/sms/api/v1/adapters/SMSServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/sms/api/v1/adapters/SMSServiceTest.java index 4d01f8102..c07b92ed9 100644 --- a/client/src/test/java/com/sinch/sdk/domains/sms/api/v1/adapters/SMSServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/sms/api/v1/adapters/SMSServiceTest.java @@ -32,11 +32,12 @@ void checkCredentialsGroups() { @Test void checkCredentialsSinchEvents() { - assertDoesNotThrow(() -> new SMSService(null, null, null, null).sinchEvents(), "Init passed"); + assertDoesNotThrow( + () -> new SMSService(null, null, null, null, true).sinchEvents(), "Init passed"); } @Test void passInitWithoutSettings() { - assertDoesNotThrow(() -> new SMSService(null, null, null, null), "Init passed"); + assertDoesNotThrow(() -> new SMSService(null, null, null, null, true), "Init passed"); } } diff --git a/client/src/test/java/com/sinch/sdk/domains/verification/api/v1/adapters/SinchEventsAuthenticationValidationTest.java b/client/src/test/java/com/sinch/sdk/domains/verification/api/v1/adapters/SinchEventsAuthenticationValidationTest.java new file mode 100644 index 000000000..2520b793d --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/verification/api/v1/adapters/SinchEventsAuthenticationValidationTest.java @@ -0,0 +1,65 @@ +package com.sinch.sdk.domains.verification.api.v1.adapters; + +import com.sinch.sdk.auth.SinchEventsApplicationAuthValidationTest; +import com.sinch.sdk.models.ApplicationCredentials; +import com.sinch.sdk.models.VerificationContext; +import java.util.Map; + +public class SinchEventsAuthenticationValidationTest + extends SinchEventsApplicationAuthValidationTest { + + static final String APPLICATION_KEY = "789"; + static final String APPLICATION_SECRET = "9876543210"; + static final String PATH = "/VerificationRequestEvent"; + static final String CONTENT_TYPE = "application/json; charset=utf-8"; + static final String TIMESTAMP = "2023-12-01T15:01:20.0406449Z"; + static final String PAYLOAD = + "{\"id\":\"018c25e1-7163-5677-b8fb-467d7b1cffa5\",\"event\":\"VerificationResultEvent\",\"method\":\"sms\",\"identity\":{\"verified\":false,\"type\":\"number\",\"endpoint\":\"+33628254417\"},\"status\":\"FAIL\",\"reason\":\"Expired\"}"; + static final String SIGNATURE = "xfKhO0XvlRNJraahUBEJzzi1f3Fn3pYO41/ZzwOHPaQ="; + + final SinchEventsService service = + new VerificationService( + ApplicationCredentials.builder() + .setApplicationKey(APPLICATION_KEY) + .setApplicationSecret(APPLICATION_SECRET) + .build(), + VerificationContext.builder().setVerificationUrl("foo").build(), + null) + .sinchEvents(); + + @Override + protected boolean validateAuthenticationHeader( + String method, String path, Map headers, String jsonPayload) { + return service.validateAuthenticationHeader(method, path, headers, jsonPayload); + } + + @Override + protected String applicationKey() { + return APPLICATION_KEY; + } + + @Override + protected String path() { + return PATH; + } + + @Override + protected String contentType() { + return CONTENT_TYPE; + } + + @Override + protected String timestamp() { + return TIMESTAMP; + } + + @Override + protected String payload() { + return PAYLOAD; + } + + @Override + protected String signature() { + return SIGNATURE; + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/verification/api/v1/adapters/SinchEventsServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/verification/api/v1/adapters/SinchEventsServiceTest.java index 446f5fdfc..4a2538bd2 100644 --- a/client/src/test/java/com/sinch/sdk/domains/verification/api/v1/adapters/SinchEventsServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/verification/api/v1/adapters/SinchEventsServiceTest.java @@ -12,11 +12,6 @@ import com.sinch.sdk.models.ApplicationCredentials; import com.sinch.sdk.models.VerificationContext; import java.io.IOException; -import java.util.AbstractMap; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.assertj.core.api.Assertions; import org.json.JSONException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -25,9 +20,6 @@ @TestWithResources public class SinchEventsServiceTest extends VerificationBaseTest { - String request = - "{\"id\":\"018c25e1-7163-5677-b8fb-467d7b1cffa5\",\"event\":\"VerificationResultEvent\",\"method\":\"sms\",\"identity\":{\"verified\":false,\"type\":\"number\",\"endpoint\":\"+33628254417\"},\"status\":\"FAIL\",\"reason\":\"Expired\"}"; - @GivenTextResource("/domains/verification/v1/sinchevents/VerificationStartEventDto.json") static String jsonVerificationRequestEventDto; @@ -52,58 +44,6 @@ public class SinchEventsServiceTest extends VerificationBaseTest { SinchEventsService sinchEventsService; - @Test - void checkApplicationAuthentication() throws ApiException { - - Map headers = - Stream.of( - new AbstractMap.SimpleEntry<>( - "authorization", - "application 789:xfKhO0XvlRNJraahUBEJzzi1f3Fn3pYO41/ZzwOHPaQ="), - new AbstractMap.SimpleEntry<>("content-type", "application/json; charset=utf-8"), - new AbstractMap.SimpleEntry<>("x-timestamp", "2023-12-01T15:01:20.0406449Z")) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - boolean authenticationResult = - sinchEventsService.validateAuthenticationHeader( - "POST", "/VerificationRequestEvent", headers, request); - - Assertions.assertThat(authenticationResult).isEqualTo(true); - } - - @Test - void checkApplicationEmptyAuthorizationHeader() throws ApiException { - - Map headers = - Stream.of( - new AbstractMap.SimpleEntry<>("authorization", ""), - new AbstractMap.SimpleEntry<>("content-type", "application/json; charset=utf-8"), - new AbstractMap.SimpleEntry<>("x-timestamp", "2023-12-01T15:01:20.0406449Z")) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - boolean authenticationResult = - sinchEventsService.validateAuthenticationHeader( - "POST", "/VerificationRequestEvent", headers, request); - - Assertions.assertThat(authenticationResult).isEqualTo(false); - } - - @Test - void checkApplicationNoAuthorizationHeader() throws ApiException { - - Map headers = - Stream.of( - new AbstractMap.SimpleEntry<>("content-type", "application/json; charset=utf-8"), - new AbstractMap.SimpleEntry<>("x-timestamp", "2023-12-01T15:01:20.0406449Z")) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - boolean authenticationResult = - sinchEventsService.validateAuthenticationHeader( - "POST", "/VerificationRequestEvent", headers, request); - - Assertions.assertThat(authenticationResult).isEqualTo(false); - } - @Test void checkParseEventVerificationRequestEventDto() throws ApiException { diff --git a/client/src/test/java/com/sinch/sdk/domains/voice/api/v1/adapters/SinchEventsAuthenticationValidationTest.java b/client/src/test/java/com/sinch/sdk/domains/voice/api/v1/adapters/SinchEventsAuthenticationValidationTest.java new file mode 100644 index 000000000..f7618eb44 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/voice/api/v1/adapters/SinchEventsAuthenticationValidationTest.java @@ -0,0 +1,65 @@ +package com.sinch.sdk.domains.voice.api.v1.adapters; + +import com.sinch.sdk.auth.SinchEventsApplicationAuthValidationTest; +import com.sinch.sdk.models.ApplicationCredentials; +import com.sinch.sdk.models.VoiceContext; +import java.util.Map; + +public class SinchEventsAuthenticationValidationTest + extends SinchEventsApplicationAuthValidationTest { + + static final String APPLICATION_KEY = "669E367E-6BBA-48AB-AF15-266871C28135"; + static final String APPLICATION_SECRET = "BeIukql3pTKJ8RGL5zo0DA=="; + static final String PATH = "/sinch/callback/ace"; + static final String CONTENT_TYPE = "application/json"; + static final String TIMESTAMP = "2014-09-24T10:59:41Z"; + static final String PAYLOAD = + "{\"event\":\"ace\",\"callid\":\"822aa4b7-05b4-4d83-87c7-1f835ee0b6f6_257\",\"timestamp\":\"2014-09-24T10:59:41Z\",\"version\":1}"; + static final String SIGNATURE = "Tg6fMyo8mj9pYfWQ9ssbx3Tc1BNC87IEygAfLbJqZb4="; + + final SinchEventsService service = + new VoiceService( + ApplicationCredentials.builder() + .setApplicationKey(APPLICATION_KEY) + .setApplicationSecret(APPLICATION_SECRET) + .build(), + VoiceContext.builder().setVoiceUrl("foo url").build(), + null) + .sinchEvents(); + + @Override + protected boolean validateAuthenticationHeader( + String method, String path, Map headers, String jsonPayload) { + return service.validateAuthenticationHeader(method, path, headers, jsonPayload); + } + + @Override + protected String applicationKey() { + return APPLICATION_KEY; + } + + @Override + protected String path() { + return PATH; + } + + @Override + protected String contentType() { + return CONTENT_TYPE; + } + + @Override + protected String timestamp() { + return TIMESTAMP; + } + + @Override + protected String payload() { + return PAYLOAD; + } + + @Override + protected String signature() { + return SIGNATURE; + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/BatchesServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/BatchesServiceTest.java new file mode 100644 index 000000000..f4c8d7847 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/BatchesServiceTest.java @@ -0,0 +1,296 @@ +package com.sinch.sdk.domains.voice.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.URLParameter; +import com.sinch.sdk.core.http.URLPathUtils; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.domains.voice.api.v2.BatchesService; +import com.sinch.sdk.domains.voice.models.v2.batches.request.StartBatchQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.batches.request.StartBatchRequestDtoTest; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchDetails; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchDetailsDtoTest; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchStopResponse; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchStopResponseDtoTest; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchSummary; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchSummaryDtoTest; +import com.sinch.sdk.domains.voice.models.v2.batches.response.StartBatchResponse; +import com.sinch.sdk.domains.voice.models.v2.batches.response.StartBatchResponseDtoTest; +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 BatchesServiceTest extends BaseTest { + + @Mock HttpClient httpClient; + @Mock ServerConfiguration serverConfiguration; + @Mock Map authManagers; + + static final String PROJECT_ID = "test_project_id"; + static final String BATCH_ID = "01BX5ZZKBKACTAV9WEVGEMMVRC"; + static final Collection AUTH_NAMES = Arrays.asList("BasicAuth", "SinchOAuth2"); + static final Collection ACCEPTS = + Arrays.asList(HttpContentType.APPLICATION_JSON, "application/problem+json"); + + BatchesService service; + + @GivenTextResource("/domains/voice/v2/batches/request/StartBatchRequestDto.json") + String jsonStartBatchRequestDto; + + @GivenTextResource("/domains/voice/v2/batches/response/StartBatchResponseDto.json") + String jsonStartBatchResponseDto; + + @GivenTextResource("/domains/voice/v2/batches/response/BatchSummaryDto.json") + String jsonBatchSummaryDto; + + @GivenTextResource("/domains/voice/v2/batches/response/BatchDetailsDto.json") + String jsonBatchDetailsDto; + + @GivenTextResource("/domains/voice/v2/batches/response/BatchStopResponseDto.json") + String jsonBatchStopResponseDto; + + @BeforeEach + public void initMocks() { + service = + new BatchesServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), PROJECT_ID); + } + + @Test + void start() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + URLPathUtils.encodePathSegment(PROJECT_ID) + "/batches", + HttpMethod.POST, + Collections.singletonList( + new URLParameter( + "serviceId", "6e124178-c29d-46a5-943c-5c2ae544aade", URLParameter.form, true)), + jsonStartBatchRequestDto, + Collections.singletonMap( + "Idempotency-Key", + StartBatchRequestDtoTest.expectedStartBatchRequest.getIdempotencyKey()), + ACCEPTS, + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES, + true); + HttpResponse httpResponse = + new HttpResponse(201, null, Collections.emptyMap(), jsonStartBatchResponseDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + StartBatchResponse response = + service.start( + StartBatchQueryParameters.builder() + .setServiceId("6e124178-c29d-46a5-943c-5c2ae544aade") + .build(), + StartBatchRequestDtoTest.expectedStartBatchRequest); + + TestHelpers.recursiveEquals(response, StartBatchResponseDtoTest.expectedStartBatchResponse); + } + + @Test + void startMissingRequestThrows() { + + ApiException thrown = Assertions.assertThrows(ApiException.class, () -> service.start(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void startMissingProjectIdThrows() { + + BatchesService serviceWithoutProjectId = + new BatchesServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> + serviceWithoutProjectId.start(StartBatchRequestDtoTest.expectedStartBatchRequest)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void get() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + + URLPathUtils.encodePathSegment(PROJECT_ID) + + "/batches/" + + URLPathUtils.encodePathSegment(BATCH_ID), + HttpMethod.GET, + Collections.emptyList(), + (String) null, + Collections.emptyMap(), + ACCEPTS, + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonBatchSummaryDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + BatchSummary response = service.get(BATCH_ID); + + TestHelpers.recursiveEquals(response, BatchSummaryDtoTest.expectedBatchSummary); + } + + @Test + void getMissingBatchIdThrows() { + + ApiException thrown = Assertions.assertThrows(ApiException.class, () -> service.get(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void getMissingProjectIdThrows() { + + BatchesService serviceWithoutProjectId = + new BatchesServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows(ApiException.class, () -> serviceWithoutProjectId.get(BATCH_ID)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void getDetails() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + + URLPathUtils.encodePathSegment(PROJECT_ID) + + "/batches/" + + URLPathUtils.encodePathSegment(BATCH_ID) + + "/details", + HttpMethod.GET, + Collections.emptyList(), + (String) null, + Collections.emptyMap(), + ACCEPTS, + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonBatchDetailsDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + BatchDetails response = service.getDetails(BATCH_ID); + + TestHelpers.recursiveEquals(response, BatchDetailsDtoTest.expectedBatchDetails); + } + + @Test + void getDetailsMissingBatchIdThrows() { + + ApiException thrown = + Assertions.assertThrows(ApiException.class, () -> service.getDetails(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void getDetailsMissingProjectIdThrows() { + + BatchesService serviceWithoutProjectId = + new BatchesServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, () -> serviceWithoutProjectId.getDetails(BATCH_ID)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void stop() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + + URLPathUtils.encodePathSegment(PROJECT_ID) + + "/batches/" + + URLPathUtils.encodePathSegment(BATCH_ID), + HttpMethod.DELETE, + Collections.emptyList(), + (String) null, + Collections.emptyMap(), + ACCEPTS, + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse(202, null, Collections.emptyMap(), jsonBatchStopResponseDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + BatchStopResponse response = service.stop(BATCH_ID); + + TestHelpers.recursiveEquals(response, BatchStopResponseDtoTest.expectedBatchStopResponse); + } + + @Test + void stopMissingBatchIdThrows() { + + ApiException thrown = Assertions.assertThrows(ApiException.class, () -> service.stop(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void stopMissingProjectIdThrows() { + + BatchesService serviceWithoutProjectId = + new BatchesServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows(ApiException.class, () -> serviceWithoutProjectId.stop(BATCH_ID)); + + Assertions.assertEquals(400, thrown.getCode()); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/CallsServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/CallsServiceTest.java new file mode 100644 index 000000000..02c881caf --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/CallsServiceTest.java @@ -0,0 +1,506 @@ +package com.sinch.sdk.domains.voice.api.v2.adapters; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +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.URLPathUtils; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.domains.voice.api.v2.CallsService; +import com.sinch.sdk.domains.voice.models.v2.Call; +import com.sinch.sdk.domains.voice.models.v2.CallDtoTest; +import com.sinch.sdk.domains.voice.models.v2.CallReason; +import com.sinch.sdk.domains.voice.models.v2.CallResult; +import com.sinch.sdk.domains.voice.models.v2.CallType; +import com.sinch.sdk.domains.voice.models.v2.calls.request.CallPatchRequestDtoTest; +import com.sinch.sdk.domains.voice.models.v2.calls.request.ListCallsQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.calls.request.StartCallQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.calls.request.StartCallRequestDtoTest; +import com.sinch.sdk.domains.voice.models.v2.calls.response.CallsListResponse; +import com.sinch.sdk.domains.voice.models.v2.calls.response.StartCallResponse; +import com.sinch.sdk.domains.voice.models.v2.calls.response.StartCallResponseDtoTest; +import com.sinch.sdk.domains.voice.models.v2.calls.response.internal.CallsListResponseInternalDtoTest; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +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 CallsServiceTest extends BaseTest { + + @Mock HttpClient httpClient; + @Mock ServerConfiguration serverConfiguration; + @Mock Map authManagers; + + static final String PROJECT_ID = "test_project_id"; + static final String CONFIGURED_SERVER_URL = "https://configured.server.com"; + static final Collection AUTH_NAMES = Arrays.asList("BasicAuth", "SinchOAuth2"); + static final Collection ACCEPTS = + Arrays.asList(HttpContentType.APPLICATION_JSON, "application/problem+json"); + + CallsService service; + + @GivenTextResource("/domains/voice/v2/calls/request/StartCallRequestDto.json") + String jsonStartCallRequestDto; + + @GivenTextResource("/domains/voice/v2/calls/request/CallPatchRequestDto.json") + String jsonCallPatchRequestDto; + + @GivenTextResource("/domains/voice/v2/calls/response/StartCallResponseDto.json") + String jsonStartCallResponseDto; + + @GivenTextResource("/domains/voice/v2/CallDto.json") + String jsonCallDto; + + @GivenTextResource("/domains/voice/v2/calls/response/internal/CallsListResponseInternalDto.json") + String jsonCallsListResponseDto; + + @GivenTextResource( + "/domains/voice/v2/calls/response/internal/CallsListResponseInternalLastPageDto.json") + String jsonCallsListLastPageResponseDto; + + @BeforeEach + public void initMocks() { + service = + new CallsServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), PROJECT_ID); + } + + @Test + void start() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + URLPathUtils.encodePathSegment(PROJECT_ID) + "/calls", + HttpMethod.POST, + Collections.singletonList( + new URLParameter( + "serviceId", "6e124178-c29d-46a5-943c-5c2ae544aade", URLParameter.form, true)), + jsonStartCallRequestDto, + Collections.singletonMap( + "Idempotency-Key", + StartCallRequestDtoTest.expectedStartCallRequest.getIdempotencyKey()), + ACCEPTS, + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES, + true); + HttpResponse httpResponse = + new HttpResponse(201, null, Collections.emptyMap(), jsonStartCallResponseDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + StartCallResponse response = + service.start( + StartCallQueryParameters.builder() + .setServiceId("6e124178-c29d-46a5-943c-5c2ae544aade") + .build(), + StartCallRequestDtoTest.expectedStartCallRequest); + + TestHelpers.recursiveEquals(response, StartCallResponseDtoTest.expectedStartCallResponse); + } + + @Test + void startMissingRequestThrows() { + + ApiException thrown = Assertions.assertThrows(ApiException.class, () -> service.start(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void startMissingProjectIdThrows() { + + CallsService serviceWithoutProjectId = + new CallsServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> serviceWithoutProjectId.start(StartCallRequestDtoTest.expectedStartCallRequest)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void get() throws ApiException { + + String callId = "01ARZ3NDEKTSV4RRFFQ69G5FAA"; + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + + URLPathUtils.encodePathSegment(PROJECT_ID) + + "/calls/" + + URLPathUtils.encodePathSegment(callId), + HttpMethod.GET, + Collections.emptyList(), + (String) null, + Collections.emptyMap(), + ACCEPTS, + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonCallDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + Call response = service.get(callId); + + TestHelpers.recursiveEquals(response, CallDtoTest.expectedCall); + } + + @Test + void getMissingCallIdThrows() { + + ApiException thrown = Assertions.assertThrows(ApiException.class, () -> service.get(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void getMissingProjectIdThrows() { + + CallsService serviceWithoutProjectId = + new CallsServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, () -> serviceWithoutProjectId.get("01ARZ3NDEKTSV4RRFFQ69G5FAA")); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void list() throws ApiException { + + List commonParameters = + Arrays.asList( + new URLParameter( + "serviceId", "6e124178-c29d-46a5-943c-5c2ae544aade", URLParameter.form, true), + new URLParameter("from", "+15551234567", URLParameter.form, true), + new URLParameter("to", "+15551234568", URLParameter.form, true), + new URLParameter("callType", CallType.PHONE, URLParameter.form, true), + new URLParameter("startTime", "2025-02-01T14:00:00Z", URLParameter.form, true), + new URLParameter("endTime", "2025-03-01T14:00:00Z", URLParameter.form, true), + new URLParameter("callResult", CallResult.COMPLETED, URLParameter.form, true), + new URLParameter("callReason", CallReason.CALLEE_HANGUP, URLParameter.form, true), + new URLParameter("pageSize", 2, URLParameter.form, true)); + + List urlParameters = new ArrayList<>(commonParameters); + urlParameters.add(new URLParameter("page", 1, URLParameter.form, true)); + + mockListPage(urlParameters, jsonCallsListResponseDto); + mockNextListPage(jsonCallsListLastPageResponseDto); + + ListCallsQueryParameters queryParameters = + ListCallsQueryParameters.builder() + .setServiceId("6e124178-c29d-46a5-943c-5c2ae544aade") + .setFrom("+15551234567") + .setTo("+15551234568") + .setCallType(CallType.PHONE) + .setStartTime(Instant.parse("2025-02-01T14:00:00Z")) + .setEndTime(Instant.parse("2025-03-01T14:00:00Z")) + .setCallResult(CallResult.COMPLETED) + .setCallReason(CallReason.CALLEE_HANGUP) + .setPageSize(2) + .setPage(1) + .build(); + + CallsListResponse response = service.list(queryParameters); + + TestHelpers.recursiveEquals( + new ArrayList<>(response.getContent()), + new ArrayList<>(CallsListResponseInternalDtoTest.expectedCallsListResponse.getCalls())); + Assertions.assertTrue(response.hasNextPage()); + + CallsListResponse nextResponse = response.nextPage(); + + TestHelpers.recursiveEquals( + new ArrayList<>(nextResponse.getContent()), + new ArrayList<>( + CallsListResponseInternalDtoTest.expectedCallsListLastPageResponse.getCalls())); + Assertions.assertFalse(nextResponse.hasNextPage()); + } + + @Test + void listIteratesOverAllPages() throws ApiException { + + mockListPage(Collections.emptyList(), jsonCallsListResponseDto); + mockNextListPage(jsonCallsListLastPageResponseDto); + + CallsListResponse response = service.list(); + + List calls = new ArrayList<>(); + response.iterator().forEachRemaining(calls::add); + + List expected = + new ArrayList<>(CallsListResponseInternalDtoTest.expectedCallsListResponse.getCalls()); + expected.addAll(CallsListResponseInternalDtoTest.expectedCallsListLastPageResponse.getCalls()); + TestHelpers.recursiveEquals(calls, expected); + } + + @Test + void listMissingProjectIdThrows() { + + CallsService serviceWithoutProjectId = + new CallsServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows(ApiException.class, () -> serviceWithoutProjectId.list()); + + Assertions.assertEquals(400, thrown.getCode()); + } + + // The "next" link served by the fixture targets https://voice.api.sinch.com: its path and query + // are expected to be sent to the configured server instead + private void mockNextListPage(String jsonResponse) { + when(serverConfiguration.getUrl()).thenReturn(CONFIGURED_SERVER_URL); + + HttpRequest httpRequest = + new HttpRequest( + CONFIGURED_SERVER_URL + + "/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=2&pageSize=2", + HttpMethod.GET, + (String) null, + Collections.emptyMap(), + ACCEPTS, + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonResponse.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + } + + private void mockListPage(List urlParameters, String jsonResponse) { + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + URLPathUtils.encodePathSegment(PROJECT_ID) + "/calls", + HttpMethod.GET, + urlParameters, + (String) null, + Collections.emptyMap(), + ACCEPTS, + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonResponse.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + } + + @Test + void interactByCallId() throws ApiException { + + String callId = "01ARZ3NDEKTSV4RRFFQ69G5FAA"; + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + + URLPathUtils.encodePathSegment(PROJECT_ID) + + "/calls/" + + URLPathUtils.encodePathSegment(callId), + HttpMethod.PATCH, + Collections.emptyList(), + jsonCallPatchRequestDto, + Collections.singletonMap( + "Idempotency-Key", + CallPatchRequestDtoTest.expectedCallPatchRequest.getIdempotencyKey()), + ACCEPTS, + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES, + true); + HttpResponse httpResponse = new HttpResponse(202, null, Collections.emptyMap(), null); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + service.interactByCallId(callId, CallPatchRequestDtoTest.expectedCallPatchRequest); + + verify(httpClient) + .invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest))); + } + + @Test + void interactByCallIdMissingCallIdThrows() { + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> service.interactByCallId(null, CallPatchRequestDtoTest.expectedCallPatchRequest)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void interactByCallIdMissingRequestThrows() { + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, () -> service.interactByCallId("01ARZ3NDEKTSV4RRFFQ69G5FAA", null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void interactByCallIdMissingProjectIdThrows() { + + CallsService serviceWithoutProjectId = + new CallsServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> + serviceWithoutProjectId.interactByCallId( + "01ARZ3NDEKTSV4RRFFQ69G5FAA", + CallPatchRequestDtoTest.expectedCallPatchRequest)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void interactByCallName() throws ApiException { + + String sessionId = "01ARZ3NDEKTSV4RRFFQ69G5FAV"; + String callName = "origin"; + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + + URLPathUtils.encodePathSegment(PROJECT_ID) + + "/sessions/" + + URLPathUtils.encodePathSegment(sessionId) + + "/calls/" + + URLPathUtils.encodePathSegment(callName), + HttpMethod.PATCH, + Collections.emptyList(), + jsonCallPatchRequestDto, + Collections.singletonMap( + "Idempotency-Key", + CallPatchRequestDtoTest.expectedCallPatchRequest.getIdempotencyKey()), + ACCEPTS, + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES, + true); + HttpResponse httpResponse = new HttpResponse(202, null, Collections.emptyMap(), null); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + service.interactByCallName( + sessionId, callName, CallPatchRequestDtoTest.expectedCallPatchRequest); + + verify(httpClient) + .invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest))); + } + + @Test + void interactByCallNameMissingSessionIdThrows() { + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> + service.interactByCallName( + null, "origin", CallPatchRequestDtoTest.expectedCallPatchRequest)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void interactByCallNameMissingCallNameThrows() { + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> + service.interactByCallName( + "01ARZ3NDEKTSV4RRFFQ69G5FAV", + null, + CallPatchRequestDtoTest.expectedCallPatchRequest)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void interactByCallNameMissingRequestThrows() { + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> service.interactByCallName("01ARZ3NDEKTSV4RRFFQ69G5FAV", "origin", null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void interactByCallNameMissingProjectIdThrows() { + + CallsService serviceWithoutProjectId = + new CallsServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> + serviceWithoutProjectId.interactByCallName( + "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "origin", + CallPatchRequestDtoTest.expectedCallPatchRequest)); + + Assertions.assertEquals(400, thrown.getCode()); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/CredentialsValidationHelper.java b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/CredentialsValidationHelper.java new file mode 100644 index 000000000..c5e98f8de --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/CredentialsValidationHelper.java @@ -0,0 +1,120 @@ +package com.sinch.sdk.domains.voice.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.domains.voice.models.v2.VoiceContext; +import com.sinch.sdk.models.UnifiedCredentials; +import java.util.function.Consumer; +import java.util.function.Supplier; + +class CredentialsValidationHelper { + + static final ServerConfiguration oAuthServer = new ServerConfiguration("https://oauth.foo.url"); + static final VoiceContext context = VoiceContext.builder().setVoiceUrl("foo url").build(); + + static void checkCredentials( + Supplier httpClientSupplier, Consumer service) { + doNotAcceptNullCredentials(httpClientSupplier, service); + doNotAcceptNullKey(httpClientSupplier, service); + doNotAcceptNullKeySecret(httpClientSupplier, service); + doNotAcceptNullProject(httpClientSupplier, service); + doNotAcceptNullContext(httpClientSupplier, service); + doNotAcceptNullVoiceUrl(httpClientSupplier, service); + passInit(httpClientSupplier, service); + } + + private static UnifiedCredentials credentials(String keyId, String keySecret, String projectId) { + return UnifiedCredentials.builder() + .setKeyId(keyId) + .setKeySecret(keySecret) + .setProjectId(projectId) + .build(); + } + + private static VoiceService voiceService( + UnifiedCredentials credentials, + VoiceContext context, + Supplier httpClientSupplier) { + return new VoiceService(credentials, context, oAuthServer, httpClientSupplier); + } + + private static void doNotAcceptNullCredentials( + Supplier httpClientSupplier, Consumer service) { + Exception exception = + assertThrows( + NullPointerException.class, + () -> service.accept(voiceService(null, context, httpClientSupplier))); + assertTrue(exception.getMessage().contains("Voice V2 service requires unified credentials")); + } + + private static void doNotAcceptNullKey( + Supplier httpClientSupplier, Consumer service) { + Exception exception = + assertThrows( + IllegalArgumentException.class, + () -> + service.accept( + voiceService(credentials(null, "foo", "foo"), context, httpClientSupplier))); + assertTrue(exception.getMessage().contains("keyId")); + } + + private static void doNotAcceptNullKeySecret( + Supplier httpClientSupplier, Consumer service) { + Exception exception = + assertThrows( + IllegalArgumentException.class, + () -> + service.accept( + voiceService(credentials("foo", null, "foo"), context, httpClientSupplier))); + assertTrue(exception.getMessage().contains("keySecret")); + } + + private static void doNotAcceptNullProject( + Supplier httpClientSupplier, Consumer service) { + Exception exception = + assertThrows( + IllegalArgumentException.class, + () -> + service.accept( + voiceService(credentials("foo", "foo", null), context, httpClientSupplier))); + assertTrue(exception.getMessage().contains("projectId")); + } + + private static void doNotAcceptNullContext( + Supplier httpClientSupplier, Consumer service) { + Exception exception = + assertThrows( + NullPointerException.class, + () -> + service.accept( + voiceService(credentials("foo", "foo", "foo"), null, httpClientSupplier))); + assertTrue(exception.getMessage().contains("Voice V2 service requires context")); + } + + private static void doNotAcceptNullVoiceUrl( + Supplier httpClientSupplier, Consumer service) { + Exception exception = + assertThrows( + IllegalArgumentException.class, + () -> + service.accept( + voiceService( + credentials("foo", "foo", "foo"), + VoiceContext.builder().build(), + httpClientSupplier))); + assertTrue(exception.getMessage().contains("voiceUrl")); + } + + private static void passInit( + Supplier httpClientSupplier, Consumer service) { + assertDoesNotThrow( + () -> + service.accept( + voiceService(credentials("foo", "foo", "foo"), context, httpClientSupplier)), + "Init passed"); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/ServicesServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/ServicesServiceTest.java new file mode 100644 index 000000000..791500d8d --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/ServicesServiceTest.java @@ -0,0 +1,469 @@ +package com.sinch.sdk.domains.voice.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.URLParameter; +import com.sinch.sdk.core.http.URLPathUtils; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.domains.voice.api.v2.ServicesService; +import com.sinch.sdk.domains.voice.models.v2.services.NoneCallBehavior; +import com.sinch.sdk.domains.voice.models.v2.services.request.CreateServiceRequest; +import com.sinch.sdk.domains.voice.models.v2.services.request.CreateServiceRequestDtoTest; +import com.sinch.sdk.domains.voice.models.v2.services.request.ListServicesQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.services.request.UpdateServiceRequest; +import com.sinch.sdk.domains.voice.models.v2.services.request.UpdateServiceRequestDtoTest; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceResponse; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceResponseDtoTest; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceShortResponse; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServicesListResponse; +import com.sinch.sdk.domains.voice.models.v2.services.response.internal.ServicesListResponseInternalDtoTest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +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 ServicesServiceTest extends BaseTest { + + @Mock HttpClient httpClient; + @Mock ServerConfiguration serverConfiguration; + @Mock Map authManagers; + + static final String PROJECT_ID = "test_project_id"; + static final String CONFIGURED_SERVER_URL = "https://configured.server.com"; + static final Collection AUTH_NAMES = Arrays.asList("BasicAuth", "SinchOAuth2"); + static final Collection ACCEPTS = + Arrays.asList(HttpContentType.APPLICATION_JSON, "application/problem+json"); + + ServicesService service; + + @GivenTextResource("/domains/voice/v2/services/request/CreateServiceRequestDto.json") + String jsonCreateServiceRequestDto; + + @GivenTextResource("/domains/voice/v2/services/request/UpdateServiceRequestDto.json") + String jsonUpdateServiceRequestDto; + + @GivenTextResource("/domains/voice/v2/services/response/ServiceResponseDto.json") + String jsonServiceResponseDto; + + @GivenTextResource( + "/domains/voice/v2/services/response/internal/ServicesListResponseInternalDto.json") + String jsonServicesListResponseDto; + + @GivenTextResource( + "/domains/voice/v2/services/response/internal/ServicesListResponseInternalLastPageDto.json") + String jsonServicesListLastPageResponseDto; + + @BeforeEach + public void initMocks() { + service = + new ServicesServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), PROJECT_ID); + } + + @Test + void create() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + URLPathUtils.encodePathSegment(PROJECT_ID) + "/services", + HttpMethod.POST, + Collections.emptyList(), + jsonCreateServiceRequestDto, + Collections.singletonMap( + "Idempotency-Key", + CreateServiceRequestDtoTest.expectedCreateServiceRequest.getIdempotencyKey()), + ACCEPTS, + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES, + true); + HttpResponse httpResponse = + new HttpResponse(201, null, Collections.emptyMap(), jsonServiceResponseDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + ServiceResponse response = + service.create(CreateServiceRequestDtoTest.expectedCreateServiceRequest); + + TestHelpers.recursiveEquals(response, ServiceResponseDtoTest.expectedServiceResponse); + } + + @Test + void createWithoutIdempotencyKeyLeavesItToTransport() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + URLPathUtils.encodePathSegment(PROJECT_ID) + "/services", + HttpMethod.POST, + Collections.emptyList(), + "{\"name\":\"Example service\",\"callBehavior\":{\"type\":\"NONE\"}}", + Collections.emptyMap(), + ACCEPTS, + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES, + true); + HttpResponse httpResponse = + new HttpResponse(201, null, Collections.emptyMap(), jsonServiceResponseDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + ServiceResponse response = + service.create( + CreateServiceRequest.builder() + .setName("Example service") + .setCallBehavior(NoneCallBehavior.NONE_CALL_BEHAVIOR) + .build()); + + TestHelpers.recursiveEquals(response, ServiceResponseDtoTest.expectedServiceResponse); + } + + @Test + void createMissingRequestThrows() { + + ApiException thrown = Assertions.assertThrows(ApiException.class, () -> service.create(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void createMissingProjectIdThrows() { + + ServicesService serviceWithoutProjectId = + new ServicesServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> + serviceWithoutProjectId.create( + CreateServiceRequestDtoTest.expectedCreateServiceRequest)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void delete() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + + URLPathUtils.encodePathSegment(PROJECT_ID) + + "/services/" + + URLPathUtils.encodePathSegment("6e124178-c29d-46a5-943c-5c2ae544aade"), + HttpMethod.DELETE, + Collections.emptyList(), + (String) null, + Collections.emptyMap(), + ACCEPTS, + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = new HttpResponse(204, null, Collections.emptyMap(), null); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + service.delete("6e124178-c29d-46a5-943c-5c2ae544aade"); + } + + @Test + void deleteMissingServiceIdThrows() { + + ApiException thrown = Assertions.assertThrows(ApiException.class, () -> service.delete(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void deleteMissingProjectIdThrows() { + + ServicesService serviceWithoutProjectId = + new ServicesServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> serviceWithoutProjectId.delete("6e124178-c29d-46a5-943c-5c2ae544aade")); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void get() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + + URLPathUtils.encodePathSegment(PROJECT_ID) + + "/services/" + + URLPathUtils.encodePathSegment("6e124178-c29d-46a5-943c-5c2ae544aade"), + HttpMethod.GET, + Collections.emptyList(), + (String) null, + Collections.emptyMap(), + ACCEPTS, + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonServiceResponseDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + ServiceResponse response = service.get("6e124178-c29d-46a5-943c-5c2ae544aade"); + + TestHelpers.recursiveEquals(response, ServiceResponseDtoTest.expectedServiceResponse); + } + + @Test + void getMissingServiceIdThrows() { + + ApiException thrown = Assertions.assertThrows(ApiException.class, () -> service.get(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void update() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + + URLPathUtils.encodePathSegment(PROJECT_ID) + + "/services/" + + URLPathUtils.encodePathSegment("6e124178-c29d-46a5-943c-5c2ae544aade"), + HttpMethod.PATCH, + Collections.emptyList(), + jsonUpdateServiceRequestDto, + Collections.singletonMap( + "Idempotency-Key", + UpdateServiceRequestDtoTest.expectedUpdateServiceRequest.getIdempotencyKey()), + ACCEPTS, + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES, + true); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonServiceResponseDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + ServiceResponse response = + service.update( + "6e124178-c29d-46a5-943c-5c2ae544aade", + UpdateServiceRequestDtoTest.expectedUpdateServiceRequest); + + TestHelpers.recursiveEquals(response, ServiceResponseDtoTest.expectedServiceResponse); + } + + @Test + void updateWithoutIdempotencyKeyLeavesItToTransport() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + + URLPathUtils.encodePathSegment(PROJECT_ID) + + "/services/" + + URLPathUtils.encodePathSegment("6e124178-c29d-46a5-943c-5c2ae544aade"), + HttpMethod.PATCH, + Collections.emptyList(), + "{\"isDefault\":true}", + Collections.emptyMap(), + ACCEPTS, + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES, + true); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonServiceResponseDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + ServiceResponse response = + service.update( + "6e124178-c29d-46a5-943c-5c2ae544aade", + UpdateServiceRequest.builder().setAsDefault().build()); + + TestHelpers.recursiveEquals(response, ServiceResponseDtoTest.expectedServiceResponse); + } + + @Test + void updateMissingServiceIdThrows() { + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> service.update(null, UpdateServiceRequestDtoTest.expectedUpdateServiceRequest)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void updateMissingRequestThrows() { + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, () -> service.update("6e124178-c29d-46a5-943c-5c2ae544aade", null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void list() throws ApiException { + + List urlParameters = + Arrays.asList( + new URLParameter("filter", "Service", URLParameter.form, true), + new URLParameter("isDefault", false, URLParameter.form, true), + new URLParameter("pageSize", 2, URLParameter.form, true), + new URLParameter("page", 1, URLParameter.form, true)); + + mockListPage(urlParameters, jsonServicesListResponseDto); + mockNextListPage(jsonServicesListLastPageResponseDto); + + ListServicesQueryParameters queryParameters = + ListServicesQueryParameters.builder() + .setFilter("Service") + .setIsDefault(false) + .setPageSize(2) + .setPage(1) + .build(); + + ServicesListResponse response = service.list(queryParameters); + + TestHelpers.recursiveEquals( + new ArrayList<>(response.getContent()), + new ArrayList<>( + ServicesListResponseInternalDtoTest.expectedServicesListResponse.getServices())); + Assertions.assertTrue(response.hasNextPage()); + + ServicesListResponse nextResponse = response.nextPage(); + + TestHelpers.recursiveEquals( + new ArrayList<>(nextResponse.getContent()), + new ArrayList<>( + ServicesListResponseInternalDtoTest.expectedServicesListLastPageResponse + .getServices())); + Assertions.assertFalse(nextResponse.hasNextPage()); + } + + @Test + void listIteratesOverAllPages() throws ApiException { + + mockListPage(Collections.emptyList(), jsonServicesListResponseDto); + mockNextListPage(jsonServicesListLastPageResponseDto); + + ServicesListResponse response = service.list(); + + List services = new ArrayList<>(); + response.iterator().forEachRemaining(services::add); + + List expected = + new ArrayList<>( + ServicesListResponseInternalDtoTest.expectedServicesListResponse.getServices()); + expected.addAll( + ServicesListResponseInternalDtoTest.expectedServicesListLastPageResponse.getServices()); + TestHelpers.recursiveEquals(services, expected); + } + + @Test + void listMissingProjectIdThrows() { + + ServicesService serviceWithoutProjectId = + new ServicesServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows(ApiException.class, () -> serviceWithoutProjectId.list()); + + Assertions.assertEquals(400, thrown.getCode()); + } + + // The "next" link served by the fixture targets https://voice.api.sinch.com: its path and query + // are expected to be sent to the configured server instead + private void mockNextListPage(String jsonResponse) { + when(serverConfiguration.getUrl()).thenReturn(CONFIGURED_SERVER_URL); + + HttpRequest httpRequest = + new HttpRequest( + CONFIGURED_SERVER_URL + + "/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/services?page=2&pageSize=2", + HttpMethod.GET, + (String) null, + Collections.emptyMap(), + ACCEPTS, + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonResponse.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + } + + private void mockListPage(List urlParameters, String jsonResponse) { + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + URLPathUtils.encodePathSegment(PROJECT_ID) + "/services", + HttpMethod.GET, + urlParameters, + (String) null, + Collections.emptyMap(), + ACCEPTS, + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonResponse.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/SessionsServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/SessionsServiceTest.java new file mode 100644 index 000000000..e4aa51866 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/SessionsServiceTest.java @@ -0,0 +1,110 @@ +package com.sinch.sdk.domains.voice.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.voice.api.v2.SessionsService; +import com.sinch.sdk.domains.voice.models.v2.sessions.response.Session; +import com.sinch.sdk.domains.voice.models.v2.sessions.response.SessionDtoTest; +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 SessionsServiceTest 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("BasicAuth", "SinchOAuth2"); + static final Collection ACCEPTS = + Arrays.asList(HttpContentType.APPLICATION_JSON, "application/problem+json"); + + SessionsService service; + + @GivenTextResource("/domains/voice/v2/sessions/response/SessionDto.json") + String jsonSessionDto; + + @BeforeEach + public void initMocks() { + service = + new SessionsServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), PROJECT_ID); + } + + @Test + void get() throws ApiException { + + String sessionId = "01BX5ZZKBKACTAV9WEVGEMMVRB"; + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + + URLPathUtils.encodePathSegment(PROJECT_ID) + + "/sessions/" + + URLPathUtils.encodePathSegment(sessionId), + HttpMethod.GET, + Collections.emptyList(), + (String) null, + Collections.emptyMap(), + ACCEPTS, + Collections.emptyList(), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse(200, null, Collections.emptyMap(), jsonSessionDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + Session response = service.get(sessionId); + + TestHelpers.recursiveEquals(response, SessionDtoTest.expectedSession); + } + + @Test + void getMissingSessionIdThrows() { + + ApiException thrown = Assertions.assertThrows(ApiException.class, () -> service.get(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void getMissingProjectIdThrows() { + + SessionsService serviceWithoutProjectId = + new SessionsServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, () -> serviceWithoutProjectId.get("01BX5ZZKBKACTAV9WEVGEMMVRB")); + + Assertions.assertEquals(400, thrown.getCode()); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/SinchEventsServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/SinchEventsServiceTest.java new file mode 100644 index 000000000..2c806f485 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/SinchEventsServiceTest.java @@ -0,0 +1,254 @@ +package com.sinch.sdk.domains.voice.api.v2.adapters; + +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.ApiMappingException; +import com.sinch.sdk.domains.voice.api.v2.SinchEventsService; +import com.sinch.sdk.domains.voice.models.v2.CallDtoTest; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.SinchEventType; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEvent; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEventDtoTest; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEventResponseDtoTest; +import java.util.HashMap; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.json.JSONException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +class SinchEventsServiceTest extends BaseTest { + + // Signature example from the Voice V2 OAS "callWebhook" description + static final String SERVICE_ID = "a74b1566-0f18-4f8e-9c23-8e6b5df8fd3e"; + static final String SERVICE_SECRET = "F5wrP9SKYU6w8sbZXkp7GA=="; + static final String PATH = "/voice-webhooks"; + static final String PAYLOAD = + "{\"event\":\"call.incoming\",\"call\":{\"callId\":\"01AN4Z07BY79KA1307SR9X4MV3\"}}"; + static final String SIGNATURE = "EWFtVTrykdhMTdyYSbn40GBJpf5UBeggO9T99sdwLyY="; + + @GivenTextResource("/domains/voice/v2/sinchevents/VoiceSinchEventDto.json") + String jsonVoiceSinchEvent; + + @GivenTextResource("/domains/voice/v2/sinchevents/VoiceSinchEventCustomEventDto.json") + String jsonVoiceSinchEventCustomEvent; + + @GivenTextResource("/domains/voice/v2/sinchevents/VoiceSinchEventResponseDto.json") + String jsonVoiceSinchEventResponse; + + SinchEventsService service; + + @BeforeEach + public void initMocks() { + // no credentials at all: Sinch Events do not depend on the client configuration + service = new VoiceService(null, null, null, null).sinchEvents(); + } + + Map signedHeaders() { + Map headers = new HashMap<>(); + headers.put("Authorization", "service " + SERVICE_ID + ":" + SIGNATURE); + headers.put("Content-Type", "application/json; charset=utf-8"); + headers.put("x-timestamp", "2026-04-01T12:00:00.0000000Z"); + headers.put("ce-type", "com.sinch.voice.call.control.v1"); + return headers; + } + + boolean validate(Map headers, String payload) { + return service.validateAuthenticationHeader( + SERVICE_ID, SERVICE_SECRET, "POST", PATH, headers, payload); + } + + @Test + void validateAuthenticationHeader() { + Assertions.assertThat(validate(signedHeaders(), PAYLOAD)).isTrue(); + } + + @Test + void validateAuthenticationHeaderCaseInsensitive() { + Map headers = new HashMap<>(); + headers.put("authorization", "Service " + SERVICE_ID + ":" + SIGNATURE); + headers.put("content-type", "application/json; charset=utf-8"); + headers.put("X-Timestamp", "2026-04-01T12:00:00.0000000Z"); + + Assertions.assertThat(validate(headers, PAYLOAD)).isTrue(); + } + + @Test + void validateAuthenticationHeaderIgnoresQueryString() { + Assertions.assertThat( + service.validateAuthenticationHeader( + SERVICE_ID, SERVICE_SECRET, "POST", PATH + "?foo=bar", signedHeaders(), PAYLOAD)) + .isTrue(); + } + + @Test + void rejectOtherServiceIdInHeader() { + // the signature is valid for SERVICE_ID, but the header claims another service + Map headers = signedHeaders(); + headers.put("Authorization", "service other-service:" + SIGNATURE); + + Assertions.assertThat(validate(headers, PAYLOAD)).isFalse(); + } + + @Test + void rejectOtherServiceIdParameter() { + Assertions.assertThat( + service.validateAuthenticationHeader( + "other-service", SERVICE_SECRET, "POST", PATH, signedHeaders(), PAYLOAD)) + .isFalse(); + } + + @Test + void rejectOtherSecret() { + Assertions.assertThat( + service.validateAuthenticationHeader( + SERVICE_ID, "YXBwU2VjcmV0", "POST", PATH, signedHeaders(), PAYLOAD)) + .isFalse(); + } + + @Test + void rejectTamperedPayload() { + Assertions.assertThat(validate(signedHeaders(), PAYLOAD.replace("incoming", "answered"))) + .isFalse(); + } + + @Test + void rejectTamperedTimestamp() { + Map headers = signedHeaders(); + headers.put("x-timestamp", "2026-04-01T12:00:01.0000000Z"); + + Assertions.assertThat(validate(headers, PAYLOAD)).isFalse(); + } + + @Test + void rejectOtherPath() { + Assertions.assertThat( + service.validateAuthenticationHeader( + SERVICE_ID, SERVICE_SECRET, "POST", "/other", signedHeaders(), PAYLOAD)) + .isFalse(); + } + + @Test + void validateAuthenticationHeaderLowerCaseMethod() { + Assertions.assertThat( + service.validateAuthenticationHeader( + SERVICE_ID, SERVICE_SECRET, "post", PATH, signedHeaders(), PAYLOAD)) + .isTrue(); + } + + @Test + void rejectOtherMethod() { + Assertions.assertThat( + service.validateAuthenticationHeader( + SERVICE_ID, SERVICE_SECRET, "PUT", PATH, signedHeaders(), PAYLOAD)) + .isFalse(); + } + + @Test + void rejectApplicationScheme() { + Map headers = signedHeaders(); + headers.put("Authorization", "Application " + SERVICE_ID + ":" + SIGNATURE); + + Assertions.assertThat(validate(headers, PAYLOAD)).isFalse(); + } + + @Test + void rejectMalformedAuthorization() { + Map headers = signedHeaders(); + headers.put("Authorization", "service " + SIGNATURE); + + Assertions.assertThat(validate(headers, PAYLOAD)).isFalse(); + + headers.put("Authorization", "service"); + Assertions.assertThat(validate(headers, PAYLOAD)).isFalse(); + } + + @Test + void rejectEmptyAuthorization() { + Map headers = signedHeaders(); + headers.put("Authorization", ""); + + Assertions.assertThat(validate(headers, PAYLOAD)).isFalse(); + } + + @Test + void rejectMissingAuthorization() { + Map headers = signedHeaders(); + headers.remove("Authorization"); + + Assertions.assertThat(validate(headers, PAYLOAD)).isFalse(); + } + + @Test + void rejectMissingTimestamp() { + Map headers = signedHeaders(); + headers.remove("x-timestamp"); + + Assertions.assertThat(validate(headers, PAYLOAD)).isFalse(); + } + + @Test + void rejectNullHeaders() { + Assertions.assertThat(validate(null, PAYLOAD)).isFalse(); + } + + @Test + void requireServiceCredentials() { + Assertions.assertThatThrownBy( + () -> + service.validateAuthenticationHeader( + "", SERVICE_SECRET, "POST", PATH, signedHeaders(), PAYLOAD)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("serviceId"); + + Assertions.assertThatThrownBy( + () -> + service.validateAuthenticationHeader( + SERVICE_ID, null, "POST", PATH, signedHeaders(), PAYLOAD)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("serviceSecret"); + + Assertions.assertThatThrownBy( + () -> + service.validateAuthenticationHeader( + SERVICE_ID, "not base64 !", "POST", PATH, signedHeaders(), PAYLOAD)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void parseEvent() throws ApiMappingException { + VoiceSinchEvent event = service.parseEvent(jsonVoiceSinchEvent); + + TestHelpers.recursiveEquals(event, VoiceSinchEventDtoTest.expectedVoiceSinchEvent); + } + + @Test + void parseCustomEvent() throws ApiMappingException { + VoiceSinchEvent event = service.parseEvent(jsonVoiceSinchEventCustomEvent); + + // sent as "call.webhook.on-answer", returned with the customEvent naming + TestHelpers.recursiveEquals( + event, + VoiceSinchEvent.builder() + .setEvent(SinchEventType.from("call.customEvent.on-answer")) + .setCall(CallDtoTest.expectedCall) + .build()); + } + + @Test + void parseEventInvalidPayload() { + Assertions.assertThatThrownBy(() -> service.parseEvent("{not json")) + .isInstanceOf(ApiMappingException.class); + } + + @Test + void serializeResponse() throws ApiMappingException, JSONException { + String serialized = + service.serializeResponse(VoiceSinchEventResponseDtoTest.expectedVoiceSinchEventResponse); + + JSONAssert.assertEquals(jsonVoiceSinchEventResponse, serialized, true); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/SvamlServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/SvamlServiceTest.java new file mode 100644 index 000000000..02b6e374f --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/SvamlServiceTest.java @@ -0,0 +1,180 @@ +package com.sinch.sdk.domains.voice.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.voice.api.v2.SvamlService; +import com.sinch.sdk.domains.voice.models.v2.svaml.request.DescribeSvamlRequestDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.request.ValidateSvamlRequestDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.SvamlDescriptionResponse; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.SvamlDescriptionResponseDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.ValidateSvamlResponse; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.ValidateSvamlResponseDtoTest; +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 SvamlServiceTest 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("BasicAuth", "SinchOAuth2"); + static final Collection ACCEPTS = + Arrays.asList(HttpContentType.APPLICATION_JSON, "application/problem+json"); + + SvamlService service; + + @GivenTextResource("/domains/voice/v2/svaml/request/DescribeSvamlRequestDto.json") + String jsonDescribeSvamlRequestDto; + + @GivenTextResource("/domains/voice/v2/svaml/response/SvamlDescriptionResponseDto.json") + String jsonSvamlDescriptionResponseDto; + + @GivenTextResource("/domains/voice/v2/svaml/request/ValidateSvamlRequestDto.json") + String jsonValidateSvamlRequestDto; + + @GivenTextResource("/domains/voice/v2/svaml/response/ValidateSvamlResponseDto.json") + String jsonValidateSvamlResponseDto; + + @BeforeEach + public void initMocks() { + service = + new SvamlServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), PROJECT_ID); + } + + @Test + void describe() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + URLPathUtils.encodePathSegment(PROJECT_ID) + "/svaml/describe", + HttpMethod.POST, + Collections.emptyList(), + jsonDescribeSvamlRequestDto, + Collections.emptyMap(), + ACCEPTS, + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse( + 200, null, Collections.emptyMap(), jsonSvamlDescriptionResponseDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + SvamlDescriptionResponse response = + service.describe(DescribeSvamlRequestDtoTest.expectedDescribeSvamlRequest); + + TestHelpers.recursiveEquals( + response, SvamlDescriptionResponseDtoTest.expectedSvamlDescriptionResponse); + } + + @Test + void describeMissingRequestThrows() { + + ApiException thrown = Assertions.assertThrows(ApiException.class, () -> service.describe(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void describeMissingProjectIdThrows() { + + SvamlService serviceWithoutProjectId = + new SvamlServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> + serviceWithoutProjectId.describe( + DescribeSvamlRequestDtoTest.expectedDescribeSvamlRequest)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void validate() throws ApiException { + + HttpRequest httpRequest = + new HttpRequest( + "/v2/projects/" + URLPathUtils.encodePathSegment(PROJECT_ID) + "/svaml/validate", + HttpMethod.POST, + Collections.emptyList(), + jsonValidateSvamlRequestDto, + Collections.emptyMap(), + ACCEPTS, + Collections.singletonList(HttpContentType.APPLICATION_JSON), + AUTH_NAMES); + HttpResponse httpResponse = + new HttpResponse( + 200, null, Collections.emptyMap(), jsonValidateSvamlResponseDto.getBytes()); + + when(httpClient.invokeAPI( + eq(serverConfiguration), + eq(authManagers), + argThat(new HttpRequestMatcher(httpRequest)))) + .thenReturn(httpResponse); + + ValidateSvamlResponse response = + service.validate(ValidateSvamlRequestDtoTest.expectedValidateSvamlRequest); + + TestHelpers.recursiveEquals( + response, ValidateSvamlResponseDtoTest.expectedValidateSvamlResponse); + } + + @Test + void validateMissingRequestThrows() { + + ApiException thrown = Assertions.assertThrows(ApiException.class, () -> service.validate(null)); + + Assertions.assertEquals(400, thrown.getCode()); + } + + @Test + void validateMissingProjectIdThrows() { + + SvamlService serviceWithoutProjectId = + new SvamlServiceImpl( + httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), null); + + ApiException thrown = + Assertions.assertThrows( + ApiException.class, + () -> + serviceWithoutProjectId.validate( + ValidateSvamlRequestDtoTest.expectedValidateSvamlRequest)); + + Assertions.assertEquals(400, thrown.getCode()); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/VoiceServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/VoiceServiceTest.java new file mode 100644 index 000000000..679141619 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/voice/api/v2/adapters/VoiceServiceTest.java @@ -0,0 +1,44 @@ +package com.sinch.sdk.domains.voice.api.v2.adapters; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.core.http.HttpClient; +import com.sinch.sdk.models.Configuration; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +class VoiceServiceTest { + + @Mock HttpClient httpClient; + + @Test + void checkCredentialsCalls() { + CredentialsValidationHelper.checkCredentials(() -> httpClient, VoiceService::calls); + } + + @Test + void checkCredentialsBatches() { + CredentialsValidationHelper.checkCredentials(() -> httpClient, VoiceService::batches); + } + + @Test + void checkCredentialsSessions() { + CredentialsValidationHelper.checkCredentials(() -> httpClient, VoiceService::sessions); + } + + @Test + void checkCredentialsServices() { + CredentialsValidationHelper.checkCredentials(() -> httpClient, VoiceService::services); + } + + @Test + void checkCredentialsSvaml() { + CredentialsValidationHelper.checkCredentials(() -> httpClient, VoiceService::svaml); + } + + @Test + void sinchEventsRequireNoCredentials() { + Assertions.assertNotNull( + new SinchClient(Configuration.builder().build()).voice().v2().sinchEvents()); + } +} 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 dd7c2ab44..bb20ec649 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/Config.java +++ b/client/src/test/java/com/sinch/sdk/e2e/Config.java @@ -32,6 +32,7 @@ public class Config { public static final String VOICE_HOST_NAME = MOCK_SERVER_URL + "/voice"; public static final String VOICE_MANAGEMENT_HOST_NAME = MOCK_SERVER_URL + "/voice-application-management"; + public static final String VOICE_V2_HOST_NAME = MOCK_SERVER_URL + "/voice-v2"; public static final String MAILGUN_HOST_NAME = MOCK_SERVER_URL + "/mailgun"; public static final String MAILGUN_API_KEY = "apiKey"; @@ -81,6 +82,10 @@ private Config() { .setVoiceApplicationMngmtUrl(VOICE_MANAGEMENT_HOST_NAME) .setVoiceUrl(VOICE_HOST_NAME) .build()) + .setVoiceV2Context( + com.sinch.sdk.domains.voice.models.v2.VoiceContext.builder() + .setVoiceUrl(VOICE_V2_HOST_NAME) + .build()) .setSmsContext( SmsContext.builder().setSmsUrl(SMS_HOST_NAME).setSmsRegion(SMSRegion.EU).build()) .setVerificationContext( diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/WebhooksHelper.java b/client/src/test/java/com/sinch/sdk/e2e/domains/WebhooksHelper.java index 45deaf9df..089b8e655 100644 --- a/client/src/test/java/com/sinch/sdk/e2e/domains/WebhooksHelper.java +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/WebhooksHelper.java @@ -3,8 +3,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -35,6 +37,19 @@ public static Response callURL(URL url, Function parseEvent) return response; } + /** Send a response payload back to the mock server; returns the HTTP status code */ + public static int postJson(URL url, String jsonPayload) throws IOException { + + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/json; charset=utf-8"); + con.setDoOutput(true); + try (OutputStream outputStream = con.getOutputStream()) { + outputStream.write(jsonPayload.getBytes(StandardCharsets.UTF_8)); + } + return con.getResponseCode(); + } + static Map transformHeaders(Map> headers) { if (null == headers) { return null; 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 index 3064112ed..61df2ed5e 100644 --- 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 @@ -9,6 +9,7 @@ import com.sinch.sdk.e2e.Config; import com.sinch.sdk.http.HttpClientApache; import com.sinch.sdk.models.HttpProxyConfiguration; +import com.sinch.sdk.models.TransportSettings; import org.junit.jupiter.api.Test; class HttpClientApacheProxyIT { @@ -28,7 +29,8 @@ void unauthenticatedProxyRoutesTraffic() throws Exception { .setPort(Config.PROXY_UNAUTHENTICATED_PORT) .build(); - try (HttpClientApache client = new HttpClientApache(proxyConfig)) { + try (HttpClientApache client = + new HttpClientApache(proxyConfig, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { HttpResponse response = client.invokeAPI(new ServerConfiguration(TARGET_URL), null, simpleGetRequest()); @@ -46,7 +48,8 @@ void authenticatedProxyRoutesTraffic() throws Exception { .setPassword("password") .build(); - try (HttpClientApache client = new HttpClientApache(proxyConfig)) { + try (HttpClientApache client = + new HttpClientApache(proxyConfig, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { HttpResponse response = client.invokeAPI(new ServerConfiguration(TARGET_URL), null, simpleGetRequest()); @@ -64,7 +67,8 @@ void authenticatedProxyRejectsWrongCredentials() throws Exception { .setPassword("wrong-pass") .build(); - try (HttpClientApache client = new HttpClientApache(proxyConfig)) { + try (HttpClientApache client = + new HttpClientApache(proxyConfig, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { HttpResponse response = client.invokeAPI(new ServerConfiguration(TARGET_URL), null, simpleGetRequest()); diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/BatchesSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/BatchesSteps.java new file mode 100644 index 000000000..d99462400 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/BatchesSteps.java @@ -0,0 +1,159 @@ +package com.sinch.sdk.e2e.domains.voice.v2; + +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.domains.voice.api.v2.BatchesService; +import com.sinch.sdk.domains.voice.models.v2.SessionState; +import com.sinch.sdk.domains.voice.models.v2.batches.request.BatchOptions; +import com.sinch.sdk.domains.voice.models.v2.batches.request.StartBatchRequest; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchDetails; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchSessionSummary; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchStopResponse; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchSummary; +import com.sinch.sdk.domains.voice.models.v2.batches.response.StartBatchResponse; +import com.sinch.sdk.domains.voice.models.v2.destination.Phone; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.CallEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.DialCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.HangupCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessageEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.SayMessage; +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.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Assertions; + +public class BatchesSteps { + + BatchesService service; + StartBatchResponse startResponse; + BatchSummary getResponse; + BatchDetails getDetailsResponse; + BatchStopResponse stopResponse; + + @Given("^the Voice-V2 service \"Batches\" is available$") + public void serviceAvailable() { + service = Config.getSinchClient().voice().v2().batches(); + Assertions.assertNotNull(service, "Voice V2 Batches service is not available"); + } + + @When("^I send a request to start a batch of calls$") + public void start() { + MessagesCommand reminder = + MessagesCommand.builder() + .setMessages( + Collections.singletonList( + SayMessage.builder() + .setText("Hello, this is an automated reminder from Sinch. Goodbye.") + .setVoiceName("Emma") + .build())) + .setEvents( + MessageEvents.builder() + .setOnFinish(Collections.singletonList(HangupCommand.builder().build())) + .build()) + .build(); + + DialCommand dial = + DialCommand.builder() + .setCallName("batch-reminder") + .setFrom(Phone.builder().setNumber("+12015555555").build()) + .setTo(Phone.builder().setNumber("@toNumber").build()) + .setDialTimeoutDurationSeconds(30) + .setMaxCallDurationSeconds(120) + .setEvents( + CallEvents.builder() + .setOnAnswer(Collections.singletonList(reminder)) + .setOnHangup(Collections.singletonList(HangupCommand.builder().build())) + .build()) + .build(); + + startResponse = + service.start( + StartBatchRequest.builder() + .setCommands(Collections.singletonList(dial)) + .setParameters( + Arrays.asList( + Collections.singletonMap("toNumber", "+12017777777"), + Collections.singletonMap("toNumber", "+12018888888"))) + .setBatchOptions(BatchOptions.builder().setMaxCps(5).setTtlSeconds(600).build()) + .setIdempotencyKey("7b2e9d41-5c6a-4f80-b3d2-9e1a4c7f6b05") + .build()); + } + + @Then("^the response contains the information about the batch started$") + public void startResult() { + StartBatchResponse expected = + StartBatchResponse.builder() + .setProjectId("b2c3d4e5-f6a7-4890-b123-c4d5e6f7a890") + .setServiceId("0a1b2c3d-4e5f-4678-9abc-d1e2f3a4b5c6") + .setBatchId("01HZXK9RSQNS9WE4KX0ZG3D5UC") + .build(); + + TestHelpers.recursiveEquals(startResponse, expected); + } + + @When("^I send a request to get a batch call summary$") + public void get() { + getResponse = service.get("01M144V4N3GSTNVJ3V32TD7H9A"); + } + + @Then("^the response contains the batch call summary$") + public void getResult() { + BatchSummary expected = + BatchSummary.builder() + .setBatchId("01M144V4N3GSTNVJ3V32TD7H9A") + .setSessionCount(2) + .setEndTime(Instant.parse("2026-08-28T12:17:49Z")) + .setQueued(0) + .setInProgress(0) + .setCompleted(2) + .setExpired(0) + .setTtlSeconds(1800) + .setRequestedCps(5) + .build(); + + TestHelpers.recursiveEquals(getResponse, expected); + } + + @When("^I send a request to get batch call details$") + public void getDetails() { + getDetailsResponse = service.getDetails("01M144V4N3GSTNVJ3V32TD7H9A"); + } + + @Then("^the response contains the batch call details$") + public void getDetailsResult() { + BatchDetails expected = + BatchDetails.builder() + .setSessions( + Arrays.asList( + BatchSessionSummary.builder() + .setId("01M144V4PE5KTSVY2AX19QC332") + .setState(SessionState.IN_PROGRESS) + .setParameters(Collections.singletonMap("toNumber", "+12017777777")) + .build(), + BatchSessionSummary.builder() + .setId("01M144V4PEH22EHB1SHMJRXBXA") + .setState(SessionState.COMPLETED) + .setParameters(Collections.singletonMap("toNumber", "+12018888888")) + .build())) + .build(); + + TestHelpers.recursiveEquals(getDetailsResponse, expected); + } + + @When("^I send a request to stop batch processing$") + public void stop() { + stopResponse = service.stop("01M144V4N3GSTNVJ3V32TD7H9A"); + } + + @Then("^the response confirms the batch stop request was accepted$") + public void stopResult() { + BatchStopResponse expected = + BatchStopResponse.builder().setResult(BatchStopResponse.ResultEnum.STOP_REQUESTED).build(); + + TestHelpers.recursiveEquals(stopResponse, expected); + } +} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/CallsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/CallsSteps.java new file mode 100644 index 000000000..86a2cd537 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/CallsSteps.java @@ -0,0 +1,232 @@ +package com.sinch.sdk.e2e.domains.voice.v2; + +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.domains.voice.api.v2.CallsService; +import com.sinch.sdk.domains.voice.models.v2.Call; +import com.sinch.sdk.domains.voice.models.v2.CallDirection; +import com.sinch.sdk.domains.voice.models.v2.CallReason; +import com.sinch.sdk.domains.voice.models.v2.CallResult; +import com.sinch.sdk.domains.voice.models.v2.CallType; +import com.sinch.sdk.domains.voice.models.v2.Money; +import com.sinch.sdk.domains.voice.models.v2.OriginationType; +import com.sinch.sdk.domains.voice.models.v2.calls.request.CallPatchRequest; +import com.sinch.sdk.domains.voice.models.v2.calls.request.ListCallsQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.calls.request.StartCallRequest; +import com.sinch.sdk.domains.voice.models.v2.calls.response.CallsListResponse; +import com.sinch.sdk.domains.voice.models.v2.calls.response.StartCallResponse; +import com.sinch.sdk.domains.voice.models.v2.destination.Phone; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.CallEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.DialCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.HangupCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessageEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.PlayMessage; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.SayMessage; +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.Arrays; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Assertions; + +public class CallsSteps { + + CallsService service; + StartCallResponse startResponse; + Call getResponse; + CallsListResponse listOnePageResponse; + boolean interactionAccepted; + + @Given("^the Voice-V2 service \"Calls\" is available$") + public void serviceAvailable() { + service = Config.getSinchClient().voice().v2().calls(); + Assertions.assertNotNull(service, "Voice V2 Calls service is not available"); + } + + @When("^I send a request to start a call$") + public void start() { + MessagesCommand notification = + MessagesCommand.builder() + .setMessagesName("notification") + .setMessages( + Arrays.asList( + PlayMessage.builder() + .setUrl("https://samplelib.com/mp3/sample-12s.mp3") + .build(), + SayMessage.builder() + .setText( + "Hello! This is a test notification from Sinch. Your" + + " verification code is 4 8 3 7.") + .setVoiceName("Emma") + .build())) + .setEvents( + MessageEvents.builder() + .setOnFinish(Collections.singletonList(HangupCommand.builder().build())) + .build()) + .build(); + + DialCommand dial = + DialCommand.builder() + .setCallName("audio-notification") + .setFrom(Phone.builder().setNumber("+12015555555").build()) + .setTo(Phone.builder().setNumber("+12017777777").build()) + .setDialTimeoutDurationSeconds(30) + .setMaxCallDurationSeconds(300) + .setEvents( + CallEvents.builder().setOnAnswer(Collections.singletonList(notification)).build()) + .build(); + + startResponse = + service.start( + StartCallRequest.builder() + .setCommands(Collections.singletonList(dial)) + .setIdempotencyKey("3f1c7a52-8d3e-4b9a-9c0e-2f6d1b7a4e21") + .build()); + } + + @Then("^the response contains the information about the call started$") + public void startResult() { + StartCallResponse expected = + StartCallResponse.builder() + .setProjectId("a1b2c3d4-e5f6-4789-a012-b3c4d5e6f789") + .setServiceId("f9e8d7c6-b5a4-4321-9876-c5d4e3f2a1b0") + .setSessionId("01HZXK7QNPMR8VD3JW9YF2C4TB") + .build(); + + TestHelpers.recursiveEquals(startResponse, expected); + } + + @When("^I send a request to get call details$") + public void get() { + getResponse = service.get("01HZXK8FQNPMR8VD3JW9YF2C5A"); + } + + @Then("^the response contains the call details$") + public void getResult() { + Call expected = + Call.builder() + .setCallId("01HZXK8FQNPMR8VD3JW9YF2C5A") + .setServiceId("f9e8d7c6-b5a4-4321-9876-c5d4e3f2a1b0") + .setProjectId("a1b2c3d4-e5f6-4789-a012-b3c4d5e6f789") + .setCallName("audio-notification") + .setSessionId("01HZXK7QNPMR8VD3JW9YF2C4TB") + .setFrom(Phone.builder().setNumber("+12015555555").build()) + .setTo(Phone.builder().setNumber("+12017777777").build()) + .setDirection(CallDirection.OUTBOUND) + .setCallResult(CallResult.NO_ANSWER) + .setCallType(CallType.PHONE) + .setOriginationType(OriginationType.SERVER) + .setStartTime(Instant.parse("2026-09-14T10:24:34Z")) + .setEndTime(Instant.parse("2026-09-14T10:24:49Z")) + .setUpdateTime(Instant.parse("2026-09-14T10:24:49Z")) + .setCallDurationSeconds(0) + .setCallRate(Money.builder().setCurrencyCode("EUR").setAmount("0.4085").build()) + .setCallReason(CallReason.NOT_AVAILABLE) + .setCallResourceUrl( + "https://eu1.voice.api.sinch.com/v2/projects/a1b2c3d4-e5f6-4789-a012-b3c4d5e6f789/calls/01HZXK8FQNPMR8VD3JW9YF2C5A") + .build(); + + TestHelpers.recursiveEquals(getResponse, expected); + } + + @When("^I send a request to list calls$") + public void listOnePage() { + listOnePageResponse = service.list(ListCallsQueryParameters.builder().setPageSize(2).build()); + } + + @When("^I send a request to list all the calls$") + public void listAll() { + listOnePageResponse = service.list(ListCallsQueryParameters.builder().setPageSize(2).build()); + } + + @When("^I iterate manually over the calls pages$") + public void listAllByPage() { + listOnePageResponse = service.list(ListCallsQueryParameters.builder().setPageSize(2).build()); + } + + @Then("the response content contains \"{int}\" calls") + public void onePageResult(int expected) { + + Assertions.assertEquals(expected, listOnePageResponse.getContent().size()); + } + + @Then("the calls list contains \"{int}\" calls") + public void listAllResult(int expected) { + + AtomicInteger count = new AtomicInteger(); + listOnePageResponse.iterator().forEachRemaining(_unused -> count.getAndIncrement()); + + Assertions.assertEquals(expected, count.get()); + } + + @Then("the calls iteration result contains the data from \"{int}\" pages") + public void listAllByPageResult(int expected) { + + int count = listOnePageResponse.getContent().isEmpty() ? 0 : 1; + CallsListResponse currentPage = listOnePageResponse; + while (currentPage.hasNextPage()) { + count++; + currentPage = currentPage.nextPage(); + } + Assertions.assertEquals(expected, count); + } + + @When("^I send a request to interact with an ongoing call by call ID$") + public void interactByCallId() { + MessagesCommand messages = + MessagesCommand.builder() + .setMessages( + Collections.singletonList( + SayMessage.builder() + .setText("Hello, your call is now connected.") + .setVoiceName("Emma") + .build())) + .setEvents( + MessageEvents.builder() + .setOnFinish(Collections.singletonList(HangupCommand.builder().build())) + .build()) + .build(); + + service.interactByCallId( + "01HZXK8FQNPMR8VD3JW9YF2C5A", + CallPatchRequest.builder() + .setCommands(Collections.singletonList(messages)) + .setIdempotencyKey("5d8f2a6c-1e4b-4c9d-8a3f-7b6e0c2d9f14") + .build()); + interactionAccepted = true; + } + + @When("^I send a request to interact with an ongoing call by call name$") + public void interactByCallName() { + MessagesCommand messages = + MessagesCommand.builder() + .setMessages( + Collections.singletonList( + SayMessage.builder() + .setText("Hello, your call is now connected.") + .setVoiceName("Emma") + .build())) + .setEvents( + MessageEvents.builder() + .setOnFinish(Collections.singletonList(HangupCommand.builder().build())) + .build()) + .build(); + + service.interactByCallName( + "01HZXK8FQNPMR8VD3JW9YF2C5B", + "origin", + CallPatchRequest.builder() + .setCommands(Collections.singletonList(messages)) + .setIdempotencyKey("7c1e9b4a-3f2d-4e8a-9b6c-0d5f8a2e1c37") + .build()); + interactionAccepted = true; + } + + @Then("^the response confirms the interaction request was accepted$") + public void interactResult() { + Assertions.assertTrue(interactionAccepted); + } +} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/ServicesSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/ServicesSteps.java new file mode 100644 index 000000000..dbc20e5e4 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/ServicesSteps.java @@ -0,0 +1,181 @@ +package com.sinch.sdk.e2e.domains.voice.v2; + +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.domains.voice.api.v2.ServicesService; +import com.sinch.sdk.domains.voice.models.v2.services.EventDestinationCallBehavior; +import com.sinch.sdk.domains.voice.models.v2.services.EventDestinationConfiguration; +import com.sinch.sdk.domains.voice.models.v2.services.NoneCallBehavior; +import com.sinch.sdk.domains.voice.models.v2.services.request.CreateServiceRequest; +import com.sinch.sdk.domains.voice.models.v2.services.request.ListServicesQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.services.request.UpdateServiceRequest; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceResponse; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServicesListResponse; +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.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Assertions; + +public class ServicesSteps { + + ServicesService service; + ServiceResponse createResponse; + ServiceResponse getResponse; + ServiceResponse updateResponse; + Boolean deletePassed; + ServicesListResponse listOnePageResponse; + + @Given("^the Voice-V2 service \"Services\" is available$") + public void serviceAvailable() { + service = Config.getSinchClient().voice().v2().services(); + Assertions.assertNotNull(service, "Voice V2 Services service is not available"); + } + + @When("^I send a request to create a Voice-V2 service$") + public void create() { + createResponse = + service.create( + CreateServiceRequest.builder() + .setName("Example service") + .setIdempotencyKey("9e4b2c7a-1d5f-4a8e-b3c6-2f7d0a9e5b18") + .build()); + } + + @Then("^the response contains the information about the Voice-V2 service created$") + public void createResult() { + ServiceResponse expected = + ServiceResponse.builder() + .setServiceId("1a2b3c4d-5e6f-4789-a012-b3c4d5e6f789") + .setServiceSecret("9+MXX5u6dky5aATA7ZfIRg==") + .setProjectId("c3d4e5f6-a7b8-4901-c234-d5e6f7a8b901") + .setCreateTime(Instant.parse("2026-09-16T10:44:09Z")) + .setUpdateTime(Instant.parse("2026-09-16T10:44:09Z")) + .setName("Example service") + .setDescription("") + .setIsDefault(false) + .build(); + + TestHelpers.recursiveEquals(createResponse, expected); + } + + @When("^I send a request to get Voice-V2 service details$") + public void get() { + getResponse = service.get("3c4d5e6f-7a8b-4901-c234-d5e6f7a8b901"); + } + + @Then("^the response contains the Voice-V2 service details$") + public void getResult() { + ServiceResponse expected = + ServiceResponse.builder() + .setServiceId("3c4d5e6f-7a8b-4901-c234-d5e6f7a8b901") + .setServiceSecret("9+MXX5u6dky5aATA7ZfIRg==") + .setProjectId("c3d4e5f6-a7b8-4901-c234-d5e6f7a8b901") + .setCreateTime(Instant.parse("2026-09-16T10:47:36Z")) + .setUpdateTime(Instant.parse("2026-09-16T10:47:36Z")) + .setName("Example service 2") + .setDescription("") + .setIsDefault(false) + .setCallBehavior(NoneCallBehavior.NONE_CALL_BEHAVIOR) + .build(); + + TestHelpers.recursiveEquals(getResponse, expected); + } + + @When("^I send a request to update a Voice-V2 service$") + public void update() { + updateResponse = + service.update( + "3c4d5e6f-7a8b-4901-c234-d5e6f7a8b901", + UpdateServiceRequest.builder() + .setDescription("Service with webhooks") + .setCallBehavior(webhookCallBehavior()) + .setIdempotencyKey("5d8a1f3c-6e2b-4c9d-a7f0-3b1e8c4d2a96") + .build()); + } + + @Then("^the response contains the information about the Voice-V2 service updated$") + public void updateResult() { + ServiceResponse expected = + ServiceResponse.builder() + .setServiceId("3c4d5e6f-7a8b-4901-c234-d5e6f7a8b901") + .setServiceSecret("9+MXX5u6dky5aATA7ZfIRg==") + .setProjectId("c3d4e5f6-a7b8-4901-c234-d5e6f7a8b901") + .setCreateTime(Instant.parse("2026-09-16T10:47:36Z")) + .setUpdateTime(Instant.parse("2026-09-16T10:51:55Z")) + .setName("Example service 2") + .setDescription("Service with webhooks") + .setIsDefault(false) + .setCallBehavior(webhookCallBehavior()) + .build(); + + TestHelpers.recursiveEquals(updateResponse, expected); + } + + @When("^I send a request to delete a Voice-V2 service$") + public void delete() { + service.delete("3c4d5e6f-7a8b-4901-c234-d5e6f7a8b901"); + deletePassed = true; + } + + @Then("^the response confirms the Voice-V2 service was deleted$") + public void deleteResult() { + Assertions.assertTrue(deletePassed); + } + + private static EventDestinationCallBehavior webhookCallBehavior() { + return EventDestinationCallBehavior.builder() + .setEventDestination( + EventDestinationConfiguration.builder() + .setUrl("https://example.com/webhook") + .setFallbackUrl("https://example.com/fallback") + .build()) + .build(); + } + + @When("^I send a request to list Voice-V2 services$") + public void listOnePage() { + listOnePageResponse = + service.list(ListServicesQueryParameters.builder().setPageSize(2).build()); + } + + @When("^I send a request to list all the Voice-V2 services$") + public void listAll() { + listOnePageResponse = + service.list(ListServicesQueryParameters.builder().setPageSize(2).build()); + } + + @When("^I iterate manually over the Voice-V2 services pages$") + public void listAllByPage() { + listOnePageResponse = + service.list(ListServicesQueryParameters.builder().setPageSize(2).build()); + } + + @Then("the response contains \"{int}\" Voice-V2 services") + public void onePageResult(int expected) { + + Assertions.assertEquals(expected, listOnePageResponse.getContent().size()); + } + + @Then("the services list contains \"{int}\" Voice-V2 services") + public void listAllResult(int expected) { + + AtomicInteger count = new AtomicInteger(); + listOnePageResponse.iterator().forEachRemaining(_unused -> count.getAndIncrement()); + + Assertions.assertEquals(expected, count.get()); + } + + @Then("the services iteration result contains the data from \"{int}\" Voice-V2 service pages") + public void listAllByPageResult(int expected) { + + int count = listOnePageResponse.getContent().isEmpty() ? 0 : 1; + ServicesListResponse currentPage = listOnePageResponse; + while (currentPage.hasNextPage()) { + count++; + currentPage = currentPage.nextPage(); + } + Assertions.assertEquals(expected, count); + } +} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/SessionsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/SessionsSteps.java new file mode 100644 index 000000000..26a1e3b2f --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/SessionsSteps.java @@ -0,0 +1,78 @@ +package com.sinch.sdk.e2e.domains.voice.v2; + +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.domains.voice.api.v2.SessionsService; +import com.sinch.sdk.domains.voice.models.v2.Call; +import com.sinch.sdk.domains.voice.models.v2.CallDirection; +import com.sinch.sdk.domains.voice.models.v2.CallReason; +import com.sinch.sdk.domains.voice.models.v2.CallResult; +import com.sinch.sdk.domains.voice.models.v2.CallType; +import com.sinch.sdk.domains.voice.models.v2.Money; +import com.sinch.sdk.domains.voice.models.v2.OriginationType; +import com.sinch.sdk.domains.voice.models.v2.SessionState; +import com.sinch.sdk.domains.voice.models.v2.destination.Phone; +import com.sinch.sdk.domains.voice.models.v2.sessions.response.Session; +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.Collections; +import org.junit.jupiter.api.Assertions; + +public class SessionsSteps { + + SessionsService service; + Session getResponse; + + @Given("^the Voice-V2 service \"Sessions\" is available$") + public void serviceAvailable() { + service = Config.getSinchClient().voice().v2().sessions(); + Assertions.assertNotNull(service, "Voice V2 Sessions service is not available"); + } + + @When("^I send a request to get a session$") + public void get() { + getResponse = service.get("01J7K3X9M2P5R8V0W4Y6Z1A3B5"); + } + + @Then("^the response contains the session details$") + public void getResult() { + Call call = + Call.builder() + .setCallId("01J7K3Y2N4Q6S9W1X5Z7A2B4C6") + .setServiceId("e52d19b4-03fa-4e89-a901-d78b12f6a9e2") + .setProjectId("a8f3b91c-4e2d-4190-883a-71b5c92e31d4") + .setSessionId("01J7K3X9M2P5R8V0W4Y6Z1A3B5") + .setBatchId("01J7K3Z5P6R8T0X2Y7A9B3C5D7") + .setFrom(Phone.builder().setNumber("+12015555555").build()) + .setTo(Phone.builder().setNumber("+12017777777").build()) + .setDirection(CallDirection.OUTBOUND) + .setCallResult(CallResult.NO_ANSWER) + .setCallType(CallType.PHONE) + .setOriginationType(OriginationType.SERVER) + .setStartTime(Instant.parse("2026-08-28T12:17:18Z")) + .setEndTime(Instant.parse("2026-08-28T12:17:49Z")) + .setUpdateTime(Instant.parse("2026-08-28T12:17:49Z")) + .setCallDurationSeconds(0) + .setCallRate(Money.builder().setCurrencyCode("EUR").setAmount("0.4085").build()) + .setCallReason(CallReason.NOT_AVAILABLE) + .setCallResourceUrl( + "https://eu1.voice.api.sinch.com/v2/projects/a8f3b91c-4e2d-4190-883a-71b5c92e31d4/calls/01J7K3Y2N4Q6S9W1X5Z7A2B4C6") + .build(); + + Session expected = + Session.builder() + .setSessionId("01J7K3X9M2P5R8V0W4Y6Z1A3B5") + .setServiceId("e52d19b4-03fa-4e89-a901-d78b12f6a9e2") + .setProjectId("a8f3b91c-4e2d-4190-883a-71b5c92e31d4") + .setCalls(Collections.singletonList(call)) + .setState(SessionState.COMPLETED) + .setCreateTime(Instant.parse("2026-08-28T12:17:18Z")) + .setEndTime(Instant.parse("2026-08-28T12:17:49Z")) + .setUpdateTime(Instant.parse("2026-08-28T12:17:49Z")) + .build(); + + TestHelpers.recursiveEquals(getResponse, expected); + } +} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/SinchEventsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/SinchEventsSteps.java new file mode 100644 index 000000000..6aeb7fb61 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/SinchEventsSteps.java @@ -0,0 +1,166 @@ +package com.sinch.sdk.e2e.domains.voice.v2; + +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.domains.voice.api.v2.SinchEventsService; +import com.sinch.sdk.domains.voice.models.v2.Call; +import com.sinch.sdk.domains.voice.models.v2.CallDirection; +import com.sinch.sdk.domains.voice.models.v2.CallResult; +import com.sinch.sdk.domains.voice.models.v2.CallType; +import com.sinch.sdk.domains.voice.models.v2.Money; +import com.sinch.sdk.domains.voice.models.v2.OriginationType; +import com.sinch.sdk.domains.voice.models.v2.destination.Phone; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.SinchEventType; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEvent; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEventResponse; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.HangupCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessageEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.SayMessage; +import com.sinch.sdk.e2e.Config; +import com.sinch.sdk.e2e.domains.WebhooksHelper; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import java.io.IOException; +import java.net.URL; +import java.time.Instant; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Assertions; + +public class SinchEventsSteps { + + static final String SERVICE_ID = "serviceKey"; + static final String SERVICE_SECRET = "BeIukql3pTKJ8RGL5zo0DA=="; + static final String WEBHOOKS_PATH_PREFIX = "/webhooks/voice-v2"; + + SinchEventsService service; + + Map> receivedEvents = new HashMap<>(); + + static Call.Builder baseCall() { + return Call.builder() + .setCallId("01HZXK7QNPMR8VD3JW9YF2C4CA") + .setServiceId("f9e8d7c6-b5a4-4321-9876-c5d4e3f2a1b0") + .setProjectId("a1b2c3d4-e5f6-4789-a012-b3c4d5e6f789") + .setSessionId("01HZXK7QNPMR8VD3JW9YF2C4TB") + .setCallType(CallType.PHONE) + .setStartTime(Instant.parse("2026-09-04T10:14:55Z")) + .setCallRate(Money.builder().setCurrencyCode("EUR").setAmount("0.0095").build()) + .setCallResourceUrl( + "/v2/projects/a1b2c3d4-e5f6-4789-a012-b3c4d5e6f789/calls/01HZXK7QNPMR8VD3JW9YF2C4CA"); + } + + static final Call incomingCall = + baseCall() + .setFrom(Phone.builder().setNumber("+12015555555").build()) + .setTo(Phone.builder().setNumber("+12017777777").build()) + .setDirection(CallDirection.INBOUND) + .setCallResult(CallResult.INITIATED) + .setOriginationType(OriginationType.PHONE) + .build(); + + static final Call answeredCall = + baseCall() + .setFrom(Phone.builder().setNumber("+12017777777").build()) + .setTo(Phone.builder().setNumber("+12015555555").build()) + .setDirection(CallDirection.OUTBOUND) + .setCallResult(CallResult.IN_PROGRESS) + .setOriginationType(OriginationType.SERVER) + .setAnswerTime(Instant.parse("2026-09-04T10:14:58Z")) + .setUpdateTime(Instant.parse("2026-09-04T10:14:58Z")) + .build(); + + static final Map expectedEvents = new HashMap<>(); + + static { + expectedEvents.put( + "call.incoming", + VoiceSinchEvent.builder() + .setEvent(SinchEventType.CALL_INCOMING) + .setCall(incomingCall) + .build()); + expectedEvents.put( + "call.answered", + VoiceSinchEvent.builder() + .setEvent(SinchEventType.CALL_ANSWERED) + .setCall(answeredCall) + .build()); + expectedEvents.put( + "call.webhook.on-answer", + VoiceSinchEvent.builder() + .setEvent(SinchEventType.from("call.customEvent.on-answer")) + .setCall(answeredCall) + .build()); + } + + static final VoiceSinchEventResponse onAnswerResponse = + VoiceSinchEventResponse.builder() + .setCommands( + Collections.singletonList( + MessagesCommand.builder() + .setMessagesName("from-webhook-server") + .setMessages( + Collections.singletonList( + SayMessage.builder() + .setText("This message came from your local webhook server.") + .setVoiceName("Emma") + .build())) + .setEvents( + MessageEvents.builder() + .setOnFinish( + Collections.singletonList(HangupCommand.builder().build())) + .build()) + .build())) + .build(); + + // "call.webhook.on-answer" is served at "/webhooks/voice-v2/call/webhook/on-answer" + static String pathOf(String event) { + return WEBHOOKS_PATH_PREFIX + "/" + event.replace('.', '/'); + } + + @Given("^the Voice-V2 Webhooks handler is available$") + public void serviceAvailable() { + service = Config.getSinchClient().voice().v2().sinchEvents(); + } + + @When("I send a request to trigger a {string} event") + public void sendEvent(String event) throws IOException { + receivedEvents.put( + event, + WebhooksHelper.callURL( + new URL(Config.VOICE_V2_HOST_NAME + pathOf(event)), service::parseEvent)); + } + + @Then("the header of the {string} event contains a valid authorization") + public void validateHeader(String event) { + WebhooksHelper.Response received = receivedEvents.get(event); + + boolean validated = + service.validateAuthenticationHeader( + SERVICE_ID, + SERVICE_SECRET, + "POST", + pathOf(event), + received.headers, + received.rawPayload); + Assertions.assertTrue(validated); + } + + @Then("the Voice-V2 event describes a {string} event") + public void validateEvent(String event) { + TestHelpers.recursiveEquals(receivedEvents.get(event).event, expectedEvents.get(event)); + } + + @Then("the response to the {string} event matches the expected call control instructions") + public void validateResponse(String event) throws IOException { + Assertions.assertEquals("call.webhook.on-answer", event); + + int status = + WebhooksHelper.postJson( + new URL(Config.VOICE_V2_HOST_NAME + pathOf(event) + "/confirm"), + service.serializeResponse(onAnswerResponse)); + Assertions.assertEquals(200, status); + } +} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/SvamlSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/SvamlSteps.java new file mode 100644 index 000000000..ecb779368 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/SvamlSteps.java @@ -0,0 +1,109 @@ +package com.sinch.sdk.e2e.domains.voice.v2; + +import com.sinch.sdk.core.TestHelpers; +import com.sinch.sdk.domains.voice.api.v2.SvamlService; +import com.sinch.sdk.domains.voice.models.v2.destination.Phone; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlInput; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.CallEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.DialCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.HangupCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessageEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.PlayMessage; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.SayMessage; +import com.sinch.sdk.domains.voice.models.v2.svaml.request.DescribeSvamlRequest; +import com.sinch.sdk.domains.voice.models.v2.svaml.request.ValidateSvamlRequest; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.SvamlDescriptionResponse; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.ValidateSvamlResponse; +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.util.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Assertions; + +public class SvamlSteps { + + SvamlService service; + ValidateSvamlResponse validateResponse; + SvamlDescriptionResponse describeResponse; + + @Given("^the Voice-V2 service \"Svaml\" is available$") + public void serviceAvailable() { + service = Config.getSinchClient().voice().v2().svaml(); + Assertions.assertNotNull(service, "Voice V2 Svaml service is not available"); + } + + @When("^I send a request to validate a SVAML payload$") + public void validate() { + validateResponse = + service.validate(ValidateSvamlRequest.builder().setSvaml(svamlPayload()).build()); + } + + @Then("^the response confirms the SVAML payload is valid$") + public void validateResult() { + ValidateSvamlResponse expected = ValidateSvamlResponse.builder().setIsValid(true).build(); + + TestHelpers.recursiveEquals(validateResponse, expected); + } + + @When("^I send a request to describe a SVAML payload$") + public void describe() { + describeResponse = + service.describe(DescribeSvamlRequest.builder().setSvaml(svamlPayload()).build()); + } + + @Then("^the response contains the description of the SVAML payload$") + public void describeResult() { + SvamlDescriptionResponse expected = + SvamlDescriptionResponse.builder() + .setDescription( + "1. A new call with name 'audio-notification' will be initiated to number" + + " +12017777777 with max duration set to 5 minutes.\n" + + " * on answer:\n" + + " 1. An audio file will be played from" + + " https://samplelib.com/mp3/sample-12s.mp3.\n" + + " 1. A TTS message will be played using the voice Emma.\n" + + " * on finish:\n" + + " 1. The call will be disconnected") + .build(); + + TestHelpers.recursiveEquals(describeResponse, expected); + } + + private static SvamlInput svamlPayload() { + MessagesCommand notification = + MessagesCommand.builder() + .setMessagesName("notification") + .setMessages( + Arrays.asList( + PlayMessage.builder() + .setUrl("https://samplelib.com/mp3/sample-12s.mp3") + .build(), + SayMessage.builder() + .setText( + "Hello! This is a test notification from Sinch. Your" + + " verification code is 4 8 3 7.") + .setVoiceName("Emma") + .build())) + .setEvents( + MessageEvents.builder() + .setOnFinish(Collections.singletonList(HangupCommand.builder().build())) + .build()) + .build(); + + DialCommand dial = + DialCommand.builder() + .setCallName("audio-notification") + .setFrom(Phone.builder().setNumber("+12015555555").build()) + .setTo(Phone.builder().setNumber("+12017777777").build()) + .setDialTimeoutDurationSeconds(30) + .setMaxCallDurationSeconds(300) + .setEvents( + CallEvents.builder().setOnAnswer(Collections.singletonList(notification)).build()) + .build(); + + return SvamlInput.builder().setCommands(Collections.singletonList(dial)).build(); + } +} diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/VoiceIT.java b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/VoiceIT.java new file mode 100644 index 000000000..800cdb2c0 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/e2e/domains/voice/v2/VoiceIT.java @@ -0,0 +1,49 @@ +package com.sinch.sdk.e2e.domains.voice.v2; + +import static io.cucumber.junit.platform.engine.Constants.FILTER_NAME_PROPERTY_NAME; +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; + +/** + * Only the scenarios of the operations this SDK implements. Widen the resource and the name filter + * as the other Voice V2 operations land. + * + *

The filter matches scenario names across all features, and they share the same {@code [Get]} + * prefix. Each {@code [Get]} is therefore matched by its full name, so that a {@code [Get]} + * scenario of an operation not implemented yet stays out. + */ +@Suite +@SuiteDisplayName("Voice V2") +@IncludeEngines("cucumber") +@SelectClasspathResource("features/voice-v2/calls.feature") +@SelectClasspathResource("features/voice-v2/batches.feature") +@SelectClasspathResource("features/voice-v2/sessions.feature") +@SelectClasspathResource("features/voice-v2/services.feature") +@SelectClasspathResource("features/voice-v2/payloads.feature") +@SelectClasspathResource("features/voice-v2/webhooks-events.feature") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.sinch.sdk.e2e.domains.voice.v2") +@ConfigurationParameter( + key = FILTER_NAME_PROPERTY_NAME, + value = + "^\\[Start\\].*|^\\[Get\\] get a batch call summary$|^\\[GetDetails\\] get batch call" + + " details$|^\\[Stop\\] stop batch processing$|^\\[Get\\] get a session$" + + "|^\\[Get\\] get call details$" + + "|^\\[List\\] list (a page of|all the) calls.*$" + + "|^\\[InteractByCallId\\] interact with an ongoing call by call ID$" + + "|^\\[InteractByCallName\\] interact with an ongoing call by call name$" + + "|^\\[Create\\] create a Voice-V2 service$" + + "|^\\[Get\\] get Voice-V2 service details$" + + "|^\\[Update\\] update a Voice-V2 service$" + + "|^\\[Delete\\] delete a Voice-V2 service$" + + "|^\\[List\\] list (a page of|all the) Voice-V2 services.*$" + + "|^\\[Validate\\] validate a SVAML payload$" + + "|^\\[Describe\\] describe a SVAML payload$" + + "|^\\[(Incoming Call|Answered Call|Answer Webhook) Event\\].*") +@ConfigurationParameter(key = PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, value = "true") +public class VoiceIT {} diff --git a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheIdempotencyKeyTest.java b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheIdempotencyKeyTest.java new file mode 100644 index 000000000..0dae65b6a --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheIdempotencyKeyTest.java @@ -0,0 +1,134 @@ +package com.sinch.sdk.http; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.sinch.sdk.core.http.HttpContentType; +import com.sinch.sdk.core.http.HttpMethod; +import com.sinch.sdk.core.http.HttpRequest; +import com.sinch.sdk.core.http.IdempotencyKey; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.models.RetryConfiguration; +import com.sinch.sdk.models.TransportSettings; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +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; + +/** + * The transport generates the Idempotency-Key of the operations that accept one, so + * that the adapters only forward the key set by the user. + */ +class HttpClientApacheIdempotencyKeyTest { + + private MockWebServer server; + private HttpClientApache client; + + @BeforeEach + void setUp() throws IOException { + server = new MockWebServer(); + server.start(); + client = + new HttpClientApache( + null, + new DefaultRetryManager(RetryConfiguration.builder().setMaxRetryCount(1).build()), + TransportSettings.DEFAULT_TIMEOUT_SECONDS); + } + + @AfterEach + void tearDown() throws Exception { + client.close(); + server.shutdown(); + } + + @Test + void generatesKeyWhenNoneIsSet() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); + + invoke(Collections.emptyMap(), true); + + assertNotNull(server.takeRequest().getHeader(IdempotencyKey.HEADER)); + } + + @Test + void keepsKeySetByUser() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); + + invoke(Collections.singletonMap(IdempotencyKey.HEADER, "user-key-0123456789"), true); + + assertEquals("user-key-0123456789", server.takeRequest().getHeader(IdempotencyKey.HEADER)); + } + + // Header names are case-insensitive: a key set in lower case must not get a second, generated one + @Test + void keepsKeySetByUserInLowerCase() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); + + invoke(Collections.singletonMap("idempotency-key", "user-key-0123456789"), true); + + RecordedRequest request = server.takeRequest(); + assertEquals(1, request.getHeaders().values(IdempotencyKey.HEADER).size()); + assertEquals("user-key-0123456789", request.getHeader(IdempotencyKey.HEADER)); + } + + @Test + void noKeyWhenOperationDoesNotAcceptOne() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); + + invoke(Collections.emptyMap(), false); + + assertNull(server.takeRequest().getHeader(IdempotencyKey.HEADER)); + } + + // The key identifies the operation: the server can only answer a retry with the cached response + // of the first attempt if both carry the same key + @Test + void rateLimitedRetrySendsSameKey() throws Exception { + server.enqueue(new MockResponse().setResponseCode(429).addHeader("Retry-After", "0")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); + + invoke(Collections.emptyMap(), true); + + assertEquals(2, server.getRequestCount(), "expected the 429 to be retried once"); + RecordedRequest first = server.takeRequest(); + RecordedRequest second = server.takeRequest(); + assertNotNull(first.getHeader(IdempotencyKey.HEADER)); + assertEquals(first.getHeader(IdempotencyKey.HEADER), second.getHeader(IdempotencyKey.HEADER)); + } + + @Test + void eachCallGetsItsOwnKey() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); + + invoke(Collections.emptyMap(), true); + invoke(Collections.emptyMap(), true); + + assertNotEquals( + server.takeRequest().getHeader(IdempotencyKey.HEADER), + server.takeRequest().getHeader(IdempotencyKey.HEADER)); + } + + private void invoke(Map headers, boolean idempotent) { + client.invokeAPI( + new ServerConfiguration(server.url("/").toString()), + Collections.emptyMap(), + new HttpRequest( + "", + HttpMethod.POST, + Collections.emptyList(), + "{}", + headers, + Collections.singletonList(HttpContentType.APPLICATION_JSON), + Collections.singletonList(HttpContentType.APPLICATION_JSON), + Collections.emptyList(), + idempotent)); + } +} 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 68f21426b..731ec8240 100644 --- a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheLifecycleTest.java +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheLifecycleTest.java @@ -3,27 +3,31 @@ import static org.junit.jupiter.api.Assertions.*; import com.sinch.sdk.models.HttpProxyConfiguration; +import com.sinch.sdk.models.TransportSettings; import org.junit.jupiter.api.Test; class HttpClientApacheLifecycleTest { @Test void freshInstanceIsNotClosed() throws Exception { - try (HttpClientApache client = new HttpClientApache()) { + try (HttpClientApache client = + new HttpClientApache(null, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { assertFalse(client.isClosed()); } } @Test void closeMarksInstanceAsClosed() throws Exception { - HttpClientApache client = new HttpClientApache(); + HttpClientApache client = + new HttpClientApache(null, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS); client.close(); assertTrue(client.isClosed()); } @Test void doubleCloseIsIdempotent() throws Exception { - HttpClientApache client = new HttpClientApache(); + HttpClientApache client = + new HttpClientApache(null, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS); client.close(); assertDoesNotThrow(client::close); } @@ -32,7 +36,8 @@ void doubleCloseIsIdempotent() throws Exception { void closeViaAutoCloseable() { assertDoesNotThrow( () -> { - try (HttpClientApache client = new HttpClientApache()) { + try (HttpClientApache client = + new HttpClientApache(null, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { assertFalse(client.isClosed()); } }); @@ -42,14 +47,16 @@ void closeViaAutoCloseable() { void proxyConstructorCreatesOpenInstance() throws Exception { HttpProxyConfiguration proxy = HttpProxyConfiguration.builder().setHostname("proxy.example.com").setPort(3128).build(); - try (HttpClientApache client = new HttpClientApache(proxy)) { + try (HttpClientApache client = + new HttpClientApache(proxy, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { assertFalse(client.isClosed(), "HttpClientApache created with proxy config must be open"); } } @Test void proxyConstructorWithNullBehavesAsNoArgConstructor() throws Exception { - try (HttpClientApache client = new HttpClientApache(null)) { + try (HttpClientApache client = + new HttpClientApache(null, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { assertFalse(client.isClosed(), "HttpClientApache(null) must behave like no-arg constructor"); } } @@ -63,7 +70,8 @@ void proxyConstructorWithAuthCreatesOpenInstance() throws Exception { .setUsername("user") .setPassword("pass") .build(); - try (HttpClientApache client = new HttpClientApache(proxy)) { + try (HttpClientApache client = + new HttpClientApache(proxy, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { assertFalse( client.isClosed(), "HttpClientApache created with authenticated proxy config must be open"); diff --git a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheNoTransportRetryTest.java b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheNoTransportRetryTest.java index f28e7c907..d78855702 100644 --- a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheNoTransportRetryTest.java +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheNoTransportRetryTest.java @@ -8,6 +8,7 @@ import com.sinch.sdk.core.models.ServerConfiguration; import com.sinch.sdk.models.RetryConfiguration; import com.sinch.sdk.models.RetryPolicy; +import com.sinch.sdk.models.TransportSettings; import java.io.IOException; import java.util.concurrent.TimeUnit; import okhttp3.mockwebserver.MockResponse; @@ -62,7 +63,10 @@ private HttpResponse invoke() throws Exception { RetryConfiguration noSdkRetries = RetryConfiguration.builder().setRetryPolicy(RetryPolicy.NONE).build(); try (HttpClientApache client = - new HttpClientApache(null, new DefaultRetryManager(noSdkRetries))) { + new HttpClientApache( + null, + new DefaultRetryManager(noSdkRetries), + TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { HttpResponse response = client.invokeAPI( new ServerConfiguration(server.url("/").toString()), diff --git a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheProxyTest.java b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheProxyTest.java index 01e40bcc5..48fc80015 100644 --- a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheProxyTest.java +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheProxyTest.java @@ -6,6 +6,7 @@ import com.sinch.sdk.core.http.HttpRequest; import com.sinch.sdk.core.models.ServerConfiguration; import com.sinch.sdk.models.HttpProxyConfiguration; +import com.sinch.sdk.models.TransportSettings; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -43,7 +44,8 @@ void unauthenticatedProxyRequestRoutedThroughProxy() throws Exception { mockProxy.enqueue( new MockResponse().setBody("{}").addHeader("Content-Type", "application/json")); - try (HttpClientApache proxyClient = new HttpClientApache(proxySettings)) { + try (HttpClientApache proxyClient = + new HttpClientApache(proxySettings, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { proxyClient.invokeAPI( new ServerConfiguration("http://foo.com"), null, @@ -90,7 +92,8 @@ void authenticatedProxyCredentialsSentAfterChallenge() throws Exception { mockProxy.enqueue( new MockResponse().setBody("{}").addHeader("Content-Type", "application/json")); - try (HttpClientApache proxyClient = new HttpClientApache(proxySettings)) { + try (HttpClientApache proxyClient = + new HttpClientApache(proxySettings, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { proxyClient.invokeAPI( new ServerConfiguration("http://foo.com"), null, @@ -152,7 +155,8 @@ void authenticatedProxyNoChallengeSucceedsDirectly() throws Exception { mockProxy.enqueue( new MockResponse().setBody("{}").addHeader("Content-Type", "application/json")); - try (HttpClientApache proxyClient = new HttpClientApache(proxySettings)) { + try (HttpClientApache proxyClient = + new HttpClientApache(proxySettings, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { proxyClient.invokeAPI( new ServerConfiguration("http://foo.com"), null, diff --git a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheRetryAuthTest.java b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheRetryAuthTest.java index 7b8e6e820..4e02804a4 100644 --- a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheRetryAuthTest.java +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheRetryAuthTest.java @@ -8,6 +8,7 @@ import com.sinch.sdk.core.http.HttpRequest; import com.sinch.sdk.core.models.ServerConfiguration; import com.sinch.sdk.models.RetryConfiguration; +import com.sinch.sdk.models.TransportSettings; import java.io.IOException; import java.util.Collections; import java.util.Map; @@ -50,7 +51,8 @@ void eachRetryAttemptIsSignedAfresh() throws Exception { try (HttpClientApache client = new HttpClientApache( null, - new DefaultRetryManager(RetryConfiguration.builder().setMaxRetryCount(1).build()))) { + new DefaultRetryManager(RetryConfiguration.builder().setMaxRetryCount(1).build()), + TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { client.invokeAPI( new ServerConfiguration(server.url("/").toString()), auth, 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 e43a5bd94..cfaef6091 100644 --- a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTest.java +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTest.java @@ -11,6 +11,7 @@ import com.sinch.sdk.core.models.ServerConfiguration; import com.sinch.sdk.models.RetryConfiguration; import com.sinch.sdk.models.RetryPolicy; +import com.sinch.sdk.models.TransportSettings; import java.io.ByteArrayInputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -35,7 +36,7 @@ void setup() throws Exception { mockAuthManager = mock(AuthManager.class); // Create an instance of HttpClientApache and inject the mock HTTP client - client = spy(new HttpClientApache()); + client = spy(new HttpClientApache(null, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)); Field clientField = HttpClientApache.class.getDeclaredField("client"); clientField.setAccessible(true); clientField.set(client, mockApacheClient); @@ -152,7 +153,8 @@ void testInvokeApi407DoesNotTriggerOAuthRefresh() throws Exception { @Test void aClientCarryingNoManagerRetriesNothing() throws Exception { - try (HttpClientApache noRetries = new HttpClientApache(null, null)) { + try (HttpClientApache noRetries = + new HttpClientApache(null, null, TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { assertSame(RetryManager.NO_RETRY, noRetries.getRetryManager().get()); } } @@ -163,7 +165,8 @@ void exposesTheManagerItWasBuiltWith() throws Exception { RetryConfiguration.builder().setRetryPolicy(RetryPolicy.BACKOFF).build(); try (HttpClientApache configuredClient = - new HttpClientApache(null, new DefaultRetryManager(configured))) { + new HttpClientApache( + null, new DefaultRetryManager(configured), TransportSettings.DEFAULT_TIMEOUT_SECONDS)) { assertEquals( RetryPolicy.BACKOFF, configuredClient.getRetryManager().get().getRetryConfiguration().getRetryPolicy()); diff --git a/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTimeoutTest.java b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTimeoutTest.java new file mode 100644 index 000000000..7ab1d60ba --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/http/HttpClientApacheTimeoutTest.java @@ -0,0 +1,62 @@ +package com.sinch.sdk.http; + +import static org.junit.jupiter.api.Assertions.*; + +import com.sinch.sdk.core.exceptions.ApiException; +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 java.io.IOException; +import java.util.concurrent.TimeUnit; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class HttpClientApacheTimeoutTest { + + private MockWebServer server; + + @BeforeEach + void setUp() throws IOException { + server = new MockWebServer(); + server.start(); + } + + @AfterEach + void tearDown() throws IOException { + server.shutdown(); + } + + private HttpResponse invoke(HttpClientApache client) { + return client.invokeAPI( + new ServerConfiguration(server.url("/").toString()), + null, + new HttpRequest("", HttpMethod.GET, null, (String) null, null, null, null, null)); + } + + @Test + void slowHeadersTripTheTimeout() throws Exception { + server.enqueue(new MockResponse().setBody("{}").setHeadersDelay(3, TimeUnit.SECONDS)); + + try (HttpClientApache client = new HttpClientApache(null, RetryManager.NO_RETRY, 1)) { + long start = System.currentTimeMillis(); + assertThrows(ApiException.class, () -> invoke(client)); + long elapsed = System.currentTimeMillis() - start; + assertTrue( + elapsed < 2_000, + "expected the 1s timeout to fire before the 3s headers delay, took " + elapsed); + } + } + + @Test + void aResponseInsideTheTimeoutSucceeds() throws Exception { + server.enqueue(new MockResponse().setBody("{}")); + + try (HttpClientApache client = new HttpClientApache(null, RetryManager.NO_RETRY, 5)) { + assertEquals(200, invoke(client).getCode()); + } + } +} diff --git a/client/src/test/java/com/sinch/sdk/models/TransportSettingsTest.java b/client/src/test/java/com/sinch/sdk/models/TransportSettingsTest.java new file mode 100644 index 000000000..f0a2da5aa --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/models/TransportSettingsTest.java @@ -0,0 +1,118 @@ +package com.sinch.sdk.models; + +import static org.junit.jupiter.api.Assertions.*; + +import com.sinch.sdk.BaseTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class TransportSettingsTest extends BaseTest { + + @Nested + class Defaults { + + @Test + void anUnconfiguredInstanceCarriesTheDocumentedDefaults() { + TransportSettings settings = TransportSettings.builder().build(); + + assertEquals(60, settings.getTimeoutSeconds()); + assertTrue(settings.getUseSinchAuth()); + } + + @Test + void theDefaultConstantsAreThemselvesValid() { + assertEquals( + TransportSettings.DEFAULT_TIMEOUT_SECONDS, + TransportSettings.DEFAULTS.getTimeoutSeconds()); + assertEquals( + TransportSettings.DEFAULT_USE_SINCH_AUTH, TransportSettings.DEFAULTS.getUseSinchAuth()); + } + + @Test + void eachFieldFallsBackIndependently() { + TransportSettings settings = TransportSettings.builder().setUseSinchAuth(false).build(); + + assertFalse(settings.getUseSinchAuth()); + assertEquals( + TransportSettings.DEFAULT_TIMEOUT_SECONDS, + settings.getTimeoutSeconds(), + "setting one field must not disturb the others"); + } + } + + @Nested + class Validation { + + @Test + void zeroIsAcceptedAndDisablesTheTimeout() { + assertEquals(0, TransportSettings.builder().setTimeoutSeconds(0).build().getTimeoutSeconds()); + } + + @Test + void aNegativeTimeoutIsRejected() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> TransportSettings.builder().setTimeoutSeconds(-1).build()); + assertTrue(exception.getMessage().contains("timeoutSeconds")); + } + } + + @Nested + class BuilderRoundTrip { + + @Test + void anExistingInstanceRebuildsIdentically() { + TransportSettings source = + TransportSettings.builder().setTimeoutSeconds(15).setUseSinchAuth(false).build(); + + TransportSettings copy = TransportSettings.builder(source).build(); + + assertEquals(source.getTimeoutSeconds(), copy.getTimeoutSeconds()); + assertEquals(source.getUseSinchAuth(), copy.getUseSinchAuth()); + } + + @Test + void aNullSourceLeavesTheBuilderAtItsDefaults() { + assertEquals( + TransportSettings.DEFAULT_TIMEOUT_SECONDS, + TransportSettings.builder(null).build().getTimeoutSeconds()); + } + } + + @Nested + class OnSinchConfiguration { + + @Test + void isEmptyWhenNotConfiguredSoTheDefaultsApply() { + assertFalse(Configuration.builder().build().getTransportSettings().isPresent()); + } + + @Test + void isCarriedThroughTheBuilder() { + Configuration configuration = + Configuration.builder() + .setTransportSettings(TransportSettings.builder().setTimeoutSeconds(30).build()) + .build(); + + assertEquals(30, configuration.getTransportSettings().get().getTimeoutSeconds()); + } + + @Test + void survivesRebuildingTheConfiguration() { + Configuration source = + Configuration.builder() + .setTransportSettings(TransportSettings.builder().setTimeoutSeconds(15).build()) + .build(); + + Configuration rebuilt = Configuration.builder(source).build(); + + assertEquals(15, rebuilt.getTransportSettings().get().getTimeoutSeconds()); + } + + @Test + void isReportedInToString() { + assertTrue(Configuration.builder().build().toString().contains("transportSettings=")); + } + } +} diff --git a/core/src/main/com/sinch/sdk/core/exceptions/ApiExceptionBuilder.java b/core/src/main/com/sinch/sdk/core/exceptions/ApiExceptionBuilder.java index 4ffdbbef5..e4195d130 100644 --- a/core/src/main/com/sinch/sdk/core/exceptions/ApiExceptionBuilder.java +++ b/core/src/main/com/sinch/sdk/core/exceptions/ApiExceptionBuilder.java @@ -34,6 +34,11 @@ public static ApiException build(String message, int code, Map mapped } exception = getExceptionFromVerificationError(mappedResponse); + if (exception.isPresent()) { + return exception.get(); + } + + exception = getExceptionFromVoiceV2Error(code, mappedResponse); return exception.orElseGet(() -> new ApiException(code, message)); } @@ -129,6 +134,29 @@ private static Optional getExceptionFromVerificationError( codeValue, String.format("%s (reference=%s)", messageValue, referenceValue))); } + private static Optional getExceptionFromVoiceV2Error( + int intCode, Map mappedResponse) { + + // excepted Voice V2 API errors have following form (RFC 9457 Problem Details) + // "type": string, + // "title": string, + // "detail": string, + // "instance": string + + if (null == mappedResponse) { + return Optional.empty(); + } + + Object title = mappedResponse.get("title"); + Object detail = mappedResponse.get("detail"); + + if (null == title || null == detail) { + return Optional.empty(); + } + + return Optional.of(new ApiException(intCode, String.format("%s: %s", title, detail))); + } + private static String extractErrorDetails(Map errorContent) { // API is having a "details" field Collection details = (Collection) errorContent.get("details"); diff --git a/core/src/main/com/sinch/sdk/core/http/HttpRequest.java b/core/src/main/com/sinch/sdk/core/http/HttpRequest.java index 64f692658..17fa40cce 100644 --- a/core/src/main/com/sinch/sdk/core/http/HttpRequest.java +++ b/core/src/main/com/sinch/sdk/core/http/HttpRequest.java @@ -16,6 +16,7 @@ public class HttpRequest { private final Collection contentType; private final Collection authNames; private final Map formParams; + private final boolean idempotent; public HttpRequest( String fullUrl, @@ -35,6 +36,7 @@ public HttpRequest( this.contentType = contentType; this.authNames = authNames; this.formParams = null; + this.idempotent = false; } public HttpRequest( @@ -46,6 +48,34 @@ public HttpRequest( Collection accept, Collection contentType, Collection authNames) { + this(path, method, queryParameters, body, headerParams, accept, contentType, authNames, false); + } + + /** + * Request to an operation that accepts an Idempotency-Key header + * + * @param path Path of the operation + * @param method HTTP method + * @param queryParameters Query parameters + * @param body Serialized body + * @param headerParams Headers, with the key set by the user if any + * @param accept Accepted content types + * @param contentType Content type of the body + * @param authNames Authentication schemes + * @param idempotent true when the operation accepts an Idempotency-Key + * header: the transport then generates one if the user did not set it + * @since 2.3 + */ + public HttpRequest( + String path, + HttpMethod method, + Collection queryParameters, + String body, + Map headerParams, + Collection accept, + Collection contentType, + Collection authNames, + boolean idempotent) { this.fullUrl = null; this.path = path; this.method = method; @@ -56,6 +86,7 @@ public HttpRequest( this.contentType = contentType; this.authNames = authNames; this.formParams = null; + this.idempotent = idempotent; } public HttpRequest( @@ -77,6 +108,7 @@ public HttpRequest( this.contentType = contentType; this.authNames = authNames; this.body = null; + this.idempotent = false; } public Optional getFullUrl() { @@ -118,4 +150,14 @@ public Collection getAuthNames() { public Map getFormParams() { return formParams; } + + /** + * Whether the operation accepts an Idempotency-Key header + * + * @return true when the transport has to generate a key if none is set + * @since 2.3 + */ + public boolean isIdempotent() { + return idempotent; + } } diff --git a/core/src/main/com/sinch/sdk/core/http/IdempotencyKey.java b/core/src/main/com/sinch/sdk/core/http/IdempotencyKey.java new file mode 100644 index 000000000..aba657399 --- /dev/null +++ b/core/src/main/com/sinch/sdk/core/http/IdempotencyKey.java @@ -0,0 +1,33 @@ +package com.sinch.sdk.core.http; + +import java.util.UUID; +import java.util.logging.Logger; + +/** + * Keys for the operations that accept an Idempotency-Key header. + * + *

A key identifies one operation: it is sent again on the retries of that operation and never + * reused for another one. + * + * @since 2.3 + */ +public final class IdempotencyKey { + + private static final Logger LOGGER = Logger.getLogger(IdempotencyKey.class.getName()); + + /** Name of the HTTP header carrying the key */ + public static final String HEADER = "Idempotency-Key"; + + private IdempotencyKey() {} + + /** + * Generate a new key: a random UUID, as recommended by the Sinch API standards. + * + * @return A new key, to use for one operation and its retries + */ + public static String generate() { + String key = UUID.randomUUID().toString(); + LOGGER.fine("Generated idempotency key: " + key); + return key; + } +} diff --git a/core/src/test/java/com/sinch/sdk/core/exceptions/ApiExceptionBuilderTest.java b/core/src/test/java/com/sinch/sdk/core/exceptions/ApiExceptionBuilderTest.java index 37e8d55c5..f446f32a2 100644 --- a/core/src/test/java/com/sinch/sdk/core/exceptions/ApiExceptionBuilderTest.java +++ b/core/src/test/java/com/sinch/sdk/core/exceptions/ApiExceptionBuilderTest.java @@ -30,6 +30,9 @@ class ApiExceptionBuilderTest extends BaseTest { @GivenTextResource("/domains/verification/v1/error-response.json") String verificationError; + @GivenTextResource("/domains/voice/v2/error-response.json") + String voiceV2Error; + @Test void buildFromObject() throws JsonProcessingException { ApiException e = @@ -81,4 +84,16 @@ void verificationError() throws JsonProcessingException { assertEquals("Requested resource was not found. (reference=a reference)", e.getMessage()); assertEquals(40400, e.getCode()); } + + @Test + void voiceV2Error() throws JsonProcessingException { + ApiException e = + ApiExceptionBuilder.build( + "message", + 400, + Mapper.getInstance() + .readValue(voiceV2Error, new TypeReference>() {})); + assertEquals("Bad Request: The request body is invalid or malformed.", e.getMessage()); + assertEquals(400, e.getCode()); + } } diff --git a/core/src/test/java/com/sinch/sdk/core/http/IdempotencyKeyTest.java b/core/src/test/java/com/sinch/sdk/core/http/IdempotencyKeyTest.java new file mode 100644 index 000000000..20f3782dd --- /dev/null +++ b/core/src/test/java/com/sinch/sdk/core/http/IdempotencyKeyTest.java @@ -0,0 +1,19 @@ +package com.sinch.sdk.core.http; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class IdempotencyKeyTest { + + // Sinch API standards: keys are 16 to 128 characters long + @Test + void generatedKeyLengthFollowsStandard() { + Assertions.assertThat(IdempotencyKey.generate()).hasSizeBetween(16, 128); + } + + // One key per operation: two calls must not return the same key + @Test + void generatedKeysAreDistinct() { + Assertions.assertThat(IdempotencyKey.generate()).isNotEqualTo(IdempotencyKey.generate()); + } +} diff --git a/examples/sinch-events/README.md b/examples/sinch-events/README.md index 2d7f4e3f4..c4f54579f 100644 --- a/examples/sinch-events/README.md +++ b/examples/sinch-events/README.md @@ -27,10 +27,11 @@ Use these keys when request authentication is enabled in each controller (`ensur - `conversation.sinchevents.secret` for Conversation callbacks - `numbers.sinchevents.secret` for Numbers callbacks - `sms.sinchevents.secret` for SMS callbacks +- `voice.v2.sinchevents.service-id` and `voice.v2.sinchevents.service-secret` for Voice V2 callbacks: the ID and secret of the service the events are sent for ### Voice and Verification authentication -Voice and Verification callback validation uses application credentials: +Voice (V1) and Verification callback validation uses application credentials: - `credentials.application-api-key` - `credentials.application-api-secret` @@ -65,6 +66,7 @@ When the server is running, controllers listen on: | [SMS](https://developers.sinch.com/docs/sms/api-reference/sms/tag/Webhooks/#tag/Webhooks/section/Callbacks) | `/SmsEvent` | | [Verification](https://developers.sinch.com/docs/verification/api-reference/verification/tag/Verification-callbacks/) | `/VerificationEvent` | | [Voice](https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks/) | `/VoiceEvent` | +| [Voice V2](https://developers.sinch.com/docs/voice-2.0) | `/VoiceV2Event` | ## Expose local server with ngrok @@ -89,5 +91,6 @@ Example callback URLs: - `https:///SmsEvent` - `https:///VerificationEvent` - `https:///VoiceEvent` +- `https:///VoiceV2Event` Dashboard: [Sinch Dashboard](https://dashboard.sinch.com) diff --git a/examples/sinch-events/src/main/java/com/mycompany/app/voice/v2/Controller.java b/examples/sinch-events/src/main/java/com/mycompany/app/voice/v2/Controller.java new file mode 100644 index 000000000..72d05d8c8 --- /dev/null +++ b/examples/sinch-events/src/main/java/com/mycompany/app/voice/v2/Controller.java @@ -0,0 +1,104 @@ +package com.mycompany.app.voice.v2; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.SinchEventsService; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.SinchEventType; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEvent; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEventResponse; +import java.util.Map; +import java.util.logging.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +@RestController("VoiceV2") +public class Controller { + + private static final Logger LOGGER = Logger.getLogger(Controller.class.getName()); + + private final SinchClient sinchClient; + private final ServerBusinessLogic serverBusinessLogic; + + @Value("${voice.v2.sinchevents.service-id: }") + private String serviceId; + + @Value("${voice.v2.sinchevents.service-secret: }") + private String serviceSecret; + + @Autowired + public Controller(SinchClient sinchClient, ServerBusinessLogic serverBusinessLogic) { + this.sinchClient = sinchClient; + this.serverBusinessLogic = serverBusinessLogic; + } + + @PostMapping( + value = "/VoiceV2Event", + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity voiceV2Event( + @RequestHeader Map headers, @RequestBody String body) { + + // Sinch Events do not need any credentials from the client configuration + SinchEventsService sinchEvents = sinchClient.voice().v2().sinchEvents(); + + // ensure valid authentication to handle request + // set this value to true to validate request from Sinch servers: requests are signed with the + // secret of the service the event belongs to ("Authorization: service :") + boolean ensureValidAuthentication = false; + if (ensureValidAuthentication) { + var validAuth = + sinchEvents.validateAuthenticationHeader( + // The service the events are sent for + serviceId, + // The secret of this service + serviceSecret, + // The HTTP verb this controller is managing + "POST", + // The URI this controller is managing + "/VoiceV2Event", + // request headers + headers, + // request payload body + body); + + // signature validation failed + if (!validAuth) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + } + + // The SDK does not protect against replayed requests: reject here any request whose + // "x-timestamp" header is outside the clock-skew window your application accepts + } + + // decode the payload request + VoiceSinchEvent event = sinchEvents.parseEvent(body); + + // let business layer process the request + SinchEventType type = event.getEvent(); + VoiceSinchEventResponse response; + if (SinchEventType.CALL_INCOMING.equals(type)) { + response = serverBusinessLogic.incoming(event); + } else if (SinchEventType.CALL_ANSWERED.equals(type)) { + response = serverBusinessLogic.answered(event); + } else if (SinchEventType.CALL_MENU.equals(type)) { + response = serverBusinessLogic.menu(event); + } else if (SinchEventType.valueOf(type).startsWith("call.customEvent.")) { + // dynamic events, triggered by the "customEvent" SVAML command + response = serverBusinessLogic.customEvent(event); + } else { + response = serverBusinessLogic.other(event); + } + + // the Voice Platform expects a SVAML document in response to every event + var serializedResponse = sinchEvents.serializeResponse(response); + LOGGER.finest("JSON response: " + serializedResponse); + return ResponseEntity.ok(serializedResponse); + } +} diff --git a/examples/sinch-events/src/main/java/com/mycompany/app/voice/v2/ServerBusinessLogic.java b/examples/sinch-events/src/main/java/com/mycompany/app/voice/v2/ServerBusinessLogic.java new file mode 100644 index 000000000..2148b47b2 --- /dev/null +++ b/examples/sinch-events/src/main/java/com/mycompany/app/voice/v2/ServerBusinessLogic.java @@ -0,0 +1,75 @@ +package com.mycompany.app.voice.v2; + +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEvent; +import com.sinch.sdk.domains.voice.models.v2.sinchevents.VoiceSinchEventResponse; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.HangupCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessageEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.SayMessage; +import java.util.Collections; +import java.util.logging.Logger; +import org.springframework.stereotype.Component; + +@Component("VoiceV2ServerBusinessLogic") +public class ServerBusinessLogic { + + private static final Logger LOGGER = Logger.getLogger(ServerBusinessLogic.class.getName()); + + // An empty list of commands takes no action + private static final VoiceSinchEventResponse NO_ACTION = + VoiceSinchEventResponse.builder().setCommands(Collections.emptyList()).build(); + + public VoiceSinchEventResponse incoming(VoiceSinchEvent event) { + + LOGGER.info("Handle event: " + event); + + // play a greeting, then hang up + // "callName" and "events" only take effect in responses to "call.incoming" events + return VoiceSinchEventResponse.builder() + .setCallName("incoming") + .setCommands( + Collections.singletonList( + MessagesCommand.builder() + .setMessages( + Collections.singletonList( + SayMessage.builder() + .setText("Thank you for calling. Goodbye.") + .setVoiceName("Emma") + .build())) + .setEvents( + MessageEvents.builder() + .setOnFinish(Collections.singletonList(HangupCommand.builder().build())) + .build()) + .build())) + .build(); + } + + public VoiceSinchEventResponse answered(VoiceSinchEvent event) { + + LOGGER.info("Handle event: " + event); + + return NO_ACTION; + } + + public VoiceSinchEventResponse menu(VoiceSinchEvent event) { + + // event.getMenu() holds the menu name and the input gathered from the user + LOGGER.info("Handle event: " + event); + + return NO_ACTION; + } + + public VoiceSinchEventResponse customEvent(VoiceSinchEvent event) { + + LOGGER.info("Handle event: " + event); + + return NO_ACTION; + } + + public VoiceSinchEventResponse other(VoiceSinchEvent event) { + + LOGGER.info("Handle event: " + event); + + return NO_ACTION; + } +} diff --git a/examples/sinch-events/src/main/resources/application.yaml b/examples/sinch-events/src/main/resources/application.yaml index 9b8a23005..08b883e6d 100644 --- a/examples/sinch-events/src/main/resources/application.yaml +++ b/examples/sinch-events/src/main/resources/application.yaml @@ -50,3 +50,11 @@ sms: # Secret value set for Sms Sinch Events calls validation # Used when "ensureValidAuthentication" within [Controller.java](../java/com/mycompany/app/sms/Controller.java) is set to true secret: + +voice: + v2: + sinchevents: + # Service ID and secret used for Voice V2 Sinch Events calls validation + # Used when "ensureValidAuthentication" within [Controller.java](../java/com/mycompany/app/voice/v2/Controller.java) is set to true + service-id: + service-secret: diff --git a/examples/snippets/src/main/java/voice/README.md b/examples/snippets/src/main/java/voice/README.md index 4b7569734..33981334e 100644 --- a/examples/snippets/src/main/java/voice/README.md +++ b/examples/snippets/src/main/java/voice/README.md @@ -3,21 +3,45 @@ Sinch Java SDK Code Snippets Repository for Voice APIs See main [README.md](../../../../README.md) for how to execute snippets -- Callouts - - [voice/callouts/Call](./callouts/Call.java) -- Calls - - [voice/calls/Get](./calls/Get.java) - - [voice/calls/ManageWithCallLeg](./calls/ManageWithCallLeg.java) - - [voice/calls/Update](./calls/Update.java) -- Conferences - - [voice/conferences/Call](./conferences/Call.java) - - [voice/conferences/Get](./conferences/Get.java) - - [voice/conferences/KickAll](./conferences/KickAll.java) - - [voice/conferences/ManageParticipant](./conferences/ManageParticipant.java) - - [voice/conferences/KickParticipant](./conferences/KickParticipant.java) -- Applications - - [voice/applications/ListNumbers](./applications/ListNumbers.java) - - [voice/applications/AssignNumbers](./applications/AssignNumbers.java) - - [voice/applications/UnAssignNumber](./applications/UnAssignNumber.java) - - [voice/applications/GetEventDestinations](./applications/GetEventDestinations.java) - - [voice/applications/UpdateEventDestinations](./applications/UpdateEventDestinations.java) +- V1 + - Callouts + - [voice/callouts/Call](./callouts/Call.java) + - Calls + - [voice/calls/Get](./calls/Get.java) + - [voice/calls/ManageWithCallLeg](./calls/ManageWithCallLeg.java) + - [voice/calls/Update](./calls/Update.java) + - Conferences + - [voice/conferences/Call](./conferences/Call.java) + - [voice/conferences/Get](./conferences/Get.java) + - [voice/conferences/KickAll](./conferences/KickAll.java) + - [voice/conferences/ManageParticipant](./conferences/ManageParticipant.java) + - [voice/conferences/KickParticipant](./conferences/KickParticipant.java) + - Applications + - [voice/applications/ListNumbers](./applications/ListNumbers.java) + - [voice/applications/AssignNumbers](./applications/AssignNumbers.java) + - [voice/applications/UnAssignNumber](./applications/UnAssignNumber.java) + - [voice/applications/GetEventDestinations](./applications/GetEventDestinations.java) + - [voice/applications/UpdateEventDestinations](./applications/UpdateEventDestinations.java) +- V2 + - Calls + - [voice/v2/calls/Start](./v2/calls/Start.java) + - [voice/v2/calls/Get](./v2/calls/Get.java) + - [voice/v2/calls/List](./v2/calls/List.java) + - [voice/v2/calls/InteractByCallId](./v2/calls/InteractByCallId.java) + - [voice/v2/calls/InteractByCallName](./v2/calls/InteractByCallName.java) + - Batches + - [voice/v2/batches/Start](./v2/batches/Start.java) + - [voice/v2/batches/Get](./v2/batches/Get.java) + - [voice/v2/batches/GetDetails](./v2/batches/GetDetails.java) + - [voice/v2/batches/Stop](./v2/batches/Stop.java) + - Sessions + - [voice/v2/sessions/Get](./v2/sessions/Get.java) + - Services + - [voice/v2/services/Create](./v2/services/Create.java) + - [voice/v2/services/Delete](./v2/services/Delete.java) + - [voice/v2/services/Get](./v2/services/Get.java) + - [voice/v2/services/List](./v2/services/List.java) + - [voice/v2/services/Update](./v2/services/Update.java) + - Svaml + - [voice/v2/svaml/Describe](./v2/svaml/Describe.java) + - [voice/v2/svaml/Validate](./v2/svaml/Validate.java) diff --git a/examples/snippets/src/main/java/voice/v2/batches/Get.java b/examples/snippets/src/main/java/voice/v2/batches/Get.java new file mode 100644 index 000000000..73f6450e2 --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/batches/Get.java @@ -0,0 +1,47 @@ +/** + * 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 voice.v2.batches; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.BatchesService; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchSummary; +import com.sinch.sdk.models.Configuration; +import java.util.logging.Logger; +import utils.Settings; + +public class Get { + + private static final Logger LOGGER = Logger.getLogger(Get.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 ID of the batch to get a summary for + String batchId = "BATCH_ID"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + BatchesService batchesService = client.voice().v2().batches(); + + LOGGER.info(String.format("Get summary for batch with ID '%s'", batchId)); + + BatchSummary response = batchesService.get(batchId); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/batches/GetDetails.java b/examples/snippets/src/main/java/voice/v2/batches/GetDetails.java new file mode 100644 index 000000000..455e2782b --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/batches/GetDetails.java @@ -0,0 +1,47 @@ +/** + * 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 voice.v2.batches; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.BatchesService; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchDetails; +import com.sinch.sdk.models.Configuration; +import java.util.logging.Logger; +import utils.Settings; + +public class GetDetails { + + private static final Logger LOGGER = Logger.getLogger(GetDetails.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 ID of the batch to get details for + String batchId = "BATCH_ID"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + BatchesService batchesService = client.voice().v2().batches(); + + LOGGER.info(String.format("Get details for batch with ID '%s'", batchId)); + + BatchDetails response = batchesService.getDetails(batchId); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/batches/Start.java b/examples/snippets/src/main/java/voice/v2/batches/Start.java new file mode 100644 index 000000000..9c43c408b --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/batches/Start.java @@ -0,0 +1,96 @@ +/** + * 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 voice.v2.batches; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.BatchesService; +import com.sinch.sdk.domains.voice.models.v2.batches.request.StartBatchRequest; +import com.sinch.sdk.domains.voice.models.v2.batches.response.StartBatchResponse; +import com.sinch.sdk.domains.voice.models.v2.destination.Phone; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.CallEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.DialCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.HangupCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessageEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.SayMessage; +import com.sinch.sdk.models.Configuration; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import utils.Settings; + +public class Start { + + private static final Logger LOGGER = Logger.getLogger(Start.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 to be used as the caller ID, in E.164 format (e.g., +12025550123) + String sinchPhoneNumber = Settings.getPhoneNumber().orElse("MY_SINCH_PHONE_NUMBER"); + // The phone numbers you want to call, in E.164 format (e.g., +12025550123). + // One call per entry, each one filling the "@toNumber" placeholder of the dial command + List> parameters = + Arrays.asList( + Collections.singletonMap("toNumber", "RECIPIENT_PHONE_NUMBER_1"), + Collections.singletonMap("toNumber", "RECIPIENT_PHONE_NUMBER_2")); + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + BatchesService batchesService = client.voice().v2().batches(); + + // Played to each recipient once their call is answered, then the call is hung up + MessagesCommand greeting = + MessagesCommand.builder() + .setMessages( + Collections.singletonList( + SayMessage.builder() + .setText("Hello, your call is now connected.") + .setVoiceName("Emma") + .build())) + .setEvents( + MessageEvents.builder() + .setOnFinish(Collections.singletonList(HangupCommand.HANGUP_COMMAND)) + .build()) + .build(); + + // "@toNumber" is a placeholder, replaced in each call by that call's parameter value + DialCommand dial = + DialCommand.builder() + .setCallName("Java_SDK_Snippet_Call") + .setFrom(Phone.builder().setNumber(sinchPhoneNumber).build()) + .setTo(Phone.builder().setNumber("@toNumber").build()) + .setEvents( + CallEvents.builder().setOnAnswer(Collections.singletonList(greeting)).build()) + .build(); + + StartBatchRequest request = + StartBatchRequest.builder() + .setCommands(Collections.singletonList(dial)) + .setParameters(parameters) + .build(); + + LOGGER.info("Start a batch of calls to: " + parameters); + + StartBatchResponse response = batchesService.start(request); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/batches/Stop.java b/examples/snippets/src/main/java/voice/v2/batches/Stop.java new file mode 100644 index 000000000..e2df9855a --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/batches/Stop.java @@ -0,0 +1,47 @@ +/** + * 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 voice.v2.batches; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.BatchesService; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchStopResponse; +import com.sinch.sdk.models.Configuration; +import java.util.logging.Logger; +import utils.Settings; + +public class Stop { + + private static final Logger LOGGER = Logger.getLogger(Stop.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 ID of the batch to stop processing + String batchId = "BATCH_ID"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + BatchesService batchesService = client.voice().v2().batches(); + + LOGGER.info(String.format("Stop processing batch with ID '%s'", batchId)); + + BatchStopResponse response = batchesService.stop(batchId); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/calls/Get.java b/examples/snippets/src/main/java/voice/v2/calls/Get.java new file mode 100644 index 000000000..9607323d5 --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/calls/Get.java @@ -0,0 +1,47 @@ +/** + * 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 voice.v2.calls; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.CallsService; +import com.sinch.sdk.domains.voice.models.v2.Call; +import com.sinch.sdk.models.Configuration; +import java.util.logging.Logger; +import utils.Settings; + +public class Get { + + private static final Logger LOGGER = Logger.getLogger(Get.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 ID of the call to retrieve + String callId = "CALL_ID"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + CallsService callsService = client.voice().v2().calls(); + + LOGGER.info(String.format("Get call with ID '%s'", callId)); + + Call response = callsService.get(callId); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/calls/InteractByCallId.java b/examples/snippets/src/main/java/voice/v2/calls/InteractByCallId.java new file mode 100644 index 000000000..14716beee --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/calls/InteractByCallId.java @@ -0,0 +1,54 @@ +/** + * 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 voice.v2.calls; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.CallsService; +import com.sinch.sdk.domains.voice.models.v2.calls.request.CallPatchRequest; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.HangupCommand; +import com.sinch.sdk.models.Configuration; +import java.util.Collections; +import java.util.logging.Logger; +import utils.Settings; + +public class InteractByCallId { + + private static final Logger LOGGER = Logger.getLogger(InteractByCallId.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 ID of the ongoing call to interact with + String callId = "CALL_ID"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + CallsService callsService = client.voice().v2().calls(); + + CallPatchRequest request = + CallPatchRequest.builder() + .setCommands(Collections.singletonList(HangupCommand.HANGUP_COMMAND)) + .build(); + + LOGGER.info(String.format("Interact with call with ID '%s'", callId)); + + callsService.interactByCallId(callId, request); + + LOGGER.info("Commands accepted for processing"); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/calls/InteractByCallName.java b/examples/snippets/src/main/java/voice/v2/calls/InteractByCallName.java new file mode 100644 index 000000000..3208c2931 --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/calls/InteractByCallName.java @@ -0,0 +1,58 @@ +/** + * 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 voice.v2.calls; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.CallsService; +import com.sinch.sdk.domains.voice.models.v2.calls.request.CallPatchRequest; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.HangupCommand; +import com.sinch.sdk.models.Configuration; +import java.util.Collections; +import java.util.logging.Logger; +import utils.Settings; + +public class InteractByCallName { + + private static final Logger LOGGER = Logger.getLogger(InteractByCallName.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 ID of the session that contains the call + String sessionId = "SESSION_ID"; + // The name of the call leg within the session, as assigned by the `callName` property in the + // `dial` command + String callName = "CALL_NAME"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + CallsService callsService = client.voice().v2().calls(); + + CallPatchRequest request = + CallPatchRequest.builder() + .setCommands(Collections.singletonList(HangupCommand.HANGUP_COMMAND)) + .build(); + + LOGGER.info( + String.format("Interact with call named '%s' in session '%s'", callName, sessionId)); + + callsService.interactByCallName(sessionId, callName, request); + + LOGGER.info("Commands accepted for processing"); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/calls/List.java b/examples/snippets/src/main/java/voice/v2/calls/List.java new file mode 100644 index 000000000..52fe241ff --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/calls/List.java @@ -0,0 +1,48 @@ +/** + * 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 voice.v2.calls; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.CallsService; +import com.sinch.sdk.domains.voice.models.v2.calls.response.CallsListResponse; +import com.sinch.sdk.models.Configuration; +import java.util.logging.Logger; +import utils.Settings; + +public class List { + + private static final Logger LOGGER = Logger.getLogger(List.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"); + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + CallsService callsService = client.voice().v2().calls(); + + LOGGER.info("Listing calls"); + + CallsListResponse response = callsService.list(); + + LOGGER.info("Response"); + + response + .iterator() + .forEachRemaining(f -> LOGGER.info(String.format("%s: %s", f.getCallId(), f))); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/calls/Start.java b/examples/snippets/src/main/java/voice/v2/calls/Start.java new file mode 100644 index 000000000..775928a00 --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/calls/Start.java @@ -0,0 +1,85 @@ +/** + * 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 voice.v2.calls; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.CallsService; +import com.sinch.sdk.domains.voice.models.v2.calls.request.StartCallRequest; +import com.sinch.sdk.domains.voice.models.v2.calls.response.StartCallResponse; +import com.sinch.sdk.domains.voice.models.v2.destination.Phone; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.CallEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.DialCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.HangupCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessageEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.SayMessage; +import com.sinch.sdk.models.Configuration; +import java.util.Collections; +import java.util.logging.Logger; +import utils.Settings; + +public class Start { + + private static final Logger LOGGER = Logger.getLogger(Start.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 to be used as the caller ID, in E.164 format (e.g., +12025550123) + String sinchPhoneNumber = Settings.getPhoneNumber().orElse("MY_SINCH_PHONE_NUMBER"); + // The phone number you want to call, in E.164 format (e.g., +12025550123) + String recipientPhoneNumber = "RECIPIENT_PHONE_NUMBER"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + CallsService callsService = client.voice().v2().calls(); + + // Played to the recipient once the call is answered, then the call is hung up + MessagesCommand greeting = + MessagesCommand.builder() + .setMessages( + Collections.singletonList( + SayMessage.builder() + .setText("Hello, your call is now connected.") + .setVoiceName("Emma") + .build())) + .setEvents( + MessageEvents.builder() + .setOnFinish(Collections.singletonList(HangupCommand.HANGUP_COMMAND)) + .build()) + .build(); + + DialCommand dial = + DialCommand.builder() + .setCallName("Java_SDK_Snippet_Call") + .setFrom(Phone.builder().setNumber(sinchPhoneNumber).build()) + .setTo(Phone.builder().setNumber(recipientPhoneNumber).build()) + .setEvents( + CallEvents.builder().setOnAnswer(Collections.singletonList(greeting)).build()) + .build(); + + StartCallRequest request = + StartCallRequest.builder().setCommands(Collections.singletonList(dial)).build(); + + LOGGER.info("Start a call to: " + recipientPhoneNumber); + + StartCallResponse response = callsService.start(request); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/services/Create.java b/examples/snippets/src/main/java/voice/v2/services/Create.java new file mode 100644 index 000000000..677067300 --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/services/Create.java @@ -0,0 +1,49 @@ +/** + * 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 voice.v2.services; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.ServicesService; +import com.sinch.sdk.domains.voice.models.v2.services.request.CreateServiceRequest; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceResponse; +import com.sinch.sdk.models.Configuration; +import java.util.logging.Logger; +import utils.Settings; + +public class Create { + + private static final Logger LOGGER = Logger.getLogger(Create.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 name = "Created from Java SDK snippet"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + ServicesService servicesService = client.voice().v2().services(); + + CreateServiceRequest request = CreateServiceRequest.builder().setName(name).build(); + + LOGGER.info(String.format("Create voice service named '%s'", name)); + + ServiceResponse response = servicesService.create(request); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/services/Delete.java b/examples/snippets/src/main/java/voice/v2/services/Delete.java new file mode 100644 index 000000000..cbe174ff5 --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/services/Delete.java @@ -0,0 +1,46 @@ +/** + * 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 voice.v2.services; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.ServicesService; +import com.sinch.sdk.models.Configuration; +import java.util.logging.Logger; +import utils.Settings; + +public class Delete { + + private static final Logger LOGGER = Logger.getLogger(Delete.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 ID of the service to delete + String serviceId = "SERVICE_ID"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + ServicesService servicesService = client.voice().v2().services(); + + LOGGER.info(String.format("Deleting voice service with ID '%s'", serviceId)); + + servicesService.delete(serviceId); + + LOGGER.info("Done"); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/services/Get.java b/examples/snippets/src/main/java/voice/v2/services/Get.java new file mode 100644 index 000000000..197c772a9 --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/services/Get.java @@ -0,0 +1,47 @@ +/** + * 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 voice.v2.services; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.ServicesService; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceResponse; +import com.sinch.sdk.models.Configuration; +import java.util.logging.Logger; +import utils.Settings; + +public class Get { + + private static final Logger LOGGER = Logger.getLogger(Get.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 ID of the service to retrieve + String serviceId = "SERVICE_ID"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + ServicesService servicesService = client.voice().v2().services(); + + LOGGER.info(String.format("Get voice service with ID '%s'", serviceId)); + + ServiceResponse response = servicesService.get(serviceId); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/services/List.java b/examples/snippets/src/main/java/voice/v2/services/List.java new file mode 100644 index 000000000..bfed341a5 --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/services/List.java @@ -0,0 +1,48 @@ +/** + * 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 voice.v2.services; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.ServicesService; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServicesListResponse; +import com.sinch.sdk.models.Configuration; +import java.util.logging.Logger; +import utils.Settings; + +public class List { + + private static final Logger LOGGER = Logger.getLogger(List.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"); + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + ServicesService servicesService = client.voice().v2().services(); + + LOGGER.info("Listing voice services"); + + ServicesListResponse response = servicesService.list(); + + LOGGER.info("Response"); + + response + .iterator() + .forEachRemaining(f -> LOGGER.info(String.format("%s: %s", f.getServiceId(), f))); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/services/Update.java b/examples/snippets/src/main/java/voice/v2/services/Update.java new file mode 100644 index 000000000..372976e62 --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/services/Update.java @@ -0,0 +1,51 @@ +/** + * 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 voice.v2.services; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.ServicesService; +import com.sinch.sdk.domains.voice.models.v2.services.request.UpdateServiceRequest; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceResponse; +import com.sinch.sdk.models.Configuration; +import java.util.logging.Logger; +import utils.Settings; + +public class Update { + + private static final Logger LOGGER = Logger.getLogger(Update.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 ID of the service to update + String serviceId = "SERVICE_ID"; + String name = "Updated from Java SDK snippet"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + ServicesService servicesService = client.voice().v2().services(); + + UpdateServiceRequest request = UpdateServiceRequest.builder().setName(name).build(); + + LOGGER.info(String.format("Update voice service with ID '%s'", serviceId)); + + ServiceResponse response = servicesService.update(serviceId, request); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/sessions/Get.java b/examples/snippets/src/main/java/voice/v2/sessions/Get.java new file mode 100644 index 000000000..c783f1992 --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/sessions/Get.java @@ -0,0 +1,47 @@ +/** + * 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 voice.v2.sessions; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.SessionsService; +import com.sinch.sdk.domains.voice.models.v2.sessions.response.Session; +import com.sinch.sdk.models.Configuration; +import java.util.logging.Logger; +import utils.Settings; + +public class Get { + + private static final Logger LOGGER = Logger.getLogger(Get.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 ID of the session to retrieve + String sessionId = "SESSION_ID"; + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + SessionsService sessionsService = client.voice().v2().sessions(); + + LOGGER.info(String.format("Get session with ID '%s'", sessionId)); + + Session response = sessionsService.get(sessionId); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/svaml/Describe.java b/examples/snippets/src/main/java/voice/v2/svaml/Describe.java new file mode 100644 index 000000000..647173dbe --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/svaml/Describe.java @@ -0,0 +1,66 @@ +/** + * 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 voice.v2.svaml; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.SvamlService; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlInput; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.SayMessage; +import com.sinch.sdk.domains.voice.models.v2.svaml.request.DescribeSvamlRequest; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.SvamlDescriptionResponse; +import com.sinch.sdk.models.Configuration; +import java.util.Collections; +import java.util.logging.Logger; +import utils.Settings; + +public class Describe { + + private static final Logger LOGGER = Logger.getLogger(Describe.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"); + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + SvamlService svamlService = client.voice().v2().svaml(); + + DescribeSvamlRequest request = + DescribeSvamlRequest.builder() + .setSvaml( + SvamlInput.builder() + .setCommands( + Collections.singletonList( + MessagesCommand.builder() + .setMessages( + Collections.singletonList( + SayMessage.builder() + .setText("Hello, your call is now connected.") + .setVoiceName("Emma") + .build())) + .build())) + .build()) + .build(); + + LOGGER.info("Describe a SVAML payload"); + + SvamlDescriptionResponse response = svamlService.describe(request); + + LOGGER.info("Response: " + response); + } +} diff --git a/examples/snippets/src/main/java/voice/v2/svaml/Validate.java b/examples/snippets/src/main/java/voice/v2/svaml/Validate.java new file mode 100644 index 000000000..50f3279a0 --- /dev/null +++ b/examples/snippets/src/main/java/voice/v2/svaml/Validate.java @@ -0,0 +1,66 @@ +/** + * 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 voice.v2.svaml; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.voice.api.v2.SvamlService; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlInput; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommand; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.SayMessage; +import com.sinch.sdk.domains.voice.models.v2.svaml.request.ValidateSvamlRequest; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.ValidateSvamlResponse; +import com.sinch.sdk.models.Configuration; +import java.util.Collections; +import java.util.logging.Logger; +import utils.Settings; + +public class Validate { + + private static final Logger LOGGER = Logger.getLogger(Validate.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"); + + Configuration configuration = + Configuration.builder() + .setProjectId(projectId) + .setKeyId(keyId) + .setKeySecret(keySecret) + .build(); + + SinchClient client = new SinchClient(configuration); + + SvamlService svamlService = client.voice().v2().svaml(); + + ValidateSvamlRequest request = + ValidateSvamlRequest.builder() + .setSvaml( + SvamlInput.builder() + .setCommands( + Collections.singletonList( + MessagesCommand.builder() + .setMessages( + Collections.singletonList( + SayMessage.builder() + .setText("Hello, your call is now connected.") + .setVoiceName("Emma") + .build())) + .build())) + .build()) + .build(); + + LOGGER.info("Validate a SVAML payload"); + + ValidateSvamlResponse response = svamlService.validate(request); + + LOGGER.info("Response: " + response); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/BatchesService.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/BatchesService.java new file mode 100644 index 000000000..647cc283e --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/BatchesService.java @@ -0,0 +1,94 @@ +package com.sinch.sdk.domains.voice.api.v2; + +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.domains.voice.models.v2.batches.request.StartBatchQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.batches.request.StartBatchRequest; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchDetails; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchStopResponse; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchSummary; +import com.sinch.sdk.domains.voice.models.v2.batches.response.StartBatchResponse; + +/** Batches Service */ +public interface BatchesService { + + /** + * Create and initiate a batch of outbound call sessions, associated to the project's default + * service + * + *

One call session is queued for each entry in {@link StartBatchRequest#getParameters()}. + * + * @param startBatchRequest The batch of call sessions to start (required) + * @return StartBatchResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + StartBatchResponse start(StartBatchRequest startBatchRequest) throws ApiException; + + /** + * Create and initiate a batch of outbound call sessions + * + *

Create a batch of outbound call sessions associated to the project's default service or to + * the service specified by {@link StartBatchQueryParameters#getServiceId()}. + * + *

The request contains a single set of SVAML commands describing the call flow, together with + * one {@link StartBatchRequest#getParameters() parameters} entry per call session to queue. + * Placeholders in the commands (prefixed with @) are substituted with the values of + * the matching entry, so each session can be personalized without changing the command structure. + * + *

Queued sessions are initiated asynchronously, paced according to {@link + * StartBatchRequest#getBatchOptions()}. The IDs of the individual call sessions are not part of + * the response: retrieve them with {@link #getDetails}. + * + * @param queryParameter (optional) + * @param startBatchRequest The batch of call sessions to start (required) + * @return StartBatchResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + StartBatchResponse start( + StartBatchQueryParameters queryParameter, StartBatchRequest startBatchRequest) + throws ApiException; + + /** + * Get a batch summary + * + *

Retrieve a summary of a batch, including statistics on completed, in-progress, queued and + * expired call sessions. + * + * @param batchId The ID of the batch, as returned by {@link #start} (required) + * @return BatchSummary + * @throws ApiException if fails to make API call + * @since 2.3 + */ + BatchSummary get(String batchId) throws ApiException; + + /** + * Get batch details + * + *

Retrieve per-session details for a batch, including the current state of each call session + * in the batch and the parameters used to initiate it. + * + *

Use this operation when individual session-level visibility is needed (for example, to + * inspect which sessions are QUEUED, IN_PROGRESS or COMPLETED + * ). EXPIRED sessions are never returned because they were never initiated. + * + * @param batchId The ID of the batch, as returned by {@link #start} (required) + * @return BatchDetails + * @throws ApiException if fails to make API call + * @since 2.3 + */ + BatchDetails getDetails(String batchId) throws ApiException; + + /** + * Stop processing a batch of call sessions + * + *

Prevents any queued calls in the batch from being initiated. Calls that are already in + * progress are not affected and continue until completion. + * + * @param batchId The ID of the batch, as returned by {@link #start} (required) + * @return BatchStopResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + BatchStopResponse stop(String batchId) throws ApiException; +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/CallsService.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/CallsService.java new file mode 100644 index 000000000..1e91f9ffc --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/CallsService.java @@ -0,0 +1,105 @@ +package com.sinch.sdk.domains.voice.api.v2; + +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.domains.voice.models.v2.Call; +import com.sinch.sdk.domains.voice.models.v2.calls.request.CallPatchRequest; +import com.sinch.sdk.domains.voice.models.v2.calls.request.ListCallsQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.calls.request.StartCallQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.calls.request.StartCallRequest; +import com.sinch.sdk.domains.voice.models.v2.calls.response.CallsListResponse; +import com.sinch.sdk.domains.voice.models.v2.calls.response.StartCallResponse; + +/** Calls Service */ +public interface CallsService { + + /** + * Create and initiate a new outbound call session, associated to the project's default service + * + * @param startCallRequest The call to start (required) + * @return StartCallResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + StartCallResponse start(StartCallRequest startCallRequest) throws ApiException; + + /** + * Create and initiate a new outbound call session + * + *

Create a new outbound call session associated to the project's default service or to the + * service specified by {@link StartCallQueryParameters#getServiceId()}. + * + * @param queryParameter (optional) + * @param startCallRequest The call to start (required) + * @return StartCallResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + StartCallResponse start( + StartCallQueryParameters queryParameter, StartCallRequest startCallRequest) + throws ApiException; + + /** + * Retrieve call details by call ID + * + *

Retrieve detailed information about a specific call using its unique identifier. + * + * @param callId The ID of the call. (required) + * @return Call + * @throws ApiException if fails to make API call + * @since 2.3 + */ + Call get(String callId) throws ApiException; + + /** + * List calls made with Sinch Voice API + * + *

List and filter calls made with Sinch + * + * @return CallsListResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + CallsListResponse list() throws ApiException; + + /** + * List calls made with Sinch Voice API + * + *

List and filter calls made with Sinch + * + * @param queryParameter (optional) + * @return CallsListResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + CallsListResponse list(ListCallsQueryParameters queryParameter) throws ApiException; + + /** + * Patch an ongoing call by call ID + * + *

Interact with an ongoing call by submitting a set of SVAML commands. Use this to force + * disconnect, play messages, bridge with another call, or perform other call control actions. + * + * @param callId The ID of the call. (required) + * @param callPatchRequest The SVAML commands to apply to the call (required) + * @throws ApiException if fails to make API call + * @since 2.3 + */ + void interactByCallId(String callId, CallPatchRequest callPatchRequest) throws ApiException; + + /** + * Patch an ongoing call by session ID and call name + * + *

Interact with an ongoing call identified by its session and call name by submitting a set of + * SVAML commands. Use this to force disconnect, play messages, bridge with another call, or + * perform other call control actions. + * + * @param sessionId The ID of the session. (required) + * @param callName The name of the call leg within the session, as assigned by the callName + * property in the dial command. (required) + * @param callPatchRequest The SVAML commands to apply to the call (required) + * @throws ApiException if fails to make API call + * @since 2.3 + */ + void interactByCallName(String sessionId, String callName, CallPatchRequest callPatchRequest) + throws ApiException; +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/ServicesService.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/ServicesService.java new file mode 100644 index 000000000..58e68a37b --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/ServicesService.java @@ -0,0 +1,108 @@ +package com.sinch.sdk.domains.voice.api.v2; + +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.domains.voice.models.v2.services.request.CreateServiceRequest; +import com.sinch.sdk.domains.voice.models.v2.services.request.ListServicesQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.services.request.UpdateServiceRequest; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceResponse; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServicesListResponse; + +/** Services Service */ +public interface ServicesService { + + /** + * Create a new voice service + * + *

Creates a new voice service in the specified project. + * + * @param createServiceRequest The service to create (required) + * @return ServiceResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + ServiceResponse create(CreateServiceRequest createServiceRequest) throws ApiException; + + /** + * Delete a voice service by ID + * + *

Deletes a service permanently. + * + *

Important: The default service cannot be deleted. To delete the current + * default service, a different service must first be designated as the default using the PATCH + * endpoint. + * + * @param serviceId The ID of the service. (required) + * @throws ApiException if fails to make API call + * @since 2.3 + */ + void delete(String serviceId) throws ApiException; + + /** + * Retrieve a voice service by ID + * + *

Retrieve the full details of a specific voice service by its serviceId. + * + * @param serviceId The ID of the service. (required) + * @return ServiceResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + ServiceResponse get(String serviceId) throws ApiException; + + /** + * List all services of a project + * + *

Retrieve a list of voice services in the specified project. + * + *

Optionally: + * + *

    + *
  • Filter services by partial match on name or description (filter) + *
  • Return only the default service (isDefault=true) + *
+ * + * @return ServicesListResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + ServicesListResponse list() throws ApiException; + + /** + * List all services of a project + * + *

Retrieve a list of voice services in the specified project. + * + *

Optionally: + * + *

    + *
  • Filter services by partial match on name or description (filter) + *
  • Return only the default service (isDefault=true) + *
+ * + * @param queryParameter (optional) + * @return ServicesListResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + ServicesListResponse list(ListServicesQueryParameters queryParameter) throws ApiException; + + /** + * Update a voice service + * + *

Updates an existing service resource with the provided properties. Only the fields included + * in the request body will be modified; omitted fields remain unchanged. + * + *

To set a service as the default for the project, set isDefault to true + * . Each project can have only one default service: setting a new default automatically + * removes the default status from the previously designated service. isDefault set + * to false is invalid and will not be accepted by the API. + * + * @param serviceId The ID of the service. (required) + * @param updateServiceRequest The properties to update (required) + * @return ServiceResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + ServiceResponse update(String serviceId, UpdateServiceRequest updateServiceRequest) + throws ApiException; +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/SessionsService.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/SessionsService.java new file mode 100644 index 000000000..82aaa6acf --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/SessionsService.java @@ -0,0 +1,22 @@ +package com.sinch.sdk.domains.voice.api.v2; + +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.domains.voice.models.v2.sessions.response.Session; + +/** Sessions Service */ +public interface SessionsService { + + /** + * Get a session details by the session ID + * + *

Retrieve detailed information about a specific session, including all associated calls and + * their current states. Sessions represent the complete interaction lifecycle and can contain + * multiple related calls. + * + * @param sessionId The ID of the session (required) + * @return Session + * @throws ApiException if fails to make API call + * @since 2.3 + */ + Session get(String sessionId) throws ApiException; +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/SvamlService.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/SvamlService.java new file mode 100644 index 000000000..023871575 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/SvamlService.java @@ -0,0 +1,39 @@ +package com.sinch.sdk.domains.voice.api.v2; + +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.domains.voice.models.v2.svaml.request.DescribeSvamlRequest; +import com.sinch.sdk.domains.voice.models.v2.svaml.request.ValidateSvamlRequest; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.SvamlDescriptionResponse; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.ValidateSvamlResponse; + +/** Svaml Service */ +public interface SvamlService { + + /** + * Describe the call flow from the SVAML payload + * + *

This endpoint is useful for understanding the structure and flow of a SVAML payload without + * executing it. It provides a detailed description of the commands, events, and messages defined + * in the SVAML. + * + * @param describeSvamlRequest The SVAML payload to describe (required) + * @return SvamlDescriptionResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + SvamlDescriptionResponse describe(DescribeSvamlRequest describeSvamlRequest) throws ApiException; + + /** + * Validate a SVAML payload + * + *

This endpoint checks the structure and content of the SVAML commands to ensure they conform + * to the expected schema and rules. Use validationType to choose NORMAL + * (same rules as initiating a call) or STRICT (errors for unrecognised properties). + * + * @param validateSvamlRequest The SVAML payload to validate (required) + * @return ValidateSvamlResponse + * @throws ApiException if fails to make API call + * @since 2.3 + */ + ValidateSvamlResponse validate(ValidateSvamlRequest validateSvamlRequest) throws ApiException; +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/BatchesServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/BatchesServiceImpl.java new file mode 100644 index 000000000..950fe99ac --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/BatchesServiceImpl.java @@ -0,0 +1,338 @@ +package com.sinch.sdk.domains.voice.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.URLParameterUtils; +import com.sinch.sdk.core.http.URLPathUtils; +import com.sinch.sdk.core.models.ServerConfiguration; +import com.sinch.sdk.domains.voice.models.v2.batches.request.StartBatchQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.batches.request.StartBatchRequest; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchDetails; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchStopResponse; +import com.sinch.sdk.domains.voice.models.v2.batches.response.BatchSummary; +import com.sinch.sdk.domains.voice.models.v2.batches.response.StartBatchResponse; +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 BatchesServiceImpl implements com.sinch.sdk.domains.voice.api.v2.BatchesService { + + private static final Logger LOGGER = Logger.getLogger(BatchesServiceImpl.class.getName()); + private final HttpClient httpClient; + private final ServerConfiguration serverConfiguration; + private final Map authManagersByOasSecuritySchemes; + private final HttpMapper mapper; + + private final String projectId; + + public BatchesServiceImpl( + 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 StartBatchResponse start(StartBatchRequest startBatchRequest) throws ApiException { + + return start((StartBatchQueryParameters) null, startBatchRequest); + } + + @Override + public StartBatchResponse start( + StartBatchQueryParameters queryParameter, StartBatchRequest startBatchRequest) + throws ApiException { + + LOGGER.finest( + "[start]" + + " " + + "queryParameter: " + + queryParameter + + ", " + + "startBatchRequest: " + + startBatchRequest); + + HttpRequest httpRequest = startRequestBuilder(queryParameter, startBatchRequest); + 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 startRequestBuilder( + StartBatchQueryParameters queryParameter, StartBatchRequest startBatchRequest) + 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 start"); + } + // verify the required parameter 'startBatchRequest' is set + if (startBatchRequest == null) { + throw new ApiException( + 400, "Missing the required parameter 'startBatchRequest' when calling start"); + } + + String localVarPath = + "/v2/projects/{projectId}/batches" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())); + + List localVarQueryParams = new ArrayList<>(); + if (null != queryParameter) { + URLParameterUtils.addQueryParam( + queryParameter.getServiceId(), + "serviceId", + URLParameter.form, + null, + localVarQueryParams, + true); + } + + Map localVarHeaderParams = new HashMap<>(); + if (startBatchRequest.getIdempotencyKey() != null) + localVarHeaderParams.put("Idempotency-Key", startBatchRequest.getIdempotencyKey()); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList("application/json"); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = mapper.serialize(localVarContentTypes, startBatchRequest); + + return new HttpRequest( + localVarPath, + HttpMethod.POST, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames, + true); + } + + @Override + public BatchSummary get(String batchId) throws ApiException { + + LOGGER.finest("[get]" + " " + "batchId: " + batchId); + + HttpRequest httpRequest = getRequestBuilder(batchId); + 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 getRequestBuilder(String batchId) 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 get"); + } + // verify the required parameter 'batchId' is set + if (batchId == null) { + throw new ApiException(400, "Missing the required parameter 'batchId' when calling get"); + } + + String localVarPath = + "/v2/projects/{projectId}/batches/{batchId}" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll( + "\\{" + "batchId" + "\\}", URLPathUtils.encodePathSegment(batchId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList(); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = null; + + return new HttpRequest( + localVarPath, + HttpMethod.GET, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } + + @Override + public BatchDetails getDetails(String batchId) throws ApiException { + + LOGGER.finest("[getDetails]" + " " + "batchId: " + batchId); + + HttpRequest httpRequest = getDetailsRequestBuilder(batchId); + 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 getDetailsRequestBuilder(String batchId) 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 getDetails"); + } + // verify the required parameter 'batchId' is set + if (batchId == null) { + throw new ApiException( + 400, "Missing the required parameter 'batchId' when calling getDetails"); + } + + String localVarPath = + "/v2/projects/{projectId}/batches/{batchId}/details" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll( + "\\{" + "batchId" + "\\}", URLPathUtils.encodePathSegment(batchId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList(); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = null; + + return new HttpRequest( + localVarPath, + HttpMethod.GET, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } + + @Override + public BatchStopResponse stop(String batchId) throws ApiException { + + LOGGER.finest("[stop]" + " " + "batchId: " + batchId); + + HttpRequest httpRequest = stopRequestBuilder(batchId); + 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 stopRequestBuilder(String batchId) 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 stop"); + } + // verify the required parameter 'batchId' is set + if (batchId == null) { + throw new ApiException(400, "Missing the required parameter 'batchId' when calling stop"); + } + + String localVarPath = + "/v2/projects/{projectId}/batches/{batchId}" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll( + "\\{" + "batchId" + "\\}", URLPathUtils.encodePathSegment(batchId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList(); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = null; + + return new HttpRequest( + localVarPath, + HttpMethod.DELETE, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/CallsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/CallsServiceImpl.java new file mode 100644 index 000000000..83c838c51 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/CallsServiceImpl.java @@ -0,0 +1,534 @@ +package com.sinch.sdk.domains.voice.api.v2.adapters; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.sinch.sdk.core.databind.query_parameter.InstantToIso8601Serializer; +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.domains.voice.models.v2.Call; +import com.sinch.sdk.domains.voice.models.v2.calls.request.CallPatchRequest; +import com.sinch.sdk.domains.voice.models.v2.calls.request.ListCallsQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.calls.request.StartCallQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.calls.request.StartCallRequest; +import com.sinch.sdk.domains.voice.models.v2.calls.response.CallsListResponse; +import com.sinch.sdk.domains.voice.models.v2.calls.response.StartCallResponse; +import com.sinch.sdk.domains.voice.models.v2.calls.response.internal.CallsListResponseInternal; +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 CallsServiceImpl implements com.sinch.sdk.domains.voice.api.v2.CallsService { + + private static final Logger LOGGER = Logger.getLogger(CallsServiceImpl.class.getName()); + private final HttpClient httpClient; + private final ServerConfiguration serverConfiguration; + private final Map authManagersByOasSecuritySchemes; + private final HttpMapper mapper; + + private final String projectId; + + public CallsServiceImpl( + 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 StartCallResponse start(StartCallRequest startCallRequest) throws ApiException { + + return start((StartCallQueryParameters) null, startCallRequest); + } + + @Override + public StartCallResponse start( + StartCallQueryParameters queryParameter, StartCallRequest startCallRequest) + throws ApiException { + + LOGGER.finest( + "[start]" + + " " + + "queryParameter: " + + queryParameter + + ", " + + "startCallRequest: " + + startCallRequest); + + HttpRequest httpRequest = startRequestBuilder(queryParameter, startCallRequest); + 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 startRequestBuilder( + StartCallQueryParameters queryParameter, StartCallRequest startCallRequest) + 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 start"); + } + // verify the required parameter 'startCallRequest' is set + if (startCallRequest == null) { + throw new ApiException( + 400, "Missing the required parameter 'startCallRequest' when calling start"); + } + + String localVarPath = + "/v2/projects/{projectId}/calls" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())); + + List localVarQueryParams = new ArrayList<>(); + if (null != queryParameter) { + URLParameterUtils.addQueryParam( + queryParameter.getServiceId(), + "serviceId", + URLParameter.form, + null, + localVarQueryParams, + true); + } + + Map localVarHeaderParams = new HashMap<>(); + if (startCallRequest.getIdempotencyKey() != null) + localVarHeaderParams.put("Idempotency-Key", startCallRequest.getIdempotencyKey()); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList("application/json"); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = mapper.serialize(localVarContentTypes, startCallRequest); + + return new HttpRequest( + localVarPath, + HttpMethod.POST, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames, + true); + } + + @Override + public Call get(String callId) throws ApiException { + + LOGGER.finest("[get]" + " " + "callId: " + callId); + + HttpRequest httpRequest = getRequestBuilder(callId); + 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 getRequestBuilder(String callId) 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 get"); + } + // verify the required parameter 'callId' is set + if (callId == null) { + throw new ApiException(400, "Missing the required parameter 'callId' when calling get"); + } + + String localVarPath = + "/v2/projects/{projectId}/calls/{callId}" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll( + "\\{" + "callId" + "\\}", URLPathUtils.encodePathSegment(callId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList(); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = null; + + return new HttpRequest( + localVarPath, + HttpMethod.GET, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } + + @Override + public CallsListResponse list() throws ApiException { + + return list((ListCallsQueryParameters) null); + } + + @Override + public CallsListResponse list(ListCallsQueryParameters queryParameter) throws ApiException { + + LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); + + HttpRequest httpRequest = listRequestBuilder(queryParameter); + return _fetchListPage(httpRequest); + } + + private CallsListResponse _fetchListPage(HttpRequest httpRequest) throws ApiException { + HttpResponse response = + httpClient.invokeAPI( + this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); + + if (HttpStatus.isSuccessfulStatus(response.getCode())) { + + CallsListResponseInternal deserialized = + mapper.deserialize(response, new TypeReference() {}); + + final HttpRequest nextHttpRequest = + PaginationLinksHelper.nextPageRequest(this.serverConfiguration, deserialized.getLinks()); + + return new CallsListResponse( + () -> _fetchListPage(nextHttpRequest), + new Page<>(deserialized.getCalls(), 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 listRequestBuilder(ListCallsQueryParameters 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 list"); + } + + String localVarPath = + "/v2/projects/{projectId}/calls" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())); + + List localVarQueryParams = new ArrayList<>(); + if (null != queryParameter) { + + URLParameterUtils.addQueryParam( + queryParameter.getServiceId(), + "serviceId", + URLParameter.form, + null, + localVarQueryParams, + true); + + URLParameterUtils.addQueryParam( + queryParameter.getFrom(), "from", URLParameter.form, null, localVarQueryParams, true); + + URLParameterUtils.addQueryParam( + queryParameter.getTo(), "to", URLParameter.form, null, localVarQueryParams, true); + + URLParameterUtils.addQueryParam( + queryParameter.getCallType(), + "callType", + URLParameter.form, + null, + localVarQueryParams, + true); + + URLParameterUtils.addQueryParam( + queryParameter.getStartTime(), + "startTime", + URLParameter.form, + InstantToIso8601Serializer.getInstance(), + localVarQueryParams, + true); + + URLParameterUtils.addQueryParam( + queryParameter.getEndTime(), + "endTime", + URLParameter.form, + InstantToIso8601Serializer.getInstance(), + localVarQueryParams, + true); + + URLParameterUtils.addQueryParam( + queryParameter.getCallResult(), + "callResult", + URLParameter.form, + null, + localVarQueryParams, + true); + + URLParameterUtils.addQueryParam( + queryParameter.getCallReason(), + "callReason", + URLParameter.form, + null, + localVarQueryParams, + true); + + URLParameterUtils.addQueryParam( + queryParameter.getPageSize(), + "pageSize", + URLParameter.form, + null, + localVarQueryParams, + true); + + URLParameterUtils.addQueryParam( + queryParameter.getPage(), "page", URLParameter.form, null, localVarQueryParams, true); + } + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList(); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = null; + + return new HttpRequest( + localVarPath, + HttpMethod.GET, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } + + @Override + public void interactByCallId(String callId, CallPatchRequest callPatchRequest) + throws ApiException { + + LOGGER.finest( + "[interactByCallId]" + + " " + + "callId: " + + callId + + ", " + + "callPatchRequest: " + + callPatchRequest); + + HttpRequest httpRequest = interactByCallIdRequestBuilder(callId, callPatchRequest); + 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 interactByCallIdRequestBuilder( + String callId, CallPatchRequest callPatchRequest) 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 interactByCallId"); + } + // verify the required parameter 'callId' is set + if (callId == null) { + throw new ApiException( + 400, "Missing the required parameter 'callId' when calling interactByCallId"); + } + // verify the required parameter 'callPatchRequest' is set + if (callPatchRequest == null) { + throw new ApiException( + 400, "Missing the required parameter 'callPatchRequest' when calling interactByCallId"); + } + + String localVarPath = + "/v2/projects/{projectId}/calls/{callId}" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll( + "\\{" + "callId" + "\\}", URLPathUtils.encodePathSegment(callId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + if (callPatchRequest.getIdempotencyKey() != null) + localVarHeaderParams.put("Idempotency-Key", callPatchRequest.getIdempotencyKey()); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList("application/json"); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = mapper.serialize(localVarContentTypes, callPatchRequest); + + return new HttpRequest( + localVarPath, + HttpMethod.PATCH, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames, + true); + } + + @Override + public void interactByCallName( + String sessionId, String callName, CallPatchRequest callPatchRequest) throws ApiException { + + LOGGER.finest( + "[interactByCallName]" + + " " + + "sessionId: " + + sessionId + + ", " + + "callName: " + + callName + + ", " + + "callPatchRequest: " + + callPatchRequest); + + HttpRequest httpRequest = + interactByCallNameRequestBuilder(sessionId, callName, callPatchRequest); + 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 interactByCallNameRequestBuilder( + String sessionId, String callName, CallPatchRequest callPatchRequest) 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 interactByCallName"); + } + // verify the required parameter 'sessionId' is set + if (sessionId == null) { + throw new ApiException( + 400, "Missing the required parameter 'sessionId' when calling interactByCallName"); + } + // verify the required parameter 'callName' is set + if (callName == null) { + throw new ApiException( + 400, "Missing the required parameter 'callName' when calling interactByCallName"); + } + // verify the required parameter 'callPatchRequest' is set + if (callPatchRequest == null) { + throw new ApiException( + 400, "Missing the required parameter 'callPatchRequest' when calling interactByCallName"); + } + + String localVarPath = + "/v2/projects/{projectId}/sessions/{sessionId}/calls/{callName}" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll( + "\\{" + "sessionId" + "\\}", URLPathUtils.encodePathSegment(sessionId.toString())) + .replaceAll( + "\\{" + "callName" + "\\}", URLPathUtils.encodePathSegment(callName.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + if (callPatchRequest.getIdempotencyKey() != null) + localVarHeaderParams.put("Idempotency-Key", callPatchRequest.getIdempotencyKey()); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList("application/json"); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = mapper.serialize(localVarContentTypes, callPatchRequest); + + return new HttpRequest( + localVarPath, + HttpMethod.PATCH, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames, + true); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/PaginationLinksHelper.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/PaginationLinksHelper.java new file mode 100644 index 000000000..7ef8227bb --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/PaginationLinksHelper.java @@ -0,0 +1,63 @@ +package com.sinch.sdk.domains.voice.api.v2.adapters; + +import com.sinch.sdk.core.exceptions.ApiException; +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.core.utils.StringUtil; +import com.sinch.sdk.domains.voice.models.v2.PaginationLinks; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashMap; + +/** Follows the links.next URL of Voice v2 list responses */ +final class PaginationLinksHelper { + + private PaginationLinksHelper() {} + + /** + * Build the request for the page after the current one + * + * @param serverConfiguration Server the request is sent to + * @param links Links of the current page + * @return The request for the next page, or null on the last page + * @throws ApiException if the next link is not a valid URI + */ + static HttpRequest nextPageRequest(ServerConfiguration serverConfiguration, PaginationLinks links) + throws ApiException { + + String nextLink = null != links ? links.getNext() : null; + if (StringUtil.isEmpty(nextLink)) { + return null; + } + + // The API serves "next" as an absolute URL: its path and query are followed as they are, but + // against the configured server, so that credentials are never sent to a host taken from a + // response + URI next; + URI server; + try { + next = new URI(nextLink); + server = new URI(serverConfiguration.getUrl()); + } catch (URISyntaxException e) { + throw new ApiException("Invalid next page link: " + nextLink, e); + } + + String fullUrl = + server.getScheme() + + "://" + + server.getRawAuthority() + + next.getRawPath() + + (null != next.getRawQuery() ? "?" + next.getRawQuery() : ""); + + return new HttpRequest( + fullUrl, + HttpMethod.GET, + null, + new HashMap<>(), + Arrays.asList("application/json", "application/problem+json"), + Arrays.asList(), + Arrays.asList("BasicAuth", "SinchOAuth2")); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/ServicesServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/ServicesServiceImpl.java new file mode 100644 index 000000000..ef69e6bb2 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/ServicesServiceImpl.java @@ -0,0 +1,433 @@ +package com.sinch.sdk.domains.voice.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.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.domains.voice.models.v2.services.request.CreateServiceRequest; +import com.sinch.sdk.domains.voice.models.v2.services.request.ListServicesQueryParameters; +import com.sinch.sdk.domains.voice.models.v2.services.request.UpdateServiceRequest; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceResponse; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServicesListResponse; +import com.sinch.sdk.domains.voice.models.v2.services.response.internal.ServicesListResponseInternal; +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 ServicesServiceImpl implements com.sinch.sdk.domains.voice.api.v2.ServicesService { + + private static final Logger LOGGER = Logger.getLogger(ServicesServiceImpl.class.getName()); + private final HttpClient httpClient; + private final ServerConfiguration serverConfiguration; + private final Map authManagersByOasSecuritySchemes; + private final HttpMapper mapper; + + private final String projectId; + + public ServicesServiceImpl( + 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 ServiceResponse create(CreateServiceRequest createServiceRequest) throws ApiException { + + LOGGER.finest("[create]" + " " + "createServiceRequest: " + createServiceRequest); + + HttpRequest httpRequest = createRequestBuilder(createServiceRequest); + 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 createRequestBuilder(CreateServiceRequest createServiceRequest) + 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 create"); + } + // verify the required parameter 'createServiceRequest' is set + if (createServiceRequest == null) { + throw new ApiException( + 400, "Missing the required parameter 'createServiceRequest' when calling create"); + } + + String localVarPath = + "/v2/projects/{projectId}/services" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + if (createServiceRequest.getIdempotencyKey() != null) + localVarHeaderParams.put("Idempotency-Key", createServiceRequest.getIdempotencyKey()); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList("application/json"); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = mapper.serialize(localVarContentTypes, createServiceRequest); + + return new HttpRequest( + localVarPath, + HttpMethod.POST, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames, + true); + } + + @Override + public void delete(String serviceId) throws ApiException { + + LOGGER.finest("[delete]" + " " + "serviceId: " + serviceId); + + HttpRequest httpRequest = deleteRequestBuilder(serviceId); + 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 deleteRequestBuilder(String serviceId) 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 delete"); + } + // verify the required parameter 'serviceId' is set + if (serviceId == null) { + throw new ApiException(400, "Missing the required parameter 'serviceId' when calling delete"); + } + + String localVarPath = + "/v2/projects/{projectId}/services/{serviceId}" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll( + "\\{" + "serviceId" + "\\}", URLPathUtils.encodePathSegment(serviceId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList(); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = null; + + return new HttpRequest( + localVarPath, + HttpMethod.DELETE, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } + + @Override + public ServiceResponse get(String serviceId) throws ApiException { + + LOGGER.finest("[get]" + " " + "serviceId: " + serviceId); + + HttpRequest httpRequest = getRequestBuilder(serviceId); + 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 getRequestBuilder(String serviceId) 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 get"); + } + // verify the required parameter 'serviceId' is set + if (serviceId == null) { + throw new ApiException(400, "Missing the required parameter 'serviceId' when calling get"); + } + + String localVarPath = + "/v2/projects/{projectId}/services/{serviceId}" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll( + "\\{" + "serviceId" + "\\}", URLPathUtils.encodePathSegment(serviceId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList(); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = null; + + return new HttpRequest( + localVarPath, + HttpMethod.GET, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } + + @Override + public ServicesListResponse list() throws ApiException { + + return list((ListServicesQueryParameters) null); + } + + @Override + public ServicesListResponse list(ListServicesQueryParameters queryParameter) throws ApiException { + + LOGGER.finest("[list]" + " " + "queryParameter: " + queryParameter); + + HttpRequest httpRequest = listRequestBuilder(queryParameter); + return _fetchListPage(httpRequest); + } + + private ServicesListResponse _fetchListPage(HttpRequest httpRequest) throws ApiException { + HttpResponse response = + httpClient.invokeAPI( + this.serverConfiguration, this.authManagersByOasSecuritySchemes, httpRequest); + + if (HttpStatus.isSuccessfulStatus(response.getCode())) { + + ServicesListResponseInternal deserialized = + mapper.deserialize(response, new TypeReference() {}); + + final HttpRequest nextHttpRequest = + PaginationLinksHelper.nextPageRequest(this.serverConfiguration, deserialized.getLinks()); + + return new ServicesListResponse( + () -> _fetchListPage(nextHttpRequest), + new Page<>(deserialized.getServices(), 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 listRequestBuilder(ListServicesQueryParameters 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 list"); + } + + String localVarPath = + "/v2/projects/{projectId}/services" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())); + + List localVarQueryParams = new ArrayList<>(); + if (null != queryParameter) { + + URLParameterUtils.addQueryParam( + queryParameter.getFilter(), "filter", URLParameter.form, null, localVarQueryParams, true); + + URLParameterUtils.addQueryParam( + queryParameter.getIsDefault(), + "isDefault", + URLParameter.form, + null, + localVarQueryParams, + true); + + URLParameterUtils.addQueryParam( + queryParameter.getPageSize(), + "pageSize", + URLParameter.form, + null, + localVarQueryParams, + true); + + URLParameterUtils.addQueryParam( + queryParameter.getPage(), "page", URLParameter.form, null, localVarQueryParams, true); + } + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList(); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = null; + + return new HttpRequest( + localVarPath, + HttpMethod.GET, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } + + @Override + public ServiceResponse update(String serviceId, UpdateServiceRequest updateServiceRequest) + throws ApiException { + + LOGGER.finest( + "[update]" + + " " + + "serviceId: " + + serviceId + + ", " + + "updateServiceRequest: " + + updateServiceRequest); + + HttpRequest httpRequest = updateRequestBuilder(serviceId, updateServiceRequest); + 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 updateRequestBuilder( + String serviceId, UpdateServiceRequest updateServiceRequest) 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 update"); + } + // verify the required parameter 'serviceId' is set + if (serviceId == null) { + throw new ApiException(400, "Missing the required parameter 'serviceId' when calling update"); + } + // verify the required parameter 'updateServiceRequest' is set + if (updateServiceRequest == null) { + throw new ApiException( + 400, "Missing the required parameter 'updateServiceRequest' when calling update"); + } + + String localVarPath = + "/v2/projects/{projectId}/services/{serviceId}" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll( + "\\{" + "serviceId" + "\\}", URLPathUtils.encodePathSegment(serviceId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + if (updateServiceRequest.getIdempotencyKey() != null) + localVarHeaderParams.put("Idempotency-Key", updateServiceRequest.getIdempotencyKey()); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList("application/json"); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = mapper.serialize(localVarContentTypes, updateServiceRequest); + + return new HttpRequest( + localVarPath, + HttpMethod.PATCH, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames, + true); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/SessionsServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/SessionsServiceImpl.java new file mode 100644 index 000000000..2944c43f9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/SessionsServiceImpl.java @@ -0,0 +1,111 @@ +package com.sinch.sdk.domains.voice.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.voice.models.v2.sessions.response.Session; +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 SessionsServiceImpl implements com.sinch.sdk.domains.voice.api.v2.SessionsService { + + private static final Logger LOGGER = Logger.getLogger(SessionsServiceImpl.class.getName()); + private final HttpClient httpClient; + private final ServerConfiguration serverConfiguration; + private final Map authManagersByOasSecuritySchemes; + private final HttpMapper mapper; + + private final String projectId; + + public SessionsServiceImpl( + 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 Session get(String sessionId) throws ApiException { + + LOGGER.finest("[get]" + " " + "sessionId: " + sessionId); + + HttpRequest httpRequest = getRequestBuilder(sessionId); + 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 getRequestBuilder(String sessionId) 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 get"); + } + // verify the required parameter 'sessionId' is set + if (sessionId == null) { + throw new ApiException(400, "Missing the required parameter 'sessionId' when calling get"); + } + + String localVarPath = + "/v2/projects/{projectId}/sessions/{sessionId}" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())) + .replaceAll( + "\\{" + "sessionId" + "\\}", URLPathUtils.encodePathSegment(sessionId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList(); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + 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/voice/api/v2/adapters/SvamlServiceImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/SvamlServiceImpl.java new file mode 100644 index 000000000..70a9d8cd6 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/api/v2/adapters/SvamlServiceImpl.java @@ -0,0 +1,180 @@ +package com.sinch.sdk.domains.voice.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.voice.models.v2.svaml.request.DescribeSvamlRequest; +import com.sinch.sdk.domains.voice.models.v2.svaml.request.ValidateSvamlRequest; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.SvamlDescriptionResponse; +import com.sinch.sdk.domains.voice.models.v2.svaml.response.ValidateSvamlResponse; +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 SvamlServiceImpl implements com.sinch.sdk.domains.voice.api.v2.SvamlService { + + private static final Logger LOGGER = Logger.getLogger(SvamlServiceImpl.class.getName()); + private final HttpClient httpClient; + private final ServerConfiguration serverConfiguration; + private final Map authManagersByOasSecuritySchemes; + private final HttpMapper mapper; + + private final String projectId; + + public SvamlServiceImpl( + 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 SvamlDescriptionResponse describe(DescribeSvamlRequest describeSvamlRequest) + throws ApiException { + + LOGGER.finest("[describe]" + " " + "describeSvamlRequest: " + describeSvamlRequest); + + HttpRequest httpRequest = describeRequestBuilder(describeSvamlRequest); + 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 describeRequestBuilder(DescribeSvamlRequest describeSvamlRequest) + 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 describe"); + } + // verify the required parameter 'describeSvamlRequest' is set + if (describeSvamlRequest == null) { + throw new ApiException( + 400, "Missing the required parameter 'describeSvamlRequest' when calling describe"); + } + + String localVarPath = + "/v2/projects/{projectId}/svaml/describe" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList("application/json"); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = mapper.serialize(localVarContentTypes, describeSvamlRequest); + + return new HttpRequest( + localVarPath, + HttpMethod.POST, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } + + @Override + public ValidateSvamlResponse validate(ValidateSvamlRequest validateSvamlRequest) + throws ApiException { + + LOGGER.finest("[validate]" + " " + "validateSvamlRequest: " + validateSvamlRequest); + + HttpRequest httpRequest = validateRequestBuilder(validateSvamlRequest); + 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 validateRequestBuilder(ValidateSvamlRequest validateSvamlRequest) + 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 validate"); + } + // verify the required parameter 'validateSvamlRequest' is set + if (validateSvamlRequest == null) { + throw new ApiException( + 400, "Missing the required parameter 'validateSvamlRequest' when calling validate"); + } + + String localVarPath = + "/v2/projects/{projectId}/svaml/validate" + .replaceAll( + "\\{" + "projectId" + "\\}", + URLPathUtils.encodePathSegment(this.projectId.toString())); + + List localVarQueryParams = new ArrayList<>(); + + Map localVarHeaderParams = new HashMap<>(); + + final Collection localVarAccepts = + Arrays.asList("application/json", "application/problem+json"); + + final Collection localVarContentTypes = Arrays.asList("application/json"); + + final Collection localVarAuthNames = Arrays.asList("BasicAuth", "SinchOAuth2"); + final String serializedBody = mapper.serialize(localVarContentTypes, validateSvamlRequest); + + return new HttpRequest( + localVarPath, + HttpMethod.POST, + localVarQueryParams, + serializedBody, + localVarHeaderParams, + localVarAccepts, + localVarContentTypes, + localVarAuthNames); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/Call.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/Call.java new file mode 100644 index 000000000..38968a3d6 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/Call.java @@ -0,0 +1,400 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.destination.CallDestination; +import com.sinch.sdk.domains.voice.models.v2.destination.CallOrigin; +import java.time.Instant; + +/** Call details */ +@JsonDeserialize(builder = CallImpl.Builder.class) +public interface Call { + + /** + * The ID of the call. + * + *

Field is required + * + * @return callId + */ + String getCallId(); + + /** + * The ID of the project associated with the call. + * + *

Field is required + * + * @return projectId + */ + String getProjectId(); + + /** + * The ID of the service used. + * + *

Field is required + * + * @return serviceId + */ + String getServiceId(); + + /** + * The ID of the session. + * + *

Field is required + * + * @return sessionId + */ + String getSessionId(); + + /** + * The name identifying this call leg within the session, as assigned by the callName + * property in the dial command or in the SVAML response to an incoming call webhook. + * + *

Omitted for calls that were not assigned a name. + * + * @return callName + */ + String getCallName(); + + /** + * The name of the bridge the call belongs to. Omitted for calls not assigned to any bridge. + * + * @return bridgeName + */ + String getBridgeName(); + + /** + * The ID of the batch. + * + * @return batchId + */ + String getBatchId(); + + /** + * Call origin - Phone Number or SIP endpoint + * + * @return from + */ + CallOrigin getFrom(); + + /** + * Call destination - Phone Number or Stream URI + * + * @return to + */ + CallDestination getTo(); + + /** + * Timestamp (RFC 3339) indicating when the call was created and call setup was initiated (start + * of the call attempt). + * + *

Field is required + * + * @return startTime + */ + Instant getStartTime(); + + /** + * Timestamp (RFC 3339) indicating when the call was last updated. + * + *

Omitted if no updates were performed on this call. + * + * @return updateTime + */ + Instant getUpdateTime(); + + /** + * The type of channel used for the call. + * + *

Field is required + * + * @return callType + */ + CallType getCallType(); + + /** + * Indicates the direction of the call. + * + *

Field is required + * + * @return direction + */ + CallDirection getDirection(); + + /** + * Timestamp (RFC 3339) indicating when the call was answered. + * + *

Omitted if the call was not answered. + * + * @return answerTime + */ + Instant getAnswerTime(); + + /** + * Timestamp (RFC 3339) indicating when the call ended. + * + *

Omitted for ongoing calls. + * + * @return endTime + */ + Instant getEndTime(); + + /** + * Duration of the call in seconds + * + * @return callDurationSeconds + */ + Integer getCallDurationSeconds(); + + /** + * The outcome/state of the call. + * + *

Field is required + * + * @return callResult + */ + CallResult getCallResult(); + + /** + * Reason explaining why the call ended in the given {@link #getCallResult() callResult}. + * + * @return callReason + */ + CallReason getCallReason(); + + /** + * Indicates the origin/source of the call. + * + *

Field is required + * + * @return originationType + */ + OriginationType getOriginationType(); + + /** + * The rate charged for this call, expressed as a monetary amount per minute in the + * specified currency. + * + *

Field is required + * + * @return callRate + */ + Money getCallRate(); + + /** + * Absolute URI to this call resource. Use this URL to retrieve the call details + * + *

Field is required + * + * @return callResourceUrl + */ + String getCallResourceUrl(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new CallImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param callId see getter + * @return Current builder + * @see #getCallId + */ + Builder setCallId(String callId); + + /** + * see getter + * + * @param projectId see getter + * @return Current builder + * @see #getProjectId + */ + Builder setProjectId(String projectId); + + /** + * see getter + * + * @param serviceId see getter + * @return Current builder + * @see #getServiceId + */ + Builder setServiceId(String serviceId); + + /** + * see getter + * + * @param sessionId see getter + * @return Current builder + * @see #getSessionId + */ + Builder setSessionId(String sessionId); + + /** + * see getter + * + * @param callName see getter + * @return Current builder + * @see #getCallName + */ + Builder setCallName(String callName); + + /** + * see getter + * + * @param bridgeName see getter + * @return Current builder + * @see #getBridgeName + */ + Builder setBridgeName(String bridgeName); + + /** + * see getter + * + * @param batchId see getter + * @return Current builder + * @see #getBatchId + */ + Builder setBatchId(String batchId); + + /** + * see getter + * + * @param from see getter + * @return Current builder + * @see #getFrom + */ + Builder setFrom(CallOrigin from); + + /** + * see getter + * + * @param to see getter + * @return Current builder + * @see #getTo + */ + Builder setTo(CallDestination to); + + /** + * see getter + * + * @param startTime see getter + * @return Current builder + * @see #getStartTime + */ + Builder setStartTime(Instant startTime); + + /** + * see getter + * + * @param updateTime see getter + * @return Current builder + * @see #getUpdateTime + */ + Builder setUpdateTime(Instant updateTime); + + /** + * see getter + * + * @param callType see getter + * @return Current builder + * @see #getCallType + */ + Builder setCallType(CallType callType); + + /** + * see getter + * + * @param direction see getter + * @return Current builder + * @see #getDirection + */ + Builder setDirection(CallDirection direction); + + /** + * see getter + * + * @param answerTime see getter + * @return Current builder + * @see #getAnswerTime + */ + Builder setAnswerTime(Instant answerTime); + + /** + * see getter + * + * @param endTime see getter + * @return Current builder + * @see #getEndTime + */ + Builder setEndTime(Instant endTime); + + /** + * see getter + * + * @param callDurationSeconds see getter + * @return Current builder + * @see #getCallDurationSeconds + */ + Builder setCallDurationSeconds(Integer callDurationSeconds); + + /** + * see getter + * + * @param callResult see getter + * @return Current builder + * @see #getCallResult + */ + Builder setCallResult(CallResult callResult); + + /** + * see getter + * + * @param callReason see getter + * @return Current builder + * @see #getCallReason + */ + Builder setCallReason(CallReason callReason); + + /** + * see getter + * + * @param originationType see getter + * @return Current builder + * @see #getOriginationType + */ + Builder setOriginationType(OriginationType originationType); + + /** + * see getter + * + * @param callRate see getter + * @return Current builder + * @see #getCallRate + */ + Builder setCallRate(Money callRate); + + /** + * see getter + * + * @param callResourceUrl see getter + * @return Current builder + * @see #getCallResourceUrl + */ + Builder setCallResourceUrl(String callResourceUrl); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + Call build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallDirection.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallDirection.java new file mode 100644 index 000000000..aa1ebfac9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallDirection.java @@ -0,0 +1,36 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** Indicates the direction of the call. */ +public class CallDirection extends EnumDynamic { + + /** A call initiated towards the Sinch Calling Platform. */ + public static final CallDirection INBOUND = new CallDirection("INBOUND"); + + /** A call initiated from the Sinch Calling Platform. */ + public static final CallDirection OUTBOUND = new CallDirection("OUTBOUND"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + CallDirection.class, CallDirection::new, Arrays.asList(INBOUND, OUTBOUND)); + + private CallDirection(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CallDirection from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CallDirection e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallImpl.java new file mode 100644 index 000000000..a21b30934 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallImpl.java @@ -0,0 +1,680 @@ +package com.sinch.sdk.domains.voice.models.v2; + +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.v2.destination.CallDestination; +import com.sinch.sdk.domains.voice.models.v2.destination.CallOrigin; +import java.time.Instant; +import java.util.Objects; + +@JsonPropertyOrder({ + CallImpl.JSON_PROPERTY_CALL_ID, + CallImpl.JSON_PROPERTY_PROJECT_ID, + CallImpl.JSON_PROPERTY_SERVICE_ID, + CallImpl.JSON_PROPERTY_SESSION_ID, + CallImpl.JSON_PROPERTY_CALL_NAME, + CallImpl.JSON_PROPERTY_BRIDGE_NAME, + CallImpl.JSON_PROPERTY_BATCH_ID, + CallImpl.JSON_PROPERTY_FROM, + CallImpl.JSON_PROPERTY_TO, + CallImpl.JSON_PROPERTY_START_TIME, + CallImpl.JSON_PROPERTY_UPDATE_TIME, + CallImpl.JSON_PROPERTY_CALL_TYPE, + CallImpl.JSON_PROPERTY_DIRECTION, + CallImpl.JSON_PROPERTY_ANSWER_TIME, + CallImpl.JSON_PROPERTY_END_TIME, + CallImpl.JSON_PROPERTY_CALL_DURATION_SECONDS, + CallImpl.JSON_PROPERTY_CALL_RESULT, + CallImpl.JSON_PROPERTY_CALL_REASON, + CallImpl.JSON_PROPERTY_ORIGINATION_TYPE, + CallImpl.JSON_PROPERTY_CALL_RATE, + CallImpl.JSON_PROPERTY_CALL_RESOURCE_URL +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class CallImpl implements Call { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_CALL_ID = "callId"; + + private OptionalValue callId; + + public static final String JSON_PROPERTY_PROJECT_ID = "projectId"; + + private OptionalValue projectId; + + public static final String JSON_PROPERTY_SERVICE_ID = "serviceId"; + + private OptionalValue serviceId; + + public static final String JSON_PROPERTY_SESSION_ID = "sessionId"; + + private OptionalValue sessionId; + + public static final String JSON_PROPERTY_CALL_NAME = "callName"; + + private OptionalValue callName; + + public static final String JSON_PROPERTY_BRIDGE_NAME = "bridgeName"; + + private OptionalValue bridgeName; + + public static final String JSON_PROPERTY_BATCH_ID = "batchId"; + + private OptionalValue batchId; + + public static final String JSON_PROPERTY_FROM = "from"; + + private OptionalValue from; + + public static final String JSON_PROPERTY_TO = "to"; + + private OptionalValue to; + + public static final String JSON_PROPERTY_START_TIME = "startTime"; + + private OptionalValue startTime; + + public static final String JSON_PROPERTY_UPDATE_TIME = "updateTime"; + + private OptionalValue updateTime; + + public static final String JSON_PROPERTY_CALL_TYPE = "callType"; + + private OptionalValue callType; + + public static final String JSON_PROPERTY_DIRECTION = "direction"; + + private OptionalValue direction; + + public static final String JSON_PROPERTY_ANSWER_TIME = "answerTime"; + + private OptionalValue answerTime; + + public static final String JSON_PROPERTY_END_TIME = "endTime"; + + private OptionalValue endTime; + + public static final String JSON_PROPERTY_CALL_DURATION_SECONDS = "callDurationSeconds"; + + private OptionalValue callDurationSeconds; + + public static final String JSON_PROPERTY_CALL_RESULT = "callResult"; + + private OptionalValue callResult; + + public static final String JSON_PROPERTY_CALL_REASON = "callReason"; + + private OptionalValue callReason; + + public static final String JSON_PROPERTY_ORIGINATION_TYPE = "originationType"; + + private OptionalValue originationType; + + public static final String JSON_PROPERTY_CALL_RATE = "callRate"; + + private OptionalValue callRate; + + public static final String JSON_PROPERTY_CALL_RESOURCE_URL = "callResourceUrl"; + + private OptionalValue callResourceUrl; + + public CallImpl() {} + + protected CallImpl( + OptionalValue callId, + OptionalValue projectId, + OptionalValue serviceId, + OptionalValue sessionId, + OptionalValue callName, + OptionalValue bridgeName, + OptionalValue batchId, + OptionalValue from, + OptionalValue to, + OptionalValue startTime, + OptionalValue updateTime, + OptionalValue callType, + OptionalValue direction, + OptionalValue answerTime, + OptionalValue endTime, + OptionalValue callDurationSeconds, + OptionalValue callResult, + OptionalValue callReason, + OptionalValue originationType, + OptionalValue callRate, + OptionalValue callResourceUrl) { + this.callId = callId; + this.projectId = projectId; + this.serviceId = serviceId; + this.sessionId = sessionId; + this.callName = callName; + this.bridgeName = bridgeName; + this.batchId = batchId; + this.from = from; + this.to = to; + this.startTime = startTime; + this.updateTime = updateTime; + this.callType = callType; + this.direction = direction; + this.answerTime = answerTime; + this.endTime = endTime; + this.callDurationSeconds = callDurationSeconds; + this.callResult = callResult; + this.callReason = callReason; + this.originationType = originationType; + this.callRate = callRate; + this.callResourceUrl = callResourceUrl; + } + + @JsonIgnore + public String getCallId() { + return callId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue callId() { + return callId; + } + + @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 getServiceId() { + return serviceId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SERVICE_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue serviceId() { + return serviceId; + } + + @JsonIgnore + public String getSessionId() { + return sessionId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SESSION_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue sessionId() { + return sessionId; + } + + @JsonIgnore + public String getCallName() { + return callName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue callName() { + return callName; + } + + @JsonIgnore + public String getBridgeName() { + return bridgeName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_BRIDGE_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue bridgeName() { + return bridgeName; + } + + @JsonIgnore + public String getBatchId() { + return batchId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_BATCH_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue batchId() { + return batchId; + } + + @JsonIgnore + public CallOrigin getFrom() { + return from.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_FROM) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue from() { + return from; + } + + @JsonIgnore + public CallDestination getTo() { + return to.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TO) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue to() { + return to; + } + + @JsonIgnore + public Instant getStartTime() { + return startTime.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_START_TIME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue startTime() { + return startTime; + } + + @JsonIgnore + public Instant getUpdateTime() { + return updateTime.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_UPDATE_TIME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue updateTime() { + return updateTime; + } + + @JsonIgnore + public CallType getCallType() { + return callType.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue callType() { + return callType; + } + + @JsonIgnore + public CallDirection getDirection() { + return direction.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DIRECTION) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue direction() { + return direction; + } + + @JsonIgnore + public Instant getAnswerTime() { + return answerTime.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ANSWER_TIME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue answerTime() { + return answerTime; + } + + @JsonIgnore + public Instant getEndTime() { + return endTime.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_END_TIME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue endTime() { + return endTime; + } + + @JsonIgnore + public Integer getCallDurationSeconds() { + return callDurationSeconds.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_DURATION_SECONDS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue callDurationSeconds() { + return callDurationSeconds; + } + + @JsonIgnore + public CallResult getCallResult() { + return callResult.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_RESULT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue callResult() { + return callResult; + } + + @JsonIgnore + public CallReason getCallReason() { + return callReason.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_REASON) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue callReason() { + return callReason; + } + + @JsonIgnore + public OriginationType getOriginationType() { + return originationType.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ORIGINATION_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue originationType() { + return originationType; + } + + @JsonIgnore + public Money getCallRate() { + return callRate.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_RATE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue callRate() { + return callRate; + } + + @JsonIgnore + public String getCallResourceUrl() { + return callResourceUrl.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_RESOURCE_URL) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue callResourceUrl() { + return callResourceUrl; + } + + /** Return true if this Call object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CallImpl call = (CallImpl) o; + return Objects.equals(this.callId, call.callId) + && Objects.equals(this.projectId, call.projectId) + && Objects.equals(this.serviceId, call.serviceId) + && Objects.equals(this.sessionId, call.sessionId) + && Objects.equals(this.callName, call.callName) + && Objects.equals(this.bridgeName, call.bridgeName) + && Objects.equals(this.batchId, call.batchId) + && Objects.equals(this.from, call.from) + && Objects.equals(this.to, call.to) + && Objects.equals(this.startTime, call.startTime) + && Objects.equals(this.updateTime, call.updateTime) + && Objects.equals(this.callType, call.callType) + && Objects.equals(this.direction, call.direction) + && Objects.equals(this.answerTime, call.answerTime) + && Objects.equals(this.endTime, call.endTime) + && Objects.equals(this.callDurationSeconds, call.callDurationSeconds) + && Objects.equals(this.callResult, call.callResult) + && Objects.equals(this.callReason, call.callReason) + && Objects.equals(this.originationType, call.originationType) + && Objects.equals(this.callRate, call.callRate) + && Objects.equals(this.callResourceUrl, call.callResourceUrl); + } + + @Override + public int hashCode() { + return Objects.hash( + callId, + projectId, + serviceId, + sessionId, + callName, + bridgeName, + batchId, + from, + to, + startTime, + updateTime, + callType, + direction, + answerTime, + endTime, + callDurationSeconds, + callResult, + callReason, + originationType, + callRate, + callResourceUrl); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class CallImpl {\n"); + sb.append(" callId: ").append(toIndentedString(callId)).append("\n"); + sb.append(" projectId: ").append(toIndentedString(projectId)).append("\n"); + sb.append(" serviceId: ").append(toIndentedString(serviceId)).append("\n"); + sb.append(" sessionId: ").append(toIndentedString(sessionId)).append("\n"); + sb.append(" callName: ").append(toIndentedString(callName)).append("\n"); + sb.append(" bridgeName: ").append(toIndentedString(bridgeName)).append("\n"); + sb.append(" batchId: ").append(toIndentedString(batchId)).append("\n"); + sb.append(" from: ").append(toIndentedString(from)).append("\n"); + sb.append(" to: ").append(toIndentedString(to)).append("\n"); + sb.append(" startTime: ").append(toIndentedString(startTime)).append("\n"); + sb.append(" updateTime: ").append(toIndentedString(updateTime)).append("\n"); + sb.append(" callType: ").append(toIndentedString(callType)).append("\n"); + sb.append(" direction: ").append(toIndentedString(direction)).append("\n"); + sb.append(" answerTime: ").append(toIndentedString(answerTime)).append("\n"); + sb.append(" endTime: ").append(toIndentedString(endTime)).append("\n"); + sb.append(" callDurationSeconds: ") + .append(toIndentedString(callDurationSeconds)) + .append("\n"); + sb.append(" callResult: ").append(toIndentedString(callResult)).append("\n"); + sb.append(" callReason: ").append(toIndentedString(callReason)).append("\n"); + sb.append(" originationType: ").append(toIndentedString(originationType)).append("\n"); + sb.append(" callRate: ").append(toIndentedString(callRate)).append("\n"); + sb.append(" callResourceUrl: ").append(toIndentedString(callResourceUrl)).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 Call.Builder { + OptionalValue callId = OptionalValue.empty(); + OptionalValue projectId = OptionalValue.empty(); + OptionalValue serviceId = OptionalValue.empty(); + OptionalValue sessionId = OptionalValue.empty(); + OptionalValue callName = OptionalValue.empty(); + OptionalValue bridgeName = OptionalValue.empty(); + OptionalValue batchId = OptionalValue.empty(); + OptionalValue from = OptionalValue.empty(); + OptionalValue to = OptionalValue.empty(); + OptionalValue startTime = OptionalValue.empty(); + OptionalValue updateTime = OptionalValue.empty(); + OptionalValue callType = OptionalValue.empty(); + OptionalValue direction = OptionalValue.empty(); + OptionalValue answerTime = OptionalValue.empty(); + OptionalValue endTime = OptionalValue.empty(); + OptionalValue callDurationSeconds = OptionalValue.empty(); + OptionalValue callResult = OptionalValue.empty(); + OptionalValue callReason = OptionalValue.empty(); + OptionalValue originationType = OptionalValue.empty(); + OptionalValue callRate = OptionalValue.empty(); + OptionalValue callResourceUrl = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_CALL_ID, required = true) + public Builder setCallId(String callId) { + this.callId = OptionalValue.of(callId); + 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_SERVICE_ID, required = true) + public Builder setServiceId(String serviceId) { + this.serviceId = OptionalValue.of(serviceId); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_SESSION_ID, required = true) + public Builder setSessionId(String sessionId) { + this.sessionId = OptionalValue.of(sessionId); + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_NAME) + public Builder setCallName(String callName) { + this.callName = OptionalValue.of(callName); + return this; + } + + @JsonProperty(JSON_PROPERTY_BRIDGE_NAME) + public Builder setBridgeName(String bridgeName) { + this.bridgeName = OptionalValue.of(bridgeName); + return this; + } + + @JsonProperty(JSON_PROPERTY_BATCH_ID) + public Builder setBatchId(String batchId) { + this.batchId = OptionalValue.of(batchId); + return this; + } + + @JsonProperty(JSON_PROPERTY_FROM) + public Builder setFrom(CallOrigin from) { + this.from = OptionalValue.of(from); + return this; + } + + @JsonProperty(JSON_PROPERTY_TO) + public Builder setTo(CallDestination to) { + this.to = OptionalValue.of(to); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_START_TIME, required = true) + public Builder setStartTime(Instant startTime) { + this.startTime = OptionalValue.of(startTime); + return this; + } + + @JsonProperty(JSON_PROPERTY_UPDATE_TIME) + public Builder setUpdateTime(Instant updateTime) { + this.updateTime = OptionalValue.of(updateTime); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_CALL_TYPE, required = true) + public Builder setCallType(CallType callType) { + this.callType = OptionalValue.of(callType); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_DIRECTION, required = true) + public Builder setDirection(CallDirection direction) { + this.direction = OptionalValue.of(direction); + return this; + } + + @JsonProperty(JSON_PROPERTY_ANSWER_TIME) + public Builder setAnswerTime(Instant answerTime) { + this.answerTime = OptionalValue.of(answerTime); + return this; + } + + @JsonProperty(JSON_PROPERTY_END_TIME) + public Builder setEndTime(Instant endTime) { + this.endTime = OptionalValue.of(endTime); + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_DURATION_SECONDS) + public Builder setCallDurationSeconds(Integer callDurationSeconds) { + this.callDurationSeconds = OptionalValue.of(callDurationSeconds); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_CALL_RESULT, required = true) + public Builder setCallResult(CallResult callResult) { + this.callResult = OptionalValue.of(callResult); + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_REASON) + public Builder setCallReason(CallReason callReason) { + this.callReason = OptionalValue.of(callReason); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_ORIGINATION_TYPE, required = true) + public Builder setOriginationType(OriginationType originationType) { + this.originationType = OptionalValue.of(originationType); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_CALL_RATE, required = true) + public Builder setCallRate(Money callRate) { + this.callRate = OptionalValue.of(callRate); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_CALL_RESOURCE_URL, required = true) + public Builder setCallResourceUrl(String callResourceUrl) { + this.callResourceUrl = OptionalValue.of(callResourceUrl); + return this; + } + + public Call build() { + return new CallImpl( + callId, + projectId, + serviceId, + sessionId, + callName, + bridgeName, + batchId, + from, + to, + startTime, + updateTime, + callType, + direction, + answerTime, + endTime, + callDurationSeconds, + callResult, + callReason, + originationType, + callRate, + callResourceUrl); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallReason.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallReason.java new file mode 100644 index 000000000..3c44328d8 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallReason.java @@ -0,0 +1,78 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * Reason explaining why the call ended in the given {@link CallResult}. + * + *

{@link CallResult} describes what happened (state/outcome). The reason provides + * additional context about the underlying cause (for example, who terminated the call, routing + * issues, or validation errors). + */ +public class CallReason extends EnumDynamic { + + /** The call was completed successfully. */ + public static final CallReason OK = new CallReason("OK"); + + /** The callee was not available. */ + public static final CallReason NOT_AVAILABLE = new CallReason("NOT_AVAILABLE"); + + /** The caller hung up the call. */ + public static final CallReason CALLER_HANGUP = new CallReason("CALLER_HANGUP"); + + /** The callee hung up the call. */ + public static final CallReason CALLEE_HANGUP = new CallReason("CALLEE_HANGUP"); + + /** The call was ended by the call manager. */ + public static final CallReason MANAGER_HANGUP = new CallReason("MANAGER_HANGUP"); + + /** The DID number was not found. */ + public static final CallReason DID_NOT_FOUND = new CallReason("DID_NOT_FOUND"); + + /** The SVAML script was invalid. */ + public static final CallReason INVALID_SCRIPT = new CallReason("INVALID_SCRIPT"); + + /** The product associated with the call is unknown. */ + public static final CallReason UNKNOWN_PRODUCT = new CallReason("UNKNOWN_PRODUCT"); + + /** There are no more routes to complete the call. */ + public static final CallReason NO_MORE_ROUTES = new CallReason("NO_MORE_ROUTES"); + + /** An error occurred during the call. */ + public static final CallReason ERROR = new CallReason("ERROR"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + CallReason.class, + CallReason::new, + Arrays.asList( + OK, + NOT_AVAILABLE, + CALLER_HANGUP, + CALLEE_HANGUP, + MANAGER_HANGUP, + DID_NOT_FOUND, + INVALID_SCRIPT, + UNKNOWN_PRODUCT, + NO_MORE_ROUTES, + ERROR)); + + private CallReason(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CallReason from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CallReason e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallResult.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallResult.java new file mode 100644 index 000000000..9cc2e505f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallResult.java @@ -0,0 +1,73 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * The outcome/state of the call. + * + *

This field includes both transitional states (during call setup and execution) and + * final states (when the call has ended). + */ +public class CallResult extends EnumDynamic { + + /** The call is queued for initiation. Transitional state. */ + public static final CallResult QUEUED = new CallResult("QUEUED"); + + /** The call is connecting but the recipient has not yet answered. Transitional state. */ + public static final CallResult INITIATED = new CallResult("INITIATED"); + + /** The call has been answered and is in progress. Transitional state. */ + public static final CallResult IN_PROGRESS = new CallResult("IN_PROGRESS"); + + /** The call was answered and is ended. Final state. */ + public static final CallResult COMPLETED = new CallResult("COMPLETED"); + + /** The call was rejected by the recipient. */ + public static final CallResult REJECTED = new CallResult("REJECTED"); + + /** The call was not answered by the recipient. */ + public static final CallResult NO_ANSWER = new CallResult("NO_ANSWER"); + + /** The call was cancelled. */ + public static final CallResult CANCEL = new CallResult("CANCEL"); + + /** The call was not answered because the recipient was busy. */ + public static final CallResult BUSY = new CallResult("BUSY"); + + /** The call could not be completed. */ + public static final CallResult FAILED = new CallResult("FAILED"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + CallResult.class, + CallResult::new, + Arrays.asList( + QUEUED, + INITIATED, + IN_PROGRESS, + COMPLETED, + REJECTED, + NO_ANSWER, + CANCEL, + BUSY, + FAILED)); + + private CallResult(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CallResult from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CallResult e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallType.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallType.java new file mode 100644 index 000000000..c21fe576d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/CallType.java @@ -0,0 +1,47 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * The type of channel used for the call. + * + *

This value indicates what kind of endpoint the call is connected to (PSTN phone number or + * WebSocket stream). + */ +public class CallType extends EnumDynamic { + + /** A call from or to the telephone network. */ + public static final CallType PHONE = new CallType("PHONE"); + + /** A call from or to a SIP endpoint. */ + public static final CallType SIP = new CallType("SIP"); + + /** A call to a stream. */ + public static final CallType STREAM = new CallType("STREAM"); + + /** A call to voice relay service. */ + public static final CallType VOICE_RELAY = new CallType("VOICE_RELAY"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + CallType.class, CallType::new, Arrays.asList(PHONE, SIP, STREAM, VOICE_RELAY)); + + private CallType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CallType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CallType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/Money.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/Money.java new file mode 100644 index 000000000..539f93ce8 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/Money.java @@ -0,0 +1,66 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** A monetary amount with an associated currency code. */ +@JsonDeserialize(builder = MoneyImpl.Builder.class) +public interface Money { + + /** + * The 3-letter currency code defined in ISO 4217. + * + *

Field is required + * + * @return currencyCode + */ + String getCurrencyCode(); + + /** + * The monetary amount as a string to preserve precision. Supports up to 4 decimal places (e.g., + * "10.5000", "0.9999"). + * + *

Field is required + * + * @return amount + */ + String getAmount(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new MoneyImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param currencyCode see getter + * @return Current builder + * @see #getCurrencyCode + */ + Builder setCurrencyCode(String currencyCode); + + /** + * see getter + * + * @param amount see getter + * @return Current builder + * @see #getAmount + */ + Builder setAmount(String amount); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + Money build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/MoneyImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/MoneyImpl.java new file mode 100644 index 000000000..7d3c725a5 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/MoneyImpl.java @@ -0,0 +1,115 @@ +package com.sinch.sdk.domains.voice.models.v2; + +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({MoneyImpl.JSON_PROPERTY_CURRENCY_CODE, MoneyImpl.JSON_PROPERTY_AMOUNT}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class MoneyImpl implements Money { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_CURRENCY_CODE = "currencyCode"; + + private OptionalValue currencyCode; + + public static final String JSON_PROPERTY_AMOUNT = "amount"; + + private OptionalValue amount; + + public MoneyImpl() {} + + protected MoneyImpl(OptionalValue currencyCode, OptionalValue amount) { + this.currencyCode = currencyCode; + this.amount = amount; + } + + @JsonIgnore + public String getCurrencyCode() { + return currencyCode.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CURRENCY_CODE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue currencyCode() { + return currencyCode; + } + + @JsonIgnore + public String getAmount() { + return amount.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_AMOUNT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue amount() { + return amount; + } + + /** Return true if this Money object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MoneyImpl money = (MoneyImpl) o; + return Objects.equals(this.currencyCode, money.currencyCode) + && Objects.equals(this.amount, money.amount); + } + + @Override + public int hashCode() { + return Objects.hash(currencyCode, amount); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class MoneyImpl {\n"); + sb.append(" currencyCode: ").append(toIndentedString(currencyCode)).append("\n"); + sb.append(" amount: ").append(toIndentedString(amount)).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 Money.Builder { + OptionalValue currencyCode = OptionalValue.empty(); + OptionalValue amount = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_CURRENCY_CODE, required = true) + public Builder setCurrencyCode(String currencyCode) { + this.currencyCode = OptionalValue.of(currencyCode); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_AMOUNT, required = true) + public Builder setAmount(String amount) { + this.amount = OptionalValue.of(amount); + return this; + } + + public Money build() { + return new MoneyImpl(currencyCode, amount); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/OriginationType.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/OriginationType.java new file mode 100644 index 000000000..43913bfa7 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/OriginationType.java @@ -0,0 +1,43 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * Indicates the origin/source of the call. + * + *

This describes how the call was initiated (PSTN or via the API). + */ +public class OriginationType extends EnumDynamic { + + /** The call originated from the telephone network (PSTN). */ + public static final OriginationType PHONE = new OriginationType("PHONE"); + + /** The call originated from a SIP trunk. */ + public static final OriginationType SIP = new OriginationType("SIP"); + + /** The call originated through the Sinch API. */ + public static final OriginationType SERVER = new OriginationType("SERVER"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + OriginationType.class, OriginationType::new, Arrays.asList(PHONE, SIP, SERVER)); + + private OriginationType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static OriginationType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(OriginationType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationLinks.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationLinks.java new file mode 100644 index 000000000..49f6f3100 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationLinks.java @@ -0,0 +1,114 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** Pagination links for navigating through pages of results in a paginated list response. */ +@JsonDeserialize(builder = PaginationLinksImpl.Builder.class) +public interface PaginationLinks { + + /** + * Absolute URI of the first page. + * + *

Field is required + * + * @return first + */ + String getFirst(); + + /** + * Absolute URI of the last page. + * + *

Field is required + * + * @return last + */ + String getLast(); + + /** + * Absolute URI of the next page (omitted if this is the last page). + * + * @return next + */ + String getNext(); + + /** + * Absolute URI of the previous page (omitted if this is the first page). + * + * @return prev + */ + String getPrev(); + + /** + * Absolute URI of the current page. + * + *

Field is required + * + * @return self + */ + String getSelf(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new PaginationLinksImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param first see getter + * @return Current builder + * @see #getFirst + */ + Builder setFirst(String first); + + /** + * see getter + * + * @param last see getter + * @return Current builder + * @see #getLast + */ + Builder setLast(String last); + + /** + * see getter + * + * @param next see getter + * @return Current builder + * @see #getNext + */ + Builder setNext(String next); + + /** + * see getter + * + * @param prev see getter + * @return Current builder + * @see #getPrev + */ + Builder setPrev(String prev); + + /** + * see getter + * + * @param self see getter + * @return Current builder + * @see #getSelf + */ + Builder setSelf(String self); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + PaginationLinks build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationLinksImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationLinksImpl.java new file mode 100644 index 000000000..ed1c2a52e --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationLinksImpl.java @@ -0,0 +1,201 @@ +package com.sinch.sdk.domains.voice.models.v2; + +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({ + PaginationLinksImpl.JSON_PROPERTY_FIRST, + PaginationLinksImpl.JSON_PROPERTY_LAST, + PaginationLinksImpl.JSON_PROPERTY_NEXT, + PaginationLinksImpl.JSON_PROPERTY_PREV, + PaginationLinksImpl.JSON_PROPERTY_SELF +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class PaginationLinksImpl implements PaginationLinks { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_FIRST = "first"; + + private OptionalValue first; + + public static final String JSON_PROPERTY_LAST = "last"; + + private OptionalValue last; + + public static final String JSON_PROPERTY_NEXT = "next"; + + private OptionalValue next; + + public static final String JSON_PROPERTY_PREV = "prev"; + + private OptionalValue prev; + + public static final String JSON_PROPERTY_SELF = "self"; + + private OptionalValue self; + + public PaginationLinksImpl() {} + + protected PaginationLinksImpl( + OptionalValue first, + OptionalValue last, + OptionalValue next, + OptionalValue prev, + OptionalValue self) { + this.first = first; + this.last = last; + this.next = next; + this.prev = prev; + this.self = self; + } + + @JsonIgnore + public String getFirst() { + return first.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_FIRST) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue first() { + return first; + } + + @JsonIgnore + public String getLast() { + return last.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_LAST) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue last() { + return last; + } + + @JsonIgnore + public String getNext() { + return next.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_NEXT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue next() { + return next; + } + + @JsonIgnore + public String getPrev() { + return prev.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_PREV) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue prev() { + return prev; + } + + @JsonIgnore + public String getSelf() { + return self.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SELF) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue self() { + return self; + } + + /** Return true if this PaginationLinks object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PaginationLinksImpl paginationLinks = (PaginationLinksImpl) o; + return Objects.equals(this.first, paginationLinks.first) + && Objects.equals(this.last, paginationLinks.last) + && Objects.equals(this.next, paginationLinks.next) + && Objects.equals(this.prev, paginationLinks.prev) + && Objects.equals(this.self, paginationLinks.self); + } + + @Override + public int hashCode() { + return Objects.hash(first, last, next, prev, self); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class PaginationLinksImpl {\n"); + sb.append(" first: ").append(toIndentedString(first)).append("\n"); + sb.append(" last: ").append(toIndentedString(last)).append("\n"); + sb.append(" next: ").append(toIndentedString(next)).append("\n"); + sb.append(" prev: ").append(toIndentedString(prev)).append("\n"); + sb.append(" self: ").append(toIndentedString(self)).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 PaginationLinks.Builder { + OptionalValue first = OptionalValue.empty(); + OptionalValue last = OptionalValue.empty(); + OptionalValue next = OptionalValue.empty(); + OptionalValue prev = OptionalValue.empty(); + OptionalValue self = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_FIRST, required = true) + public Builder setFirst(String first) { + this.first = OptionalValue.of(first); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_LAST, required = true) + public Builder setLast(String last) { + this.last = OptionalValue.of(last); + return this; + } + + @JsonProperty(JSON_PROPERTY_NEXT) + public Builder setNext(String next) { + this.next = OptionalValue.of(next); + return this; + } + + @JsonProperty(JSON_PROPERTY_PREV) + public Builder setPrev(String prev) { + this.prev = OptionalValue.of(prev); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_SELF, required = true) + public Builder setSelf(String self) { + this.self = OptionalValue.of(self); + return this; + } + + public PaginationLinks build() { + return new PaginationLinksImpl(first, last, next, prev, self); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationMeta.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationMeta.java new file mode 100644 index 000000000..d54f5f7cf --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationMeta.java @@ -0,0 +1,64 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** Metadata about the paginated list response. */ +@JsonDeserialize(builder = PaginationMetaImpl.Builder.class) +public interface PaginationMeta { + + /** + * Total number of items across all pages. + * + *

Field is required + * + * @return totalCount + */ + Integer getTotalCount(); + + /** + * Total number of pages. + * + *

Field is required + * + * @return pageCount + */ + Integer getPageCount(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new PaginationMetaImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param totalCount see getter + * @return Current builder + * @see #getTotalCount + */ + Builder setTotalCount(Integer totalCount); + + /** + * see getter + * + * @param pageCount see getter + * @return Current builder + * @see #getPageCount + */ + Builder setPageCount(Integer pageCount); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + PaginationMeta build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationMetaImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationMetaImpl.java new file mode 100644 index 000000000..9c25a86da --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/PaginationMetaImpl.java @@ -0,0 +1,119 @@ +package com.sinch.sdk.domains.voice.models.v2; + +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({ + PaginationMetaImpl.JSON_PROPERTY_TOTAL_COUNT, + PaginationMetaImpl.JSON_PROPERTY_PAGE_COUNT +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class PaginationMetaImpl implements PaginationMeta { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_TOTAL_COUNT = "totalCount"; + + private OptionalValue totalCount; + + public static final String JSON_PROPERTY_PAGE_COUNT = "pageCount"; + + private OptionalValue pageCount; + + public PaginationMetaImpl() {} + + protected PaginationMetaImpl( + OptionalValue totalCount, OptionalValue pageCount) { + this.totalCount = totalCount; + this.pageCount = pageCount; + } + + @JsonIgnore + public Integer getTotalCount() { + return totalCount.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TOTAL_COUNT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue totalCount() { + return totalCount; + } + + @JsonIgnore + public Integer getPageCount() { + return pageCount.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_PAGE_COUNT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue pageCount() { + return pageCount; + } + + /** Return true if this PaginationMeta object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PaginationMetaImpl paginationMeta = (PaginationMetaImpl) o; + return Objects.equals(this.totalCount, paginationMeta.totalCount) + && Objects.equals(this.pageCount, paginationMeta.pageCount); + } + + @Override + public int hashCode() { + return Objects.hash(totalCount, pageCount); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class PaginationMetaImpl {\n"); + sb.append(" totalCount: ").append(toIndentedString(totalCount)).append("\n"); + sb.append(" pageCount: ").append(toIndentedString(pageCount)).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 PaginationMeta.Builder { + OptionalValue totalCount = OptionalValue.empty(); + OptionalValue pageCount = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_TOTAL_COUNT, required = true) + public Builder setTotalCount(Integer totalCount) { + this.totalCount = OptionalValue.of(totalCount); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_PAGE_COUNT, required = true) + public Builder setPageCount(Integer pageCount) { + this.pageCount = OptionalValue.of(pageCount); + return this; + } + + public PaginationMeta build() { + return new PaginationMetaImpl(totalCount, pageCount); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/SessionState.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/SessionState.java new file mode 100644 index 000000000..144433387 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/SessionState.java @@ -0,0 +1,50 @@ +package com.sinch.sdk.domains.voice.models.v2; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * The state of the current session. Sessions can either be in a "transitional" or + * "final" state. Sessions in a transitional state are ongoing. Sessions in a final state + * will not send any more events. + */ +public class SessionState extends EnumDynamic { + + /** The session is queued, no calls have been initiated for this session. Transitional state. */ + public static final SessionState QUEUED = new SessionState("QUEUED"); + + /** At least one call has been initiated or received for this session. Transitional state. */ + public static final SessionState IN_PROGRESS = new SessionState("IN_PROGRESS"); + + /** The session was completed. Final state. */ + public static final SessionState COMPLETED = new SessionState("COMPLETED"); + + /** + * The call session was not initiated before the batch time-to-live (TTL) elapsed. Final state. + */ + public static final SessionState EXPIRED = new SessionState("EXPIRED"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + SessionState.class, + SessionState::new, + Arrays.asList(QUEUED, IN_PROGRESS, COMPLETED, EXPIRED)); + + private SessionState(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static SessionState from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(SessionState e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/BatchOptions.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/BatchOptions.java new file mode 100644 index 000000000..60e1a6001 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/BatchOptions.java @@ -0,0 +1,67 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** Options for batch dialing */ +@JsonDeserialize(builder = BatchOptionsImpl.Builder.class) +public interface BatchOptions { + + /** + * Requested maximum call initiation rate, in calls per second (CPS), for this batch. + * + *

Actual CPS may be lower depending on routing, carrier, and platform capacity, as well as + * account limitations. minimum: 1 maximum: 1000 + * + * @return maxCps + */ + Integer getMaxCps(); + + /** + * Batch time-to-live (TTL). The maximum amount of time the platform will keep attempting to start + * queued call sessions in this batch. + * + *

When the TTL expires, any call sessions that have not yet been initiated will stop being + * processed (calls already in progress are not affected). minimum: 1 maximum: 10800 + * + * @return ttlSeconds + */ + Integer getTtlSeconds(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new BatchOptionsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param maxCps see getter + * @return Current builder + * @see #getMaxCps + */ + Builder setMaxCps(Integer maxCps); + + /** + * see getter + * + * @param ttlSeconds see getter + * @return Current builder + * @see #getTtlSeconds + */ + Builder setTtlSeconds(Integer ttlSeconds); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + BatchOptions build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/BatchOptionsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/BatchOptionsImpl.java new file mode 100644 index 000000000..457eb8e64 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/BatchOptionsImpl.java @@ -0,0 +1,118 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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.Objects; + +@JsonPropertyOrder({ + BatchOptionsImpl.JSON_PROPERTY_MAX_CPS, + BatchOptionsImpl.JSON_PROPERTY_TTL_SECONDS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class BatchOptionsImpl implements BatchOptions { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_MAX_CPS = "maxCps"; + + private OptionalValue maxCps; + + public static final String JSON_PROPERTY_TTL_SECONDS = "ttlSeconds"; + + private OptionalValue ttlSeconds; + + public BatchOptionsImpl() {} + + protected BatchOptionsImpl(OptionalValue maxCps, OptionalValue ttlSeconds) { + this.maxCps = maxCps; + this.ttlSeconds = ttlSeconds; + } + + @JsonIgnore + public Integer getMaxCps() { + return maxCps.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MAX_CPS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue maxCps() { + return maxCps; + } + + @JsonIgnore + public Integer getTtlSeconds() { + return ttlSeconds.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TTL_SECONDS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue ttlSeconds() { + return ttlSeconds; + } + + /** Return true if this batchOptions object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BatchOptionsImpl batchOptions = (BatchOptionsImpl) o; + return Objects.equals(this.maxCps, batchOptions.maxCps) + && Objects.equals(this.ttlSeconds, batchOptions.ttlSeconds); + } + + @Override + public int hashCode() { + return Objects.hash(maxCps, ttlSeconds); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class BatchOptionsImpl {\n"); + sb.append(" maxCps: ").append(toIndentedString(maxCps)).append("\n"); + sb.append(" ttlSeconds: ").append(toIndentedString(ttlSeconds)).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 BatchOptions.Builder { + OptionalValue maxCps = OptionalValue.empty(); + OptionalValue ttlSeconds = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_MAX_CPS) + public Builder setMaxCps(Integer maxCps) { + this.maxCps = OptionalValue.of(maxCps); + return this; + } + + @JsonProperty(JSON_PROPERTY_TTL_SECONDS) + public Builder setTtlSeconds(Integer ttlSeconds) { + this.ttlSeconds = OptionalValue.of(ttlSeconds); + return this; + } + + public BatchOptions build() { + return new BatchOptionsImpl(maxCps, ttlSeconds); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchQueryParameters.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchQueryParameters.java new file mode 100644 index 000000000..ce212dc3e --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchQueryParameters.java @@ -0,0 +1,57 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.request; + +import com.sinch.sdk.core.models.OptionalValue; + +/** + * Query parameters for starting a batch of calls + * + * @since 2.3 + */ +public interface StartBatchQueryParameters { + + /** + * The ID of the service to use. If omitted, the project's default service is used. + * + * @return serviceId + */ + OptionalValue getServiceId(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StartBatchQueryParametersImpl.Builder(); + } + + /** + * Getting builder from existing instance + * + * @param parameters Instance to copy the values from + * @return New Builder instance + */ + static Builder builder(StartBatchQueryParameters parameters) { + return new StartBatchQueryParametersImpl.Builder(parameters); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param serviceId see getter + * @return Current builder + * @see #getServiceId + */ + Builder setServiceId(String serviceId); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StartBatchQueryParameters build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchQueryParametersImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchQueryParametersImpl.java new file mode 100644 index 000000000..14010916f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchQueryParametersImpl.java @@ -0,0 +1,77 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.request; + +import com.sinch.sdk.core.models.OptionalValue; +import java.util.Objects; + +public class StartBatchQueryParametersImpl implements StartBatchQueryParameters { + + private final OptionalValue serviceId; + + private StartBatchQueryParametersImpl(OptionalValue serviceId) { + this.serviceId = serviceId; + } + + public OptionalValue getServiceId() { + return serviceId; + } + + /** Return true if this StartBatchQueryParameters object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StartBatchQueryParametersImpl parameters = (StartBatchQueryParametersImpl) o; + return Objects.equals(this.serviceId, parameters.serviceId); + } + + @Override + public int hashCode() { + return Objects.hash(serviceId); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StartBatchQueryParametersImpl {\n"); + sb.append(" serviceId: ").append(toIndentedString(serviceId)).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 StartBatchQueryParameters.Builder { + OptionalValue serviceId = OptionalValue.empty(); + + protected Builder() {} + + protected Builder(StartBatchQueryParameters _parameters) { + if (null == _parameters) { + return; + } + StartBatchQueryParametersImpl parameters = (StartBatchQueryParametersImpl) _parameters; + this.serviceId = parameters.getServiceId(); + } + + public Builder setServiceId(String serviceId) { + this.serviceId = OptionalValue.of(serviceId); + return this; + } + + public StartBatchQueryParameters build() { + return new StartBatchQueryParametersImpl(serviceId); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchRequest.java new file mode 100644 index 000000000..e3b9ac391 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchRequest.java @@ -0,0 +1,127 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.List; +import java.util.Map; + +/** + * Request to start a batch of outbound calls. + * + *

Each entry in {@link #getParameters()} queues one call, using that entry's values for the + * placeholders in the commands. + */ +@JsonDeserialize(builder = StartBatchRequestImpl.Builder.class) +public interface StartBatchRequest { + + /** + * An ordered list of SVAML v2 (Sinch Voice Application Markup Language) commands that describe a + * call flow. Commands are executed sequentially in the order they are defined. + * + *

Blocking vs. non-blocking: Some commands block execution until they complete (pause + * , webhook, menu, gotoMenu), while others return + * immediately and run in parallel (dial, messages, amd, + * answer, hangup, startRecording, stopRecording + * , bridgeCall, stopMessages). Each command's description + * specifies its behavior. + * + *

Nesting scope: Commands that appear inside event handlers (e.g., dial.events.onAnswer + * , messages.events.onFinish) form independent sequences and execute in their + * own context — they are not continuations of the parent sequence. + * + *

Field is required + * + * @return commands + */ + List getCommands(); + + /** + * An array of parameter objects that define values for dynamic placeholders in commands. Each + * object represents a set of parameters for a single queued call. + * + *

Use these parameters to inject customer-defined values into your SVAML commands, enabling + * personalized call flows without modifying the base command structure. + * + *

Field is required + * + * @return parameters + */ + List> getParameters(); + + /** + * Options for batch dialing + * + * @return batchOptions + */ + BatchOptions getBatchOptions(); + + /** + * Client-generated idempotency key to safely retry requests. The server uses this key to + * recognize retries of the same request. If a request with the same key is received within 10 + * minutes, the server returns the cached response from the original request. Using a random UUID + * (v4) is strongly recommended. + * + *

Sent as the Idempotency-Key header. If not set, the SDK generates one for the + * call and sends it again on the retries of that call. Set your own key if you need to retry the + * call yourself, since the generated one is not returned. + * + * @return idempotencyKey + */ + String getIdempotencyKey(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StartBatchRequestImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param commands see getter + * @return Current builder + * @see #getCommands + */ + Builder setCommands(List commands); + + /** + * see getter + * + * @param parameters see getter + * @return Current builder + * @see #getParameters + */ + Builder setParameters(List> parameters); + + /** + * see getter + * + * @param batchOptions see getter + * @return Current builder + * @see #getBatchOptions + */ + Builder setBatchOptions(BatchOptions batchOptions); + + /** + * see getter + * + * @param idempotencyKey see getter + * @return Current builder + * @see #getIdempotencyKey + */ + Builder setIdempotencyKey(String idempotencyKey); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StartBatchRequest build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchRequestImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchRequestImpl.java new file mode 100644 index 000000000..454708ab5 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchRequestImpl.java @@ -0,0 +1,173 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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.v2.svaml.SvamlCommand; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@JsonPropertyOrder({ + StartBatchRequestImpl.JSON_PROPERTY_COMMANDS, + StartBatchRequestImpl.JSON_PROPERTY_PARAMETERS, + StartBatchRequestImpl.JSON_PROPERTY_BATCH_OPTIONS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class StartBatchRequestImpl implements StartBatchRequest { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_COMMANDS = "commands"; + + private OptionalValue> commands; + + public static final String JSON_PROPERTY_PARAMETERS = "parameters"; + + private OptionalValue>> parameters; + + public static final String JSON_PROPERTY_BATCH_OPTIONS = "batchOptions"; + + private OptionalValue batchOptions; + + private OptionalValue idempotencyKey; + + public StartBatchRequestImpl() {} + + protected StartBatchRequestImpl( + OptionalValue> commands, + OptionalValue>> parameters, + OptionalValue batchOptions, + OptionalValue idempotencyKey) { + this.commands = commands; + this.parameters = parameters; + this.batchOptions = batchOptions; + this.idempotencyKey = idempotencyKey; + } + + @JsonIgnore + public List getCommands() { + return commands.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMANDS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> commands() { + return commands; + } + + @JsonIgnore + public List> getParameters() { + return parameters.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_PARAMETERS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue>> parameters() { + return parameters; + } + + @JsonIgnore + public BatchOptions getBatchOptions() { + return batchOptions.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_BATCH_OPTIONS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue batchOptions() { + return batchOptions; + } + + @JsonIgnore + public String getIdempotencyKey() { + return idempotencyKey.orElse(null); + } + + @JsonIgnore + public OptionalValue idempotencyKey() { + return idempotencyKey; + } + + /** Return true if this StartBatchRequest object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StartBatchRequestImpl startBatchRequest = (StartBatchRequestImpl) o; + return Objects.equals(this.commands, startBatchRequest.commands) + && Objects.equals(this.parameters, startBatchRequest.parameters) + && Objects.equals(this.batchOptions, startBatchRequest.batchOptions) + && Objects.equals(this.idempotencyKey, startBatchRequest.idempotencyKey); + } + + @Override + public int hashCode() { + return Objects.hash(commands, parameters, batchOptions, idempotencyKey); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StartBatchRequestImpl {\n"); + sb.append(" commands: ").append(toIndentedString(commands)).append("\n"); + sb.append(" parameters: ").append(toIndentedString(parameters)).append("\n"); + sb.append(" batchOptions: ").append(toIndentedString(batchOptions)).append("\n"); + sb.append(" idempotencyKey: ").append(toIndentedString(idempotencyKey)).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 StartBatchRequest.Builder { + OptionalValue> commands = OptionalValue.empty(); + OptionalValue>> parameters = OptionalValue.empty(); + OptionalValue batchOptions = OptionalValue.empty(); + OptionalValue idempotencyKey = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMANDS, required = true) + public Builder setCommands(List commands) { + this.commands = OptionalValue.of(commands); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_PARAMETERS, required = true) + public Builder setParameters(List> parameters) { + this.parameters = OptionalValue.of(parameters); + return this; + } + + @JsonProperty(JSON_PROPERTY_BATCH_OPTIONS) + public Builder setBatchOptions(BatchOptions batchOptions) { + this.batchOptions = OptionalValue.of(batchOptions); + return this; + } + + @JsonIgnore + public Builder setIdempotencyKey(String idempotencyKey) { + this.idempotencyKey = OptionalValue.of(idempotencyKey); + return this; + } + + public StartBatchRequest build() { + return new StartBatchRequestImpl(commands, parameters, batchOptions, idempotencyKey); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchDetails.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchDetails.java new file mode 100644 index 000000000..7e8915f59 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchDetails.java @@ -0,0 +1,52 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.List; + +/** + * Detailed per-session view of a batch. Contains one entry for each call session in the batch, + * including the session identifier and its current execution state. + */ +@JsonDeserialize(builder = BatchDetailsImpl.Builder.class) +public interface BatchDetails { + + /** + * Per-session details for the batch. + * + *

EXPIRED sessions are never returned because they were never initiated. + * + *

Field is required + * + * @return sessions + */ + List getSessions(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new BatchDetailsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param sessions see getter + * @return Current builder + * @see #getSessions + */ + Builder setSessions(List sessions); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + BatchDetails build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchDetailsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchDetailsImpl.java new file mode 100644 index 000000000..ecb9bec4e --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchDetailsImpl.java @@ -0,0 +1,91 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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.List; +import java.util.Objects; + +@JsonPropertyOrder({BatchDetailsImpl.JSON_PROPERTY_SESSIONS}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class BatchDetailsImpl implements BatchDetails { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_SESSIONS = "sessions"; + + private OptionalValue> sessions; + + public BatchDetailsImpl() {} + + protected BatchDetailsImpl(OptionalValue> sessions) { + this.sessions = sessions; + } + + @JsonIgnore + public List getSessions() { + return sessions.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SESSIONS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> sessions() { + return sessions; + } + + /** Return true if this BatchDetails object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BatchDetailsImpl batchDetails = (BatchDetailsImpl) o; + return Objects.equals(this.sessions, batchDetails.sessions); + } + + @Override + public int hashCode() { + return Objects.hash(sessions); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class BatchDetailsImpl {\n"); + sb.append(" sessions: ").append(toIndentedString(sessions)).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 BatchDetails.Builder { + OptionalValue> sessions = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_SESSIONS, required = true) + public Builder setSessions(List sessions) { + this.sessions = OptionalValue.of(sessions); + return this; + } + + public BatchDetails build() { + return new BatchDetailsImpl(sessions); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSessionSummary.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSessionSummary.java new file mode 100644 index 000000000..f8b1bb32c --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSessionSummary.java @@ -0,0 +1,84 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.SessionState; +import java.util.Map; + +/** Identifier and current execution state of a call session within a batch. */ +@JsonDeserialize(builder = BatchSessionSummaryImpl.Builder.class) +public interface BatchSessionSummary { + + /** + * Unique identifier of the call session within the batch (ULID). This identifies the session, not + * an individual call. + * + *

Use it with /v2/projects/{projectId}/sessions/{sessionId} to retrieve full + * session details. + * + * @return id + */ + String getId(); + + /** + * Current execution state of the call session. + * + * @return state + */ + SessionState getState(); + + /** + * Plain key-value set of parameters from the {@link + * com.sinch.sdk.domains.voice.models.v2.batches.request.StartBatchRequest#getParameters() + * parameters} entry that was used to initiate this call session. + * + * @return parameters + */ + Map getParameters(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new BatchSessionSummaryImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param id see getter + * @return Current builder + * @see #getId + */ + Builder setId(String id); + + /** + * see getter + * + * @param state see getter + * @return Current builder + * @see #getState + */ + Builder setState(SessionState state); + + /** + * see getter + * + * @param parameters see getter + * @return Current builder + * @see #getParameters + */ + Builder setParameters(Map parameters); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + BatchSessionSummary build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSessionSummaryImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSessionSummaryImpl.java new file mode 100644 index 000000000..72e272ca8 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSessionSummaryImpl.java @@ -0,0 +1,149 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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.voice.models.v2.SessionState; +import java.util.Map; +import java.util.Objects; + +@JsonPropertyOrder({ + BatchSessionSummaryImpl.JSON_PROPERTY_ID, + BatchSessionSummaryImpl.JSON_PROPERTY_STATE, + BatchSessionSummaryImpl.JSON_PROPERTY_PARAMETERS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class BatchSessionSummaryImpl implements BatchSessionSummary { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ID = "id"; + + private OptionalValue id; + + public static final String JSON_PROPERTY_STATE = "state"; + + private OptionalValue state; + + public static final String JSON_PROPERTY_PARAMETERS = "parameters"; + + private OptionalValue> parameters; + + public BatchSessionSummaryImpl() {} + + protected BatchSessionSummaryImpl( + OptionalValue id, + OptionalValue state, + OptionalValue> parameters) { + this.id = id; + this.state = state; + this.parameters = parameters; + } + + @JsonIgnore + public String getId() { + return id.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue id() { + return id; + } + + @JsonIgnore + public SessionState getState() { + return state.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_STATE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue state() { + return state; + } + + @JsonIgnore + public Map getParameters() { + return parameters.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_PARAMETERS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> parameters() { + return parameters; + } + + /** Return true if this BatchSessionSummary object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BatchSessionSummaryImpl batchSessionSummary = (BatchSessionSummaryImpl) o; + return Objects.equals(this.id, batchSessionSummary.id) + && Objects.equals(this.state, batchSessionSummary.state) + && Objects.equals(this.parameters, batchSessionSummary.parameters); + } + + @Override + public int hashCode() { + return Objects.hash(id, state, parameters); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class BatchSessionSummaryImpl {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" state: ").append(toIndentedString(state)).append("\n"); + sb.append(" parameters: ").append(toIndentedString(parameters)).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 BatchSessionSummary.Builder { + OptionalValue id = OptionalValue.empty(); + OptionalValue state = OptionalValue.empty(); + OptionalValue> parameters = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_ID) + public Builder setId(String id) { + this.id = OptionalValue.of(id); + return this; + } + + @JsonProperty(JSON_PROPERTY_STATE) + public Builder setState(SessionState state) { + this.state = OptionalValue.of(state); + return this; + } + + @JsonProperty(JSON_PROPERTY_PARAMETERS) + public Builder setParameters(Map parameters) { + this.parameters = OptionalValue.of(parameters); + return this; + } + + public BatchSessionSummary build() { + return new BatchSessionSummaryImpl(id, state, parameters); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchStopResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchStopResponse.java new file mode 100644 index 000000000..0546a2c5b --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchStopResponse.java @@ -0,0 +1,77 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * Response returned when the request to stop processing a batch of call sessions has been received. + */ +@JsonDeserialize(builder = BatchStopResponseImpl.Builder.class) +public interface BatchStopResponse { + + /** State of the batch processing cancellation request */ + public class ResultEnum extends EnumDynamic { + public static final ResultEnum STOP_REQUESTED = new ResultEnum("STOP_REQUESTED"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(ResultEnum.class, ResultEnum::new, Arrays.asList(STOP_REQUESTED)); + + private ResultEnum(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static ResultEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(ResultEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + /** + * State of the batch processing cancellation request. + * + *

STOP_REQUESTED: batch call processing cancellation requested. No new calls will + * be initiated. + * + * @return result + */ + ResultEnum getResult(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new BatchStopResponseImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param result see getter + * @return Current builder + * @see #getResult + */ + Builder setResult(ResultEnum result); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + BatchStopResponse build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchStopResponseImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchStopResponseImpl.java new file mode 100644 index 000000000..79253e50e --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchStopResponseImpl.java @@ -0,0 +1,90 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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({BatchStopResponseImpl.JSON_PROPERTY_RESULT}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class BatchStopResponseImpl implements BatchStopResponse { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_RESULT = "result"; + + private OptionalValue result; + + public BatchStopResponseImpl() {} + + protected BatchStopResponseImpl(OptionalValue result) { + this.result = result; + } + + @JsonIgnore + public ResultEnum getResult() { + return result.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_RESULT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue result() { + return result; + } + + /** Return true if this BatchStopResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BatchStopResponseImpl batchStopResponse = (BatchStopResponseImpl) o; + return Objects.equals(this.result, batchStopResponse.result); + } + + @Override + public int hashCode() { + return Objects.hash(result); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class BatchStopResponseImpl {\n"); + sb.append(" result: ").append(toIndentedString(result)).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 BatchStopResponse.Builder { + OptionalValue result = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_RESULT) + public Builder setResult(ResultEnum result) { + this.result = OptionalValue.of(result); + return this; + } + + public BatchStopResponse build() { + return new BatchStopResponseImpl(result); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSummary.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSummary.java new file mode 100644 index 000000000..f9c8f65c4 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSummary.java @@ -0,0 +1,198 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.time.Instant; + +/** Summary of a batch, including counts of call sessions by state and overall progress. */ +@JsonDeserialize(builder = BatchSummaryImpl.Builder.class) +public interface BatchSummary { + + /** + * Unique identifier of the batch (ULID). + * + *

Field is required + * + * @return batchId + */ + String getBatchId(); + + /** + * Total number of call sessions requested in this batch. + * + *

Field is required + * + * @return sessionCount + */ + Integer getSessionCount(); + + /** + * Timestamp (RFC 3339) when the batch finished processing (all call sessions reached a final + * state). + * + *

Omitted if the batch is still in progress or has not completed yet. + * + * @return endTime + */ + Instant getEndTime(); + + /** + * Number of call sessions that are queued and waiting to be initiated (not yet in progress). + * + *

Field is required + * + * @return queued + */ + Integer getQueued(); + + /** + * Number of call sessions that are in progress. + * + *

Field is required + * + * @return inProgress + */ + Integer getInProgress(); + + /** + * Number of call sessions that have completed successfully. + * + *

Field is required + * + * @return completed + */ + Integer getCompleted(); + + /** + * Number of queued call sessions that were not initiated before the batch TTL ({@link + * #getTtlSeconds()}) elapsed and therefore expired. + * + *

Field is required + * + * @return expired + */ + Integer getExpired(); + + /** + * Batch time-to-live (TTL) in seconds. The maximum amount of time the platform will keep + * attempting to start queued call sessions in this batch. + * + *

When the TTL expires, any call sessions that have not yet been initiated will stop being + * processed (calls already in progress are not affected). + * + * @return ttlSeconds + */ + Integer getTtlSeconds(); + + /** + * Requested maximum call initiation rate, in calls per second (CPS), for this batch. + * + *

Actual CPS may be lower depending on routing, carrier, and platform capacity, as well as + * account limitations. + * + *

Field is required + * + * @return requestedCps + */ + Integer getRequestedCps(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new BatchSummaryImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param batchId see getter + * @return Current builder + * @see #getBatchId + */ + Builder setBatchId(String batchId); + + /** + * see getter + * + * @param sessionCount see getter + * @return Current builder + * @see #getSessionCount + */ + Builder setSessionCount(Integer sessionCount); + + /** + * see getter + * + * @param endTime see getter + * @return Current builder + * @see #getEndTime + */ + Builder setEndTime(Instant endTime); + + /** + * see getter + * + * @param queued see getter + * @return Current builder + * @see #getQueued + */ + Builder setQueued(Integer queued); + + /** + * see getter + * + * @param inProgress see getter + * @return Current builder + * @see #getInProgress + */ + Builder setInProgress(Integer inProgress); + + /** + * see getter + * + * @param completed see getter + * @return Current builder + * @see #getCompleted + */ + Builder setCompleted(Integer completed); + + /** + * see getter + * + * @param expired see getter + * @return Current builder + * @see #getExpired + */ + Builder setExpired(Integer expired); + + /** + * see getter + * + * @param ttlSeconds see getter + * @return Current builder + * @see #getTtlSeconds + */ + Builder setTtlSeconds(Integer ttlSeconds); + + /** + * see getter + * + * @param requestedCps see getter + * @return Current builder + * @see #getRequestedCps + */ + Builder setRequestedCps(Integer requestedCps); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + BatchSummary build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSummaryImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSummaryImpl.java new file mode 100644 index 000000000..74884e9be --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSummaryImpl.java @@ -0,0 +1,328 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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.time.Instant; +import java.util.Objects; + +@JsonPropertyOrder({ + BatchSummaryImpl.JSON_PROPERTY_BATCH_ID, + BatchSummaryImpl.JSON_PROPERTY_SESSION_COUNT, + BatchSummaryImpl.JSON_PROPERTY_END_TIME, + BatchSummaryImpl.JSON_PROPERTY_QUEUED, + BatchSummaryImpl.JSON_PROPERTY_IN_PROGRESS, + BatchSummaryImpl.JSON_PROPERTY_COMPLETED, + BatchSummaryImpl.JSON_PROPERTY_EXPIRED, + BatchSummaryImpl.JSON_PROPERTY_TTL_SECONDS, + BatchSummaryImpl.JSON_PROPERTY_REQUESTED_CPS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class BatchSummaryImpl implements BatchSummary { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_BATCH_ID = "batchId"; + + private OptionalValue batchId; + + public static final String JSON_PROPERTY_SESSION_COUNT = "sessionCount"; + + private OptionalValue sessionCount; + + public static final String JSON_PROPERTY_END_TIME = "endTime"; + + private OptionalValue endTime; + + public static final String JSON_PROPERTY_QUEUED = "queued"; + + private OptionalValue queued; + + public static final String JSON_PROPERTY_IN_PROGRESS = "inProgress"; + + private OptionalValue inProgress; + + public static final String JSON_PROPERTY_COMPLETED = "completed"; + + private OptionalValue completed; + + public static final String JSON_PROPERTY_EXPIRED = "expired"; + + private OptionalValue expired; + + public static final String JSON_PROPERTY_TTL_SECONDS = "ttlSeconds"; + + private OptionalValue ttlSeconds; + + public static final String JSON_PROPERTY_REQUESTED_CPS = "requestedCps"; + + private OptionalValue requestedCps; + + public BatchSummaryImpl() {} + + protected BatchSummaryImpl( + OptionalValue batchId, + OptionalValue sessionCount, + OptionalValue endTime, + OptionalValue queued, + OptionalValue inProgress, + OptionalValue completed, + OptionalValue expired, + OptionalValue ttlSeconds, + OptionalValue requestedCps) { + this.batchId = batchId; + this.sessionCount = sessionCount; + this.endTime = endTime; + this.queued = queued; + this.inProgress = inProgress; + this.completed = completed; + this.expired = expired; + this.ttlSeconds = ttlSeconds; + this.requestedCps = requestedCps; + } + + @JsonIgnore + public String getBatchId() { + return batchId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_BATCH_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue batchId() { + return batchId; + } + + @JsonIgnore + public Integer getSessionCount() { + return sessionCount.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SESSION_COUNT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue sessionCount() { + return sessionCount; + } + + @JsonIgnore + public Instant getEndTime() { + return endTime.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_END_TIME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue endTime() { + return endTime; + } + + @JsonIgnore + public Integer getQueued() { + return queued.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_QUEUED) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue queued() { + return queued; + } + + @JsonIgnore + public Integer getInProgress() { + return inProgress.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_IN_PROGRESS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue inProgress() { + return inProgress; + } + + @JsonIgnore + public Integer getCompleted() { + return completed.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMPLETED) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue completed() { + return completed; + } + + @JsonIgnore + public Integer getExpired() { + return expired.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_EXPIRED) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue expired() { + return expired; + } + + @JsonIgnore + public Integer getTtlSeconds() { + return ttlSeconds.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TTL_SECONDS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue ttlSeconds() { + return ttlSeconds; + } + + @JsonIgnore + public Integer getRequestedCps() { + return requestedCps.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_REQUESTED_CPS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue requestedCps() { + return requestedCps; + } + + /** Return true if this BatchSummary object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BatchSummaryImpl batchSummary = (BatchSummaryImpl) o; + return Objects.equals(this.batchId, batchSummary.batchId) + && Objects.equals(this.sessionCount, batchSummary.sessionCount) + && Objects.equals(this.endTime, batchSummary.endTime) + && Objects.equals(this.queued, batchSummary.queued) + && Objects.equals(this.inProgress, batchSummary.inProgress) + && Objects.equals(this.completed, batchSummary.completed) + && Objects.equals(this.expired, batchSummary.expired) + && Objects.equals(this.ttlSeconds, batchSummary.ttlSeconds) + && Objects.equals(this.requestedCps, batchSummary.requestedCps); + } + + @Override + public int hashCode() { + return Objects.hash( + batchId, + sessionCount, + endTime, + queued, + inProgress, + completed, + expired, + ttlSeconds, + requestedCps); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class BatchSummaryImpl {\n"); + sb.append(" batchId: ").append(toIndentedString(batchId)).append("\n"); + sb.append(" sessionCount: ").append(toIndentedString(sessionCount)).append("\n"); + sb.append(" endTime: ").append(toIndentedString(endTime)).append("\n"); + sb.append(" queued: ").append(toIndentedString(queued)).append("\n"); + sb.append(" inProgress: ").append(toIndentedString(inProgress)).append("\n"); + sb.append(" completed: ").append(toIndentedString(completed)).append("\n"); + sb.append(" expired: ").append(toIndentedString(expired)).append("\n"); + sb.append(" ttlSeconds: ").append(toIndentedString(ttlSeconds)).append("\n"); + sb.append(" requestedCps: ").append(toIndentedString(requestedCps)).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 BatchSummary.Builder { + OptionalValue batchId = OptionalValue.empty(); + OptionalValue sessionCount = OptionalValue.empty(); + OptionalValue endTime = OptionalValue.empty(); + OptionalValue queued = OptionalValue.empty(); + OptionalValue inProgress = OptionalValue.empty(); + OptionalValue completed = OptionalValue.empty(); + OptionalValue expired = OptionalValue.empty(); + OptionalValue ttlSeconds = OptionalValue.empty(); + OptionalValue requestedCps = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_BATCH_ID, required = true) + public Builder setBatchId(String batchId) { + this.batchId = OptionalValue.of(batchId); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_SESSION_COUNT, required = true) + public Builder setSessionCount(Integer sessionCount) { + this.sessionCount = OptionalValue.of(sessionCount); + return this; + } + + @JsonProperty(JSON_PROPERTY_END_TIME) + public Builder setEndTime(Instant endTime) { + this.endTime = OptionalValue.of(endTime); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_QUEUED, required = true) + public Builder setQueued(Integer queued) { + this.queued = OptionalValue.of(queued); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_IN_PROGRESS, required = true) + public Builder setInProgress(Integer inProgress) { + this.inProgress = OptionalValue.of(inProgress); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_COMPLETED, required = true) + public Builder setCompleted(Integer completed) { + this.completed = OptionalValue.of(completed); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_EXPIRED, required = true) + public Builder setExpired(Integer expired) { + this.expired = OptionalValue.of(expired); + return this; + } + + @JsonProperty(JSON_PROPERTY_TTL_SECONDS) + public Builder setTtlSeconds(Integer ttlSeconds) { + this.ttlSeconds = OptionalValue.of(ttlSeconds); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_REQUESTED_CPS, required = true) + public Builder setRequestedCps(Integer requestedCps) { + this.requestedCps = OptionalValue.of(requestedCps); + return this; + } + + public BatchSummary build() { + return new BatchSummaryImpl( + batchId, + sessionCount, + endTime, + queued, + inProgress, + completed, + expired, + ttlSeconds, + requestedCps); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/StartBatchResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/StartBatchResponse.java new file mode 100644 index 000000000..5d39a5cb9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/StartBatchResponse.java @@ -0,0 +1,87 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + * Response returned after starting a batch of outbound calls. + * + *

Contains the ID of the created batch and of the project and service it belongs to. The IDs of + * the individual call sessions are not included: they are part of the batch details. + */ +@JsonDeserialize(builder = StartBatchResponseImpl.Builder.class) +public interface StartBatchResponse { + + /** + * The ID of the project. + * + *

Field is required + * + * @return projectId + */ + String getProjectId(); + + /** + * The ID of the service. + * + *

Field is required + * + * @return serviceId + */ + String getServiceId(); + + /** + * The ID of the batch. + * + *

Field is required + * + * @return batchId + */ + String getBatchId(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StartBatchResponseImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param projectId see getter + * @return Current builder + * @see #getProjectId + */ + Builder setProjectId(String projectId); + + /** + * see getter + * + * @param serviceId see getter + * @return Current builder + * @see #getServiceId + */ + Builder setServiceId(String serviceId); + + /** + * see getter + * + * @param batchId see getter + * @return Current builder + * @see #getBatchId + */ + Builder setBatchId(String batchId); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StartBatchResponse build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/StartBatchResponseImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/StartBatchResponseImpl.java new file mode 100644 index 000000000..6848b55e7 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/batches/response/StartBatchResponseImpl.java @@ -0,0 +1,147 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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({ + StartBatchResponseImpl.JSON_PROPERTY_PROJECT_ID, + StartBatchResponseImpl.JSON_PROPERTY_SERVICE_ID, + StartBatchResponseImpl.JSON_PROPERTY_BATCH_ID +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class StartBatchResponseImpl implements StartBatchResponse { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_PROJECT_ID = "projectId"; + + private OptionalValue projectId; + + public static final String JSON_PROPERTY_SERVICE_ID = "serviceId"; + + private OptionalValue serviceId; + + public static final String JSON_PROPERTY_BATCH_ID = "batchId"; + + private OptionalValue batchId; + + public StartBatchResponseImpl() {} + + protected StartBatchResponseImpl( + OptionalValue projectId, + OptionalValue serviceId, + OptionalValue batchId) { + this.projectId = projectId; + this.serviceId = serviceId; + this.batchId = batchId; + } + + @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 getServiceId() { + return serviceId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SERVICE_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue serviceId() { + return serviceId; + } + + @JsonIgnore + public String getBatchId() { + return batchId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_BATCH_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue batchId() { + return batchId; + } + + /** Return true if this StartBatchResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StartBatchResponseImpl startBatchResponse = (StartBatchResponseImpl) o; + return Objects.equals(this.projectId, startBatchResponse.projectId) + && Objects.equals(this.serviceId, startBatchResponse.serviceId) + && Objects.equals(this.batchId, startBatchResponse.batchId); + } + + @Override + public int hashCode() { + return Objects.hash(projectId, serviceId, batchId); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StartBatchResponseImpl {\n"); + sb.append(" projectId: ").append(toIndentedString(projectId)).append("\n"); + sb.append(" serviceId: ").append(toIndentedString(serviceId)).append("\n"); + sb.append(" batchId: ").append(toIndentedString(batchId)).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 StartBatchResponse.Builder { + OptionalValue projectId = OptionalValue.empty(); + OptionalValue serviceId = OptionalValue.empty(); + OptionalValue batchId = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_PROJECT_ID, required = true) + public Builder setProjectId(String projectId) { + this.projectId = OptionalValue.of(projectId); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_SERVICE_ID, required = true) + public Builder setServiceId(String serviceId) { + this.serviceId = OptionalValue.of(serviceId); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_BATCH_ID, required = true) + public Builder setBatchId(String batchId) { + this.batchId = OptionalValue.of(batchId); + return this; + } + + public StartBatchResponse build() { + return new StartBatchResponseImpl(projectId, serviceId, batchId); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/CallPatchRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/CallPatchRequest.java new file mode 100644 index 000000000..3bd36b73a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/CallPatchRequest.java @@ -0,0 +1,83 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.List; + +/** Request body for patching an ongoing call with SVAML commands. */ +@JsonDeserialize(builder = CallPatchRequestImpl.Builder.class) +public interface CallPatchRequest { + + /** + * An ordered list of SVAML v2 (Sinch Voice Application Markup Language) commands that describe a + * call flow. Commands are executed sequentially in the order they are defined. + * + *

Blocking vs. non-blocking: Some commands block execution until they complete (pause + * , webhook, menu, gotoMenu), while others return + * immediately and run in parallel (dial, messages, amd, + * answer, hangup, startRecording, stopRecording + * , bridgeCall, stopMessages). Each command's description + * specifies its behavior. + * + *

Nesting scope: Commands that appear inside event handlers (e.g., dial.events.onAnswer + * , messages.events.onFinish) form independent sequences and execute in their + * own context — they are not continuations of the parent sequence. + * + *

Field is required + * + * @return commands + */ + List getCommands(); + + /** + * Client-generated idempotency key to safely retry requests. The server uses this key to + * recognize retries of the same request. If a request with the same key is received within 10 + * minutes, the server returns the cached response from the original request. Using a random UUID + * (v4) is strongly recommended. + * + *

Sent as the Idempotency-Key header. If not set, the SDK generates one for the + * call and sends it again on the retries of that call. Set your own key if you need to retry the + * call yourself, since the generated one is not returned. + * + * @return idempotencyKey + */ + String getIdempotencyKey(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new CallPatchRequestImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param commands see getter + * @return Current builder + * @see #getCommands + */ + Builder setCommands(List commands); + + /** + * see getter + * + * @param idempotencyKey see getter + * @return Current builder + * @see #getIdempotencyKey + */ + Builder setIdempotencyKey(String idempotencyKey); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + CallPatchRequest build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/CallPatchRequestImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/CallPatchRequestImpl.java new file mode 100644 index 000000000..26eebdff0 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/CallPatchRequestImpl.java @@ -0,0 +1,115 @@ +package com.sinch.sdk.domains.voice.models.v2.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.v2.svaml.SvamlCommand; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({CallPatchRequestImpl.JSON_PROPERTY_COMMANDS}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class CallPatchRequestImpl implements CallPatchRequest { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_COMMANDS = "commands"; + + private OptionalValue> commands; + + private OptionalValue idempotencyKey; + + public CallPatchRequestImpl() {} + + protected CallPatchRequestImpl( + OptionalValue> commands, OptionalValue idempotencyKey) { + this.commands = commands; + this.idempotencyKey = idempotencyKey; + } + + @JsonIgnore + public List getCommands() { + return commands.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMANDS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> commands() { + return commands; + } + + @JsonIgnore + public String getIdempotencyKey() { + return idempotencyKey.orElse(null); + } + + @JsonIgnore + public OptionalValue idempotencyKey() { + return idempotencyKey; + } + + /** Return true if this CallPatchRequest object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CallPatchRequestImpl callPatchRequest = (CallPatchRequestImpl) o; + return Objects.equals(this.commands, callPatchRequest.commands) + && Objects.equals(this.idempotencyKey, callPatchRequest.idempotencyKey); + } + + @Override + public int hashCode() { + return Objects.hash(commands, idempotencyKey); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class CallPatchRequestImpl {\n"); + sb.append(" commands: ").append(toIndentedString(commands)).append("\n"); + sb.append(" idempotencyKey: ").append(toIndentedString(idempotencyKey)).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 CallPatchRequest.Builder { + OptionalValue> commands = OptionalValue.empty(); + OptionalValue idempotencyKey = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMANDS, required = true) + public Builder setCommands(List commands) { + this.commands = OptionalValue.of(commands); + return this; + } + + @JsonIgnore + public Builder setIdempotencyKey(String idempotencyKey) { + this.idempotencyKey = OptionalValue.of(idempotencyKey); + return this; + } + + public CallPatchRequest build() { + return new CallPatchRequestImpl(commands, idempotencyKey); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/ListCallsQueryParameters.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/ListCallsQueryParameters.java new file mode 100644 index 000000000..7f72232dc --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/ListCallsQueryParameters.java @@ -0,0 +1,213 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.request; + +import com.sinch.sdk.core.models.OptionalValue; +import com.sinch.sdk.domains.voice.models.v2.CallReason; +import com.sinch.sdk.domains.voice.models.v2.CallResult; +import com.sinch.sdk.domains.voice.models.v2.CallType; +import java.time.Instant; + +/** + * Query parameters for listing calls + * + * @since 2.3 + */ +public interface ListCallsQueryParameters { + + /** + * The ID of the service. + * + * @return serviceId + */ + OptionalValue getServiceId(); + + /** + * Only include calls where from matches this origin. For inbound calls, this is the + * caller; for outbound calls, this is the calling party. + * + * @return from + */ + OptionalValue getFrom(); + + /** + * Only include calls where to matches this destination. For inbound calls, this is + * the called party; for outbound calls, this is the callee/recipient. + * + * @return to + */ + OptionalValue getTo(); + + /** + * Only include calls of the specified type. + * + *

If omitted, calls of all types are included. + * + * @return callType + */ + OptionalValue getCallType(); + + /** + * Only include calls that started at or after startTime. + * + * @return startTime + */ + OptionalValue getStartTime(); + + /** + * Only include calls that ended before endTime (exclusive). + * + * @return endTime + */ + OptionalValue getEndTime(); + + /** + * Filter results to only include calls whose callResult matches the specified value. + * + *

If omitted, calls with any result are included. + * + * @return callResult + */ + OptionalValue getCallResult(); + + /** + * Filter results to only include calls whose callReason matches the specified value. + * + *

If omitted, calls with any reason are included. + * + * @return callReason + */ + OptionalValue getCallReason(); + + /** + * Number of items to be returned on each page. minimum: 1 maximum: 100 + * + * @return pageSize + */ + OptionalValue getPageSize(); + + /** + * Page number (1-based) minimum: 1 + * + * @return page + */ + OptionalValue getPage(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new ListCallsQueryParametersImpl.Builder(); + } + + /** + * Getting builder from existing instance + * + * @param parameters Instance to copy the values from + * @return New Builder instance + */ + static Builder builder(ListCallsQueryParameters parameters) { + return new ListCallsQueryParametersImpl.Builder(parameters); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param serviceId see getter + * @return Current builder + * @see #getServiceId + */ + Builder setServiceId(String serviceId); + + /** + * see getter + * + * @param from see getter + * @return Current builder + * @see #getFrom + */ + Builder setFrom(String from); + + /** + * see getter + * + * @param to see getter + * @return Current builder + * @see #getTo + */ + Builder setTo(String to); + + /** + * see getter + * + * @param callType see getter + * @return Current builder + * @see #getCallType + */ + Builder setCallType(CallType callType); + + /** + * see getter + * + * @param startTime see getter + * @return Current builder + * @see #getStartTime + */ + Builder setStartTime(Instant startTime); + + /** + * see getter + * + * @param endTime see getter + * @return Current builder + * @see #getEndTime + */ + Builder setEndTime(Instant endTime); + + /** + * see getter + * + * @param callResult see getter + * @return Current builder + * @see #getCallResult + */ + Builder setCallResult(CallResult callResult); + + /** + * see getter + * + * @param callReason see getter + * @return Current builder + * @see #getCallReason + */ + Builder setCallReason(CallReason callReason); + + /** + * see getter + * + * @param pageSize see getter + * @return Current builder + * @see #getPageSize + */ + Builder setPageSize(Integer pageSize); + + /** + * see getter + * + * @param page see getter + * @return Current builder + * @see #getPage + */ + Builder setPage(Integer page); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + ListCallsQueryParameters build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/ListCallsQueryParametersImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/ListCallsQueryParametersImpl.java new file mode 100644 index 000000000..7694ce03f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/ListCallsQueryParametersImpl.java @@ -0,0 +1,237 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.request; + +import com.sinch.sdk.core.models.OptionalValue; +import com.sinch.sdk.domains.voice.models.v2.CallReason; +import com.sinch.sdk.domains.voice.models.v2.CallResult; +import com.sinch.sdk.domains.voice.models.v2.CallType; +import java.time.Instant; +import java.util.Objects; + +public class ListCallsQueryParametersImpl implements ListCallsQueryParameters { + + private final OptionalValue serviceId; + private final OptionalValue from; + private final OptionalValue to; + private final OptionalValue callType; + private final OptionalValue startTime; + private final OptionalValue endTime; + private final OptionalValue callResult; + private final OptionalValue callReason; + private final OptionalValue pageSize; + private final OptionalValue page; + + private ListCallsQueryParametersImpl( + OptionalValue serviceId, + OptionalValue from, + OptionalValue to, + OptionalValue callType, + OptionalValue startTime, + OptionalValue endTime, + OptionalValue callResult, + OptionalValue callReason, + OptionalValue pageSize, + OptionalValue page) { + this.serviceId = serviceId; + this.from = from; + this.to = to; + this.callType = callType; + this.startTime = startTime; + this.endTime = endTime; + this.callResult = callResult; + this.callReason = callReason; + this.pageSize = pageSize; + this.page = page; + } + + public OptionalValue getServiceId() { + return serviceId; + } + + public OptionalValue getFrom() { + return from; + } + + public OptionalValue getTo() { + return to; + } + + public OptionalValue getCallType() { + return callType; + } + + public OptionalValue getStartTime() { + return startTime; + } + + public OptionalValue getEndTime() { + return endTime; + } + + public OptionalValue getCallResult() { + return callResult; + } + + public OptionalValue getCallReason() { + return callReason; + } + + public OptionalValue getPageSize() { + return pageSize; + } + + public OptionalValue getPage() { + return page; + } + + /** Return true if this ListCallsQueryParameters object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ListCallsQueryParametersImpl parameters = (ListCallsQueryParametersImpl) o; + return Objects.equals(this.serviceId, parameters.serviceId) + && Objects.equals(this.from, parameters.from) + && Objects.equals(this.to, parameters.to) + && Objects.equals(this.callType, parameters.callType) + && Objects.equals(this.startTime, parameters.startTime) + && Objects.equals(this.endTime, parameters.endTime) + && Objects.equals(this.callResult, parameters.callResult) + && Objects.equals(this.callReason, parameters.callReason) + && Objects.equals(this.pageSize, parameters.pageSize) + && Objects.equals(this.page, parameters.page); + } + + @Override + public int hashCode() { + return Objects.hash( + serviceId, from, to, callType, startTime, endTime, callResult, callReason, pageSize, page); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ListCallsQueryParametersImpl {\n"); + sb.append(" serviceId: ").append(toIndentedString(serviceId)).append("\n"); + sb.append(" from: ").append(toIndentedString(from)).append("\n"); + sb.append(" to: ").append(toIndentedString(to)).append("\n"); + sb.append(" callType: ").append(toIndentedString(callType)).append("\n"); + sb.append(" startTime: ").append(toIndentedString(startTime)).append("\n"); + sb.append(" endTime: ").append(toIndentedString(endTime)).append("\n"); + sb.append(" callResult: ").append(toIndentedString(callResult)).append("\n"); + sb.append(" callReason: ").append(toIndentedString(callReason)).append("\n"); + sb.append(" pageSize: ").append(toIndentedString(pageSize)).append("\n"); + sb.append(" page: ").append(toIndentedString(page)).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 ListCallsQueryParameters.Builder { + OptionalValue serviceId = OptionalValue.empty(); + OptionalValue from = OptionalValue.empty(); + OptionalValue to = OptionalValue.empty(); + OptionalValue callType = OptionalValue.empty(); + OptionalValue startTime = OptionalValue.empty(); + OptionalValue endTime = OptionalValue.empty(); + OptionalValue callResult = OptionalValue.empty(); + OptionalValue callReason = OptionalValue.empty(); + OptionalValue pageSize = OptionalValue.empty(); + OptionalValue page = OptionalValue.empty(); + + protected Builder() {} + + protected Builder(ListCallsQueryParameters _parameters) { + if (null == _parameters) { + return; + } + ListCallsQueryParametersImpl parameters = (ListCallsQueryParametersImpl) _parameters; + this.serviceId = parameters.getServiceId(); + this.from = parameters.getFrom(); + this.to = parameters.getTo(); + this.callType = parameters.getCallType(); + this.startTime = parameters.getStartTime(); + this.endTime = parameters.getEndTime(); + this.callResult = parameters.getCallResult(); + this.callReason = parameters.getCallReason(); + this.pageSize = parameters.getPageSize(); + this.page = parameters.getPage(); + } + + public Builder setServiceId(String serviceId) { + this.serviceId = OptionalValue.of(serviceId); + return this; + } + + public Builder setFrom(String from) { + this.from = OptionalValue.of(from); + return this; + } + + public Builder setTo(String to) { + this.to = OptionalValue.of(to); + return this; + } + + public Builder setCallType(CallType callType) { + this.callType = OptionalValue.of(callType); + return this; + } + + public Builder setStartTime(Instant startTime) { + this.startTime = OptionalValue.of(startTime); + return this; + } + + public Builder setEndTime(Instant endTime) { + this.endTime = OptionalValue.of(endTime); + return this; + } + + public Builder setCallResult(CallResult callResult) { + this.callResult = OptionalValue.of(callResult); + return this; + } + + public Builder setCallReason(CallReason callReason) { + this.callReason = OptionalValue.of(callReason); + return this; + } + + public Builder setPageSize(Integer pageSize) { + this.pageSize = OptionalValue.of(pageSize); + return this; + } + + public Builder setPage(Integer page) { + this.page = OptionalValue.of(page); + return this; + } + + public ListCallsQueryParameters build() { + return new ListCallsQueryParametersImpl( + serviceId, + from, + to, + callType, + startTime, + endTime, + callResult, + callReason, + pageSize, + page); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallQueryParameters.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallQueryParameters.java new file mode 100644 index 000000000..d6981fbf0 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallQueryParameters.java @@ -0,0 +1,57 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.request; + +import com.sinch.sdk.core.models.OptionalValue; + +/** + * Query parameters for starting a call + * + * @since 2.3 + */ +public interface StartCallQueryParameters { + + /** + * The ID of the service to use. If omitted, the project's default service is used. + * + * @return serviceId + */ + OptionalValue getServiceId(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StartCallQueryParametersImpl.Builder(); + } + + /** + * Getting builder from existing instance + * + * @param parameters Instance to copy the values from + * @return New Builder instance + */ + static Builder builder(StartCallQueryParameters parameters) { + return new StartCallQueryParametersImpl.Builder(parameters); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param serviceId see getter + * @return Current builder + * @see #getServiceId + */ + Builder setServiceId(String serviceId); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StartCallQueryParameters build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallQueryParametersImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallQueryParametersImpl.java new file mode 100644 index 000000000..154be6dbb --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallQueryParametersImpl.java @@ -0,0 +1,77 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.request; + +import com.sinch.sdk.core.models.OptionalValue; +import java.util.Objects; + +public class StartCallQueryParametersImpl implements StartCallQueryParameters { + + private final OptionalValue serviceId; + + private StartCallQueryParametersImpl(OptionalValue serviceId) { + this.serviceId = serviceId; + } + + public OptionalValue getServiceId() { + return serviceId; + } + + /** Return true if this StartCallQueryParameters object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StartCallQueryParametersImpl parameters = (StartCallQueryParametersImpl) o; + return Objects.equals(this.serviceId, parameters.serviceId); + } + + @Override + public int hashCode() { + return Objects.hash(serviceId); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StartCallQueryParametersImpl {\n"); + sb.append(" serviceId: ").append(toIndentedString(serviceId)).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 StartCallQueryParameters.Builder { + OptionalValue serviceId = OptionalValue.empty(); + + protected Builder() {} + + protected Builder(StartCallQueryParameters _parameters) { + if (null == _parameters) { + return; + } + StartCallQueryParametersImpl parameters = (StartCallQueryParametersImpl) _parameters; + this.serviceId = parameters.getServiceId(); + } + + public Builder setServiceId(String serviceId) { + this.serviceId = OptionalValue.of(serviceId); + return this; + } + + public StartCallQueryParameters build() { + return new StartCallQueryParametersImpl(serviceId); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallRequest.java new file mode 100644 index 000000000..9cfcfd75a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallRequest.java @@ -0,0 +1,83 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.List; + +/** Request to start a single outbound call. */ +@JsonDeserialize(builder = StartCallRequestImpl.Builder.class) +public interface StartCallRequest { + + /** + * An ordered list of SVAML v2 (Sinch Voice Application Markup Language) commands that describe a + * call flow. Commands are executed sequentially in the order they are defined. + * + *

Blocking vs. non-blocking: Some commands block execution until they complete (pause + * , webhook, menu, gotoMenu), while others return + * immediately and run in parallel (dial, messages, amd, + * answer, hangup, startRecording, stopRecording + * , bridgeCall, stopMessages). Each command's description + * specifies its behavior. + * + *

Nesting scope: Commands that appear inside event handlers (e.g., dial.events.onAnswer + * , messages.events.onFinish) form independent sequences and execute in their + * own context — they are not continuations of the parent sequence. + * + *

Field is required + * + * @return commands + */ + List getCommands(); + + /** + * Client-generated idempotency key to safely retry requests. The server uses this key to + * recognize retries of the same request. If a request with the same key is received within 10 + * minutes, the server returns the cached response from the original request. Using a random UUID + * (v4) is strongly recommended. + * + *

Sent as the Idempotency-Key header. If not set, the SDK generates one for the + * call and sends it again on the retries of that call. Set your own key if you need to retry the + * call yourself, since the generated one is not returned. + * + * @return idempotencyKey + */ + String getIdempotencyKey(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StartCallRequestImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param commands see getter + * @return Current builder + * @see #getCommands + */ + Builder setCommands(List commands); + + /** + * see getter + * + * @param idempotencyKey see getter + * @return Current builder + * @see #getIdempotencyKey + */ + Builder setIdempotencyKey(String idempotencyKey); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StartCallRequest build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallRequestImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallRequestImpl.java new file mode 100644 index 000000000..d0ac96bf9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallRequestImpl.java @@ -0,0 +1,115 @@ +package com.sinch.sdk.domains.voice.models.v2.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.v2.svaml.SvamlCommand; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({StartCallRequestImpl.JSON_PROPERTY_COMMANDS}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class StartCallRequestImpl implements StartCallRequest { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_COMMANDS = "commands"; + + private OptionalValue> commands; + + private OptionalValue idempotencyKey; + + public StartCallRequestImpl() {} + + protected StartCallRequestImpl( + OptionalValue> commands, OptionalValue idempotencyKey) { + this.commands = commands; + this.idempotencyKey = idempotencyKey; + } + + @JsonIgnore + public List getCommands() { + return commands.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMANDS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> commands() { + return commands; + } + + @JsonIgnore + public String getIdempotencyKey() { + return idempotencyKey.orElse(null); + } + + @JsonIgnore + public OptionalValue idempotencyKey() { + return idempotencyKey; + } + + /** Return true if this StartCallRequest object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StartCallRequestImpl startCallRequest = (StartCallRequestImpl) o; + return Objects.equals(this.commands, startCallRequest.commands) + && Objects.equals(this.idempotencyKey, startCallRequest.idempotencyKey); + } + + @Override + public int hashCode() { + return Objects.hash(commands, idempotencyKey); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StartCallRequestImpl {\n"); + sb.append(" commands: ").append(toIndentedString(commands)).append("\n"); + sb.append(" idempotencyKey: ").append(toIndentedString(idempotencyKey)).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 StartCallRequest.Builder { + OptionalValue> commands = OptionalValue.empty(); + OptionalValue idempotencyKey = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMANDS, required = true) + public Builder setCommands(List commands) { + this.commands = OptionalValue.of(commands); + return this; + } + + @JsonIgnore + public Builder setIdempotencyKey(String idempotencyKey) { + this.idempotencyKey = OptionalValue.of(idempotencyKey); + return this; + } + + public StartCallRequest build() { + return new StartCallRequestImpl(commands, idempotencyKey); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/CallsListResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/CallsListResponse.java new file mode 100644 index 000000000..f7f69e6a0 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/CallsListResponse.java @@ -0,0 +1,48 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.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.voice.models.v2.Call; +import java.util.Collection; +import java.util.Collections; +import java.util.NoSuchElementException; +import java.util.function.Supplier; + +/** Auto paginated response for list of Call */ +public class CallsListResponse extends ListResponse { + + private final Page page; + private final Supplier supplier; + + public CallsListResponse(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 CallsListResponse 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 "CallsListResponse {" + "page=" + page + '}'; + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/StartCallResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/StartCallResponse.java new file mode 100644 index 000000000..4be5954b4 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/StartCallResponse.java @@ -0,0 +1,86 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + * Response returned after starting a single outbound call. + * + *

Contains the ID of the created call session and of the project and service it belongs to. + */ +@JsonDeserialize(builder = StartCallResponseImpl.Builder.class) +public interface StartCallResponse { + + /** + * The ID of the project. + * + *

Field is required + * + * @return projectId + */ + String getProjectId(); + + /** + * The ID of the service. + * + *

Field is required + * + * @return serviceId + */ + String getServiceId(); + + /** + * The ID of the call session. + * + *

Field is required + * + * @return sessionId + */ + String getSessionId(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StartCallResponseImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param projectId see getter + * @return Current builder + * @see #getProjectId + */ + Builder setProjectId(String projectId); + + /** + * see getter + * + * @param serviceId see getter + * @return Current builder + * @see #getServiceId + */ + Builder setServiceId(String serviceId); + + /** + * see getter + * + * @param sessionId see getter + * @return Current builder + * @see #getSessionId + */ + Builder setSessionId(String sessionId); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StartCallResponse build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/StartCallResponseImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/StartCallResponseImpl.java new file mode 100644 index 000000000..c82fae680 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/StartCallResponseImpl.java @@ -0,0 +1,147 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.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({ + StartCallResponseImpl.JSON_PROPERTY_PROJECT_ID, + StartCallResponseImpl.JSON_PROPERTY_SERVICE_ID, + StartCallResponseImpl.JSON_PROPERTY_SESSION_ID +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class StartCallResponseImpl implements StartCallResponse { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_PROJECT_ID = "projectId"; + + private OptionalValue projectId; + + public static final String JSON_PROPERTY_SERVICE_ID = "serviceId"; + + private OptionalValue serviceId; + + public static final String JSON_PROPERTY_SESSION_ID = "sessionId"; + + private OptionalValue sessionId; + + public StartCallResponseImpl() {} + + protected StartCallResponseImpl( + OptionalValue projectId, + OptionalValue serviceId, + OptionalValue sessionId) { + this.projectId = projectId; + this.serviceId = serviceId; + this.sessionId = sessionId; + } + + @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 getServiceId() { + return serviceId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SERVICE_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue serviceId() { + return serviceId; + } + + @JsonIgnore + public String getSessionId() { + return sessionId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SESSION_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue sessionId() { + return sessionId; + } + + /** Return true if this StartCallResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StartCallResponseImpl startCallResponse = (StartCallResponseImpl) o; + return Objects.equals(this.projectId, startCallResponse.projectId) + && Objects.equals(this.serviceId, startCallResponse.serviceId) + && Objects.equals(this.sessionId, startCallResponse.sessionId); + } + + @Override + public int hashCode() { + return Objects.hash(projectId, serviceId, sessionId); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StartCallResponseImpl {\n"); + sb.append(" projectId: ").append(toIndentedString(projectId)).append("\n"); + sb.append(" serviceId: ").append(toIndentedString(serviceId)).append("\n"); + sb.append(" sessionId: ").append(toIndentedString(sessionId)).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 StartCallResponse.Builder { + OptionalValue projectId = OptionalValue.empty(); + OptionalValue serviceId = OptionalValue.empty(); + OptionalValue sessionId = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_PROJECT_ID, required = true) + public Builder setProjectId(String projectId) { + this.projectId = OptionalValue.of(projectId); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_SERVICE_ID, required = true) + public Builder setServiceId(String serviceId) { + this.serviceId = OptionalValue.of(serviceId); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_SESSION_ID, required = true) + public Builder setSessionId(String sessionId) { + this.sessionId = OptionalValue.of(sessionId); + return this; + } + + public StartCallResponse build() { + return new StartCallResponseImpl(projectId, serviceId, sessionId); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/internal/CallsListResponseInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/internal/CallsListResponseInternal.java new file mode 100644 index 000000000..1ea431d4f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/internal/CallsListResponseInternal.java @@ -0,0 +1,86 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.response.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.Call; +import com.sinch.sdk.domains.voice.models.v2.PaginationLinks; +import com.sinch.sdk.domains.voice.models.v2.PaginationMeta; +import java.util.List; + +/** List of calls. */ +@JsonDeserialize(builder = CallsListResponseInternalImpl.Builder.class) +public interface CallsListResponseInternal { + + /** + * Array of call resources + * + *

Field is required + * + * @return calls + */ + List getCalls(); + + /** + * Get links + * + *

Field is required + * + * @return links + */ + PaginationLinks getLinks(); + + /** + * Get meta + * + *

Field is required + * + * @return meta + */ + PaginationMeta getMeta(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new CallsListResponseInternalImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param calls see getter + * @return Current builder + * @see #getCalls + */ + Builder setCalls(List calls); + + /** + * see getter + * + * @param links see getter + * @return Current builder + * @see #getLinks + */ + Builder setLinks(PaginationLinks links); + + /** + * see getter + * + * @param meta see getter + * @return Current builder + * @see #getMeta + */ + Builder setMeta(PaginationMeta meta); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + CallsListResponseInternal build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/internal/CallsListResponseInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/internal/CallsListResponseInternalImpl.java new file mode 100644 index 000000000..6ae46860b --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/calls/response/internal/CallsListResponseInternalImpl.java @@ -0,0 +1,151 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.response.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.voice.models.v2.Call; +import com.sinch.sdk.domains.voice.models.v2.PaginationLinks; +import com.sinch.sdk.domains.voice.models.v2.PaginationMeta; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + CallsListResponseInternalImpl.JSON_PROPERTY_CALLS, + CallsListResponseInternalImpl.JSON_PROPERTY_LINKS, + CallsListResponseInternalImpl.JSON_PROPERTY_META +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class CallsListResponseInternalImpl implements CallsListResponseInternal { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_CALLS = "calls"; + + private OptionalValue> calls; + + public static final String JSON_PROPERTY_LINKS = "links"; + + private OptionalValue links; + + public static final String JSON_PROPERTY_META = "meta"; + + private OptionalValue meta; + + public CallsListResponseInternalImpl() {} + + protected CallsListResponseInternalImpl( + OptionalValue> calls, + OptionalValue links, + OptionalValue meta) { + this.calls = calls; + this.links = links; + this.meta = meta; + } + + @JsonIgnore + public List getCalls() { + return calls.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALLS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> calls() { + return calls; + } + + @JsonIgnore + public PaginationLinks getLinks() { + return links.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_LINKS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue links() { + return links; + } + + @JsonIgnore + public PaginationMeta getMeta() { + return meta.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_META) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue meta() { + return meta; + } + + /** Return true if this CallsListResponseInternal object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CallsListResponseInternalImpl callsListResponseInternal = (CallsListResponseInternalImpl) o; + return Objects.equals(this.calls, callsListResponseInternal.calls) + && Objects.equals(this.links, callsListResponseInternal.links) + && Objects.equals(this.meta, callsListResponseInternal.meta); + } + + @Override + public int hashCode() { + return Objects.hash(calls, links, meta); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class CallsListResponseInternalImpl {\n"); + sb.append(" calls: ").append(toIndentedString(calls)).append("\n"); + sb.append(" links: ").append(toIndentedString(links)).append("\n"); + sb.append(" meta: ").append(toIndentedString(meta)).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 CallsListResponseInternal.Builder { + OptionalValue> calls = OptionalValue.empty(); + OptionalValue links = OptionalValue.empty(); + OptionalValue meta = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_CALLS, required = true) + public Builder setCalls(List calls) { + this.calls = OptionalValue.of(calls); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_LINKS, required = true) + public Builder setLinks(PaginationLinks links) { + this.links = OptionalValue.of(links); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_META, required = true) + public Builder setMeta(PaginationMeta meta) { + this.meta = OptionalValue.of(meta); + return this; + } + + public CallsListResponseInternal build() { + return new CallsListResponseInternalImpl(calls, links, meta); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallHeader.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallHeader.java new file mode 100644 index 000000000..afe6d5ee9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallHeader.java @@ -0,0 +1,62 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** Custom headers to be sent in the call setup. */ +@JsonDeserialize(builder = CallHeaderImpl.Builder.class) +public interface CallHeader { + + /** + * Name of the header. + * + *

Field is required + * + * @return key + */ + String getKey(); + + /** + * Value of the header. + * + * @return value + */ + String getValue(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new CallHeaderImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param key see getter + * @return Current builder + * @see #getKey + */ + Builder setKey(String key); + + /** + * see getter + * + * @param value see getter + * @return Current builder + * @see #getValue + */ + Builder setValue(String value); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + CallHeader build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallHeaderImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallHeaderImpl.java new file mode 100644 index 000000000..63318c12f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/CallHeaderImpl.java @@ -0,0 +1,114 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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({CallHeaderImpl.JSON_PROPERTY_KEY, CallHeaderImpl.JSON_PROPERTY_VALUE}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class CallHeaderImpl implements CallHeader { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_KEY = "key"; + + private OptionalValue key; + + public static final String JSON_PROPERTY_VALUE = "value"; + + private OptionalValue value; + + public CallHeaderImpl() {} + + protected CallHeaderImpl(OptionalValue key, OptionalValue value) { + this.key = key; + this.value = value; + } + + @JsonIgnore + public String getKey() { + return key.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_KEY) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue key() { + return key; + } + + @JsonIgnore + public String getValue() { + return value.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue value() { + return value; + } + + /** Return true if this CallHeader object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CallHeaderImpl callHeader = (CallHeaderImpl) o; + return Objects.equals(this.key, callHeader.key) && Objects.equals(this.value, callHeader.value); + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class CallHeaderImpl {\n"); + sb.append(" key: ").append(toIndentedString(key)).append("\n"); + sb.append(" value: ").append(toIndentedString(value)).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 CallHeader.Builder { + OptionalValue key = OptionalValue.empty(); + OptionalValue value = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_KEY, required = true) + public Builder setKey(String key) { + this.key = OptionalValue.of(key); + return this; + } + + @JsonProperty(JSON_PROPERTY_VALUE) + public Builder setValue(String value) { + this.value = OptionalValue.of(value); + return this; + } + + public CallHeader build() { + return new CallHeaderImpl(key, value); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/Phone.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/Phone.java new file mode 100644 index 000000000..7c75f055f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/Phone.java @@ -0,0 +1,46 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** Routes the call to a phone number on the Public Switched Telephone Network (PSTN). */ +@JsonDeserialize(builder = PhoneImpl.Builder.class) +public interface Phone extends CallOrigin, CallDestination { + + /** + * E.164 Phone number + * + *

Field is required + * + * @return number + */ + String getNumber(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new PhoneImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * 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 + */ + Phone build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/PhoneImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/PhoneImpl.java new file mode 100644 index 000000000..05cc70ac9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/PhoneImpl.java @@ -0,0 +1,173 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.destination.internal.PhoneInternal; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({PhoneImpl.JSON_PROPERTY_TYPE, PhoneImpl.JSON_PROPERTY_PHONE}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class PhoneImpl implements Phone, CallOrigin, CallDestination { + private static final long serialVersionUID = 1L; + + /** The type property. Must have the value PHONE. */ + public static class TypeEnum extends EnumDynamic { + /** The PHONE type. */ + public static final TypeEnum PHONE = new TypeEnum("PHONE"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(TypeEnum.class, TypeEnum::new, Arrays.asList(PHONE)); + + private TypeEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static TypeEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(TypeEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public static final String JSON_PROPERTY_PHONE = "phone"; + + private OptionalValue phone; + + public PhoneImpl() {} + + protected PhoneImpl(OptionalValue type, OptionalValue phone) { + this.type = type; + this.phone = phone; + } + + @JsonIgnore + public TypeEnum getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue type() { + return type; + } + + @JsonIgnore + public PhoneInternal getPhone() { + return phone.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_PHONE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue phone() { + return phone; + } + + @JsonIgnore + public String getNumber() { + if (null == phone || !phone.isPresent() || null == phone.get().getNumber()) { + return null; + } + return phone.get().getNumber(); + } + + /** Return true if this Phone object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PhoneImpl phone = (PhoneImpl) o; + return Objects.equals(this.type, phone.type) && Objects.equals(this.phone, phone.phone); + } + + @Override + public int hashCode() { + return Objects.hash(type, phone); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class PhoneImpl {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" phone: ").append(toIndentedString(phone)).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 Phone.Builder { + OptionalValue type = OptionalValue.of(TypeEnum.PHONE); + OptionalValue phone = OptionalValue.empty(); + + PhoneInternal.Builder _delegatedBuilder = null; + + @JsonProperty(value = JSON_PROPERTY_TYPE, required = true) + Builder setType(TypeEnum type) { + if (!Objects.equals(type, TypeEnum.PHONE)) { + throw new IllegalArgumentException( + String.format("'type' must be '%s' (is '%s')", TypeEnum.PHONE, type)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_PHONE, required = true) + public Builder setPhone(PhoneInternal phone) { + this.phone = OptionalValue.of(phone); + return this; + } + + @JsonIgnore + public Builder setNumber(String number) { + getDelegatedBuilder().setNumber(number); + return this; + } + + private PhoneInternal.Builder getDelegatedBuilder() { + if (null == _delegatedBuilder) { + this._delegatedBuilder = PhoneInternal.builder(); + } + return this._delegatedBuilder; + } + + public Phone build() { + // delegated builder was used: filling the related source of delegation field + if (null != this._delegatedBuilder) { + this.phone = OptionalValue.of(this._delegatedBuilder.build()); + } + return new PhoneImpl(type, phone); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/Sip.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/Sip.java new file mode 100644 index 000000000..1d446172f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/Sip.java @@ -0,0 +1,116 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.List; + +@JsonDeserialize(builder = SipImpl.Builder.class) +public interface Sip extends CallDestination { + + /** Gets or Sets transport */ + public class TransportEnum extends EnumDynamic { + public static final TransportEnum UDP = new TransportEnum("UDP"); + + public static final TransportEnum TCP = new TransportEnum("TCP"); + + public static final TransportEnum TLS = new TransportEnum("TLS"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + TransportEnum.class, TransportEnum::new, Arrays.asList(UDP, TCP, TLS)); + + private TransportEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static TransportEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(TransportEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + /** + * SIP URI of the destination endpoint. Both sip: (unencrypted) and sips: + * (TLS-encrypted) schemes are supported. + * + *

Field is required + * + * @return endpoint + */ + String getEndpoint(); + + /** + * Transport protocol to use for the SIP signalling channel. + * + *

If omitted, the platform selects a default based on the URI scheme: UDP for + * sip: and TLS for sips:. Setting this explicitly + * overrides that default — for example, to force TCP for a sip: URI or + * to use TLS without switching to the sips: scheme. + * + * @return transport + */ + TransportEnum getTransport(); + + /** + * Custom SIP headers to be sent in the call setup. + * + * @return callHeaders + */ + List getCallHeaders(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SipImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param endpoint see getter + * @return Current builder + * @see #getEndpoint + */ + Builder setEndpoint(String endpoint); + + /** + * see getter + * + * @param transport see getter + * @return Current builder + * @see #getTransport + */ + Builder setTransport(TransportEnum transport); + + /** + * see getter + * + * @param callHeaders see getter + * @return Current builder + * @see #getCallHeaders + */ + Builder setCallHeaders(List callHeaders); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + Sip build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipCallHeader.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipCallHeader.java new file mode 100644 index 000000000..1ca1da21e --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipCallHeader.java @@ -0,0 +1,62 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** Custom SIP headers to be sent in the call setup. */ +@JsonDeserialize(builder = SipCallHeaderImpl.Builder.class) +public interface SipCallHeader { + + /** + * Name of the SIP header. + * + *

Field is required + * + * @return key + */ + String getKey(); + + /** + * Value of the SIP header. + * + * @return value + */ + String getValue(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SipCallHeaderImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param key see getter + * @return Current builder + * @see #getKey + */ + Builder setKey(String key); + + /** + * see getter + * + * @param value see getter + * @return Current builder + * @see #getValue + */ + Builder setValue(String value); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + SipCallHeader build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipCallHeaderImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipCallHeaderImpl.java new file mode 100644 index 000000000..15d5ccff3 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipCallHeaderImpl.java @@ -0,0 +1,115 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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({SipCallHeaderImpl.JSON_PROPERTY_KEY, SipCallHeaderImpl.JSON_PROPERTY_VALUE}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SipCallHeaderImpl implements SipCallHeader { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_KEY = "key"; + + private OptionalValue key; + + public static final String JSON_PROPERTY_VALUE = "value"; + + private OptionalValue value; + + public SipCallHeaderImpl() {} + + protected SipCallHeaderImpl(OptionalValue key, OptionalValue value) { + this.key = key; + this.value = value; + } + + @JsonIgnore + public String getKey() { + return key.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_KEY) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue key() { + return key; + } + + @JsonIgnore + public String getValue() { + return value.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue value() { + return value; + } + + /** Return true if this SipCallHeader object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SipCallHeaderImpl sipCallHeader = (SipCallHeaderImpl) o; + return Objects.equals(this.key, sipCallHeader.key) + && Objects.equals(this.value, sipCallHeader.value); + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SipCallHeaderImpl {\n"); + sb.append(" key: ").append(toIndentedString(key)).append("\n"); + sb.append(" value: ").append(toIndentedString(value)).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 SipCallHeader.Builder { + OptionalValue key = OptionalValue.empty(); + OptionalValue value = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_KEY, required = true) + public Builder setKey(String key) { + this.key = OptionalValue.of(key); + return this; + } + + @JsonProperty(JSON_PROPERTY_VALUE) + public Builder setValue(String value) { + this.value = OptionalValue.of(value); + return this; + } + + public SipCallHeader build() { + return new SipCallHeaderImpl(key, value); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipFrom.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipFrom.java new file mode 100644 index 000000000..2551fe5df --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipFrom.java @@ -0,0 +1,64 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(builder = SipFromImpl.Builder.class) +public interface SipFrom extends CallOrigin { + + /** + * SIP URI of the originating endpoint. Both sip: (unencrypted) and sips: + * (TLS-encrypted) schemes are supported. + * + *

Field is required + * + * @return endpoint + */ + String getEndpoint(); + + /** + * Display name presented to the called party as the caller identity. Transmitted as the display + * name part of the SIP From header (for example, + * Alice <sip:alice@example.com>). + * + * @return displayName + */ + String getDisplayName(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SipFromImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param endpoint see getter + * @return Current builder + * @see #getEndpoint + */ + Builder setEndpoint(String endpoint); + + /** + * see getter + * + * @param displayName see getter + * @return Current builder + * @see #getDisplayName + */ + Builder setDisplayName(String displayName); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + SipFrom build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipFromImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipFromImpl.java new file mode 100644 index 000000000..a70f47cb6 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipFromImpl.java @@ -0,0 +1,187 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.destination.internal.SipFromInternal; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({SipFromImpl.JSON_PROPERTY_TYPE, SipFromImpl.JSON_PROPERTY_SIP}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SipFromImpl implements SipFrom, CallOrigin { + private static final long serialVersionUID = 1L; + + /** The type property. Must have the value SIP. */ + public static class TypeEnum extends EnumDynamic { + /** The SIP type. */ + public static final TypeEnum SIP = new TypeEnum("SIP"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(TypeEnum.class, TypeEnum::new, Arrays.asList(SIP)); + + private TypeEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static TypeEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(TypeEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public static final String JSON_PROPERTY_SIP = "sip"; + + private OptionalValue sip; + + public SipFromImpl() {} + + protected SipFromImpl(OptionalValue type, OptionalValue sip) { + this.type = type; + this.sip = sip; + } + + @JsonIgnore + public TypeEnum getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue type() { + return type; + } + + @JsonIgnore + public SipFromInternal getSip() { + return sip.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SIP) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue sip() { + return sip; + } + + @JsonIgnore + public String getEndpoint() { + if (null == sip || !sip.isPresent() || null == sip.get().getEndpoint()) { + return null; + } + return sip.get().getEndpoint(); + } + + @JsonIgnore + public String getDisplayName() { + if (null == sip || !sip.isPresent() || null == sip.get().getDisplayName()) { + return null; + } + return sip.get().getDisplayName(); + } + + /** Return true if this SipFrom object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SipFromImpl sipFrom = (SipFromImpl) o; + return Objects.equals(this.type, sipFrom.type) && Objects.equals(this.sip, sipFrom.sip); + } + + @Override + public int hashCode() { + return Objects.hash(type, sip); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SipFromImpl {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" sip: ").append(toIndentedString(sip)).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 SipFrom.Builder { + OptionalValue type = OptionalValue.of(TypeEnum.SIP); + OptionalValue sip = OptionalValue.empty(); + + SipFromInternal.Builder _delegatedBuilder = null; + + @JsonProperty(value = JSON_PROPERTY_TYPE, required = true) + Builder setType(TypeEnum type) { + if (!Objects.equals(type, TypeEnum.SIP)) { + throw new IllegalArgumentException( + String.format("'type' must be '%s' (is '%s')", TypeEnum.SIP, type)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_SIP, required = true) + public Builder setSip(SipFromInternal sip) { + this.sip = OptionalValue.of(sip); + return this; + } + + @JsonIgnore + public Builder setEndpoint(String endpoint) { + getDelegatedBuilder().setEndpoint(endpoint); + return this; + } + + @JsonIgnore + public Builder setDisplayName(String displayName) { + getDelegatedBuilder().setDisplayName(displayName); + return this; + } + + private SipFromInternal.Builder getDelegatedBuilder() { + if (null == _delegatedBuilder) { + this._delegatedBuilder = SipFromInternal.builder(); + } + return this._delegatedBuilder; + } + + public SipFrom build() { + // delegated builder was used: filling the related source of delegation field + if (null != this._delegatedBuilder) { + this.sip = OptionalValue.of(this._delegatedBuilder.build()); + } + return new SipFromImpl(type, sip); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipImpl.java new file mode 100644 index 000000000..ed251f53d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/SipImpl.java @@ -0,0 +1,202 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.destination.internal.SipInternal; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({SipImpl.JSON_PROPERTY_TYPE, SipImpl.JSON_PROPERTY_SIP}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SipImpl implements Sip, CallDestination { + private static final long serialVersionUID = 1L; + + /** The type property. Must have the value SIP. */ + public static class TypeEnum extends EnumDynamic { + /** The SIP type. */ + public static final TypeEnum SIP = new TypeEnum("SIP"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(TypeEnum.class, TypeEnum::new, Arrays.asList(SIP)); + + private TypeEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static TypeEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(TypeEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public static final String JSON_PROPERTY_SIP = "sip"; + + private OptionalValue sip; + + public SipImpl() {} + + protected SipImpl(OptionalValue type, OptionalValue sip) { + this.type = type; + this.sip = sip; + } + + @JsonIgnore + public TypeEnum getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue type() { + return type; + } + + @JsonIgnore + public SipInternal getSip() { + return sip.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SIP) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue sip() { + return sip; + } + + @JsonIgnore + public String getEndpoint() { + if (null == sip || !sip.isPresent() || null == sip.get().getEndpoint()) { + return null; + } + return sip.get().getEndpoint(); + } + + @JsonIgnore + public TransportEnum getTransport() { + if (null == sip || !sip.isPresent() || null == sip.get().getTransport()) { + return null; + } + return sip.get().getTransport(); + } + + @JsonIgnore + public List getCallHeaders() { + if (null == sip || !sip.isPresent() || null == sip.get().getCallHeaders()) { + return null; + } + return sip.get().getCallHeaders(); + } + + /** Return true if this Sip object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SipImpl sip = (SipImpl) o; + return Objects.equals(this.type, sip.type) && Objects.equals(this.sip, sip.sip); + } + + @Override + public int hashCode() { + return Objects.hash(type, sip); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SipImpl {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" sip: ").append(toIndentedString(sip)).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 Sip.Builder { + OptionalValue type = OptionalValue.of(TypeEnum.SIP); + OptionalValue sip = OptionalValue.empty(); + + SipInternal.Builder _delegatedBuilder = null; + + @JsonProperty(value = JSON_PROPERTY_TYPE, required = true) + Builder setType(TypeEnum type) { + if (!Objects.equals(type, TypeEnum.SIP)) { + throw new IllegalArgumentException( + String.format("'type' must be '%s' (is '%s')", TypeEnum.SIP, type)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_SIP, required = true) + public Builder setSip(SipInternal sip) { + this.sip = OptionalValue.of(sip); + return this; + } + + @JsonIgnore + public Builder setEndpoint(String endpoint) { + getDelegatedBuilder().setEndpoint(endpoint); + return this; + } + + @JsonIgnore + public Builder setTransport(TransportEnum transport) { + getDelegatedBuilder().setTransport(transport); + return this; + } + + @JsonIgnore + public Builder setCallHeaders(List callHeaders) { + getDelegatedBuilder().setCallHeaders(callHeaders); + return this; + } + + private SipInternal.Builder getDelegatedBuilder() { + if (null == _delegatedBuilder) { + this._delegatedBuilder = SipInternal.builder(); + } + return this._delegatedBuilder; + } + + public Sip build() { + // delegated builder was used: filling the related source of delegation field + if (null != this._delegatedBuilder) { + this.sip = OptionalValue.of(this._delegatedBuilder.build()); + } + return new SipImpl(type, sip); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/Stream.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/Stream.java new file mode 100644 index 000000000..e85be3a57 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/Stream.java @@ -0,0 +1,82 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.List; + +/** Routes the call to a WebSocket stream endpoint for real-time audio processing. */ +@JsonDeserialize(builder = StreamImpl.Builder.class) +public interface Stream extends CallDestination { + + /** + * WebSocket endpoint that will accept the incoming connection for real-time audio streaming. Must + * be a valid WebSocket URL using either ws:// or wss:// (recommended). + * The URL must be reachable from the public internet and capable of handling the negotiated + * stream protocol. + * + *

Field is required + * + * @return endpoint + */ + String getEndpoint(); + + /** + * Get streamOptions + * + * @return streamOptions + */ + StreamOptions getStreamOptions(); + + /** + * Custom headers to be sent in the call setup. + * + * @return callHeaders + */ + List getCallHeaders(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StreamImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param endpoint see getter + * @return Current builder + * @see #getEndpoint + */ + Builder setEndpoint(String endpoint); + + /** + * see getter + * + * @param streamOptions see getter + * @return Current builder + * @see #getStreamOptions + */ + Builder setStreamOptions(StreamOptions streamOptions); + + /** + * see getter + * + * @param callHeaders see getter + * @return Current builder + * @see #getCallHeaders + */ + Builder setCallHeaders(List callHeaders); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + Stream build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/StreamImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/StreamImpl.java new file mode 100644 index 000000000..53b73ca8d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/StreamImpl.java @@ -0,0 +1,202 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.destination.internal.StreamInternal; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({StreamImpl.JSON_PROPERTY_TYPE, StreamImpl.JSON_PROPERTY_STREAM}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class StreamImpl implements Stream, CallDestination { + private static final long serialVersionUID = 1L; + + /** The type property. Must have the value STREAM. */ + public static class TypeEnum extends EnumDynamic { + /** The STREAM type. */ + public static final TypeEnum STREAM = new TypeEnum("STREAM"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(TypeEnum.class, TypeEnum::new, Arrays.asList(STREAM)); + + private TypeEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static TypeEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(TypeEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public static final String JSON_PROPERTY_STREAM = "stream"; + + private OptionalValue stream; + + public StreamImpl() {} + + protected StreamImpl(OptionalValue type, OptionalValue stream) { + this.type = type; + this.stream = stream; + } + + @JsonIgnore + public TypeEnum getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue type() { + return type; + } + + @JsonIgnore + public StreamInternal getStream() { + return stream.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_STREAM) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue stream() { + return stream; + } + + @JsonIgnore + public String getEndpoint() { + if (null == stream || !stream.isPresent() || null == stream.get().getEndpoint()) { + return null; + } + return stream.get().getEndpoint(); + } + + @JsonIgnore + public StreamOptions getStreamOptions() { + if (null == stream || !stream.isPresent() || null == stream.get().getStreamOptions()) { + return null; + } + return stream.get().getStreamOptions(); + } + + @JsonIgnore + public List getCallHeaders() { + if (null == stream || !stream.isPresent() || null == stream.get().getCallHeaders()) { + return null; + } + return stream.get().getCallHeaders(); + } + + /** Return true if this Stream object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StreamImpl stream = (StreamImpl) o; + return Objects.equals(this.type, stream.type) && Objects.equals(this.stream, stream.stream); + } + + @Override + public int hashCode() { + return Objects.hash(type, stream); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StreamImpl {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" stream: ").append(toIndentedString(stream)).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 Stream.Builder { + OptionalValue type = OptionalValue.of(TypeEnum.STREAM); + OptionalValue stream = OptionalValue.empty(); + + StreamInternal.Builder _delegatedBuilder = null; + + @JsonProperty(value = JSON_PROPERTY_TYPE, required = true) + Builder setType(TypeEnum type) { + if (!Objects.equals(type, TypeEnum.STREAM)) { + throw new IllegalArgumentException( + String.format("'type' must be '%s' (is '%s')", TypeEnum.STREAM, type)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_STREAM, required = true) + public Builder setStream(StreamInternal stream) { + this.stream = OptionalValue.of(stream); + return this; + } + + @JsonIgnore + public Builder setEndpoint(String endpoint) { + getDelegatedBuilder().setEndpoint(endpoint); + return this; + } + + @JsonIgnore + public Builder setStreamOptions(StreamOptions streamOptions) { + getDelegatedBuilder().setStreamOptions(streamOptions); + return this; + } + + @JsonIgnore + public Builder setCallHeaders(List callHeaders) { + getDelegatedBuilder().setCallHeaders(callHeaders); + return this; + } + + private StreamInternal.Builder getDelegatedBuilder() { + if (null == _delegatedBuilder) { + this._delegatedBuilder = StreamInternal.builder(); + } + return this._delegatedBuilder; + } + + public Stream build() { + // delegated builder was used: filling the related source of delegation field + if (null != this._delegatedBuilder) { + this.stream = OptionalValue.of(this._delegatedBuilder.build()); + } + return new StreamImpl(type, stream); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/StreamOptions.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/StreamOptions.java new file mode 100644 index 000000000..5178042ed --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/StreamOptions.java @@ -0,0 +1,147 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; + +@JsonDeserialize(builder = StreamOptionsImpl.Builder.class) +public interface StreamOptions { + + /** Gets or Sets codec */ + public class CodecEnum extends EnumDynamic { + public static final CodecEnum PCM = new CodecEnum("PCM"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CodecEnum.class, CodecEnum::new, Arrays.asList(PCM)); + + private CodecEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CodecEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CodecEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + /** Gets or Sets sampleRate */ + public class SampleRateEnum extends EnumDynamic { + public static final SampleRateEnum HZ_8000 = new SampleRateEnum(8000); + + public static final SampleRateEnum HZ_16000 = new SampleRateEnum(16000); + + public static final SampleRateEnum HZ_24000 = new SampleRateEnum(24000); + + public static final SampleRateEnum HZ_44100 = new SampleRateEnum(44100); + + public static final SampleRateEnum HZ_48000 = new SampleRateEnum(48000); + + public static final SampleRateEnum HZ_96000 = new SampleRateEnum(96000); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + SampleRateEnum.class, + SampleRateEnum::new, + Arrays.asList(HZ_8000, HZ_16000, HZ_24000, HZ_44100, HZ_48000, HZ_96000)); + + private SampleRateEnum(Integer value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static SampleRateEnum from(Integer value) { + return ENUM_SUPPORT.from(value); + } + + public static Integer valueOf(SampleRateEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + /** + * Defines the version of the stream protocol. + * + * @return version + */ + Integer getVersion(); + + /** + * Defines the audio codec/format used for the stream audio payload. + * + *

Currently, only PCM is supported (uncompressed raw audio). Use sampleRate + * to configure the sampling rate for the stream. + * + * @return codec + */ + CodecEnum getCodec(); + + /** + * Defines the audio sampling rate (Hz) used for the stream. + * + *

For calls that traverse the PSTN, audio is typically sampled at 8 kHz, so using a higher + * value will not improve perceived quality. Higher sample rates can be useful for non-PSTN + * scenarios (for example, SIP/streaming paths), but will increase bandwidth usage and processing + * load. + * + * @return sampleRate + */ + SampleRateEnum getSampleRate(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StreamOptionsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param version see getter + * @return Current builder + * @see #getVersion + */ + Builder setVersion(Integer version); + + /** + * see getter + * + * @param codec see getter + * @return Current builder + * @see #getCodec + */ + Builder setCodec(CodecEnum codec); + + /** + * see getter + * + * @param sampleRate see getter + * @return Current builder + * @see #getSampleRate + */ + Builder setSampleRate(SampleRateEnum sampleRate); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StreamOptions build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/StreamOptionsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/StreamOptionsImpl.java new file mode 100644 index 000000000..ad02f01bc --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/StreamOptionsImpl.java @@ -0,0 +1,147 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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({ + StreamOptionsImpl.JSON_PROPERTY_VERSION, + StreamOptionsImpl.JSON_PROPERTY_CODEC, + StreamOptionsImpl.JSON_PROPERTY_SAMPLE_RATE +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class StreamOptionsImpl implements StreamOptions { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_VERSION = "version"; + + private OptionalValue version; + + public static final String JSON_PROPERTY_CODEC = "codec"; + + private OptionalValue codec; + + public static final String JSON_PROPERTY_SAMPLE_RATE = "sampleRate"; + + private OptionalValue sampleRate; + + public StreamOptionsImpl() {} + + protected StreamOptionsImpl( + OptionalValue version, + OptionalValue codec, + OptionalValue sampleRate) { + this.version = version; + this.codec = codec; + this.sampleRate = sampleRate; + } + + @JsonIgnore + public Integer getVersion() { + return version.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_VERSION) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue version() { + return version; + } + + @JsonIgnore + public CodecEnum getCodec() { + return codec.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CODEC) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue codec() { + return codec; + } + + @JsonIgnore + public SampleRateEnum getSampleRate() { + return sampleRate.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SAMPLE_RATE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue sampleRate() { + return sampleRate; + } + + /** Return true if this StreamOptions object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StreamOptionsImpl streamOptions = (StreamOptionsImpl) o; + return Objects.equals(this.version, streamOptions.version) + && Objects.equals(this.codec, streamOptions.codec) + && Objects.equals(this.sampleRate, streamOptions.sampleRate); + } + + @Override + public int hashCode() { + return Objects.hash(version, codec, sampleRate); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StreamOptionsImpl {\n"); + sb.append(" version: ").append(toIndentedString(version)).append("\n"); + sb.append(" codec: ").append(toIndentedString(codec)).append("\n"); + sb.append(" sampleRate: ").append(toIndentedString(sampleRate)).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 StreamOptions.Builder { + OptionalValue version = OptionalValue.empty(); + OptionalValue codec = OptionalValue.empty(); + OptionalValue sampleRate = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_VERSION) + public Builder setVersion(Integer version) { + this.version = OptionalValue.of(version); + return this; + } + + @JsonProperty(JSON_PROPERTY_CODEC) + public Builder setCodec(CodecEnum codec) { + this.codec = OptionalValue.of(codec); + return this; + } + + @JsonProperty(JSON_PROPERTY_SAMPLE_RATE) + public Builder setSampleRate(SampleRateEnum sampleRate) { + this.sampleRate = OptionalValue.of(sampleRate); + return this; + } + + public StreamOptions build() { + return new StreamOptionsImpl(version, codec, sampleRate); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/VoiceRelay.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/VoiceRelay.java new file mode 100644 index 000000000..0fd5badc8 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/VoiceRelay.java @@ -0,0 +1,133 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.List; + +/** + * Routes the call through the Voice Relay service, enabling real-time speech-to-text (STT) and + * text-to-speech (TTS) via a WebSocket connection to the application backend. + */ +@JsonDeserialize(builder = VoiceRelayImpl.Builder.class) +public interface VoiceRelay extends CallDestination { + + /** + * URL to the server that will accept the web-socket request + * + *

Field is required + * + * @return endpoint + */ + String getEndpoint(); + + /** + * Allow "barge-in" during text-to-speech (TTS) playback. + * + *

When true, TTS playback is interrupted as soon as inbound speech is detected, + * unless the currently playing content is marked as uninterruptible. + * + *

When false, TTS playback continues uninterrupted, but an interruption signal is + * still sent over the WebSocket so the client application can choose to stop playback manually if + * needed. + * + * @return enableInterruptions + */ + Boolean getEnableInterruptions(); + + /** + * Name of the voice to be used when synthesizing speech. + * + *

This is the default voice used, if no override voice is provided in the web-socket TTS + * message. + * + *

Supported voices include: Emma, Brian, and others. For a complete list of available voices + * and their characteristics, see the Text-to-Speech Voices documentation. + * + *

Field is required + * + * @return ttsVoice + */ + String getTtsVoice(); + + /** + * BCP-47 language tag used for speech-to-text transcription of the inbound audio. + * + *

This value determines which language model is used for transcription. + * + *

Field is required + * + * @return sttLanguage + */ + String getSttLanguage(); + + /** + * Custom headers to be sent in the call setup. + * + * @return callHeaders + */ + List getCallHeaders(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new VoiceRelayImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param endpoint see getter + * @return Current builder + * @see #getEndpoint + */ + Builder setEndpoint(String endpoint); + + /** + * see getter + * + * @param enableInterruptions see getter + * @return Current builder + * @see #getEnableInterruptions + */ + Builder setEnableInterruptions(Boolean enableInterruptions); + + /** + * see getter + * + * @param ttsVoice see getter + * @return Current builder + * @see #getTtsVoice + */ + Builder setTtsVoice(String ttsVoice); + + /** + * see getter + * + * @param sttLanguage see getter + * @return Current builder + * @see #getSttLanguage + */ + Builder setSttLanguage(String sttLanguage); + + /** + * see getter + * + * @param callHeaders see getter + * @return Current builder + * @see #getCallHeaders + */ + Builder setCallHeaders(List callHeaders); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + VoiceRelay build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/VoiceRelayImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/VoiceRelayImpl.java new file mode 100644 index 000000000..48bfea6e4 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/VoiceRelayImpl.java @@ -0,0 +1,238 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.destination.internal.VoiceRelayInternal; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({VoiceRelayImpl.JSON_PROPERTY_TYPE, VoiceRelayImpl.JSON_PROPERTY_VOICE_RELAY}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class VoiceRelayImpl implements VoiceRelay, CallDestination { + private static final long serialVersionUID = 1L; + + /** The type property. Must have the value VOICE_RELAY. */ + public static class TypeEnum extends EnumDynamic { + /** The VOICE_RELAY type. */ + public static final TypeEnum VOICE_RELAY = new TypeEnum("VOICE_RELAY"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(TypeEnum.class, TypeEnum::new, Arrays.asList(VOICE_RELAY)); + + private TypeEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static TypeEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(TypeEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public static final String JSON_PROPERTY_VOICE_RELAY = "voiceRelay"; + + private OptionalValue voiceRelay; + + public VoiceRelayImpl() {} + + protected VoiceRelayImpl( + OptionalValue type, OptionalValue voiceRelay) { + this.type = type; + this.voiceRelay = voiceRelay; + } + + @JsonIgnore + public TypeEnum getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue type() { + return type; + } + + @JsonIgnore + public VoiceRelayInternal getVoiceRelay() { + return voiceRelay.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_VOICE_RELAY) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue voiceRelay() { + return voiceRelay; + } + + @JsonIgnore + public String getEndpoint() { + if (null == voiceRelay || !voiceRelay.isPresent() || null == voiceRelay.get().getEndpoint()) { + return null; + } + return voiceRelay.get().getEndpoint(); + } + + @JsonIgnore + public Boolean getEnableInterruptions() { + if (null == voiceRelay + || !voiceRelay.isPresent() + || null == voiceRelay.get().getEnableInterruptions()) { + return null; + } + return voiceRelay.get().getEnableInterruptions(); + } + + @JsonIgnore + public String getTtsVoice() { + if (null == voiceRelay || !voiceRelay.isPresent() || null == voiceRelay.get().getTtsVoice()) { + return null; + } + return voiceRelay.get().getTtsVoice(); + } + + @JsonIgnore + public String getSttLanguage() { + if (null == voiceRelay + || !voiceRelay.isPresent() + || null == voiceRelay.get().getSttLanguage()) { + return null; + } + return voiceRelay.get().getSttLanguage(); + } + + @JsonIgnore + public List getCallHeaders() { + if (null == voiceRelay + || !voiceRelay.isPresent() + || null == voiceRelay.get().getCallHeaders()) { + return null; + } + return voiceRelay.get().getCallHeaders(); + } + + /** Return true if this VoiceRelay object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VoiceRelayImpl voiceRelay = (VoiceRelayImpl) o; + return Objects.equals(this.type, voiceRelay.type) + && Objects.equals(this.voiceRelay, voiceRelay.voiceRelay); + } + + @Override + public int hashCode() { + return Objects.hash(type, voiceRelay); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class VoiceRelayImpl {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" voiceRelay: ").append(toIndentedString(voiceRelay)).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 VoiceRelay.Builder { + OptionalValue type = OptionalValue.of(TypeEnum.VOICE_RELAY); + OptionalValue voiceRelay = OptionalValue.empty(); + + VoiceRelayInternal.Builder _delegatedBuilder = null; + + @JsonProperty(value = JSON_PROPERTY_TYPE, required = true) + Builder setType(TypeEnum type) { + if (!Objects.equals(type, TypeEnum.VOICE_RELAY)) { + throw new IllegalArgumentException( + String.format("'type' must be '%s' (is '%s')", TypeEnum.VOICE_RELAY, type)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_VOICE_RELAY, required = true) + public Builder setVoiceRelay(VoiceRelayInternal voiceRelay) { + this.voiceRelay = OptionalValue.of(voiceRelay); + return this; + } + + @JsonIgnore + public Builder setEndpoint(String endpoint) { + getDelegatedBuilder().setEndpoint(endpoint); + return this; + } + + @JsonIgnore + public Builder setEnableInterruptions(Boolean enableInterruptions) { + getDelegatedBuilder().setEnableInterruptions(enableInterruptions); + return this; + } + + @JsonIgnore + public Builder setTtsVoice(String ttsVoice) { + getDelegatedBuilder().setTtsVoice(ttsVoice); + return this; + } + + @JsonIgnore + public Builder setSttLanguage(String sttLanguage) { + getDelegatedBuilder().setSttLanguage(sttLanguage); + return this; + } + + @JsonIgnore + public Builder setCallHeaders(List callHeaders) { + getDelegatedBuilder().setCallHeaders(callHeaders); + return this; + } + + private VoiceRelayInternal.Builder getDelegatedBuilder() { + if (null == _delegatedBuilder) { + this._delegatedBuilder = VoiceRelayInternal.builder(); + } + return this._delegatedBuilder; + } + + public VoiceRelay build() { + // delegated builder was used: filling the related source of delegation field + if (null != this._delegatedBuilder) { + this.voiceRelay = OptionalValue.of(this._delegatedBuilder.build()); + } + return new VoiceRelayImpl(type, voiceRelay); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallDestinationInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallDestinationInternal.java new file mode 100644 index 000000000..bb02c8b66 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallDestinationInternal.java @@ -0,0 +1,6 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(using = CallDestinationInternalImpl.CallDestinationInternalImplDeserializer.class) +public interface CallDestinationInternal {} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallDestinationInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallDestinationInternalImpl.java new file mode 100644 index 000000000..2f6b2c989 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallDestinationInternalImpl.java @@ -0,0 +1,182 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.internal; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +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.v2.destination.PhoneImpl; +import com.sinch.sdk.domains.voice.models.v2.destination.SipImpl; +import com.sinch.sdk.domains.voice.models.v2.destination.StreamImpl; +import com.sinch.sdk.domains.voice.models.v2.destination.VoiceRelayImpl; +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 = CallDestinationInternalImpl.CallDestinationInternalImplDeserializer.class) +@JsonSerialize(using = CallDestinationInternalImpl.CallDestinationInternalImplSerializer.class) +public class CallDestinationInternalImpl extends AbstractOpenApiSchema + implements CallDestinationInternal { + private static final Logger log = Logger.getLogger(CallDestinationInternalImpl.class.getName()); + + public static final class CallDestinationInternalImplSerializer + extends StdSerializer { + private static final long serialVersionUID = 1L; + + public CallDestinationInternalImplSerializer(Class t) { + super(t); + } + + public CallDestinationInternalImplSerializer() { + this(null); + } + + @Override + public void serialize( + CallDestinationInternalImpl value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static final class CallDestinationInternalImplDeserializer + extends StdDeserializer { + + private static final long serialVersionUID = 1L; + + public CallDestinationInternalImplDeserializer() { + this(CallDestinationInternalImpl.class); + } + + public CallDestinationInternalImplDeserializer(Class vc) { + super(vc); + } + + @Override + public CallDestinationInternalImpl deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + JsonNode discriminator = tree.get("type"); + String discriminatorValue = null != discriminator ? discriminator.asText() : null; + Class cls = null != discriminatorValue ? mappings.get(discriminatorValue) : null; + if (null == cls) { + throw new IOException( + String.format( + "Failed deserialization for CallDestinationInternalImpl: unknown 'type' value '%s'." + + " Possible values: %s", + discriminatorValue, mappings.keySet())); + } + Object deserialized = tree.traverse(jp.getCodec()).readValueAs(cls); + CallDestinationInternalImpl ret = new CallDestinationInternalImpl(); + ret.setActualInstance(deserialized); + return ret; + } + + /** Handle deserialization of the 'null' value. */ + @Override + public CallDestinationInternalImpl getNullValue(DeserializationContext ctxt) + throws JsonMappingException { + throw new JsonMappingException( + ctxt.getParser(), "CallDestinationInternalImpl cannot be null"); + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap<>(); + + // discriminator value -> class + private static final Map> mappings = new HashMap<>(); + + public CallDestinationInternalImpl() { + super("oneOf", Boolean.FALSE); + } + + static { + schemas.put("PhoneImpl", PhoneImpl.class); + schemas.put("SipImpl", SipImpl.class); + schemas.put("StreamImpl", StreamImpl.class); + schemas.put("VoiceRelayImpl", VoiceRelayImpl.class); + JSONNavigator.registerDescendants( + CallDestinationInternalImpl.class, Collections.unmodifiableMap(schemas)); + // Initialize and register the discriminator mappings. + mappings.put("PHONE", PhoneImpl.class); + mappings.put("SIP", SipImpl.class); + mappings.put("STREAM", StreamImpl.class); + mappings.put("VOICE_RELAY", VoiceRelayImpl.class); + JSONNavigator.registerDiscriminator(CallDestinationInternalImpl.class, "type", mappings); + } + + @Override + public Map> getSchemas() { + return CallDestinationInternalImpl.schemas; + } + + /** + * Set the instance that matches the oneOf child schema, check the instance parameter is valid + * against the oneOf child schemas: PhoneImpl, SipImpl, StreamImpl, VoiceRelayImpl + */ + @Override + public void setActualInstance(Object instance) { + for (Class schema : schemas.values()) { + if (JSONNavigator.isInstanceOf(schema, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + } + throw new RuntimeException( + "Invalid instance type. Must be PhoneImpl, SipImpl, StreamImpl, VoiceRelayImpl"); + } + + /** + * Get the actual instance, which can be the following: PhoneImpl, SipImpl, StreamImpl, + * VoiceRelayImpl + * + * @return The actual instance + */ + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + public static class Deserializer + extends StdDeserializer { + + public Deserializer() { + this(null); + } + + public Deserializer( + Class vc) { + super(vc); + } + + @Override + public com.sinch.sdk.domains.voice.models.v2.destination.CallDestination deserialize( + JsonParser jp, DeserializationContext ctxt) throws IOException { + + Object deserialized = jp.readValueAs(CallDestinationInternalImpl.class).getActualInstance(); + if (null == deserialized) { + return null; + } + if (!(deserialized + instanceof com.sinch.sdk.domains.voice.models.v2.destination.CallDestination)) { + log.log(Level.SEVERE, "Input data does not match schema ", deserialized); + return null; + } + + return (com.sinch.sdk.domains.voice.models.v2.destination.CallDestination) deserialized; + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallOriginInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallOriginInternal.java new file mode 100644 index 000000000..75a1019b9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallOriginInternal.java @@ -0,0 +1,6 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(using = CallOriginInternalImpl.CallOriginInternalImplDeserializer.class) +public interface CallOriginInternal {} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallOriginInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallOriginInternalImpl.java new file mode 100644 index 000000000..01cf8cf58 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/CallOriginInternalImpl.java @@ -0,0 +1,170 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.internal; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +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.v2.destination.PhoneImpl; +import com.sinch.sdk.domains.voice.models.v2.destination.SipFromImpl; +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 = CallOriginInternalImpl.CallOriginInternalImplDeserializer.class) +@JsonSerialize(using = CallOriginInternalImpl.CallOriginInternalImplSerializer.class) +public class CallOriginInternalImpl extends AbstractOpenApiSchema implements CallOriginInternal { + private static final Logger log = Logger.getLogger(CallOriginInternalImpl.class.getName()); + + public static final class CallOriginInternalImplSerializer + extends StdSerializer { + private static final long serialVersionUID = 1L; + + public CallOriginInternalImplSerializer(Class t) { + super(t); + } + + public CallOriginInternalImplSerializer() { + this(null); + } + + @Override + public void serialize( + CallOriginInternalImpl value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static final class CallOriginInternalImplDeserializer + extends StdDeserializer { + + private static final long serialVersionUID = 1L; + + public CallOriginInternalImplDeserializer() { + this(CallOriginInternalImpl.class); + } + + public CallOriginInternalImplDeserializer(Class vc) { + super(vc); + } + + @Override + public CallOriginInternalImpl deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + JsonNode discriminator = tree.get("type"); + String discriminatorValue = null != discriminator ? discriminator.asText() : null; + Class cls = null != discriminatorValue ? mappings.get(discriminatorValue) : null; + if (null == cls) { + throw new IOException( + String.format( + "Failed deserialization for CallOriginInternalImpl: unknown 'type' value '%s'." + + " Possible values: %s", + discriminatorValue, mappings.keySet())); + } + Object deserialized = tree.traverse(jp.getCodec()).readValueAs(cls); + CallOriginInternalImpl ret = new CallOriginInternalImpl(); + ret.setActualInstance(deserialized); + return ret; + } + + /** Handle deserialization of the 'null' value. */ + @Override + public CallOriginInternalImpl getNullValue(DeserializationContext ctxt) + throws JsonMappingException { + throw new JsonMappingException(ctxt.getParser(), "CallOriginInternalImpl cannot be null"); + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap<>(); + + // discriminator value -> class + private static final Map> mappings = new HashMap<>(); + + public CallOriginInternalImpl() { + super("oneOf", Boolean.FALSE); + } + + static { + schemas.put("PhoneImpl", PhoneImpl.class); + schemas.put("SipFromImpl", SipFromImpl.class); + JSONNavigator.registerDescendants( + CallOriginInternalImpl.class, Collections.unmodifiableMap(schemas)); + // Initialize and register the discriminator mappings. + mappings.put("PHONE", PhoneImpl.class); + mappings.put("SIP", SipFromImpl.class); + JSONNavigator.registerDiscriminator(CallOriginInternalImpl.class, "type", mappings); + } + + @Override + public Map> getSchemas() { + return CallOriginInternalImpl.schemas; + } + + /** + * Set the instance that matches the oneOf child schema, check the instance parameter is valid + * against the oneOf child schemas: PhoneImpl, SipFromImpl + */ + @Override + public void setActualInstance(Object instance) { + for (Class schema : schemas.values()) { + if (JSONNavigator.isInstanceOf(schema, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + } + throw new RuntimeException("Invalid instance type. Must be PhoneImpl, SipFromImpl"); + } + + /** + * Get the actual instance, which can be the following: PhoneImpl, SipFromImpl + * + * @return The actual instance + */ + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + public static class Deserializer + extends StdDeserializer { + + public Deserializer() { + this(null); + } + + public Deserializer(Class vc) { + super(vc); + } + + @Override + public com.sinch.sdk.domains.voice.models.v2.destination.CallOrigin deserialize( + JsonParser jp, DeserializationContext ctxt) throws IOException { + + Object deserialized = jp.readValueAs(CallOriginInternalImpl.class).getActualInstance(); + if (null == deserialized) { + return null; + } + if (!(deserialized instanceof com.sinch.sdk.domains.voice.models.v2.destination.CallOrigin)) { + log.log(Level.SEVERE, "Input data does not match schema ", deserialized); + return null; + } + + return (com.sinch.sdk.domains.voice.models.v2.destination.CallOrigin) deserialized; + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/PhoneInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/PhoneInternal.java new file mode 100644 index 000000000..66da045b9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/PhoneInternal.java @@ -0,0 +1,45 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(builder = PhoneInternalImpl.Builder.class) +public interface PhoneInternal { + + /** + * E.164 Phone number + * + *

Field is required + * + * @return number + */ + String getNumber(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new PhoneInternalImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * 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 + */ + PhoneInternal build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/PhoneInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/PhoneInternalImpl.java new file mode 100644 index 000000000..f3f7d6851 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/PhoneInternalImpl.java @@ -0,0 +1,90 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.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 java.util.Objects; + +@JsonPropertyOrder({PhoneInternalImpl.JSON_PROPERTY_NUMBER}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class PhoneInternalImpl implements PhoneInternal { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_NUMBER = "number"; + + private OptionalValue number; + + public PhoneInternalImpl() {} + + protected PhoneInternalImpl(OptionalValue number) { + this.number = number; + } + + @JsonIgnore + public String getNumber() { + return number.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_NUMBER) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue number() { + return number; + } + + /** Return true if this PhoneInternal object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PhoneInternalImpl phoneDetails = (PhoneInternalImpl) o; + return Objects.equals(this.number, phoneDetails.number); + } + + @Override + public int hashCode() { + return Objects.hash(number); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class PhoneInternalImpl {\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 PhoneInternal.Builder { + OptionalValue number = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_NUMBER, required = true) + public Builder setNumber(String number) { + this.number = OptionalValue.of(number); + return this; + } + + public PhoneInternal build() { + return new PhoneInternalImpl(number); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipFromInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipFromInternal.java new file mode 100644 index 000000000..076ef9dff --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipFromInternal.java @@ -0,0 +1,64 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(builder = SipFromInternalImpl.Builder.class) +public interface SipFromInternal { + + /** + * SIP URI of the originating endpoint. Both sip: (unencrypted) and sips: + * (TLS-encrypted) schemes are supported. + * + *

Field is required + * + * @return endpoint + */ + String getEndpoint(); + + /** + * Display name presented to the called party as the caller identity. Transmitted as the display + * name part of the SIP From header (for example, + * Alice <sip:alice@example.com>). + * + * @return displayName + */ + String getDisplayName(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SipFromInternalImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param endpoint see getter + * @return Current builder + * @see #getEndpoint + */ + Builder setEndpoint(String endpoint); + + /** + * see getter + * + * @param displayName see getter + * @return Current builder + * @see #getDisplayName + */ + Builder setDisplayName(String displayName); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + SipFromInternal build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipFromInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipFromInternalImpl.java new file mode 100644 index 000000000..cbee10747 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipFromInternalImpl.java @@ -0,0 +1,118 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.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 java.util.Objects; + +@JsonPropertyOrder({ + SipFromInternalImpl.JSON_PROPERTY_ENDPOINT, + SipFromInternalImpl.JSON_PROPERTY_DISPLAY_NAME +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SipFromInternalImpl implements SipFromInternal { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ENDPOINT = "endpoint"; + + private OptionalValue endpoint; + + public static final String JSON_PROPERTY_DISPLAY_NAME = "displayName"; + + private OptionalValue displayName; + + public SipFromInternalImpl() {} + + protected SipFromInternalImpl(OptionalValue endpoint, OptionalValue displayName) { + this.endpoint = endpoint; + this.displayName = displayName; + } + + @JsonIgnore + public String getEndpoint() { + return endpoint.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ENDPOINT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue endpoint() { + return endpoint; + } + + @JsonIgnore + public String getDisplayName() { + return displayName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DISPLAY_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue displayName() { + return displayName; + } + + /** Return true if this SipFromInternal object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SipFromInternalImpl sipFromDetails = (SipFromInternalImpl) o; + return Objects.equals(this.endpoint, sipFromDetails.endpoint) + && Objects.equals(this.displayName, sipFromDetails.displayName); + } + + @Override + public int hashCode() { + return Objects.hash(endpoint, displayName); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SipFromInternalImpl {\n"); + sb.append(" endpoint: ").append(toIndentedString(endpoint)).append("\n"); + sb.append(" displayName: ").append(toIndentedString(displayName)).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 SipFromInternal.Builder { + OptionalValue endpoint = OptionalValue.empty(); + OptionalValue displayName = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_ENDPOINT, required = true) + public Builder setEndpoint(String endpoint) { + this.endpoint = OptionalValue.of(endpoint); + return this; + } + + @JsonProperty(JSON_PROPERTY_DISPLAY_NAME) + public Builder setDisplayName(String displayName) { + this.displayName = OptionalValue.of(displayName); + return this; + } + + public SipFromInternal build() { + return new SipFromInternalImpl(endpoint, displayName); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipInternal.java new file mode 100644 index 000000000..3534b5f68 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipInternal.java @@ -0,0 +1,86 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.destination.Sip.TransportEnum; +import com.sinch.sdk.domains.voice.models.v2.destination.SipCallHeader; +import java.util.List; + +@JsonDeserialize(builder = SipInternalImpl.Builder.class) +public interface SipInternal { + + /** + * SIP URI of the destination endpoint. Both sip: (unencrypted) and sips: + * (TLS-encrypted) schemes are supported. + * + *

Field is required + * + * @return endpoint + */ + String getEndpoint(); + + /** + * Transport protocol to use for the SIP signalling channel. + * + *

If omitted, the platform selects a default based on the URI scheme: UDP for + * sip: and TLS for sips:. Setting this explicitly + * overrides that default — for example, to force TCP for a sip: URI or + * to use TLS without switching to the sips: scheme. + * + * @return transport + */ + TransportEnum getTransport(); + + /** + * Custom SIP headers to be sent in the call setup. + * + * @return callHeaders + */ + List getCallHeaders(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SipInternalImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param endpoint see getter + * @return Current builder + * @see #getEndpoint + */ + Builder setEndpoint(String endpoint); + + /** + * see getter + * + * @param transport see getter + * @return Current builder + * @see #getTransport + */ + Builder setTransport(TransportEnum transport); + + /** + * see getter + * + * @param callHeaders see getter + * @return Current builder + * @see #getCallHeaders + */ + Builder setCallHeaders(List callHeaders); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + SipInternal build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipInternalImpl.java new file mode 100644 index 000000000..4fb21a3d5 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/SipInternalImpl.java @@ -0,0 +1,150 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.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.voice.models.v2.destination.Sip.TransportEnum; +import com.sinch.sdk.domains.voice.models.v2.destination.SipCallHeader; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + SipInternalImpl.JSON_PROPERTY_ENDPOINT, + SipInternalImpl.JSON_PROPERTY_TRANSPORT, + SipInternalImpl.JSON_PROPERTY_CALL_HEADERS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SipInternalImpl implements SipInternal { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ENDPOINT = "endpoint"; + + private OptionalValue endpoint; + + public static final String JSON_PROPERTY_TRANSPORT = "transport"; + + private OptionalValue transport; + + public static final String JSON_PROPERTY_CALL_HEADERS = "callHeaders"; + + private OptionalValue> callHeaders; + + public SipInternalImpl() {} + + protected SipInternalImpl( + OptionalValue endpoint, + OptionalValue transport, + OptionalValue> callHeaders) { + this.endpoint = endpoint; + this.transport = transport; + this.callHeaders = callHeaders; + } + + @JsonIgnore + public String getEndpoint() { + return endpoint.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ENDPOINT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue endpoint() { + return endpoint; + } + + @JsonIgnore + public TransportEnum getTransport() { + return transport.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TRANSPORT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue transport() { + return transport; + } + + @JsonIgnore + public List getCallHeaders() { + return callHeaders.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_HEADERS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> callHeaders() { + return callHeaders; + } + + /** Return true if this SipInternal object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SipInternalImpl sipDetails = (SipInternalImpl) o; + return Objects.equals(this.endpoint, sipDetails.endpoint) + && Objects.equals(this.transport, sipDetails.transport) + && Objects.equals(this.callHeaders, sipDetails.callHeaders); + } + + @Override + public int hashCode() { + return Objects.hash(endpoint, transport, callHeaders); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SipInternalImpl {\n"); + sb.append(" endpoint: ").append(toIndentedString(endpoint)).append("\n"); + sb.append(" transport: ").append(toIndentedString(transport)).append("\n"); + sb.append(" callHeaders: ").append(toIndentedString(callHeaders)).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 SipInternal.Builder { + OptionalValue endpoint = OptionalValue.empty(); + OptionalValue transport = OptionalValue.empty(); + OptionalValue> callHeaders = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_ENDPOINT, required = true) + public Builder setEndpoint(String endpoint) { + this.endpoint = OptionalValue.of(endpoint); + return this; + } + + @JsonProperty(JSON_PROPERTY_TRANSPORT) + public Builder setTransport(TransportEnum transport) { + this.transport = OptionalValue.of(transport); + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_HEADERS) + public Builder setCallHeaders(List callHeaders) { + this.callHeaders = OptionalValue.of(callHeaders); + return this; + } + + public SipInternal build() { + return new SipInternalImpl(endpoint, transport, callHeaders); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/StreamInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/StreamInternal.java new file mode 100644 index 000000000..9adfc56c8 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/StreamInternal.java @@ -0,0 +1,83 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.destination.CallHeader; +import com.sinch.sdk.domains.voice.models.v2.destination.StreamOptions; +import java.util.List; + +@JsonDeserialize(builder = StreamInternalImpl.Builder.class) +public interface StreamInternal { + + /** + * WebSocket endpoint that will accept the incoming connection for real-time audio streaming. Must + * be a valid WebSocket URL using either ws:// or wss:// (recommended). + * The URL must be reachable from the public internet and capable of handling the negotiated + * stream protocol. + * + *

Field is required + * + * @return endpoint + */ + String getEndpoint(); + + /** + * Get streamOptions + * + * @return streamOptions + */ + StreamOptions getStreamOptions(); + + /** + * Custom headers to be sent in the call setup. + * + * @return callHeaders + */ + List getCallHeaders(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StreamInternalImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param endpoint see getter + * @return Current builder + * @see #getEndpoint + */ + Builder setEndpoint(String endpoint); + + /** + * see getter + * + * @param streamOptions see getter + * @return Current builder + * @see #getStreamOptions + */ + Builder setStreamOptions(StreamOptions streamOptions); + + /** + * see getter + * + * @param callHeaders see getter + * @return Current builder + * @see #getCallHeaders + */ + Builder setCallHeaders(List callHeaders); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StreamInternal build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/StreamInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/StreamInternalImpl.java new file mode 100644 index 000000000..7e745f780 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/StreamInternalImpl.java @@ -0,0 +1,150 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.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.voice.models.v2.destination.CallHeader; +import com.sinch.sdk.domains.voice.models.v2.destination.StreamOptions; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + StreamInternalImpl.JSON_PROPERTY_ENDPOINT, + StreamInternalImpl.JSON_PROPERTY_STREAM_OPTIONS, + StreamInternalImpl.JSON_PROPERTY_CALL_HEADERS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class StreamInternalImpl implements StreamInternal { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ENDPOINT = "endpoint"; + + private OptionalValue endpoint; + + public static final String JSON_PROPERTY_STREAM_OPTIONS = "streamOptions"; + + private OptionalValue streamOptions; + + public static final String JSON_PROPERTY_CALL_HEADERS = "callHeaders"; + + private OptionalValue> callHeaders; + + public StreamInternalImpl() {} + + protected StreamInternalImpl( + OptionalValue endpoint, + OptionalValue streamOptions, + OptionalValue> callHeaders) { + this.endpoint = endpoint; + this.streamOptions = streamOptions; + this.callHeaders = callHeaders; + } + + @JsonIgnore + public String getEndpoint() { + return endpoint.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ENDPOINT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue endpoint() { + return endpoint; + } + + @JsonIgnore + public StreamOptions getStreamOptions() { + return streamOptions.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_STREAM_OPTIONS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue streamOptions() { + return streamOptions; + } + + @JsonIgnore + public List getCallHeaders() { + return callHeaders.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_HEADERS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> callHeaders() { + return callHeaders; + } + + /** Return true if this StreamInternal object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StreamInternalImpl streamDetails = (StreamInternalImpl) o; + return Objects.equals(this.endpoint, streamDetails.endpoint) + && Objects.equals(this.streamOptions, streamDetails.streamOptions) + && Objects.equals(this.callHeaders, streamDetails.callHeaders); + } + + @Override + public int hashCode() { + return Objects.hash(endpoint, streamOptions, callHeaders); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StreamInternalImpl {\n"); + sb.append(" endpoint: ").append(toIndentedString(endpoint)).append("\n"); + sb.append(" streamOptions: ").append(toIndentedString(streamOptions)).append("\n"); + sb.append(" callHeaders: ").append(toIndentedString(callHeaders)).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 StreamInternal.Builder { + OptionalValue endpoint = OptionalValue.empty(); + OptionalValue streamOptions = OptionalValue.empty(); + OptionalValue> callHeaders = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_ENDPOINT, required = true) + public Builder setEndpoint(String endpoint) { + this.endpoint = OptionalValue.of(endpoint); + return this; + } + + @JsonProperty(JSON_PROPERTY_STREAM_OPTIONS) + public Builder setStreamOptions(StreamOptions streamOptions) { + this.streamOptions = OptionalValue.of(streamOptions); + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_HEADERS) + public Builder setCallHeaders(List callHeaders) { + this.callHeaders = OptionalValue.of(callHeaders); + return this; + } + + public StreamInternal build() { + return new StreamInternalImpl(endpoint, streamOptions, callHeaders); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/VoiceRelayInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/VoiceRelayInternal.java new file mode 100644 index 000000000..2cd6f496d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/VoiceRelayInternal.java @@ -0,0 +1,130 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.destination.CallHeader; +import java.util.List; + +@JsonDeserialize(builder = VoiceRelayInternalImpl.Builder.class) +public interface VoiceRelayInternal { + + /** + * URL to the server that will accept the web-socket request + * + *

Field is required + * + * @return endpoint + */ + String getEndpoint(); + + /** + * Allow "barge-in" during text-to-speech (TTS) playback. + * + *

When true, TTS playback is interrupted as soon as inbound speech is detected, + * unless the currently playing content is marked as uninterruptible. + * + *

When false, TTS playback continues uninterrupted, but an interruption signal is + * still sent over the WebSocket so the client application can choose to stop playback manually if + * needed. + * + * @return enableInterruptions + */ + Boolean getEnableInterruptions(); + + /** + * Name of the voice to be used when synthesizing speech. + * + *

This is the default voice used, if no override voice is provided in the web-socket TTS + * message. + * + *

Supported voices include: Emma, Brian, and others. For a complete list of available voices + * and their characteristics, see the Text-to-Speech Voices documentation. + * + *

Field is required + * + * @return ttsVoice + */ + String getTtsVoice(); + + /** + * BCP-47 language tag used for speech-to-text transcription of the inbound audio. + * + *

This value determines which language model is used for transcription. + * + *

Field is required + * + * @return sttLanguage + */ + String getSttLanguage(); + + /** + * Custom headers to be sent in the call setup. + * + * @return callHeaders + */ + List getCallHeaders(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new VoiceRelayInternalImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param endpoint see getter + * @return Current builder + * @see #getEndpoint + */ + Builder setEndpoint(String endpoint); + + /** + * see getter + * + * @param enableInterruptions see getter + * @return Current builder + * @see #getEnableInterruptions + */ + Builder setEnableInterruptions(Boolean enableInterruptions); + + /** + * see getter + * + * @param ttsVoice see getter + * @return Current builder + * @see #getTtsVoice + */ + Builder setTtsVoice(String ttsVoice); + + /** + * see getter + * + * @param sttLanguage see getter + * @return Current builder + * @see #getSttLanguage + */ + Builder setSttLanguage(String sttLanguage); + + /** + * see getter + * + * @param callHeaders see getter + * @return Current builder + * @see #getCallHeaders + */ + Builder setCallHeaders(List callHeaders); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + VoiceRelayInternal build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/VoiceRelayInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/VoiceRelayInternalImpl.java new file mode 100644 index 000000000..682b59437 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/destination/internal/VoiceRelayInternalImpl.java @@ -0,0 +1,206 @@ +package com.sinch.sdk.domains.voice.models.v2.destination.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.voice.models.v2.destination.CallHeader; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + VoiceRelayInternalImpl.JSON_PROPERTY_ENDPOINT, + VoiceRelayInternalImpl.JSON_PROPERTY_ENABLE_INTERRUPTIONS, + VoiceRelayInternalImpl.JSON_PROPERTY_TTS_VOICE, + VoiceRelayInternalImpl.JSON_PROPERTY_STT_LANGUAGE, + VoiceRelayInternalImpl.JSON_PROPERTY_CALL_HEADERS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class VoiceRelayInternalImpl implements VoiceRelayInternal { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ENDPOINT = "endpoint"; + + private OptionalValue endpoint; + + public static final String JSON_PROPERTY_ENABLE_INTERRUPTIONS = "enableInterruptions"; + + private OptionalValue enableInterruptions; + + public static final String JSON_PROPERTY_TTS_VOICE = "ttsVoice"; + + private OptionalValue ttsVoice; + + public static final String JSON_PROPERTY_STT_LANGUAGE = "sttLanguage"; + + private OptionalValue sttLanguage; + + public static final String JSON_PROPERTY_CALL_HEADERS = "callHeaders"; + + private OptionalValue> callHeaders; + + public VoiceRelayInternalImpl() {} + + protected VoiceRelayInternalImpl( + OptionalValue endpoint, + OptionalValue enableInterruptions, + OptionalValue ttsVoice, + OptionalValue sttLanguage, + OptionalValue> callHeaders) { + this.endpoint = endpoint; + this.enableInterruptions = enableInterruptions; + this.ttsVoice = ttsVoice; + this.sttLanguage = sttLanguage; + this.callHeaders = callHeaders; + } + + @JsonIgnore + public String getEndpoint() { + return endpoint.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ENDPOINT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue endpoint() { + return endpoint; + } + + @JsonIgnore + public Boolean getEnableInterruptions() { + return enableInterruptions.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ENABLE_INTERRUPTIONS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue enableInterruptions() { + return enableInterruptions; + } + + @JsonIgnore + public String getTtsVoice() { + return ttsVoice.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TTS_VOICE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue ttsVoice() { + return ttsVoice; + } + + @JsonIgnore + public String getSttLanguage() { + return sttLanguage.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_STT_LANGUAGE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue sttLanguage() { + return sttLanguage; + } + + @JsonIgnore + public List getCallHeaders() { + return callHeaders.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_HEADERS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> callHeaders() { + return callHeaders; + } + + /** Return true if this VoiceRelayInternal object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VoiceRelayInternalImpl voiceRelayDetails = (VoiceRelayInternalImpl) o; + return Objects.equals(this.endpoint, voiceRelayDetails.endpoint) + && Objects.equals(this.enableInterruptions, voiceRelayDetails.enableInterruptions) + && Objects.equals(this.ttsVoice, voiceRelayDetails.ttsVoice) + && Objects.equals(this.sttLanguage, voiceRelayDetails.sttLanguage) + && Objects.equals(this.callHeaders, voiceRelayDetails.callHeaders); + } + + @Override + public int hashCode() { + return Objects.hash(endpoint, enableInterruptions, ttsVoice, sttLanguage, callHeaders); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class VoiceRelayInternalImpl {\n"); + sb.append(" endpoint: ").append(toIndentedString(endpoint)).append("\n"); + sb.append(" enableInterruptions: ") + .append(toIndentedString(enableInterruptions)) + .append("\n"); + sb.append(" ttsVoice: ").append(toIndentedString(ttsVoice)).append("\n"); + sb.append(" sttLanguage: ").append(toIndentedString(sttLanguage)).append("\n"); + sb.append(" callHeaders: ").append(toIndentedString(callHeaders)).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 VoiceRelayInternal.Builder { + OptionalValue endpoint = OptionalValue.empty(); + OptionalValue enableInterruptions = OptionalValue.empty(); + OptionalValue ttsVoice = OptionalValue.empty(); + OptionalValue sttLanguage = OptionalValue.empty(); + OptionalValue> callHeaders = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_ENDPOINT, required = true) + public Builder setEndpoint(String endpoint) { + this.endpoint = OptionalValue.of(endpoint); + return this; + } + + @JsonProperty(JSON_PROPERTY_ENABLE_INTERRUPTIONS) + public Builder setEnableInterruptions(Boolean enableInterruptions) { + this.enableInterruptions = OptionalValue.of(enableInterruptions); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_TTS_VOICE, required = true) + public Builder setTtsVoice(String ttsVoice) { + this.ttsVoice = OptionalValue.of(ttsVoice); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_STT_LANGUAGE, required = true) + public Builder setSttLanguage(String sttLanguage) { + this.sttLanguage = OptionalValue.of(sttLanguage); + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_HEADERS) + public Builder setCallHeaders(List callHeaders) { + this.callHeaders = OptionalValue.of(callHeaders); + return this; + } + + public VoiceRelayInternal build() { + return new VoiceRelayInternalImpl( + endpoint, enableInterruptions, ttsVoice, sttLanguage, callHeaders); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationCallBehavior.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationCallBehavior.java new file mode 100644 index 000000000..9658a0794 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationCallBehavior.java @@ -0,0 +1,51 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + * Calls are handled dynamically by sending Sinch Events to the configured event destination. The + * backend responds with SVAML commands that control the call flow in real time. + * + *

Sent on the wire as the WEBHOOK call behavior. + */ +@JsonDeserialize(builder = EventDestinationCallBehaviorImpl.Builder.class) +public interface EventDestinationCallBehavior extends CallBehavior { + + /** + * Event destination receiving the Sinch Events. + * + *

Field is required + * + * @return eventDestination + */ + EventDestinationConfiguration getEventDestination(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new EventDestinationCallBehaviorImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param eventDestination see getter + * @return Current builder + * @see #getEventDestination + */ + Builder setEventDestination(EventDestinationConfiguration eventDestination); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + EventDestinationCallBehavior build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationCallBehaviorImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationCallBehaviorImpl.java new file mode 100644 index 000000000..4551798c4 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationCallBehaviorImpl.java @@ -0,0 +1,152 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({ + EventDestinationCallBehaviorImpl.JSON_PROPERTY_TYPE, + EventDestinationCallBehaviorImpl.JSON_PROPERTY_WEBHOOK +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class EventDestinationCallBehaviorImpl + implements EventDestinationCallBehavior, CallBehavior { + private static final long serialVersionUID = 1L; + + /** The type property. Must have the value WEBHOOK. */ + public static class TypeEnum extends EnumDynamic { + /** Calls are handled by sending Sinch Events to the configured event destination. */ + public static final TypeEnum WEBHOOK = new TypeEnum("WEBHOOK"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(TypeEnum.class, TypeEnum::new, Arrays.asList(WEBHOOK)); + + private TypeEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static TypeEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(TypeEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public static final String JSON_PROPERTY_WEBHOOK = "webhook"; + + private OptionalValue eventDestination; + + public EventDestinationCallBehaviorImpl() {} + + protected EventDestinationCallBehaviorImpl( + OptionalValue type, OptionalValue eventDestination) { + this.type = type; + this.eventDestination = eventDestination; + } + + @JsonIgnore + public TypeEnum getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue type() { + return type; + } + + @JsonIgnore + public EventDestinationConfiguration getEventDestination() { + return eventDestination.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_WEBHOOK) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue eventDestination() { + return eventDestination; + } + + /** Return true if this EventDestinationCallBehavior object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EventDestinationCallBehaviorImpl eventDestinationCallBehavior = + (EventDestinationCallBehaviorImpl) o; + return Objects.equals(this.type, eventDestinationCallBehavior.type) + && Objects.equals(this.eventDestination, eventDestinationCallBehavior.eventDestination); + } + + @Override + public int hashCode() { + return Objects.hash(type, eventDestination); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class EventDestinationCallBehaviorImpl {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" eventDestination: ").append(toIndentedString(eventDestination)).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 EventDestinationCallBehavior.Builder { + OptionalValue type = OptionalValue.of(TypeEnum.WEBHOOK); + OptionalValue eventDestination = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_TYPE, required = true) + Builder setType(TypeEnum type) { + if (!Objects.equals(type, TypeEnum.WEBHOOK)) { + throw new IllegalArgumentException( + String.format("'type' must be '%s' (is '%s')", TypeEnum.WEBHOOK, type)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_WEBHOOK, required = true) + public Builder setEventDestination(EventDestinationConfiguration eventDestination) { + this.eventDestination = OptionalValue.of(eventDestination); + return this; + } + + public EventDestinationCallBehavior build() { + return new EventDestinationCallBehaviorImpl(type, eventDestination); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationConfiguration.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationConfiguration.java new file mode 100644 index 000000000..9d65e2f93 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationConfiguration.java @@ -0,0 +1,68 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** Event destination configuration */ +@JsonDeserialize(builder = EventDestinationConfigurationImpl.Builder.class) +public interface EventDestinationConfiguration { + + /** + * Event destination URL + * + *

Field is required + * + * @return url + */ + String getUrl(); + + /** + * Fallback event destination URL used when the primary URL fails. + * + *

A failed request is re-sent to this URL immediately. After repeated consecutive failures of + * the primary URL, requests are sent only here until the primary URL recovers. + * + *

See Timeouts and failover in the Webhooks section for the + * authoritative algorithm. + * + * @return fallbackUrl + */ + String getFallbackUrl(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new EventDestinationConfigurationImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param url see getter + * @return Current builder + * @see #getUrl + */ + Builder setUrl(String url); + + /** + * see getter + * + * @param fallbackUrl see getter + * @return Current builder + * @see #getFallbackUrl + */ + Builder setFallbackUrl(String fallbackUrl); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + EventDestinationConfiguration build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationConfigurationImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationConfigurationImpl.java new file mode 100644 index 000000000..d3968f104 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationConfigurationImpl.java @@ -0,0 +1,120 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +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({ + EventDestinationConfigurationImpl.JSON_PROPERTY_URL, + EventDestinationConfigurationImpl.JSON_PROPERTY_FALLBACK_URL +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class EventDestinationConfigurationImpl implements EventDestinationConfiguration { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_URL = "url"; + + private OptionalValue url; + + public static final String JSON_PROPERTY_FALLBACK_URL = "fallbackUrl"; + + private OptionalValue fallbackUrl; + + public EventDestinationConfigurationImpl() {} + + protected EventDestinationConfigurationImpl( + OptionalValue url, OptionalValue fallbackUrl) { + this.url = url; + this.fallbackUrl = fallbackUrl; + } + + @JsonIgnore + public String getUrl() { + return url.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_URL) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue url() { + return url; + } + + @JsonIgnore + public String getFallbackUrl() { + return fallbackUrl.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_FALLBACK_URL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue fallbackUrl() { + return fallbackUrl; + } + + /** Return true if this EventDestinationConfiguration object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EventDestinationConfigurationImpl eventDestinationConfiguration = + (EventDestinationConfigurationImpl) o; + return Objects.equals(this.url, eventDestinationConfiguration.url) + && Objects.equals(this.fallbackUrl, eventDestinationConfiguration.fallbackUrl); + } + + @Override + public int hashCode() { + return Objects.hash(url, fallbackUrl); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class EventDestinationConfigurationImpl {\n"); + sb.append(" url: ").append(toIndentedString(url)).append("\n"); + sb.append(" fallbackUrl: ").append(toIndentedString(fallbackUrl)).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 EventDestinationConfiguration.Builder { + OptionalValue url = OptionalValue.empty(); + OptionalValue fallbackUrl = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_URL, required = true) + public Builder setUrl(String url) { + this.url = OptionalValue.of(url); + return this; + } + + @JsonProperty(JSON_PROPERTY_FALLBACK_URL) + public Builder setFallbackUrl(String fallbackUrl) { + this.fallbackUrl = OptionalValue.of(fallbackUrl); + return this; + } + + public EventDestinationConfiguration build() { + return new EventDestinationConfigurationImpl(url, fallbackUrl); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/NoneCallBehavior.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/NoneCallBehavior.java new file mode 100644 index 000000000..38d16cf6d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/NoneCallBehavior.java @@ -0,0 +1,34 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + * No call behavior is configured for the service. Incoming calls will not be handled and outbound + * calls can still be initiated via the API. + */ +@JsonDeserialize(builder = NoneCallBehaviorImpl.Builder.class) +public interface NoneCallBehavior extends CallBehavior { + + /** Default none call behavior, to be used instead of building an empty one */ + NoneCallBehavior NONE_CALL_BEHAVIOR = NoneCallBehavior.builder().build(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new NoneCallBehaviorImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * Create instance + * + * @return The instance build with current builder values + */ + NoneCallBehavior build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/NoneCallBehaviorImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/NoneCallBehaviorImpl.java new file mode 100644 index 000000000..d48ad3250 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/NoneCallBehaviorImpl.java @@ -0,0 +1,121 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({NoneCallBehaviorImpl.JSON_PROPERTY_TYPE}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class NoneCallBehaviorImpl implements NoneCallBehavior, CallBehavior { + private static final long serialVersionUID = 1L; + + /** The type property. Must have the value NONE. */ + public static class TypeEnum extends EnumDynamic { + /** No call behavior is configured. */ + public static final TypeEnum NONE = new TypeEnum("NONE"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(TypeEnum.class, TypeEnum::new, Arrays.asList(NONE)); + + private TypeEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static TypeEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(TypeEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public NoneCallBehaviorImpl() {} + + protected NoneCallBehaviorImpl(OptionalValue type) { + this.type = type; + } + + @JsonIgnore + public TypeEnum getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue type() { + return type; + } + + /** Return true if this NoneCallBehavior object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NoneCallBehaviorImpl noneCallBehavior = (NoneCallBehaviorImpl) o; + return Objects.equals(this.type, noneCallBehavior.type); + } + + @Override + public int hashCode() { + return Objects.hash(type); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class NoneCallBehaviorImpl {\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 NoneCallBehavior.Builder { + OptionalValue type = OptionalValue.of(TypeEnum.NONE); + + @JsonProperty(value = JSON_PROPERTY_TYPE, required = true) + Builder setType(TypeEnum type) { + if (!Objects.equals(type, TypeEnum.NONE)) { + throw new IllegalArgumentException( + String.format("'type' must be '%s' (is '%s')", TypeEnum.NONE, type)); + } + return this; + } + + public NoneCallBehavior build() { + return new NoneCallBehaviorImpl(type); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/StaticCallBehavior.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/StaticCallBehavior.java new file mode 100644 index 000000000..cfe49807c --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/StaticCallBehavior.java @@ -0,0 +1,50 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlInput; + +/** + * Calls are handled using a predefined static SVAML script. The commands are executed for every + * call on this service, without any backend involvement. + */ +@JsonDeserialize(builder = StaticCallBehaviorImpl.Builder.class) +public interface StaticCallBehavior extends CallBehavior { + + /** + * Predefined SVAML commands executed for every call on this service. + * + *

Field is required + * + * @return static + */ + SvamlInput getStatic(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StaticCallBehaviorImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param _static see getter + * @return Current builder + * @see #getStatic + */ + Builder setStatic(SvamlInput _static); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StaticCallBehavior build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/StaticCallBehaviorImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/StaticCallBehaviorImpl.java new file mode 100644 index 000000000..f6f9cf232 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/StaticCallBehaviorImpl.java @@ -0,0 +1,151 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlInput; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({ + StaticCallBehaviorImpl.JSON_PROPERTY_TYPE, + StaticCallBehaviorImpl.JSON_PROPERTY_STATIC +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class StaticCallBehaviorImpl implements StaticCallBehavior, CallBehavior { + private static final long serialVersionUID = 1L; + + /** The type property. Must have the value STATIC. */ + public static class TypeEnum extends EnumDynamic { + /** Calls are handled using predefined static SVAML commands. */ + public static final TypeEnum STATIC = new TypeEnum("STATIC"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(TypeEnum.class, TypeEnum::new, Arrays.asList(STATIC)); + + private TypeEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static TypeEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(TypeEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public static final String JSON_PROPERTY_STATIC = "static"; + + private OptionalValue _static; + + public StaticCallBehaviorImpl() {} + + protected StaticCallBehaviorImpl( + OptionalValue type, OptionalValue _static) { + this.type = type; + this._static = _static; + } + + @JsonIgnore + public TypeEnum getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue type() { + return type; + } + + @JsonIgnore + public SvamlInput getStatic() { + return _static.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_STATIC) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue _static() { + return _static; + } + + /** Return true if this StaticCallBehavior object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StaticCallBehaviorImpl staticCallBehavior = (StaticCallBehaviorImpl) o; + return Objects.equals(this.type, staticCallBehavior.type) + && Objects.equals(this._static, staticCallBehavior._static); + } + + @Override + public int hashCode() { + return Objects.hash(type, _static); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StaticCallBehaviorImpl {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" _static: ").append(toIndentedString(_static)).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 StaticCallBehavior.Builder { + OptionalValue type = OptionalValue.of(TypeEnum.STATIC); + OptionalValue _static = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_TYPE, required = true) + Builder setType(TypeEnum type) { + if (!Objects.equals(type, TypeEnum.STATIC)) { + throw new IllegalArgumentException( + String.format("'type' must be '%s' (is '%s')", TypeEnum.STATIC, type)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_STATIC, required = true) + public Builder setStatic(SvamlInput _static) { + this._static = OptionalValue.of(_static); + return this; + } + + public StaticCallBehavior build() { + return new StaticCallBehaviorImpl(type, _static); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/internal/CallBehaviorInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/internal/CallBehaviorInternal.java new file mode 100644 index 000000000..0a29fdd4a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/internal/CallBehaviorInternal.java @@ -0,0 +1,6 @@ +package com.sinch.sdk.domains.voice.models.v2.services.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(using = CallBehaviorInternalImpl.CallBehaviorInternalImplDeserializer.class) +public interface CallBehaviorInternal {} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/internal/CallBehaviorInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/internal/CallBehaviorInternalImpl.java new file mode 100644 index 000000000..d249f28aa --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/internal/CallBehaviorInternalImpl.java @@ -0,0 +1,178 @@ +package com.sinch.sdk.domains.voice.models.v2.services.internal; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +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.v2.services.EventDestinationCallBehaviorImpl; +import com.sinch.sdk.domains.voice.models.v2.services.NoneCallBehaviorImpl; +import com.sinch.sdk.domains.voice.models.v2.services.StaticCallBehaviorImpl; +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 = CallBehaviorInternalImpl.CallBehaviorInternalImplDeserializer.class) +@JsonSerialize(using = CallBehaviorInternalImpl.CallBehaviorInternalImplSerializer.class) +public class CallBehaviorInternalImpl extends AbstractOpenApiSchema + implements CallBehaviorInternal { + private static final Logger log = Logger.getLogger(CallBehaviorInternalImpl.class.getName()); + + public static final class CallBehaviorInternalImplSerializer + extends StdSerializer { + private static final long serialVersionUID = 1L; + + public CallBehaviorInternalImplSerializer(Class t) { + super(t); + } + + public CallBehaviorInternalImplSerializer() { + this(null); + } + + @Override + public void serialize( + CallBehaviorInternalImpl value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static final class CallBehaviorInternalImplDeserializer + extends StdDeserializer { + + private static final long serialVersionUID = 1L; + + public CallBehaviorInternalImplDeserializer() { + this(CallBehaviorInternalImpl.class); + } + + public CallBehaviorInternalImplDeserializer(Class vc) { + super(vc); + } + + @Override + public CallBehaviorInternalImpl deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + JsonNode discriminator = tree.get("type"); + String discriminatorValue = null != discriminator ? discriminator.asText() : null; + Class cls = null != discriminatorValue ? mappings.get(discriminatorValue) : null; + if (null == cls) { + throw new IOException( + String.format( + "Failed deserialization for CallBehaviorInternalImpl: unknown 'type' value '%s'." + + " Possible values: %s", + discriminatorValue, mappings.keySet())); + } + Object deserialized = tree.traverse(jp.getCodec()).readValueAs(cls); + CallBehaviorInternalImpl ret = new CallBehaviorInternalImpl(); + ret.setActualInstance(deserialized); + return ret; + } + + /** Handle deserialization of the 'null' value. */ + @Override + public CallBehaviorInternalImpl getNullValue(DeserializationContext ctxt) + throws JsonMappingException { + throw new JsonMappingException(ctxt.getParser(), "CallBehaviorInternalImpl cannot be null"); + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap<>(); + + // discriminator value -> class + private static final Map> mappings = new HashMap<>(); + + public CallBehaviorInternalImpl() { + super("oneOf", Boolean.FALSE); + } + + static { + schemas.put("NoneCallBehaviorImpl", NoneCallBehaviorImpl.class); + schemas.put("EventDestinationCallBehaviorImpl", EventDestinationCallBehaviorImpl.class); + schemas.put("StaticCallBehaviorImpl", StaticCallBehaviorImpl.class); + JSONNavigator.registerDescendants( + CallBehaviorInternalImpl.class, Collections.unmodifiableMap(schemas)); + // Initialize and register the discriminator mappings. + mappings.put("NONE", NoneCallBehaviorImpl.class); + mappings.put("WEBHOOK", EventDestinationCallBehaviorImpl.class); + mappings.put("STATIC", StaticCallBehaviorImpl.class); + JSONNavigator.registerDiscriminator(CallBehaviorInternalImpl.class, "type", mappings); + } + + @Override + public Map> getSchemas() { + return CallBehaviorInternalImpl.schemas; + } + + /** + * Set the instance that matches the oneOf child schema, check the instance parameter is valid + * against the oneOf child schemas: NoneCallBehaviorImpl, EventDestinationCallBehaviorImpl, + * StaticCallBehaviorImpl + */ + @Override + public void setActualInstance(Object instance) { + for (Class schema : schemas.values()) { + if (JSONNavigator.isInstanceOf(schema, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + } + throw new RuntimeException( + "Invalid instance type. Must be NoneCallBehaviorImpl, EventDestinationCallBehaviorImpl," + + " StaticCallBehaviorImpl"); + } + + /** + * Get the actual instance, which can be the following: NoneCallBehaviorImpl, + * EventDestinationCallBehaviorImpl, StaticCallBehaviorImpl + * + * @return The actual instance + */ + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + public static class Deserializer + extends StdDeserializer { + + public Deserializer() { + this(null); + } + + public Deserializer(Class vc) { + super(vc); + } + + @Override + public com.sinch.sdk.domains.voice.models.v2.services.CallBehavior deserialize( + JsonParser jp, DeserializationContext ctxt) throws IOException { + + Object deserialized = jp.readValueAs(CallBehaviorInternalImpl.class).getActualInstance(); + if (null == deserialized) { + return null; + } + if (!(deserialized instanceof com.sinch.sdk.domains.voice.models.v2.services.CallBehavior)) { + log.log(Level.SEVERE, "Input data does not match schema ", deserialized); + return null; + } + + return (com.sinch.sdk.domains.voice.models.v2.services.CallBehavior) deserialized; + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/CreateServiceRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/CreateServiceRequest.java new file mode 100644 index 000000000..04c31785a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/CreateServiceRequest.java @@ -0,0 +1,119 @@ +package com.sinch.sdk.domains.voice.models.v2.services.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.services.CallBehavior; + +/** Request body for creating a new voice service. */ +@JsonDeserialize(builder = CreateServiceRequestImpl.Builder.class) +public interface CreateServiceRequest { + + /** + * Voice service name. No leading or trailing whitespaces allowed. + * + *

Field is required + * + * @return name + */ + String getName(); + + /** + * Description of the service + * + * @return description + */ + String getDescription(); + + /** + * Indicates whether this service is set as the default for the project. The default service is + * used when no specific service is specified in API requests. + * + * @return isDefault + */ + Boolean getIsDefault(); + + /** + * Defines how calls are handled for this service + * + * @return callBehavior + */ + CallBehavior getCallBehavior(); + + /** + * Client-generated idempotency key to safely retry requests. The server uses this key to + * recognize retries of the same request. If a request with the same key is received within 10 + * minutes, the server returns the cached response from the original request. Using a random UUID + * (v4) is strongly recommended. + * + *

Sent as the Idempotency-Key header. If not set, the SDK generates one for the + * call and sends it again on the retries of that call. Set your own key if you need to retry the + * call yourself, since the generated one is not returned. + * + * @return idempotencyKey + */ + String getIdempotencyKey(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new CreateServiceRequestImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param name see getter + * @return Current builder + * @see #getName + */ + Builder setName(String name); + + /** + * see getter + * + * @param description see getter + * @return Current builder + * @see #getDescription + */ + Builder setDescription(String description); + + /** + * see getter + * + * @param isDefault see getter + * @return Current builder + * @see #getIsDefault + */ + Builder setIsDefault(Boolean isDefault); + + /** + * see getter + * + * @param callBehavior see getter + * @return Current builder + * @see #getCallBehavior + */ + Builder setCallBehavior(CallBehavior callBehavior); + + /** + * see getter + * + * @param idempotencyKey see getter + * @return Current builder + * @see #getIdempotencyKey + */ + Builder setIdempotencyKey(String idempotencyKey); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + CreateServiceRequest build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/CreateServiceRequestImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/CreateServiceRequestImpl.java new file mode 100644 index 000000000..22c590f5d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/CreateServiceRequestImpl.java @@ -0,0 +1,199 @@ +package com.sinch.sdk.domains.voice.models.v2.services.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.v2.services.CallBehavior; +import java.util.Objects; + +@JsonPropertyOrder({ + CreateServiceRequestImpl.JSON_PROPERTY_NAME, + CreateServiceRequestImpl.JSON_PROPERTY_DESCRIPTION, + CreateServiceRequestImpl.JSON_PROPERTY_IS_DEFAULT, + CreateServiceRequestImpl.JSON_PROPERTY_CALL_BEHAVIOR +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class CreateServiceRequestImpl implements CreateServiceRequest { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_NAME = "name"; + + private OptionalValue name; + + public static final String JSON_PROPERTY_DESCRIPTION = "description"; + + private OptionalValue description; + + public static final String JSON_PROPERTY_IS_DEFAULT = "isDefault"; + + private OptionalValue isDefault; + + public static final String JSON_PROPERTY_CALL_BEHAVIOR = "callBehavior"; + + private OptionalValue callBehavior; + + private OptionalValue idempotencyKey; + + public CreateServiceRequestImpl() {} + + protected CreateServiceRequestImpl( + OptionalValue name, + OptionalValue description, + OptionalValue isDefault, + OptionalValue callBehavior, + OptionalValue idempotencyKey) { + this.name = name; + this.description = description; + this.isDefault = isDefault; + this.callBehavior = callBehavior; + this.idempotencyKey = idempotencyKey; + } + + @JsonIgnore + public String getName() { + return name.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_NAME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue name() { + return name; + } + + @JsonIgnore + public String getDescription() { + return description.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DESCRIPTION) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue description() { + return description; + } + + @JsonIgnore + public Boolean getIsDefault() { + return isDefault.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_IS_DEFAULT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue isDefault() { + return isDefault; + } + + @JsonIgnore + public CallBehavior getCallBehavior() { + return callBehavior.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_BEHAVIOR) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue callBehavior() { + return callBehavior; + } + + @JsonIgnore + public String getIdempotencyKey() { + return idempotencyKey.orElse(null); + } + + @JsonIgnore + public OptionalValue idempotencyKey() { + return idempotencyKey; + } + + /** Return true if this CreateServiceRequest object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CreateServiceRequestImpl createServiceRequest = (CreateServiceRequestImpl) o; + return Objects.equals(this.name, createServiceRequest.name) + && Objects.equals(this.description, createServiceRequest.description) + && Objects.equals(this.isDefault, createServiceRequest.isDefault) + && Objects.equals(this.callBehavior, createServiceRequest.callBehavior) + && Objects.equals(this.idempotencyKey, createServiceRequest.idempotencyKey); + } + + @Override + public int hashCode() { + return Objects.hash(name, description, isDefault, callBehavior, idempotencyKey); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class CreateServiceRequestImpl {\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" description: ").append(toIndentedString(description)).append("\n"); + sb.append(" isDefault: ").append(toIndentedString(isDefault)).append("\n"); + sb.append(" callBehavior: ").append(toIndentedString(callBehavior)).append("\n"); + sb.append(" idempotencyKey: ").append(toIndentedString(idempotencyKey)).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 CreateServiceRequest.Builder { + OptionalValue name = OptionalValue.empty(); + OptionalValue description = OptionalValue.empty(); + OptionalValue isDefault = OptionalValue.empty(); + OptionalValue callBehavior = OptionalValue.empty(); + OptionalValue idempotencyKey = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_NAME, required = true) + public Builder setName(String name) { + this.name = OptionalValue.of(name); + return this; + } + + @JsonProperty(JSON_PROPERTY_DESCRIPTION) + public Builder setDescription(String description) { + this.description = OptionalValue.of(description); + return this; + } + + @JsonProperty(JSON_PROPERTY_IS_DEFAULT) + public Builder setIsDefault(Boolean isDefault) { + this.isDefault = OptionalValue.of(isDefault); + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_BEHAVIOR) + public Builder setCallBehavior(CallBehavior callBehavior) { + this.callBehavior = OptionalValue.of(callBehavior); + return this; + } + + @JsonIgnore + public Builder setIdempotencyKey(String idempotencyKey) { + this.idempotencyKey = OptionalValue.of(idempotencyKey); + return this; + } + + public CreateServiceRequest build() { + return new CreateServiceRequestImpl( + name, description, isDefault, callBehavior, idempotencyKey); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/ListServicesQueryParameters.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/ListServicesQueryParameters.java new file mode 100644 index 000000000..51b58788d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/ListServicesQueryParameters.java @@ -0,0 +1,106 @@ +package com.sinch.sdk.domains.voice.models.v2.services.request; + +import com.sinch.sdk.core.models.OptionalValue; + +/** + * Query parameters for listing services + * + * @since 2.3 + */ +public interface ListServicesQueryParameters { + + /** + * Filter services by name or description. Returns all services where either the name or + * description contains the specified value (case-insensitive partial match). + * + * @return filter + */ + OptionalValue getFilter(); + + /** + * Return the default service only. + * + * @return isDefault + */ + OptionalValue getIsDefault(); + + /** + * Number of items to be returned on each page. minimum: 1 maximum: 100 + * + * @return pageSize + */ + OptionalValue getPageSize(); + + /** + * Page number (1-based) minimum: 1 + * + * @return page + */ + OptionalValue getPage(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new ListServicesQueryParametersImpl.Builder(); + } + + /** + * Getting builder from existing instance + * + * @param parameters Instance to copy the values from + * @return New Builder instance + */ + static Builder builder(ListServicesQueryParameters parameters) { + return new ListServicesQueryParametersImpl.Builder(parameters); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param filter see getter + * @return Current builder + * @see #getFilter + */ + Builder setFilter(String filter); + + /** + * see getter + * + * @param isDefault see getter + * @return Current builder + * @see #getIsDefault + */ + Builder setIsDefault(Boolean isDefault); + + /** + * see getter + * + * @param pageSize see getter + * @return Current builder + * @see #getPageSize + */ + Builder setPageSize(Integer pageSize); + + /** + * see getter + * + * @param page see getter + * @return Current builder + * @see #getPage + */ + Builder setPage(Integer page); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + ListServicesQueryParameters build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/ListServicesQueryParametersImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/ListServicesQueryParametersImpl.java new file mode 100644 index 000000000..276910c6a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/ListServicesQueryParametersImpl.java @@ -0,0 +1,126 @@ +package com.sinch.sdk.domains.voice.models.v2.services.request; + +import com.sinch.sdk.core.models.OptionalValue; +import java.util.Objects; + +public class ListServicesQueryParametersImpl implements ListServicesQueryParameters { + + private final OptionalValue filter; + private final OptionalValue isDefault; + private final OptionalValue pageSize; + private final OptionalValue page; + + private ListServicesQueryParametersImpl( + OptionalValue filter, + OptionalValue isDefault, + OptionalValue pageSize, + OptionalValue page) { + this.filter = filter; + this.isDefault = isDefault; + this.pageSize = pageSize; + this.page = page; + } + + public OptionalValue getFilter() { + return filter; + } + + public OptionalValue getIsDefault() { + return isDefault; + } + + public OptionalValue getPageSize() { + return pageSize; + } + + public OptionalValue getPage() { + return page; + } + + /** Return true if this ListServicesQueryParameters object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ListServicesQueryParametersImpl parameters = (ListServicesQueryParametersImpl) o; + return Objects.equals(this.filter, parameters.filter) + && Objects.equals(this.isDefault, parameters.isDefault) + && Objects.equals(this.pageSize, parameters.pageSize) + && Objects.equals(this.page, parameters.page); + } + + @Override + public int hashCode() { + return Objects.hash(filter, isDefault, pageSize, page); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ListServicesQueryParametersImpl {\n"); + sb.append(" filter: ").append(toIndentedString(filter)).append("\n"); + sb.append(" isDefault: ").append(toIndentedString(isDefault)).append("\n"); + sb.append(" pageSize: ").append(toIndentedString(pageSize)).append("\n"); + sb.append(" page: ").append(toIndentedString(page)).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 ListServicesQueryParameters.Builder { + OptionalValue filter = OptionalValue.empty(); + OptionalValue isDefault = OptionalValue.empty(); + OptionalValue pageSize = OptionalValue.empty(); + OptionalValue page = OptionalValue.empty(); + + protected Builder() {} + + protected Builder(ListServicesQueryParameters _parameters) { + if (null == _parameters) { + return; + } + ListServicesQueryParametersImpl parameters = (ListServicesQueryParametersImpl) _parameters; + this.filter = parameters.getFilter(); + this.isDefault = parameters.getIsDefault(); + this.pageSize = parameters.getPageSize(); + this.page = parameters.getPage(); + } + + public Builder setFilter(String filter) { + this.filter = OptionalValue.of(filter); + return this; + } + + public Builder setIsDefault(Boolean isDefault) { + this.isDefault = OptionalValue.of(isDefault); + return this; + } + + public Builder setPageSize(Integer pageSize) { + this.pageSize = OptionalValue.of(pageSize); + return this; + } + + public Builder setPage(Integer page) { + this.page = OptionalValue.of(page); + return this; + } + + public ListServicesQueryParameters build() { + return new ListServicesQueryParametersImpl(filter, isDefault, pageSize, page); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/UpdateServiceRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/UpdateServiceRequest.java new file mode 100644 index 000000000..503118bc5 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/UpdateServiceRequest.java @@ -0,0 +1,125 @@ +package com.sinch.sdk.domains.voice.models.v2.services.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.services.CallBehavior; + +/** + * Request body for updating an existing voice service. Only the fields provided will be modified; + * omitted fields remain unchanged. + */ +@JsonDeserialize(builder = UpdateServiceRequestImpl.Builder.class) +public interface UpdateServiceRequest { + + /** + * Voice service name. No leading or trailing whitespaces allowed. + * + * @return name + */ + String getName(); + + /** + * Description of the service + * + * @return description + */ + String getDescription(); + + /** + * Indicates whether this service is set as the default for the project. The default service is + * used when no specific service is specified in API requests. + * + *

Each project can have only one default service: setting a new default removes the default + * status from the previously designated service. This property cannot be set to false + * : in such case, a BadRequest will be returned by the API. + * + * @return isDefault + */ + Boolean getIsDefault(); + + /** + * Defines how calls are handled for this service + * + * @return callBehavior + */ + CallBehavior getCallBehavior(); + + /** + * Client-generated idempotency key to safely retry requests. The server uses this key to + * recognize retries of the same request. If a request with the same key is received within 10 + * minutes, the server returns the cached response from the original request. Using a random UUID + * (v4) is strongly recommended. + * + *

Sent as the Idempotency-Key header. If not set, the SDK generates one for the + * call and sends it again on the retries of that call. Set your own key if you need to retry the + * call yourself, since the generated one is not returned. + * + * @return idempotencyKey + */ + String getIdempotencyKey(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new UpdateServiceRequestImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param name see getter + * @return Current builder + * @see #getName + */ + Builder setName(String name); + + /** + * see getter + * + * @param description see getter + * @return Current builder + * @see #getDescription + */ + Builder setDescription(String description); + + /** + * Set this service as the default for the project + * + *

The API does not accept false on update, so there is no value to pass + * + * @return Current builder + * @see #getIsDefault + */ + Builder setAsDefault(); + + /** + * see getter + * + * @param callBehavior see getter + * @return Current builder + * @see #getCallBehavior + */ + Builder setCallBehavior(CallBehavior callBehavior); + + /** + * see getter + * + * @param idempotencyKey see getter + * @return Current builder + * @see #getIdempotencyKey + */ + Builder setIdempotencyKey(String idempotencyKey); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + UpdateServiceRequest build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/UpdateServiceRequestImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/UpdateServiceRequestImpl.java new file mode 100644 index 000000000..ef214fe66 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/request/UpdateServiceRequestImpl.java @@ -0,0 +1,204 @@ +package com.sinch.sdk.domains.voice.models.v2.services.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.v2.services.CallBehavior; +import java.util.Objects; + +@JsonPropertyOrder({ + UpdateServiceRequestImpl.JSON_PROPERTY_NAME, + UpdateServiceRequestImpl.JSON_PROPERTY_DESCRIPTION, + UpdateServiceRequestImpl.JSON_PROPERTY_IS_DEFAULT, + UpdateServiceRequestImpl.JSON_PROPERTY_CALL_BEHAVIOR +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class UpdateServiceRequestImpl implements UpdateServiceRequest { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_NAME = "name"; + + private OptionalValue name; + + public static final String JSON_PROPERTY_DESCRIPTION = "description"; + + private OptionalValue description; + + public static final String JSON_PROPERTY_IS_DEFAULT = "isDefault"; + + private OptionalValue isDefault; + + public static final String JSON_PROPERTY_CALL_BEHAVIOR = "callBehavior"; + + private OptionalValue callBehavior; + + private OptionalValue idempotencyKey; + + public UpdateServiceRequestImpl() {} + + protected UpdateServiceRequestImpl( + OptionalValue name, + OptionalValue description, + OptionalValue isDefault, + OptionalValue callBehavior, + OptionalValue idempotencyKey) { + this.name = name; + this.description = description; + this.isDefault = isDefault; + this.callBehavior = callBehavior; + this.idempotencyKey = idempotencyKey; + } + + @JsonIgnore + public String getName() { + return name.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue name() { + return name; + } + + @JsonIgnore + public String getDescription() { + return description.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DESCRIPTION) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue description() { + return description; + } + + @JsonIgnore + public Boolean getIsDefault() { + return isDefault.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_IS_DEFAULT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue isDefault() { + return isDefault; + } + + @JsonIgnore + public CallBehavior getCallBehavior() { + return callBehavior.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_BEHAVIOR) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue callBehavior() { + return callBehavior; + } + + @JsonIgnore + public String getIdempotencyKey() { + return idempotencyKey.orElse(null); + } + + @JsonIgnore + public OptionalValue idempotencyKey() { + return idempotencyKey; + } + + /** Return true if this UpdateServiceRequest object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UpdateServiceRequestImpl updateServiceRequest = (UpdateServiceRequestImpl) o; + return Objects.equals(this.name, updateServiceRequest.name) + && Objects.equals(this.description, updateServiceRequest.description) + && Objects.equals(this.isDefault, updateServiceRequest.isDefault) + && Objects.equals(this.callBehavior, updateServiceRequest.callBehavior) + && Objects.equals(this.idempotencyKey, updateServiceRequest.idempotencyKey); + } + + @Override + public int hashCode() { + return Objects.hash(name, description, isDefault, callBehavior, idempotencyKey); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class UpdateServiceRequestImpl {\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" description: ").append(toIndentedString(description)).append("\n"); + sb.append(" isDefault: ").append(toIndentedString(isDefault)).append("\n"); + sb.append(" callBehavior: ").append(toIndentedString(callBehavior)).append("\n"); + sb.append(" idempotencyKey: ").append(toIndentedString(idempotencyKey)).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 UpdateServiceRequest.Builder { + OptionalValue name = OptionalValue.empty(); + OptionalValue description = OptionalValue.empty(); + OptionalValue isDefault = OptionalValue.empty(); + OptionalValue callBehavior = OptionalValue.empty(); + OptionalValue idempotencyKey = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_NAME) + public Builder setName(String name) { + this.name = OptionalValue.of(name); + return this; + } + + @JsonProperty(JSON_PROPERTY_DESCRIPTION) + public Builder setDescription(String description) { + this.description = OptionalValue.of(description); + return this; + } + + public Builder setAsDefault() { + return setIsDefault(true); + } + + // used by Jackson only: the public builder exposes setAsDefault() + @JsonProperty(JSON_PROPERTY_IS_DEFAULT) + Builder setIsDefault(Boolean isDefault) { + this.isDefault = OptionalValue.of(isDefault); + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_BEHAVIOR) + public Builder setCallBehavior(CallBehavior callBehavior) { + this.callBehavior = OptionalValue.of(callBehavior); + return this; + } + + @JsonIgnore + public Builder setIdempotencyKey(String idempotencyKey) { + this.idempotencyKey = OptionalValue.of(idempotencyKey); + return this; + } + + public UpdateServiceRequest build() { + return new UpdateServiceRequestImpl( + name, description, isDefault, callBehavior, idempotencyKey); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceResponse.java new file mode 100644 index 000000000..1574332f6 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceResponse.java @@ -0,0 +1,195 @@ +package com.sinch.sdk.domains.voice.models.v2.services.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.services.CallBehavior; +import java.time.Instant; + +/** + * Full representation of a voice service resource, including its configuration and call behavior. + */ +@JsonDeserialize(builder = ServiceResponseImpl.Builder.class) +public interface ServiceResponse { + + /** + * The ID of the service used. + * + *

Field is required + * + * @return serviceId + */ + String getServiceId(); + + /** + * The secret of the service used. + * + *

Field is required + * + * @return serviceSecret + */ + String getServiceSecret(); + + /** + * The ID of the project associated with the call. + * + *

Field is required + * + * @return projectId + */ + String getProjectId(); + + /** + * Timestamp (RFC 3339) indicating when the service was created. + * + *

Field is required + * + * @return createTime + * @readOnly This field is returned by the server and cannot be modified + */ + Instant getCreateTime(); + + /** + * Timestamp (RFC 3339) indicating when the service was last updated. + * + *

Omitted if no updates were performed on this service. + * + * @return updateTime + * @readOnly This field is returned by the server and cannot be modified + */ + Instant getUpdateTime(); + + /** + * Voice service name. No leading or trailing whitespaces allowed. + * + *

Field is required + * + * @return name + */ + String getName(); + + /** + * Description of the service + * + * @return description + */ + String getDescription(); + + /** + * Indicates whether this service is set as the default for the project. The default service is + * used when no specific service is specified in API requests. + * + *

Field is required + * + * @return isDefault + */ + Boolean getIsDefault(); + + /** + * Defines how calls are handled for this service + * + * @return callBehavior + */ + CallBehavior getCallBehavior(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new ServiceResponseImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param serviceId see getter + * @return Current builder + * @see #getServiceId + */ + Builder setServiceId(String serviceId); + + /** + * see getter + * + * @param serviceSecret see getter + * @return Current builder + * @see #getServiceSecret + */ + Builder setServiceSecret(String serviceSecret); + + /** + * see getter + * + * @param projectId see getter + * @return Current builder + * @see #getProjectId + */ + Builder setProjectId(String projectId); + + /** + * see getter + * + * @param createTime see getter + * @return Current builder + * @see #getCreateTime + * @readOnly This field is returned by the server and cannot be modified + */ + Builder setCreateTime(Instant createTime); + + /** + * see getter + * + * @param updateTime see getter + * @return Current builder + * @see #getUpdateTime + * @readOnly This field is returned by the server and cannot be modified + */ + Builder setUpdateTime(Instant updateTime); + + /** + * see getter + * + * @param name see getter + * @return Current builder + * @see #getName + */ + Builder setName(String name); + + /** + * see getter + * + * @param description see getter + * @return Current builder + * @see #getDescription + */ + Builder setDescription(String description); + + /** + * see getter + * + * @param isDefault see getter + * @return Current builder + * @see #getIsDefault + */ + Builder setIsDefault(Boolean isDefault); + + /** + * see getter + * + * @param callBehavior see getter + * @return Current builder + * @see #getCallBehavior + */ + Builder setCallBehavior(CallBehavior callBehavior); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + ServiceResponse build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceResponseImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceResponseImpl.java new file mode 100644 index 000000000..ae1ea7bb5 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceResponseImpl.java @@ -0,0 +1,327 @@ +package com.sinch.sdk.domains.voice.models.v2.services.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.voice.models.v2.services.CallBehavior; +import java.time.Instant; +import java.util.Objects; + +@JsonPropertyOrder({ + ServiceResponseImpl.JSON_PROPERTY_SERVICE_ID, + ServiceResponseImpl.JSON_PROPERTY_SERVICE_SECRET, + ServiceResponseImpl.JSON_PROPERTY_PROJECT_ID, + ServiceResponseImpl.JSON_PROPERTY_CREATE_TIME, + ServiceResponseImpl.JSON_PROPERTY_UPDATE_TIME, + ServiceResponseImpl.JSON_PROPERTY_NAME, + ServiceResponseImpl.JSON_PROPERTY_DESCRIPTION, + ServiceResponseImpl.JSON_PROPERTY_IS_DEFAULT, + ServiceResponseImpl.JSON_PROPERTY_CALL_BEHAVIOR +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class ServiceResponseImpl implements ServiceResponse { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_SERVICE_ID = "serviceId"; + + private OptionalValue serviceId; + + public static final String JSON_PROPERTY_SERVICE_SECRET = "serviceSecret"; + + private OptionalValue serviceSecret; + + public static final String JSON_PROPERTY_PROJECT_ID = "projectId"; + + private OptionalValue projectId; + + public static final String JSON_PROPERTY_CREATE_TIME = "createTime"; + + private OptionalValue createTime; + + public static final String JSON_PROPERTY_UPDATE_TIME = "updateTime"; + + private OptionalValue updateTime; + + public static final String JSON_PROPERTY_NAME = "name"; + + private OptionalValue name; + + public static final String JSON_PROPERTY_DESCRIPTION = "description"; + + private OptionalValue description; + + public static final String JSON_PROPERTY_IS_DEFAULT = "isDefault"; + + private OptionalValue isDefault; + + public static final String JSON_PROPERTY_CALL_BEHAVIOR = "callBehavior"; + + private OptionalValue callBehavior; + + public ServiceResponseImpl() {} + + protected ServiceResponseImpl( + OptionalValue serviceId, + OptionalValue serviceSecret, + OptionalValue projectId, + OptionalValue createTime, + OptionalValue updateTime, + OptionalValue name, + OptionalValue description, + OptionalValue isDefault, + OptionalValue callBehavior) { + this.serviceId = serviceId; + this.serviceSecret = serviceSecret; + this.projectId = projectId; + this.createTime = createTime; + this.updateTime = updateTime; + this.name = name; + this.description = description; + this.isDefault = isDefault; + this.callBehavior = callBehavior; + } + + @JsonIgnore + public String getServiceId() { + return serviceId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SERVICE_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue serviceId() { + return serviceId; + } + + @JsonIgnore + public String getServiceSecret() { + return serviceSecret.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SERVICE_SECRET) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue serviceSecret() { + return serviceSecret; + } + + @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 Instant getCreateTime() { + return createTime.orElse(null); + } + + @JsonIgnore + public OptionalValue createTime() { + return createTime; + } + + @JsonIgnore + public Instant getUpdateTime() { + return updateTime.orElse(null); + } + + @JsonIgnore + public OptionalValue updateTime() { + return updateTime; + } + + @JsonIgnore + public String getName() { + return name.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_NAME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue name() { + return name; + } + + @JsonIgnore + public String getDescription() { + return description.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DESCRIPTION) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue description() { + return description; + } + + @JsonIgnore + public Boolean getIsDefault() { + return isDefault.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_IS_DEFAULT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue isDefault() { + return isDefault; + } + + @JsonIgnore + public CallBehavior getCallBehavior() { + return callBehavior.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_BEHAVIOR) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue callBehavior() { + return callBehavior; + } + + /** Return true if this ServiceResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServiceResponseImpl serviceResponse = (ServiceResponseImpl) o; + return Objects.equals(this.serviceId, serviceResponse.serviceId) + && Objects.equals(this.serviceSecret, serviceResponse.serviceSecret) + && Objects.equals(this.projectId, serviceResponse.projectId) + && Objects.equals(this.createTime, serviceResponse.createTime) + && Objects.equals(this.updateTime, serviceResponse.updateTime) + && Objects.equals(this.name, serviceResponse.name) + && Objects.equals(this.description, serviceResponse.description) + && Objects.equals(this.isDefault, serviceResponse.isDefault) + && Objects.equals(this.callBehavior, serviceResponse.callBehavior); + } + + @Override + public int hashCode() { + return Objects.hash( + serviceId, + serviceSecret, + projectId, + createTime, + updateTime, + name, + description, + isDefault, + callBehavior); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ServiceResponseImpl {\n"); + sb.append(" serviceId: ").append(toIndentedString(serviceId)).append("\n"); + sb.append(" serviceSecret: ").append(toIndentedString(serviceSecret)).append("\n"); + sb.append(" projectId: ").append(toIndentedString(projectId)).append("\n"); + sb.append(" createTime: ").append(toIndentedString(createTime)).append("\n"); + sb.append(" updateTime: ").append(toIndentedString(updateTime)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" description: ").append(toIndentedString(description)).append("\n"); + sb.append(" isDefault: ").append(toIndentedString(isDefault)).append("\n"); + sb.append(" callBehavior: ").append(toIndentedString(callBehavior)).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 ServiceResponse.Builder { + OptionalValue serviceId = OptionalValue.empty(); + OptionalValue serviceSecret = OptionalValue.empty(); + OptionalValue projectId = OptionalValue.empty(); + OptionalValue createTime = OptionalValue.empty(); + OptionalValue updateTime = OptionalValue.empty(); + OptionalValue name = OptionalValue.empty(); + OptionalValue description = OptionalValue.empty(); + OptionalValue isDefault = OptionalValue.empty(); + OptionalValue callBehavior = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_SERVICE_ID, required = true) + public Builder setServiceId(String serviceId) { + this.serviceId = OptionalValue.of(serviceId); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_SERVICE_SECRET, required = true) + public Builder setServiceSecret(String serviceSecret) { + this.serviceSecret = OptionalValue.of(serviceSecret); + 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_CREATE_TIME, required = true) + public Builder setCreateTime(Instant createTime) { + this.createTime = OptionalValue.of(createTime); + return this; + } + + @JsonProperty(JSON_PROPERTY_UPDATE_TIME) + public Builder setUpdateTime(Instant updateTime) { + this.updateTime = OptionalValue.of(updateTime); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_NAME, required = true) + public Builder setName(String name) { + this.name = OptionalValue.of(name); + return this; + } + + @JsonProperty(JSON_PROPERTY_DESCRIPTION) + public Builder setDescription(String description) { + this.description = OptionalValue.of(description); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_IS_DEFAULT, required = true) + public Builder setIsDefault(Boolean isDefault) { + this.isDefault = OptionalValue.of(isDefault); + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_BEHAVIOR) + public Builder setCallBehavior(CallBehavior callBehavior) { + this.callBehavior = OptionalValue.of(callBehavior); + return this; + } + + public ServiceResponse build() { + return new ServiceResponseImpl( + serviceId, + serviceSecret, + projectId, + createTime, + updateTime, + name, + description, + isDefault, + callBehavior); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceShortResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceShortResponse.java new file mode 100644 index 000000000..99583a394 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceShortResponse.java @@ -0,0 +1,164 @@ +package com.sinch.sdk.domains.voice.models.v2.services.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.time.Instant; + +/** Abbreviated representation of a voice service, returned in list responses. */ +@JsonDeserialize(builder = ServiceShortResponseImpl.Builder.class) +public interface ServiceShortResponse { + + /** + * The ID of the service used. + * + *

Field is required + * + * @return serviceId + */ + String getServiceId(); + + /** + * The ID of the project associated with the call. + * + *

Field is required + * + * @return projectId + */ + String getProjectId(); + + /** + * Timestamp (RFC 3339) indicating when the service was created. + * + *

Field is required + * + * @return createTime + * @readOnly This field is returned by the server and cannot be modified + */ + Instant getCreateTime(); + + /** + * Timestamp (RFC 3339) indicating when the service was last updated. + * + *

Omitted if no updates were performed on this service. + * + * @return updateTime + * @readOnly This field is returned by the server and cannot be modified + */ + Instant getUpdateTime(); + + /** + * Name of the service + * + *

Field is required + * + * @return name + * @readOnly This field is returned by the server and cannot be modified + */ + String getName(); + + /** + * Description of the service + * + * @return description + * @readOnly This field is returned by the server and cannot be modified + */ + String getDescription(); + + /** + * Indicates whether this service is set as the default for the project. The default service is + * used when no specific service is specified in API requests. + * + *

Field is required + * + * @return isDefault + * @readOnly This field is returned by the server and cannot be modified + */ + Boolean getIsDefault(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new ServiceShortResponseImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param serviceId see getter + * @return Current builder + * @see #getServiceId + */ + Builder setServiceId(String serviceId); + + /** + * see getter + * + * @param projectId see getter + * @return Current builder + * @see #getProjectId + */ + Builder setProjectId(String projectId); + + /** + * see getter + * + * @param createTime see getter + * @return Current builder + * @see #getCreateTime + * @readOnly This field is returned by the server and cannot be modified + */ + Builder setCreateTime(Instant createTime); + + /** + * see getter + * + * @param updateTime see getter + * @return Current builder + * @see #getUpdateTime + * @readOnly This field is returned by the server and cannot be modified + */ + Builder setUpdateTime(Instant updateTime); + + /** + * see getter + * + * @param name see getter + * @return Current builder + * @see #getName + * @readOnly This field is returned by the server and cannot be modified + */ + Builder setName(String name); + + /** + * see getter + * + * @param description see getter + * @return Current builder + * @see #getDescription + * @readOnly This field is returned by the server and cannot be modified + */ + Builder setDescription(String description); + + /** + * see getter + * + * @param isDefault see getter + * @return Current builder + * @see #getIsDefault + * @readOnly This field is returned by the server and cannot be modified + */ + Builder setIsDefault(Boolean isDefault); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + ServiceShortResponse build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceShortResponseImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceShortResponseImpl.java new file mode 100644 index 000000000..f06da0e9c --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceShortResponseImpl.java @@ -0,0 +1,252 @@ +package com.sinch.sdk.domains.voice.models.v2.services.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.time.Instant; +import java.util.Objects; + +@JsonPropertyOrder({ + ServiceShortResponseImpl.JSON_PROPERTY_SERVICE_ID, + ServiceShortResponseImpl.JSON_PROPERTY_PROJECT_ID, + ServiceShortResponseImpl.JSON_PROPERTY_CREATE_TIME, + ServiceShortResponseImpl.JSON_PROPERTY_UPDATE_TIME, + ServiceShortResponseImpl.JSON_PROPERTY_NAME, + ServiceShortResponseImpl.JSON_PROPERTY_DESCRIPTION, + ServiceShortResponseImpl.JSON_PROPERTY_IS_DEFAULT +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class ServiceShortResponseImpl implements ServiceShortResponse { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_SERVICE_ID = "serviceId"; + + private OptionalValue serviceId; + + public static final String JSON_PROPERTY_PROJECT_ID = "projectId"; + + private OptionalValue projectId; + + public static final String JSON_PROPERTY_CREATE_TIME = "createTime"; + + private OptionalValue createTime; + + public static final String JSON_PROPERTY_UPDATE_TIME = "updateTime"; + + private OptionalValue updateTime; + + public static final String JSON_PROPERTY_NAME = "name"; + + private OptionalValue name; + + public static final String JSON_PROPERTY_DESCRIPTION = "description"; + + private OptionalValue description; + + public static final String JSON_PROPERTY_IS_DEFAULT = "isDefault"; + + private OptionalValue isDefault; + + public ServiceShortResponseImpl() {} + + protected ServiceShortResponseImpl( + OptionalValue serviceId, + OptionalValue projectId, + OptionalValue createTime, + OptionalValue updateTime, + OptionalValue name, + OptionalValue description, + OptionalValue isDefault) { + this.serviceId = serviceId; + this.projectId = projectId; + this.createTime = createTime; + this.updateTime = updateTime; + this.name = name; + this.description = description; + this.isDefault = isDefault; + } + + @JsonIgnore + public String getServiceId() { + return serviceId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SERVICE_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue serviceId() { + return serviceId; + } + + @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 Instant getCreateTime() { + return createTime.orElse(null); + } + + @JsonIgnore + public OptionalValue createTime() { + return createTime; + } + + @JsonIgnore + public Instant getUpdateTime() { + return updateTime.orElse(null); + } + + @JsonIgnore + public OptionalValue updateTime() { + return updateTime; + } + + @JsonIgnore + public String getName() { + return name.orElse(null); + } + + @JsonIgnore + public OptionalValue name() { + return name; + } + + @JsonIgnore + public String getDescription() { + return description.orElse(null); + } + + @JsonIgnore + public OptionalValue description() { + return description; + } + + @JsonIgnore + public Boolean getIsDefault() { + return isDefault.orElse(null); + } + + @JsonIgnore + public OptionalValue isDefault() { + return isDefault; + } + + /** Return true if this ServiceShortResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServiceShortResponseImpl serviceShortResponse = (ServiceShortResponseImpl) o; + return Objects.equals(this.serviceId, serviceShortResponse.serviceId) + && Objects.equals(this.projectId, serviceShortResponse.projectId) + && Objects.equals(this.createTime, serviceShortResponse.createTime) + && Objects.equals(this.updateTime, serviceShortResponse.updateTime) + && Objects.equals(this.name, serviceShortResponse.name) + && Objects.equals(this.description, serviceShortResponse.description) + && Objects.equals(this.isDefault, serviceShortResponse.isDefault); + } + + @Override + public int hashCode() { + return Objects.hash(serviceId, projectId, createTime, updateTime, name, description, isDefault); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ServiceShortResponseImpl {\n"); + sb.append(" serviceId: ").append(toIndentedString(serviceId)).append("\n"); + sb.append(" projectId: ").append(toIndentedString(projectId)).append("\n"); + sb.append(" createTime: ").append(toIndentedString(createTime)).append("\n"); + sb.append(" updateTime: ").append(toIndentedString(updateTime)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" description: ").append(toIndentedString(description)).append("\n"); + sb.append(" isDefault: ").append(toIndentedString(isDefault)).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 ServiceShortResponse.Builder { + OptionalValue serviceId = OptionalValue.empty(); + OptionalValue projectId = OptionalValue.empty(); + OptionalValue createTime = OptionalValue.empty(); + OptionalValue updateTime = OptionalValue.empty(); + OptionalValue name = OptionalValue.empty(); + OptionalValue description = OptionalValue.empty(); + OptionalValue isDefault = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_SERVICE_ID, required = true) + public Builder setServiceId(String serviceId) { + this.serviceId = OptionalValue.of(serviceId); + 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_CREATE_TIME, required = true) + public Builder setCreateTime(Instant createTime) { + this.createTime = OptionalValue.of(createTime); + return this; + } + + @JsonProperty(JSON_PROPERTY_UPDATE_TIME) + public Builder setUpdateTime(Instant updateTime) { + this.updateTime = OptionalValue.of(updateTime); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_NAME, required = true) + public Builder setName(String name) { + this.name = OptionalValue.of(name); + return this; + } + + @JsonProperty(JSON_PROPERTY_DESCRIPTION) + public Builder setDescription(String description) { + this.description = OptionalValue.of(description); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_IS_DEFAULT, required = true) + public Builder setIsDefault(Boolean isDefault) { + this.isDefault = OptionalValue.of(isDefault); + return this; + } + + public ServiceShortResponse build() { + return new ServiceShortResponseImpl( + serviceId, projectId, createTime, updateTime, name, description, isDefault); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServicesListResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServicesListResponse.java new file mode 100644 index 000000000..93028b0eb --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/ServicesListResponse.java @@ -0,0 +1,48 @@ +package com.sinch.sdk.domains.voice.models.v2.services.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 java.util.Collection; +import java.util.Collections; +import java.util.NoSuchElementException; +import java.util.function.Supplier; + +/** Auto paginated response for list of ServiceShortResponse */ +public class ServicesListResponse extends ListResponse { + + private final Page page; + private final Supplier supplier; + + public ServicesListResponse( + 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 ServicesListResponse 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 "ServicesListResponse {" + "page=" + page + '}'; + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/internal/ServicesListResponseInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/internal/ServicesListResponseInternal.java new file mode 100644 index 000000000..93d8aee4a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/internal/ServicesListResponseInternal.java @@ -0,0 +1,86 @@ +package com.sinch.sdk.domains.voice.models.v2.services.response.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.PaginationLinks; +import com.sinch.sdk.domains.voice.models.v2.PaginationMeta; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceShortResponse; +import java.util.List; + +/** List of available voice services in the project. */ +@JsonDeserialize(builder = ServicesListResponseInternalImpl.Builder.class) +public interface ServicesListResponseInternal { + + /** + * Get services + * + *

Field is required + * + * @return services + */ + List getServices(); + + /** + * Get links + * + *

Field is required + * + * @return links + */ + PaginationLinks getLinks(); + + /** + * Get meta + * + *

Field is required + * + * @return meta + */ + PaginationMeta getMeta(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new ServicesListResponseInternalImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param services see getter + * @return Current builder + * @see #getServices + */ + Builder setServices(List services); + + /** + * see getter + * + * @param links see getter + * @return Current builder + * @see #getLinks + */ + Builder setLinks(PaginationLinks links); + + /** + * see getter + * + * @param meta see getter + * @return Current builder + * @see #getMeta + */ + Builder setMeta(PaginationMeta meta); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + ServicesListResponseInternal build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/internal/ServicesListResponseInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/internal/ServicesListResponseInternalImpl.java new file mode 100644 index 000000000..46bb3a91b --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/services/response/internal/ServicesListResponseInternalImpl.java @@ -0,0 +1,152 @@ +package com.sinch.sdk.domains.voice.models.v2.services.response.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.voice.models.v2.PaginationLinks; +import com.sinch.sdk.domains.voice.models.v2.PaginationMeta; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceShortResponse; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + ServicesListResponseInternalImpl.JSON_PROPERTY_SERVICES, + ServicesListResponseInternalImpl.JSON_PROPERTY_LINKS, + ServicesListResponseInternalImpl.JSON_PROPERTY_META +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class ServicesListResponseInternalImpl implements ServicesListResponseInternal { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_SERVICES = "services"; + + private OptionalValue> services; + + public static final String JSON_PROPERTY_LINKS = "links"; + + private OptionalValue links; + + public static final String JSON_PROPERTY_META = "meta"; + + private OptionalValue meta; + + public ServicesListResponseInternalImpl() {} + + protected ServicesListResponseInternalImpl( + OptionalValue> services, + OptionalValue links, + OptionalValue meta) { + this.services = services; + this.links = links; + this.meta = meta; + } + + @JsonIgnore + public List getServices() { + return services.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SERVICES) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> services() { + return services; + } + + @JsonIgnore + public PaginationLinks getLinks() { + return links.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_LINKS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue links() { + return links; + } + + @JsonIgnore + public PaginationMeta getMeta() { + return meta.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_META) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue meta() { + return meta; + } + + /** Return true if this ServicesListResponseInternal object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServicesListResponseInternalImpl servicesListResponseInternal = + (ServicesListResponseInternalImpl) o; + return Objects.equals(this.services, servicesListResponseInternal.services) + && Objects.equals(this.links, servicesListResponseInternal.links) + && Objects.equals(this.meta, servicesListResponseInternal.meta); + } + + @Override + public int hashCode() { + return Objects.hash(services, links, meta); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ServicesListResponseInternalImpl {\n"); + sb.append(" services: ").append(toIndentedString(services)).append("\n"); + sb.append(" links: ").append(toIndentedString(links)).append("\n"); + sb.append(" meta: ").append(toIndentedString(meta)).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 ServicesListResponseInternal.Builder { + OptionalValue> services = OptionalValue.empty(); + OptionalValue links = OptionalValue.empty(); + OptionalValue meta = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_SERVICES, required = true) + public Builder setServices(List services) { + this.services = OptionalValue.of(services); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_LINKS, required = true) + public Builder setLinks(PaginationLinks links) { + this.links = OptionalValue.of(links); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_META, required = true) + public Builder setMeta(PaginationMeta meta) { + this.meta = OptionalValue.of(meta); + return this; + } + + public ServicesListResponseInternal build() { + return new ServicesListResponseInternalImpl(services, links, meta); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sessions/response/Session.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sessions/response/Session.java new file mode 100644 index 000000000..aa70f850c --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sessions/response/Session.java @@ -0,0 +1,184 @@ +package com.sinch.sdk.domains.voice.models.v2.sessions.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.Call; +import com.sinch.sdk.domains.voice.models.v2.SessionState; +import java.time.Instant; +import java.util.List; + +/** + * A session represents the complete lifecycle of a voice interaction initiated through the Voice + * API. + * + *

A session can contain one or more related call legs (for example, an original outbound call + * plus additional dials or transfers triggered by SVAML commands). Use {@link #getCalls()} to + * inspect all call legs associated with the session and {@link #getState()} to determine whether + * the session is still ongoing or has reached a final state. + */ +@JsonDeserialize(builder = SessionImpl.Builder.class) +public interface Session { + + /** + * The ID of the session. + * + *

Field is required + * + * @return sessionId + */ + String getSessionId(); + + /** + * The ID of the project associated with the call. + * + *

Field is required + * + * @return projectId + */ + String getProjectId(); + + /** + * The ID of the service used. + * + *

Field is required + * + * @return serviceId + */ + String getServiceId(); + + /** + * Call legs associated with the session. + * + *

Field is required + * + * @return calls + */ + List getCalls(); + + /** + * Timestamp (RFC 3339) indicating when the session was created. + * + *

Field is required + * + * @return createTime + */ + Instant getCreateTime(); + + /** + * Timestamp (RFC 3339) indicating when the session was last updated. + * + *

Omitted if no updates were performed on this session. + * + * @return updateTime + */ + Instant getUpdateTime(); + + /** + * Timestamp (RFC 3339) indicating when the session ended. + * + *

Omitted for ongoing sessions. + * + * @return endTime + */ + Instant getEndTime(); + + /** + * Current state of the session. + * + *

Field is required + * + * @return state + */ + SessionState getState(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SessionImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param sessionId see getter + * @return Current builder + * @see #getSessionId + */ + Builder setSessionId(String sessionId); + + /** + * see getter + * + * @param projectId see getter + * @return Current builder + * @see #getProjectId + */ + Builder setProjectId(String projectId); + + /** + * see getter + * + * @param serviceId see getter + * @return Current builder + * @see #getServiceId + */ + Builder setServiceId(String serviceId); + + /** + * see getter + * + * @param calls see getter + * @return Current builder + * @see #getCalls + */ + Builder setCalls(List calls); + + /** + * see getter + * + * @param createTime see getter + * @return Current builder + * @see #getCreateTime + */ + Builder setCreateTime(Instant createTime); + + /** + * see getter + * + * @param updateTime see getter + * @return Current builder + * @see #getUpdateTime + */ + Builder setUpdateTime(Instant updateTime); + + /** + * see getter + * + * @param endTime see getter + * @return Current builder + * @see #getEndTime + */ + Builder setEndTime(Instant endTime); + + /** + * see getter + * + * @param state see getter + * @return Current builder + * @see #getState + */ + Builder setState(SessionState state); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + Session build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sessions/response/SessionImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sessions/response/SessionImpl.java new file mode 100644 index 000000000..9bbdcf855 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sessions/response/SessionImpl.java @@ -0,0 +1,288 @@ +package com.sinch.sdk.domains.voice.models.v2.sessions.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.voice.models.v2.Call; +import com.sinch.sdk.domains.voice.models.v2.SessionState; +import java.time.Instant; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + SessionImpl.JSON_PROPERTY_SESSION_ID, + SessionImpl.JSON_PROPERTY_PROJECT_ID, + SessionImpl.JSON_PROPERTY_SERVICE_ID, + SessionImpl.JSON_PROPERTY_CALLS, + SessionImpl.JSON_PROPERTY_CREATE_TIME, + SessionImpl.JSON_PROPERTY_UPDATE_TIME, + SessionImpl.JSON_PROPERTY_END_TIME, + SessionImpl.JSON_PROPERTY_STATE +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SessionImpl implements Session { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_SESSION_ID = "sessionId"; + + private OptionalValue sessionId; + + public static final String JSON_PROPERTY_PROJECT_ID = "projectId"; + + private OptionalValue projectId; + + public static final String JSON_PROPERTY_SERVICE_ID = "serviceId"; + + private OptionalValue serviceId; + + public static final String JSON_PROPERTY_CALLS = "calls"; + + private OptionalValue> calls; + + public static final String JSON_PROPERTY_CREATE_TIME = "createTime"; + + private OptionalValue createTime; + + public static final String JSON_PROPERTY_UPDATE_TIME = "updateTime"; + + private OptionalValue updateTime; + + public static final String JSON_PROPERTY_END_TIME = "endTime"; + + private OptionalValue endTime; + + public static final String JSON_PROPERTY_STATE = "state"; + + private OptionalValue state; + + public SessionImpl() {} + + protected SessionImpl( + OptionalValue sessionId, + OptionalValue projectId, + OptionalValue serviceId, + OptionalValue> calls, + OptionalValue createTime, + OptionalValue updateTime, + OptionalValue endTime, + OptionalValue state) { + this.sessionId = sessionId; + this.projectId = projectId; + this.serviceId = serviceId; + this.calls = calls; + this.createTime = createTime; + this.updateTime = updateTime; + this.endTime = endTime; + this.state = state; + } + + @JsonIgnore + public String getSessionId() { + return sessionId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SESSION_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue sessionId() { + return sessionId; + } + + @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 getServiceId() { + return serviceId.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SERVICE_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue serviceId() { + return serviceId; + } + + @JsonIgnore + public List getCalls() { + return calls.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALLS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> calls() { + return calls; + } + + @JsonIgnore + public Instant getCreateTime() { + return createTime.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CREATE_TIME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue createTime() { + return createTime; + } + + @JsonIgnore + public Instant getUpdateTime() { + return updateTime.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_UPDATE_TIME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue updateTime() { + return updateTime; + } + + @JsonIgnore + public Instant getEndTime() { + return endTime.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_END_TIME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue endTime() { + return endTime; + } + + @JsonIgnore + public SessionState getState() { + return state.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_STATE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue state() { + return state; + } + + /** Return true if this Session object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SessionImpl session = (SessionImpl) o; + return Objects.equals(this.sessionId, session.sessionId) + && Objects.equals(this.projectId, session.projectId) + && Objects.equals(this.serviceId, session.serviceId) + && Objects.equals(this.calls, session.calls) + && Objects.equals(this.createTime, session.createTime) + && Objects.equals(this.updateTime, session.updateTime) + && Objects.equals(this.endTime, session.endTime) + && Objects.equals(this.state, session.state); + } + + @Override + public int hashCode() { + return Objects.hash( + sessionId, projectId, serviceId, calls, createTime, updateTime, endTime, state); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SessionImpl {\n"); + sb.append(" sessionId: ").append(toIndentedString(sessionId)).append("\n"); + sb.append(" projectId: ").append(toIndentedString(projectId)).append("\n"); + sb.append(" serviceId: ").append(toIndentedString(serviceId)).append("\n"); + sb.append(" calls: ").append(toIndentedString(calls)).append("\n"); + sb.append(" createTime: ").append(toIndentedString(createTime)).append("\n"); + sb.append(" updateTime: ").append(toIndentedString(updateTime)).append("\n"); + sb.append(" endTime: ").append(toIndentedString(endTime)).append("\n"); + sb.append(" state: ").append(toIndentedString(state)).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 Session.Builder { + OptionalValue sessionId = OptionalValue.empty(); + OptionalValue projectId = OptionalValue.empty(); + OptionalValue serviceId = OptionalValue.empty(); + OptionalValue> calls = OptionalValue.empty(); + OptionalValue createTime = OptionalValue.empty(); + OptionalValue updateTime = OptionalValue.empty(); + OptionalValue endTime = OptionalValue.empty(); + OptionalValue state = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_SESSION_ID, required = true) + public Builder setSessionId(String sessionId) { + this.sessionId = OptionalValue.of(sessionId); + 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_SERVICE_ID, required = true) + public Builder setServiceId(String serviceId) { + this.serviceId = OptionalValue.of(serviceId); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_CALLS, required = true) + public Builder setCalls(List calls) { + this.calls = OptionalValue.of(calls); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_CREATE_TIME, required = true) + public Builder setCreateTime(Instant createTime) { + this.createTime = OptionalValue.of(createTime); + return this; + } + + @JsonProperty(JSON_PROPERTY_UPDATE_TIME) + public Builder setUpdateTime(Instant updateTime) { + this.updateTime = OptionalValue.of(updateTime); + return this; + } + + @JsonProperty(JSON_PROPERTY_END_TIME) + public Builder setEndTime(Instant endTime) { + this.endTime = OptionalValue.of(endTime); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_STATE, required = true) + public Builder setState(SessionState state) { + this.state = OptionalValue.of(state); + return this; + } + + public Session build() { + return new SessionImpl( + sessionId, projectId, serviceId, calls, createTime, updateTime, endTime, state); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/MenuInput.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/MenuInput.java new file mode 100644 index 000000000..6c296cc81 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/MenuInput.java @@ -0,0 +1,70 @@ +package com.sinch.sdk.domains.voice.models.v2.sinchevents; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + * Information about the menu interaction that triggered the Sinch Event, including the menu name + * and the input sequence received from the user. + * + *

This property is also included for Sinch Events triggered by the webhook command + * within a menu context. + */ +@JsonDeserialize(builder = MenuInputImpl.Builder.class) +public interface MenuInput { + + /** + * The name of the menu that triggered this Sinch Event. + * + *

Field is required + * + * @return menuName + */ + String getMenuName(); + + /** + * The input sequence gathered from the user by the menu. + * + *

Field is required + * + * @return input + */ + String getInput(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new MenuInputImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param menuName see getter + * @return Current builder + * @see #getMenuName + */ + Builder setMenuName(String menuName); + + /** + * see getter + * + * @param input see getter + * @return Current builder + * @see #getInput + */ + Builder setInput(String input); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + MenuInput build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/MenuInputImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/MenuInputImpl.java new file mode 100644 index 000000000..7c306949f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/MenuInputImpl.java @@ -0,0 +1,115 @@ +package com.sinch.sdk.domains.voice.models.v2.sinchevents; + +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({MenuInputImpl.JSON_PROPERTY_MENU_NAME, MenuInputImpl.JSON_PROPERTY_INPUT}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class MenuInputImpl implements MenuInput { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_MENU_NAME = "menuName"; + + private OptionalValue menuName; + + public static final String JSON_PROPERTY_INPUT = "input"; + + private OptionalValue input; + + public MenuInputImpl() {} + + protected MenuInputImpl(OptionalValue menuName, OptionalValue input) { + this.menuName = menuName; + this.input = input; + } + + @JsonIgnore + public String getMenuName() { + return menuName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MENU_NAME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue menuName() { + return menuName; + } + + @JsonIgnore + public String getInput() { + return input.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_INPUT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue input() { + return input; + } + + /** Return true if this MenuInput object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MenuInputImpl menuInput = (MenuInputImpl) o; + return Objects.equals(this.menuName, menuInput.menuName) + && Objects.equals(this.input, menuInput.input); + } + + @Override + public int hashCode() { + return Objects.hash(menuName, input); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class MenuInputImpl {\n"); + sb.append(" menuName: ").append(toIndentedString(menuName)).append("\n"); + sb.append(" input: ").append(toIndentedString(input)).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 MenuInput.Builder { + OptionalValue menuName = OptionalValue.empty(); + OptionalValue input = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_MENU_NAME, required = true) + public Builder setMenuName(String menuName) { + this.menuName = OptionalValue.of(menuName); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_INPUT, required = true) + public Builder setInput(String input) { + this.input = OptionalValue.of(input); + return this; + } + + public MenuInput build() { + return new MenuInputImpl(menuName, input); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/SinchEventType.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/SinchEventType.java new file mode 100644 index 000000000..06d303f10 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/SinchEventType.java @@ -0,0 +1,117 @@ +package com.sinch.sdk.domains.voice.models.v2.sinchevents; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * Identifies the type of call event that triggered this Sinch Event notification. + * + *

Call-related events always start with call. followed by the type of event that + * triggered them. Events triggered by the {@link + * com.sinch.sdk.domains.voice.models.v2.svaml.customevents.CustomEventCommand} are dynamic and + * follow the pattern call.customEvent.<customEventName>: they are not listed as + * constants and are returned as a dynamic value. The API sends them as + * call.webhook.<customEventName>, and parseEvent renames them. + */ +public class SinchEventType extends EnumDynamic { + + /** Triggered when an inbound call arrives on a service configured with a webhook URL. */ + public static final SinchEventType CALL_INCOMING = new SinchEventType("call.incoming"); + + /** Triggered when an outbound call is answered by the recipient. */ + public static final SinchEventType CALL_ANSWERED = new SinchEventType("call.answered"); + + /** Triggered when the outbound call recipient is busy. */ + public static final SinchEventType CALL_BUSY = new SinchEventType("call.busy"); + + /** Triggered when the outbound call is rejected by the recipient. */ + public static final SinchEventType CALL_REJECTED = new SinchEventType("call.rejected"); + + /** Triggered when the outbound call was not answered within the dial timeout. */ + public static final SinchEventType CALL_TIMEOUT = new SinchEventType("call.timeout"); + + /** Triggered when the call is disconnected. */ + public static final SinchEventType CALL_HANGUP = new SinchEventType("call.hangup"); + + /** Triggered when the call could not be set up. */ + public static final SinchEventType CALL_FAILED = new SinchEventType("call.failed"); + + /** Triggered when answering machine detection determines that a human answered the call. */ + public static final SinchEventType CALL_AMD_HUMAN = new SinchEventType("call.amd.human"); + + /** Triggered when answering machine detection determines that a machine answered the call. */ + public static final SinchEventType CALL_AMD_MACHINE = new SinchEventType("call.amd.machine"); + + /** Triggered when answering machine detection detects a voicemail beep. */ + public static final SinchEventType CALL_AMD_BEEP = new SinchEventType("call.amd.beep"); + + /** + * Triggered when answering machine detection cannot determine whether a human, machine, or beep + * was detected. + */ + public static final SinchEventType CALL_AMD_UNKNOWN = new SinchEventType("call.amd.unknown"); + + /** Triggered when all messages in a messages sequence have finished playing. */ + public static final SinchEventType CALL_MESSAGE_FINISHED = + new SinchEventType("call.message.finished"); + + /** + * Triggered when a recording is successfully stopped. This event does not guarantee that the + * recorded file has been delivered to the configured destination yet. + */ + public static final SinchEventType CALL_RECORDING_FINISHED = + new SinchEventType("call.recording.finished"); + + /** Triggered when recording could not be started. */ + public static final SinchEventType CALL_RECORDING_FAILED = + new SinchEventType("call.recording.failed"); + + /** + * Triggered when a menu fails to collect a matching input and no onFailure commands + * are defined for that menu. + * + *

Input that matches a menu match item does not trigger this event; a {@link + * com.sinch.sdk.domains.voice.models.v2.svaml.customevents.CustomEventCommand} within the menu + * matches can be used to send an event for a specific input. + */ + public static final SinchEventType CALL_MENU = new SinchEventType("call.menu"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + SinchEventType.class, + SinchEventType::new, + Arrays.asList( + CALL_INCOMING, + CALL_ANSWERED, + CALL_BUSY, + CALL_REJECTED, + CALL_TIMEOUT, + CALL_HANGUP, + CALL_FAILED, + CALL_AMD_HUMAN, + CALL_AMD_MACHINE, + CALL_AMD_BEEP, + CALL_AMD_UNKNOWN, + CALL_MESSAGE_FINISHED, + CALL_RECORDING_FINISHED, + CALL_RECORDING_FAILED, + CALL_MENU)); + + private SinchEventType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static SinchEventType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(SinchEventType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEvent.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEvent.java new file mode 100644 index 000000000..b30ba66e4 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEvent.java @@ -0,0 +1,87 @@ +package com.sinch.sdk.domains.voice.models.v2.sinchevents; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.Call; + +/** The event payload delivered by the Voice Platform to the Sinch Events endpoint. */ +@JsonDeserialize(builder = VoiceSinchEventImpl.Builder.class) +public interface VoiceSinchEvent { + + /** + * Identifies the type of call event that triggered this Sinch Event notification. + * + *

Call-related events always start with call. followed by the type of event that + * triggered them. Events triggered by the customEvent SVAML command are dynamic and + * follow the pattern call.customEvent.<customEventName>. + * + *

Field is required + * + * @return event + */ + SinchEventType getEvent(); + + /** + * The current state of the call at the time the event was triggered. + * + *

Field is required + * + * @return call + */ + Call getCall(); + + /** + * Information about the menu interaction that triggered the Sinch Event. Present for + * call.menu events, and for call.customEvent.<customEventName> events + * triggered within a menu context. + * + * @return menu + */ + MenuInput getMenu(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new VoiceSinchEventImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param event see getter + * @return Current builder + * @see #getEvent + */ + Builder setEvent(SinchEventType event); + + /** + * see getter + * + * @param call see getter + * @return Current builder + * @see #getCall + */ + Builder setCall(Call call); + + /** + * see getter + * + * @param menu see getter + * @return Current builder + * @see #getMenu + */ + Builder setMenu(MenuInput menu); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + VoiceSinchEvent build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventImpl.java new file mode 100644 index 000000000..851b68dc5 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventImpl.java @@ -0,0 +1,148 @@ +package com.sinch.sdk.domains.voice.models.v2.sinchevents; + +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.v2.Call; +import java.util.Objects; + +@JsonPropertyOrder({ + VoiceSinchEventImpl.JSON_PROPERTY_EVENT, + VoiceSinchEventImpl.JSON_PROPERTY_CALL, + VoiceSinchEventImpl.JSON_PROPERTY_MENU +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class VoiceSinchEventImpl implements VoiceSinchEvent { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_EVENT = "event"; + + private OptionalValue event; + + public static final String JSON_PROPERTY_CALL = "call"; + + private OptionalValue call; + + public static final String JSON_PROPERTY_MENU = "menu"; + + private OptionalValue menu; + + public VoiceSinchEventImpl() {} + + protected VoiceSinchEventImpl( + OptionalValue event, + OptionalValue call, + OptionalValue menu) { + this.event = event; + this.call = call; + this.menu = menu; + } + + @JsonIgnore + public SinchEventType getEvent() { + return event.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_EVENT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue event() { + return event; + } + + @JsonIgnore + public Call getCall() { + return call.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue call() { + return call; + } + + @JsonIgnore + public MenuInput getMenu() { + return menu.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MENU) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue menu() { + return menu; + } + + /** Return true if this VoiceSinchEvent object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VoiceSinchEventImpl voiceSinchEvent = (VoiceSinchEventImpl) o; + return Objects.equals(this.event, voiceSinchEvent.event) + && Objects.equals(this.call, voiceSinchEvent.call) + && Objects.equals(this.menu, voiceSinchEvent.menu); + } + + @Override + public int hashCode() { + return Objects.hash(event, call, menu); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class VoiceSinchEventImpl {\n"); + sb.append(" event: ").append(toIndentedString(event)).append("\n"); + sb.append(" call: ").append(toIndentedString(call)).append("\n"); + sb.append(" menu: ").append(toIndentedString(menu)).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 VoiceSinchEvent.Builder { + OptionalValue event = OptionalValue.empty(); + OptionalValue call = OptionalValue.empty(); + OptionalValue menu = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_EVENT, required = true) + public Builder setEvent(SinchEventType event) { + this.event = OptionalValue.of(event); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_CALL, required = true) + public Builder setCall(Call call) { + this.call = OptionalValue.of(call); + return this; + } + + @JsonProperty(JSON_PROPERTY_MENU) + public Builder setMenu(MenuInput menu) { + this.menu = OptionalValue.of(menu); + return this; + } + + public VoiceSinchEvent build() { + return new VoiceSinchEventImpl(event, call, menu); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventResponse.java new file mode 100644 index 000000000..126ccff67 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventResponse.java @@ -0,0 +1,106 @@ +package com.sinch.sdk.domains.voice.models.v2.sinchevents; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.IncomingCallResponseEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.List; + +/** + * A SVAML document returned in response to a Sinch Event. + * + *

Note: callName and events only take effect in + * responses to events triggered by an incoming call. In responses to other event types, they are + * ignored. + */ +@JsonDeserialize(builder = VoiceSinchEventResponseImpl.Builder.class) +public interface VoiceSinchEventResponse { + + /** + * The ordered list of SVAML commands to execute. Contains at least one command. + * + *

Blocking vs. non-blocking: Some commands block execution until they complete (pause + * , webhook, menu, gotoMenu), while others return + * immediately and run in parallel (dial, messages, amd, + * answer, hangup, startRecording, stopRecording + * , bridgeCall, stopMessages). Each command's description + * specifies its behavior. + * + *

Nesting scope: Commands that appear inside event handlers (e.g., dial.events.onAnswer + * , messages.events.onFinish) form independent sequences and execute in their + * own context — they are not continuations of the parent sequence. + * + *

Field is required + * + * @return commands + */ + List getCommands(); + + /** + * Name of the call. + * + *

Must be 1-32 characters, without whitespace. + * + *

Note: only takes effect in responses to events triggered by an incoming + * call. In responses to other event types, it is ignored. + * + * @return callName + */ + String getCallName(); + + /** + * Commands to execute on specific events for this call. + * + *

Note: only takes effect in responses to events triggered by an incoming + * call. In responses to other event types, it is ignored. + * + * @return events + */ + IncomingCallResponseEvents getEvents(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new VoiceSinchEventResponseImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param commands see getter + * @return Current builder + * @see #getCommands + */ + Builder setCommands(List commands); + + /** + * see getter + * + * @param callName see getter + * @return Current builder + * @see #getCallName + */ + Builder setCallName(String callName); + + /** + * see getter + * + * @param events see getter + * @return Current builder + * @see #getEvents + */ + Builder setEvents(IncomingCallResponseEvents events); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + VoiceSinchEventResponse build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventResponseImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventResponseImpl.java new file mode 100644 index 000000000..1f9ba57f3 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventResponseImpl.java @@ -0,0 +1,150 @@ +package com.sinch.sdk.domains.voice.models.v2.sinchevents; + +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.v2.svaml.IncomingCallResponseEvents; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + VoiceSinchEventResponseImpl.JSON_PROPERTY_COMMANDS, + VoiceSinchEventResponseImpl.JSON_PROPERTY_CALL_NAME, + VoiceSinchEventResponseImpl.JSON_PROPERTY_EVENTS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class VoiceSinchEventResponseImpl implements VoiceSinchEventResponse { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_COMMANDS = "commands"; + + private OptionalValue> commands; + + public static final String JSON_PROPERTY_CALL_NAME = "callName"; + + private OptionalValue callName; + + public static final String JSON_PROPERTY_EVENTS = "events"; + + private OptionalValue events; + + public VoiceSinchEventResponseImpl() {} + + protected VoiceSinchEventResponseImpl( + OptionalValue> commands, + OptionalValue callName, + OptionalValue events) { + this.commands = commands; + this.callName = callName; + this.events = events; + } + + @JsonIgnore + public List getCommands() { + return commands.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMANDS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> commands() { + return commands; + } + + @JsonIgnore + public String getCallName() { + return callName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue callName() { + return callName; + } + + @JsonIgnore + public IncomingCallResponseEvents getEvents() { + return events.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue events() { + return events; + } + + /** Return true if this VoiceSinchEventResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VoiceSinchEventResponseImpl voiceSinchEventResponse = (VoiceSinchEventResponseImpl) o; + return Objects.equals(this.commands, voiceSinchEventResponse.commands) + && Objects.equals(this.callName, voiceSinchEventResponse.callName) + && Objects.equals(this.events, voiceSinchEventResponse.events); + } + + @Override + public int hashCode() { + return Objects.hash(commands, callName, events); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class VoiceSinchEventResponseImpl {\n"); + sb.append(" commands: ").append(toIndentedString(commands)).append("\n"); + sb.append(" callName: ").append(toIndentedString(callName)).append("\n"); + sb.append(" events: ").append(toIndentedString(events)).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 VoiceSinchEventResponse.Builder { + OptionalValue> commands = OptionalValue.empty(); + OptionalValue callName = OptionalValue.empty(); + OptionalValue events = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMANDS, required = true) + public Builder setCommands(List commands) { + this.commands = OptionalValue.of(commands); + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_NAME) + public Builder setCallName(String callName) { + this.callName = OptionalValue.of(callName); + return this; + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + public Builder setEvents(IncomingCallResponseEvents events) { + this.events = OptionalValue.of(events); + return this; + } + + public VoiceSinchEventResponse build() { + return new VoiceSinchEventResponseImpl(commands, callName, events); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/IncomingCallResponseEvents.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/IncomingCallResponseEvents.java new file mode 100644 index 000000000..2888d55c2 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/IncomingCallResponseEvents.java @@ -0,0 +1,45 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.List; + +/** Commands to execute on specific events for this call. */ +@JsonDeserialize(builder = IncomingCallResponseEventsImpl.Builder.class) +public interface IncomingCallResponseEvents { + + /** + * SVAML commands to be executed when the call is hung up. + * + * @return onHangup + */ + List getOnHangup(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new IncomingCallResponseEventsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param onHangup see getter + * @return Current builder + * @see #getOnHangup + */ + Builder setOnHangup(List onHangup); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + IncomingCallResponseEvents build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/IncomingCallResponseEventsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/IncomingCallResponseEventsImpl.java new file mode 100644 index 000000000..8e2fbaa2c --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/IncomingCallResponseEventsImpl.java @@ -0,0 +1,91 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml; + +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({IncomingCallResponseEventsImpl.JSON_PROPERTY_ON_HANGUP}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class IncomingCallResponseEventsImpl implements IncomingCallResponseEvents { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ON_HANGUP = "onHangup"; + + private OptionalValue> onHangup; + + public IncomingCallResponseEventsImpl() {} + + protected IncomingCallResponseEventsImpl(OptionalValue> onHangup) { + this.onHangup = onHangup; + } + + @JsonIgnore + public List getOnHangup() { + return onHangup.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_HANGUP) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onHangup() { + return onHangup; + } + + /** Return true if this IncomingCallResponseEvents object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IncomingCallResponseEventsImpl incomingCallResponseEvents = (IncomingCallResponseEventsImpl) o; + return Objects.equals(this.onHangup, incomingCallResponseEvents.onHangup); + } + + @Override + public int hashCode() { + return Objects.hash(onHangup); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class IncomingCallResponseEventsImpl {\n"); + sb.append(" onHangup: ").append(toIndentedString(onHangup)).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 IncomingCallResponseEvents.Builder { + OptionalValue> onHangup = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_ON_HANGUP) + public Builder setOnHangup(List onHangup) { + this.onHangup = OptionalValue.of(onHangup); + return this; + } + + public IncomingCallResponseEvents build() { + return new IncomingCallResponseEventsImpl(onHangup); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlInput.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlInput.java new file mode 100644 index 000000000..fb5757a94 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlInput.java @@ -0,0 +1,95 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.List; + +/** + * A SVAML v2 (Sinch Voice Application Markup Language) document describing a call flow. Contains + * the ordered list of commands to execute, an optional call name, and optional event handlers. + */ +@JsonDeserialize(builder = SvamlInputImpl.Builder.class) +public interface SvamlInput { + + /** + * The ordered list of SVAML commands to execute. Contains at least one command. + * + *

Blocking vs. non-blocking: Some commands block execution until they complete (pause + * , webhook, menu, gotoMenu), while others return + * immediately and run in parallel (dial, messages, amd, + * answer, hangup, startRecording, stopRecording + * , bridgeCall, stopMessages). Each command's description + * specifies its behavior. + * + *

Nesting scope: Commands that appear inside event handlers (e.g., dial.events.onAnswer + * , messages.events.onFinish) form independent sequences and execute in their + * own context — they are not continuations of the parent sequence. + * + *

Field is required + * + * @return commands + */ + List getCommands(); + + /** + * Name of the call. + * + *

Must be 1-32 characters, without whitespace. + * + * @return callName + */ + String getCallName(); + + /** + * Commands to execute on specific events for this call. + * + * @return events + */ + IncomingCallResponseEvents getEvents(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SvamlInputImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param commands see getter + * @return Current builder + * @see #getCommands + */ + Builder setCommands(List commands); + + /** + * see getter + * + * @param callName see getter + * @return Current builder + * @see #getCallName + */ + Builder setCallName(String callName); + + /** + * see getter + * + * @param events see getter + * @return Current builder + * @see #getEvents + */ + Builder setEvents(IncomingCallResponseEvents events); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + SvamlInput build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlInputImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlInputImpl.java new file mode 100644 index 000000000..c42477476 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlInputImpl.java @@ -0,0 +1,148 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml; + +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({ + SvamlInputImpl.JSON_PROPERTY_COMMANDS, + SvamlInputImpl.JSON_PROPERTY_CALL_NAME, + SvamlInputImpl.JSON_PROPERTY_EVENTS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SvamlInputImpl implements SvamlInput { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_COMMANDS = "commands"; + + private OptionalValue> commands; + + public static final String JSON_PROPERTY_CALL_NAME = "callName"; + + private OptionalValue callName; + + public static final String JSON_PROPERTY_EVENTS = "events"; + + private OptionalValue events; + + public SvamlInputImpl() {} + + protected SvamlInputImpl( + OptionalValue> commands, + OptionalValue callName, + OptionalValue events) { + this.commands = commands; + this.callName = callName; + this.events = events; + } + + @JsonIgnore + public List getCommands() { + return commands.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMANDS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> commands() { + return commands; + } + + @JsonIgnore + public String getCallName() { + return callName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue callName() { + return callName; + } + + @JsonIgnore + public IncomingCallResponseEvents getEvents() { + return events.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue events() { + return events; + } + + /** Return true if this SvamlInput object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SvamlInputImpl svamlInput = (SvamlInputImpl) o; + return Objects.equals(this.commands, svamlInput.commands) + && Objects.equals(this.callName, svamlInput.callName) + && Objects.equals(this.events, svamlInput.events); + } + + @Override + public int hashCode() { + return Objects.hash(commands, callName, events); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SvamlInputImpl {\n"); + sb.append(" commands: ").append(toIndentedString(commands)).append("\n"); + sb.append(" callName: ").append(toIndentedString(callName)).append("\n"); + sb.append(" events: ").append(toIndentedString(events)).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 SvamlInput.Builder { + OptionalValue> commands = OptionalValue.empty(); + OptionalValue callName = OptionalValue.empty(); + OptionalValue events = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMANDS, required = true) + public Builder setCommands(List commands) { + this.commands = OptionalValue.of(commands); + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_NAME) + public Builder setCallName(String callName) { + this.callName = OptionalValue.of(callName); + return this; + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + public Builder setEvents(IncomingCallResponseEvents events) { + this.events = OptionalValue.of(events); + return this; + } + + public SvamlInput build() { + return new SvamlInputImpl(commands, callName, events); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdCommand.java new file mode 100644 index 000000000..a2e594921 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdCommand.java @@ -0,0 +1,53 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.amd; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; + +/** + * AMD (Answering Machine Detection) command to detect what answered the call. Possible outcomes + * are: human, machine, beep, or unknown. + * + *

This is a non-blocking command — the next command in the sequence executes immediately while + * detection runs in parallel. Results are delivered via the events property. + */ +@JsonDeserialize(builder = AmdCommandImpl.Builder.class) +public interface AmdCommand extends SvamlCommand { + + /** + * SVAML commands to execute based on the answering machine detection result. These events define + * different call flows depending on whether a human, machine, beep, or unknown entity answers the + * call. + * + * @return events + */ + AmdEvents getEvents(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new AmdCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param events see getter + * @return Current builder + * @see #getEvents + */ + Builder setEvents(AmdEvents events); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + AmdCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdCommandImpl.java new file mode 100644 index 000000000..bb007d42e --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdCommandImpl.java @@ -0,0 +1,147 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.amd; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({AmdCommandImpl.JSON_PROPERTY_COMMAND, AmdCommandImpl.JSON_PROPERTY_EVENTS}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class AmdCommandImpl implements AmdCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value amd. */ + public static class CommandEnum extends EnumDynamic { + /** The amd command. */ + public static final CommandEnum AMD = new CommandEnum("amd"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CommandEnum.class, CommandEnum::new, Arrays.asList(AMD)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_EVENTS = "events"; + + private OptionalValue events; + + public AmdCommandImpl() {} + + protected AmdCommandImpl(OptionalValue command, OptionalValue events) { + this.command = command; + this.events = events; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public AmdEvents getEvents() { + return events.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue events() { + return events; + } + + /** Return true if this AmdCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AmdCommandImpl amdCommand = (AmdCommandImpl) o; + return Objects.equals(this.command, amdCommand.command) + && Objects.equals(this.events, amdCommand.events); + } + + @Override + public int hashCode() { + return Objects.hash(command, events); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class AmdCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" events: ").append(toIndentedString(events)).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 AmdCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.AMD); + OptionalValue events = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.AMD)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.AMD, command)); + } + return this; + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + public Builder setEvents(AmdEvents events) { + this.events = OptionalValue.of(events); + return this; + } + + public AmdCommand build() { + return new AmdCommandImpl(command, events); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdEvents.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdEvents.java new file mode 100644 index 000000000..544977830 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdEvents.java @@ -0,0 +1,98 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.amd; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.List; + +/** + * SVAML commands to execute based on the answering machine detection result. These events define + * different call flows depending on whether a human, machine, beep, or unknown entity answers the + * call. + */ +@JsonDeserialize(builder = AmdEventsImpl.Builder.class) +public interface AmdEvents { + + /** + * SVAML commands to be executed when a human is detected + * + * @return onHuman + */ + List getOnHuman(); + + /** + * SVAML commands to be executed when a machine is detected + * + * @return onMachine + */ + List getOnMachine(); + + /** + * SVAML commands to be executed when a beep is detected + * + * @return onBeep + */ + List getOnBeep(); + + /** + * SVAML commands to be executed when an unknown event is detected + * + * @return onUnknown + */ + List getOnUnknown(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new AmdEventsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param onHuman see getter + * @return Current builder + * @see #getOnHuman + */ + Builder setOnHuman(List onHuman); + + /** + * see getter + * + * @param onMachine see getter + * @return Current builder + * @see #getOnMachine + */ + Builder setOnMachine(List onMachine); + + /** + * see getter + * + * @param onBeep see getter + * @return Current builder + * @see #getOnBeep + */ + Builder setOnBeep(List onBeep); + + /** + * see getter + * + * @param onUnknown see getter + * @return Current builder + * @see #getOnUnknown + */ + Builder setOnUnknown(List onUnknown); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + AmdEvents build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdEventsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdEventsImpl.java new file mode 100644 index 000000000..bad73bf70 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdEventsImpl.java @@ -0,0 +1,176 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.amd; + +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.v2.svaml.SvamlCommand; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + AmdEventsImpl.JSON_PROPERTY_ON_HUMAN, + AmdEventsImpl.JSON_PROPERTY_ON_MACHINE, + AmdEventsImpl.JSON_PROPERTY_ON_BEEP, + AmdEventsImpl.JSON_PROPERTY_ON_UNKNOWN +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class AmdEventsImpl implements AmdEvents { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ON_HUMAN = "onHuman"; + + private OptionalValue> onHuman; + + public static final String JSON_PROPERTY_ON_MACHINE = "onMachine"; + + private OptionalValue> onMachine; + + public static final String JSON_PROPERTY_ON_BEEP = "onBeep"; + + private OptionalValue> onBeep; + + public static final String JSON_PROPERTY_ON_UNKNOWN = "onUnknown"; + + private OptionalValue> onUnknown; + + public AmdEventsImpl() {} + + protected AmdEventsImpl( + OptionalValue> onHuman, + OptionalValue> onMachine, + OptionalValue> onBeep, + OptionalValue> onUnknown) { + this.onHuman = onHuman; + this.onMachine = onMachine; + this.onBeep = onBeep; + this.onUnknown = onUnknown; + } + + @JsonIgnore + public List getOnHuman() { + return onHuman.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_HUMAN) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onHuman() { + return onHuman; + } + + @JsonIgnore + public List getOnMachine() { + return onMachine.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_MACHINE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onMachine() { + return onMachine; + } + + @JsonIgnore + public List getOnBeep() { + return onBeep.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_BEEP) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onBeep() { + return onBeep; + } + + @JsonIgnore + public List getOnUnknown() { + return onUnknown.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_UNKNOWN) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onUnknown() { + return onUnknown; + } + + /** Return true if this AmdEvents object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AmdEventsImpl amdEvents = (AmdEventsImpl) o; + return Objects.equals(this.onHuman, amdEvents.onHuman) + && Objects.equals(this.onMachine, amdEvents.onMachine) + && Objects.equals(this.onBeep, amdEvents.onBeep) + && Objects.equals(this.onUnknown, amdEvents.onUnknown); + } + + @Override + public int hashCode() { + return Objects.hash(onHuman, onMachine, onBeep, onUnknown); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class AmdEventsImpl {\n"); + sb.append(" onHuman: ").append(toIndentedString(onHuman)).append("\n"); + sb.append(" onMachine: ").append(toIndentedString(onMachine)).append("\n"); + sb.append(" onBeep: ").append(toIndentedString(onBeep)).append("\n"); + sb.append(" onUnknown: ").append(toIndentedString(onUnknown)).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 AmdEvents.Builder { + OptionalValue> onHuman = OptionalValue.empty(); + OptionalValue> onMachine = OptionalValue.empty(); + OptionalValue> onBeep = OptionalValue.empty(); + OptionalValue> onUnknown = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_ON_HUMAN) + public Builder setOnHuman(List onHuman) { + this.onHuman = OptionalValue.of(onHuman); + return this; + } + + @JsonProperty(JSON_PROPERTY_ON_MACHINE) + public Builder setOnMachine(List onMachine) { + this.onMachine = OptionalValue.of(onMachine); + return this; + } + + @JsonProperty(JSON_PROPERTY_ON_BEEP) + public Builder setOnBeep(List onBeep) { + this.onBeep = OptionalValue.of(onBeep); + return this; + } + + @JsonProperty(JSON_PROPERTY_ON_UNKNOWN) + public Builder setOnUnknown(List onUnknown) { + this.onUnknown = OptionalValue.of(onUnknown); + return this; + } + + public AmdEvents build() { + return new AmdEventsImpl(onHuman, onMachine, onBeep, onUnknown); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/AnswerCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/AnswerCommand.java new file mode 100644 index 000000000..f2fa37470 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/AnswerCommand.java @@ -0,0 +1,35 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; + +/** + * Answers an inbound call leg. This is a non-blocking command — execution continues to the next + * command in the sequence immediately after the answer is initiated. + */ +@JsonDeserialize(builder = AnswerCommandImpl.Builder.class) +public interface AnswerCommand extends SvamlCommand { + + /** Default answer command, to be used instead of building an empty one */ + AnswerCommand ANSWER_COMMAND = AnswerCommand.builder().build(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new AnswerCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * Create instance + * + * @return The instance build with current builder values + */ + AnswerCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/AnswerCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/AnswerCommandImpl.java new file mode 100644 index 000000000..de509d08f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/AnswerCommandImpl.java @@ -0,0 +1,122 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({AnswerCommandImpl.JSON_PROPERTY_COMMAND}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class AnswerCommandImpl implements AnswerCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value answer. */ + public static class CommandEnum extends EnumDynamic { + /** The answer command. */ + public static final CommandEnum ANSWER = new CommandEnum("answer"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CommandEnum.class, CommandEnum::new, Arrays.asList(ANSWER)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public AnswerCommandImpl() {} + + protected AnswerCommandImpl(OptionalValue command) { + this.command = command; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + /** Return true if this AnswerCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AnswerCommandImpl answerCommand = (AnswerCommandImpl) o; + return Objects.equals(this.command, answerCommand.command); + } + + @Override + public int hashCode() { + return Objects.hash(command); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class AnswerCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).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 AnswerCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.ANSWER); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.ANSWER)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.ANSWER, command)); + } + return this; + } + + public AnswerCommand build() { + return new AnswerCommandImpl(command); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/BridgeCallCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/BridgeCallCommand.java new file mode 100644 index 000000000..58829c20b --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/BridgeCallCommand.java @@ -0,0 +1,65 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; + +/** + * Adds the current call to a bridge, enabling bidirectional audio communication with other calls in + * the same session. This is a non-blocking command — execution continues to the next command in the + * sequence immediately after the call joins the bridge. + * + *

Bridges are created automatically when referenced by name. If a bridge with the specified name + * already exists, the call joins that bridge; otherwise, a new bridge is created. + */ +@JsonDeserialize(builder = BridgeCallCommandImpl.Builder.class) +public interface BridgeCallCommand extends SvamlCommand { + + /** + * Name of the bridge to join. If no bridge with this name exists in the session, a new one is + * created automatically. + * + *

Field is required + * + * @return bridgeName + */ + String getBridgeName(); + + /** + * Create a bridgeCall command joining the given bridge + * + * @param bridgeName see {@link #getBridgeName()} + * @return A new BridgeCallCommand + */ + static BridgeCallCommand of(String bridgeName) { + return builder().setBridgeName(bridgeName).build(); + } + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new BridgeCallCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param bridgeName see getter + * @return Current builder + * @see #getBridgeName + */ + Builder setBridgeName(String bridgeName); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + BridgeCallCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/BridgeCallCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/BridgeCallCommandImpl.java new file mode 100644 index 000000000..ec7e2018d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/BridgeCallCommandImpl.java @@ -0,0 +1,151 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({ + BridgeCallCommandImpl.JSON_PROPERTY_COMMAND, + BridgeCallCommandImpl.JSON_PROPERTY_BRIDGE_NAME +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class BridgeCallCommandImpl implements BridgeCallCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value bridgeCall. */ + public static class CommandEnum extends EnumDynamic { + /** The bridgeCall command. */ + public static final CommandEnum BRIDGE_CALL = new CommandEnum("bridgeCall"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CommandEnum.class, CommandEnum::new, Arrays.asList(BRIDGE_CALL)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_BRIDGE_NAME = "bridgeName"; + + private OptionalValue bridgeName; + + public BridgeCallCommandImpl() {} + + protected BridgeCallCommandImpl( + OptionalValue command, OptionalValue bridgeName) { + this.command = command; + this.bridgeName = bridgeName; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public String getBridgeName() { + return bridgeName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_BRIDGE_NAME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue bridgeName() { + return bridgeName; + } + + /** Return true if this BridgeCallCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BridgeCallCommandImpl bridgeCallCommand = (BridgeCallCommandImpl) o; + return Objects.equals(this.command, bridgeCallCommand.command) + && Objects.equals(this.bridgeName, bridgeCallCommand.bridgeName); + } + + @Override + public int hashCode() { + return Objects.hash(command, bridgeName); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class BridgeCallCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" bridgeName: ").append(toIndentedString(bridgeName)).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 BridgeCallCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.BRIDGE_CALL); + OptionalValue bridgeName = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.BRIDGE_CALL)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.BRIDGE_CALL, command)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_BRIDGE_NAME, required = true) + public Builder setBridgeName(String bridgeName) { + this.bridgeName = OptionalValue.of(bridgeName); + return this; + } + + public BridgeCallCommand build() { + return new BridgeCallCommandImpl(command, bridgeName); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/CallEvents.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/CallEvents.java new file mode 100644 index 000000000..cb0546568 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/CallEvents.java @@ -0,0 +1,126 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.List; + +/** Webhook to handle call events, used when callBehaviors are set to WEBHOOK */ +@JsonDeserialize(builder = CallEventsImpl.Builder.class) +public interface CallEvents { + + /** + * SVAML commands to be executed when the call is answered + * + * @return onAnswer + */ + List getOnAnswer(); + + /** + * SVAML commands to be executed when the call is busy + * + * @return onBusy + */ + List getOnBusy(); + + /** + * SVAML commands to be executed when the call is rejected + * + * @return onReject + */ + List getOnReject(); + + /** + * SVAML commands to be executed when the call is timed out + * + * @return onTimeout + */ + List getOnTimeout(); + + /** + * SVAML commands to be executed when the call is hung up + * + * @return onHangup + */ + List getOnHangup(); + + /** + * SVAML commands to be executed when the call fails + * + * @return onFailure + */ + List getOnFailure(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new CallEventsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param onAnswer see getter + * @return Current builder + * @see #getOnAnswer + */ + Builder setOnAnswer(List onAnswer); + + /** + * see getter + * + * @param onBusy see getter + * @return Current builder + * @see #getOnBusy + */ + Builder setOnBusy(List onBusy); + + /** + * see getter + * + * @param onReject see getter + * @return Current builder + * @see #getOnReject + */ + Builder setOnReject(List onReject); + + /** + * see getter + * + * @param onTimeout see getter + * @return Current builder + * @see #getOnTimeout + */ + Builder setOnTimeout(List onTimeout); + + /** + * see getter + * + * @param onHangup see getter + * @return Current builder + * @see #getOnHangup + */ + Builder setOnHangup(List onHangup); + + /** + * see getter + * + * @param onFailure see getter + * @return Current builder + * @see #getOnFailure + */ + Builder setOnFailure(List onFailure); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + CallEvents build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/CallEventsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/CallEventsImpl.java new file mode 100644 index 000000000..61bd31c9b --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/CallEventsImpl.java @@ -0,0 +1,230 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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.v2.svaml.SvamlCommand; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + CallEventsImpl.JSON_PROPERTY_ON_ANSWER, + CallEventsImpl.JSON_PROPERTY_ON_BUSY, + CallEventsImpl.JSON_PROPERTY_ON_REJECT, + CallEventsImpl.JSON_PROPERTY_ON_TIMEOUT, + CallEventsImpl.JSON_PROPERTY_ON_HANGUP, + CallEventsImpl.JSON_PROPERTY_ON_FAILURE +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class CallEventsImpl implements CallEvents { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ON_ANSWER = "onAnswer"; + + private OptionalValue> onAnswer; + + public static final String JSON_PROPERTY_ON_BUSY = "onBusy"; + + private OptionalValue> onBusy; + + public static final String JSON_PROPERTY_ON_REJECT = "onReject"; + + private OptionalValue> onReject; + + public static final String JSON_PROPERTY_ON_TIMEOUT = "onTimeout"; + + private OptionalValue> onTimeout; + + public static final String JSON_PROPERTY_ON_HANGUP = "onHangup"; + + private OptionalValue> onHangup; + + public static final String JSON_PROPERTY_ON_FAILURE = "onFailure"; + + private OptionalValue> onFailure; + + public CallEventsImpl() {} + + protected CallEventsImpl( + OptionalValue> onAnswer, + OptionalValue> onBusy, + OptionalValue> onReject, + OptionalValue> onTimeout, + OptionalValue> onHangup, + OptionalValue> onFailure) { + this.onAnswer = onAnswer; + this.onBusy = onBusy; + this.onReject = onReject; + this.onTimeout = onTimeout; + this.onHangup = onHangup; + this.onFailure = onFailure; + } + + @JsonIgnore + public List getOnAnswer() { + return onAnswer.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_ANSWER) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onAnswer() { + return onAnswer; + } + + @JsonIgnore + public List getOnBusy() { + return onBusy.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_BUSY) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onBusy() { + return onBusy; + } + + @JsonIgnore + public List getOnReject() { + return onReject.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_REJECT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onReject() { + return onReject; + } + + @JsonIgnore + public List getOnTimeout() { + return onTimeout.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_TIMEOUT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onTimeout() { + return onTimeout; + } + + @JsonIgnore + public List getOnHangup() { + return onHangup.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_HANGUP) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onHangup() { + return onHangup; + } + + @JsonIgnore + public List getOnFailure() { + return onFailure.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_FAILURE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onFailure() { + return onFailure; + } + + /** Return true if this CallEvents object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CallEventsImpl callEvents = (CallEventsImpl) o; + return Objects.equals(this.onAnswer, callEvents.onAnswer) + && Objects.equals(this.onBusy, callEvents.onBusy) + && Objects.equals(this.onReject, callEvents.onReject) + && Objects.equals(this.onTimeout, callEvents.onTimeout) + && Objects.equals(this.onHangup, callEvents.onHangup) + && Objects.equals(this.onFailure, callEvents.onFailure); + } + + @Override + public int hashCode() { + return Objects.hash(onAnswer, onBusy, onReject, onTimeout, onHangup, onFailure); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class CallEventsImpl {\n"); + sb.append(" onAnswer: ").append(toIndentedString(onAnswer)).append("\n"); + sb.append(" onBusy: ").append(toIndentedString(onBusy)).append("\n"); + sb.append(" onReject: ").append(toIndentedString(onReject)).append("\n"); + sb.append(" onTimeout: ").append(toIndentedString(onTimeout)).append("\n"); + sb.append(" onHangup: ").append(toIndentedString(onHangup)).append("\n"); + sb.append(" onFailure: ").append(toIndentedString(onFailure)).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 CallEvents.Builder { + OptionalValue> onAnswer = OptionalValue.empty(); + OptionalValue> onBusy = OptionalValue.empty(); + OptionalValue> onReject = OptionalValue.empty(); + OptionalValue> onTimeout = OptionalValue.empty(); + OptionalValue> onHangup = OptionalValue.empty(); + OptionalValue> onFailure = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_ON_ANSWER) + public Builder setOnAnswer(List onAnswer) { + this.onAnswer = OptionalValue.of(onAnswer); + return this; + } + + @JsonProperty(JSON_PROPERTY_ON_BUSY) + public Builder setOnBusy(List onBusy) { + this.onBusy = OptionalValue.of(onBusy); + return this; + } + + @JsonProperty(JSON_PROPERTY_ON_REJECT) + public Builder setOnReject(List onReject) { + this.onReject = OptionalValue.of(onReject); + return this; + } + + @JsonProperty(JSON_PROPERTY_ON_TIMEOUT) + public Builder setOnTimeout(List onTimeout) { + this.onTimeout = OptionalValue.of(onTimeout); + return this; + } + + @JsonProperty(JSON_PROPERTY_ON_HANGUP) + public Builder setOnHangup(List onHangup) { + this.onHangup = OptionalValue.of(onHangup); + return this; + } + + @JsonProperty(JSON_PROPERTY_ON_FAILURE) + public Builder setOnFailure(List onFailure) { + this.onFailure = OptionalValue.of(onFailure); + return this; + } + + public CallEvents build() { + return new CallEventsImpl(onAnswer, onBusy, onReject, onTimeout, onHangup, onFailure); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/DialCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/DialCommand.java new file mode 100644 index 000000000..3798d93ea --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/DialCommand.java @@ -0,0 +1,147 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.destination.CallDestination; +import com.sinch.sdk.domains.voice.models.v2.destination.CallOrigin; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; + +/** + * Initiates a new outbound call leg within the current session. + * + *

This is a non-blocking command — the next command in the sequence executes immediately while + * the call is being established in parallel. Call lifecycle events (answer, busy, reject, timeout, + * hangup, failure) are handled via the events property. + * + *

The from and to endpoint types should ideally match. If they differ, + * the platform attempts to convert the from value to be compatible with the to + * type. For example, PSTN supports only E.164 phone numbers, so a SIP address such as + * sip:46701234567@acme.se can be converted to an E.164 number. If the from + * value cannot be converted, it defaults to null (anonymous). + */ +@JsonDeserialize(builder = DialCommandImpl.Builder.class) +public interface DialCommand extends SvamlCommand { + + /** + * Identifier for this call leg within the session. Must be unique across all active call legs in + * the session. + * + *

Other commands (e.g., hangup) can reference this name to target this specific + * leg. + * + * @return callName + */ + String getCallName(); + + /** + * Call origin - Phone Number or SIP endpoint + * + * @return from + */ + CallOrigin getFrom(); + + /** + * Call destination - Phone Number or Stream URI + * + *

Field is required + * + * @return to + */ + CallDestination getTo(); + + /** + * Maximum time in seconds to wait for the call to be answered. If the timeout expires without an + * answer, the onTimeout event is triggered. + * + * @return dialTimeoutDurationSeconds + */ + Integer getDialTimeoutDurationSeconds(); + + /** + * Maximum duration of the call in seconds. The call is terminated automatically when this limit + * is reached. + * + * @return maxCallDurationSeconds + */ + Integer getMaxCallDurationSeconds(); + + /** + * Webhook to handle call events, used when callBehaviors are set to WEBHOOK + * + * @return events + */ + CallEvents getEvents(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new DialCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param callName see getter + * @return Current builder + * @see #getCallName + */ + Builder setCallName(String callName); + + /** + * see getter + * + * @param from see getter + * @return Current builder + * @see #getFrom + */ + Builder setFrom(CallOrigin from); + + /** + * see getter + * + * @param to see getter + * @return Current builder + * @see #getTo + */ + Builder setTo(CallDestination to); + + /** + * see getter + * + * @param dialTimeoutDurationSeconds see getter + * @return Current builder + * @see #getDialTimeoutDurationSeconds + */ + Builder setDialTimeoutDurationSeconds(Integer dialTimeoutDurationSeconds); + + /** + * see getter + * + * @param maxCallDurationSeconds see getter + * @return Current builder + * @see #getMaxCallDurationSeconds + */ + Builder setMaxCallDurationSeconds(Integer maxCallDurationSeconds); + + /** + * see getter + * + * @param events see getter + * @return Current builder + * @see #getEvents + */ + Builder setEvents(CallEvents events); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + DialCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/DialCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/DialCommandImpl.java new file mode 100644 index 000000000..1c0e4c89d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/DialCommandImpl.java @@ -0,0 +1,296 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.destination.CallDestination; +import com.sinch.sdk.domains.voice.models.v2.destination.CallOrigin; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({ + DialCommandImpl.JSON_PROPERTY_COMMAND, + DialCommandImpl.JSON_PROPERTY_CALL_NAME, + DialCommandImpl.JSON_PROPERTY_FROM, + DialCommandImpl.JSON_PROPERTY_TO, + DialCommandImpl.JSON_PROPERTY_DIAL_TIMEOUT_DURATION_SECONDS, + DialCommandImpl.JSON_PROPERTY_MAX_CALL_DURATION_SECONDS, + DialCommandImpl.JSON_PROPERTY_EVENTS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class DialCommandImpl implements DialCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value dial. */ + public static class CommandEnum extends EnumDynamic { + /** The dial command. */ + public static final CommandEnum DIAL = new CommandEnum("dial"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CommandEnum.class, CommandEnum::new, Arrays.asList(DIAL)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_CALL_NAME = "callName"; + + private OptionalValue callName; + + public static final String JSON_PROPERTY_FROM = "from"; + + private OptionalValue from; + + public static final String JSON_PROPERTY_TO = "to"; + + private OptionalValue to; + + public static final String JSON_PROPERTY_DIAL_TIMEOUT_DURATION_SECONDS = + "dialTimeoutDurationSeconds"; + + private OptionalValue dialTimeoutDurationSeconds; + + public static final String JSON_PROPERTY_MAX_CALL_DURATION_SECONDS = "maxCallDurationSeconds"; + + private OptionalValue maxCallDurationSeconds; + + public static final String JSON_PROPERTY_EVENTS = "events"; + + private OptionalValue events; + + public DialCommandImpl() {} + + protected DialCommandImpl( + OptionalValue command, + OptionalValue callName, + OptionalValue from, + OptionalValue to, + OptionalValue dialTimeoutDurationSeconds, + OptionalValue maxCallDurationSeconds, + OptionalValue events) { + this.command = command; + this.callName = callName; + this.from = from; + this.to = to; + this.dialTimeoutDurationSeconds = dialTimeoutDurationSeconds; + this.maxCallDurationSeconds = maxCallDurationSeconds; + this.events = events; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public String getCallName() { + return callName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue callName() { + return callName; + } + + @JsonIgnore + public CallOrigin getFrom() { + return from.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_FROM) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue from() { + return from; + } + + @JsonIgnore + public CallDestination getTo() { + return to.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TO) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue to() { + return to; + } + + @JsonIgnore + public Integer getDialTimeoutDurationSeconds() { + return dialTimeoutDurationSeconds.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DIAL_TIMEOUT_DURATION_SECONDS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue dialTimeoutDurationSeconds() { + return dialTimeoutDurationSeconds; + } + + @JsonIgnore + public Integer getMaxCallDurationSeconds() { + return maxCallDurationSeconds.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MAX_CALL_DURATION_SECONDS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue maxCallDurationSeconds() { + return maxCallDurationSeconds; + } + + @JsonIgnore + public CallEvents getEvents() { + return events.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue events() { + return events; + } + + /** Return true if this DialCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DialCommandImpl dialCommand = (DialCommandImpl) o; + return Objects.equals(this.command, dialCommand.command) + && Objects.equals(this.callName, dialCommand.callName) + && Objects.equals(this.from, dialCommand.from) + && Objects.equals(this.to, dialCommand.to) + && Objects.equals(this.dialTimeoutDurationSeconds, dialCommand.dialTimeoutDurationSeconds) + && Objects.equals(this.maxCallDurationSeconds, dialCommand.maxCallDurationSeconds) + && Objects.equals(this.events, dialCommand.events); + } + + @Override + public int hashCode() { + return Objects.hash( + command, callName, from, to, dialTimeoutDurationSeconds, maxCallDurationSeconds, events); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class DialCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" callName: ").append(toIndentedString(callName)).append("\n"); + sb.append(" from: ").append(toIndentedString(from)).append("\n"); + sb.append(" to: ").append(toIndentedString(to)).append("\n"); + sb.append(" dialTimeoutDurationSeconds: ") + .append(toIndentedString(dialTimeoutDurationSeconds)) + .append("\n"); + sb.append(" maxCallDurationSeconds: ") + .append(toIndentedString(maxCallDurationSeconds)) + .append("\n"); + sb.append(" events: ").append(toIndentedString(events)).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 DialCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.DIAL); + OptionalValue callName = OptionalValue.empty(); + OptionalValue from = OptionalValue.empty(); + OptionalValue to = OptionalValue.empty(); + OptionalValue dialTimeoutDurationSeconds = OptionalValue.empty(); + OptionalValue maxCallDurationSeconds = OptionalValue.empty(); + OptionalValue events = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.DIAL)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.DIAL, command)); + } + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_NAME) + public Builder setCallName(String callName) { + this.callName = OptionalValue.of(callName); + return this; + } + + @JsonProperty(JSON_PROPERTY_FROM) + public Builder setFrom(CallOrigin from) { + this.from = OptionalValue.of(from); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_TO, required = true) + public Builder setTo(CallDestination to) { + this.to = OptionalValue.of(to); + return this; + } + + @JsonProperty(JSON_PROPERTY_DIAL_TIMEOUT_DURATION_SECONDS) + public Builder setDialTimeoutDurationSeconds(Integer dialTimeoutDurationSeconds) { + this.dialTimeoutDurationSeconds = OptionalValue.of(dialTimeoutDurationSeconds); + return this; + } + + @JsonProperty(JSON_PROPERTY_MAX_CALL_DURATION_SECONDS) + public Builder setMaxCallDurationSeconds(Integer maxCallDurationSeconds) { + this.maxCallDurationSeconds = OptionalValue.of(maxCallDurationSeconds); + return this; + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + public Builder setEvents(CallEvents events) { + this.events = OptionalValue.of(events); + return this; + } + + public DialCommand build() { + return new DialCommandImpl( + command, callName, from, to, dialTimeoutDurationSeconds, maxCallDurationSeconds, events); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/HangupCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/HangupCommand.java new file mode 100644 index 000000000..4391c5157 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/HangupCommand.java @@ -0,0 +1,69 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; + +/** + * Ends a call leg. This is a non-blocking command — execution continues to the next command in the + * sequence even though the call has been ended. The onHangup event is triggered for + * the call leg that was ended. + * + *

Any subsequent commands that target the ended call leg (such as messages or other + * media commands) are valid but will not be executed. Commands that operate independently — such as + * initiating a new call with dial — will execute normally. This makes it possible, for + * example, to end one call and immediately start another within the same sequence. + */ +@JsonDeserialize(builder = HangupCommandImpl.Builder.class) +public interface HangupCommand extends SvamlCommand { + + /** + * Name of the call leg to end, as set by callName in the dial command. + * + *

If omitted, the current call leg is ended. + * + * @return callName + */ + String getCallName(); + + /** Hangup command for the current call leg, to be used instead of building an empty one */ + HangupCommand HANGUP_COMMAND = HangupCommand.builder().build(); + + /** + * Create a hangup command for the given call leg + * + * @param callName see {@link #getCallName()} + * @return A new HangupCommand + */ + static HangupCommand of(String callName) { + return builder().setCallName(callName).build(); + } + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new HangupCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param callName see getter + * @return Current builder + * @see #getCallName + */ + Builder setCallName(String callName); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + HangupCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/HangupCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/HangupCommandImpl.java new file mode 100644 index 000000000..e50741672 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/HangupCommandImpl.java @@ -0,0 +1,150 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({ + HangupCommandImpl.JSON_PROPERTY_COMMAND, + HangupCommandImpl.JSON_PROPERTY_CALL_NAME +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class HangupCommandImpl implements HangupCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value hangup. */ + public static class CommandEnum extends EnumDynamic { + /** The hangup command. */ + public static final CommandEnum HANGUP = new CommandEnum("hangup"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CommandEnum.class, CommandEnum::new, Arrays.asList(HANGUP)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_CALL_NAME = "callName"; + + private OptionalValue callName; + + public HangupCommandImpl() {} + + protected HangupCommandImpl(OptionalValue command, OptionalValue callName) { + this.command = command; + this.callName = callName; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public String getCallName() { + return callName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CALL_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue callName() { + return callName; + } + + /** Return true if this HangupCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HangupCommandImpl hangupCommand = (HangupCommandImpl) o; + return Objects.equals(this.command, hangupCommand.command) + && Objects.equals(this.callName, hangupCommand.callName); + } + + @Override + public int hashCode() { + return Objects.hash(command, callName); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class HangupCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" callName: ").append(toIndentedString(callName)).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 HangupCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.HANGUP); + OptionalValue callName = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.HANGUP)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.HANGUP, command)); + } + return this; + } + + @JsonProperty(JSON_PROPERTY_CALL_NAME) + public Builder setCallName(String callName) { + this.callName = OptionalValue.of(callName); + return this; + } + + public HangupCommand build() { + return new HangupCommandImpl(command, callName); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/PauseCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/PauseCommand.java new file mode 100644 index 000000000..5d052d036 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/PauseCommand.java @@ -0,0 +1,63 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; + +/** + * Delays execution of the next command in the sequence for a specified duration. This is a blocking + * command — no further commands execute until the pause completes. + * + *

The pause does not affect call audio; the call remains connected and audio continues + * uninterrupted. + */ +@JsonDeserialize(builder = PauseCommandImpl.Builder.class) +public interface PauseCommand extends SvamlCommand { + + /** + * Duration of the pause in milliseconds. + * + *

Field is required + * + * @return durationMilliseconds + */ + Integer getDurationMilliseconds(); + + /** + * Create a pause command of the given duration + * + * @param durationMilliseconds see {@link #getDurationMilliseconds()} + * @return A new PauseCommand + */ + static PauseCommand of(int durationMilliseconds) { + return builder().setDurationMilliseconds(durationMilliseconds).build(); + } + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new PauseCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param durationMilliseconds see getter + * @return Current builder + * @see #getDurationMilliseconds + */ + Builder setDurationMilliseconds(Integer durationMilliseconds); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + PauseCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/PauseCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/PauseCommandImpl.java new file mode 100644 index 000000000..f61a75ba3 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/calls/PauseCommandImpl.java @@ -0,0 +1,153 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({ + PauseCommandImpl.JSON_PROPERTY_COMMAND, + PauseCommandImpl.JSON_PROPERTY_DURATION_MILLISECONDS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class PauseCommandImpl implements PauseCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value pause. */ + public static class CommandEnum extends EnumDynamic { + /** The pause command. */ + public static final CommandEnum PAUSE = new CommandEnum("pause"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CommandEnum.class, CommandEnum::new, Arrays.asList(PAUSE)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_DURATION_MILLISECONDS = "durationMilliseconds"; + + private OptionalValue durationMilliseconds; + + public PauseCommandImpl() {} + + protected PauseCommandImpl( + OptionalValue command, OptionalValue durationMilliseconds) { + this.command = command; + this.durationMilliseconds = durationMilliseconds; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public Integer getDurationMilliseconds() { + return durationMilliseconds.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DURATION_MILLISECONDS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue durationMilliseconds() { + return durationMilliseconds; + } + + /** Return true if this PauseCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PauseCommandImpl pauseCommand = (PauseCommandImpl) o; + return Objects.equals(this.command, pauseCommand.command) + && Objects.equals(this.durationMilliseconds, pauseCommand.durationMilliseconds); + } + + @Override + public int hashCode() { + return Objects.hash(command, durationMilliseconds); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class PauseCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" durationMilliseconds: ") + .append(toIndentedString(durationMilliseconds)) + .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 PauseCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.PAUSE); + OptionalValue durationMilliseconds = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.PAUSE)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.PAUSE, command)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_DURATION_MILLISECONDS, required = true) + public Builder setDurationMilliseconds(Integer durationMilliseconds) { + this.durationMilliseconds = OptionalValue.of(durationMilliseconds); + return this; + } + + public PauseCommand build() { + return new PauseCommandImpl(command, durationMilliseconds); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/CustomEventCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/CustomEventCommand.java new file mode 100644 index 000000000..74629137e --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/CustomEventCommand.java @@ -0,0 +1,93 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.customevents; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; + +/** + * Triggers a mid-call Custom Event: sends a Sinch Event request to url and executes + * the SVAML commands returned in the response. + * + *

Sent on the wire as the webhook command. + */ +@JsonDeserialize(builder = CustomEventCommandImpl.Builder.class) +public interface CustomEventCommand extends SvamlCommand { + + /** + * Name for this Custom Event. When triggered, the Sinch Event request's event + * property will contain this name prepended with call.customEvent.. + * + *

For example, if customEventName is set to "my.custom.event", the + * event will be delivered as "call.customEvent.my.custom.event". + * + *

Field is required + * + * @return customEventName + */ + String getCustomEventName(); + + /** + * URL of the endpoint to send the mid-call Sinch Event to. + * + *

Field is required + * + * @return url + */ + String getUrl(); + + /** + * Fallback URL used when the primary URL fails. + * + *

A failed request is re-sent to this URL immediately. After repeated consecutive failures of + * the primary URL, requests are sent only here until the primary URL recovers. + * + * @return fallbackUrl + */ + String getFallbackUrl(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new CustomEventCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param customEventName see getter + * @return Current builder + * @see #getCustomEventName + */ + Builder setCustomEventName(String customEventName); + + /** + * see getter + * + * @param url see getter + * @return Current builder + * @see #getUrl + */ + Builder setUrl(String url); + + /** + * see getter + * + * @param fallbackUrl see getter + * @return Current builder + * @see #getFallbackUrl + */ + Builder setFallbackUrl(String fallbackUrl); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + CustomEventCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/CustomEventCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/CustomEventCommandImpl.java new file mode 100644 index 000000000..ae7a40678 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/CustomEventCommandImpl.java @@ -0,0 +1,206 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.customevents; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({ + CustomEventCommandImpl.JSON_PROPERTY_COMMAND, + CustomEventCommandImpl.JSON_PROPERTY_WEBHOOK_NAME, + CustomEventCommandImpl.JSON_PROPERTY_URL, + CustomEventCommandImpl.JSON_PROPERTY_FALLBACK_URL +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class CustomEventCommandImpl implements CustomEventCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value webhook. */ + public static class CommandEnum extends EnumDynamic { + /** The webhook command. */ + public static final CommandEnum WEBHOOK = new CommandEnum("webhook"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CommandEnum.class, CommandEnum::new, Arrays.asList(WEBHOOK)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_WEBHOOK_NAME = "webhookName"; + + private OptionalValue customEventName; + + public static final String JSON_PROPERTY_URL = "url"; + + private OptionalValue url; + + public static final String JSON_PROPERTY_FALLBACK_URL = "fallbackUrl"; + + private OptionalValue fallbackUrl; + + public CustomEventCommandImpl() {} + + protected CustomEventCommandImpl( + OptionalValue command, + OptionalValue customEventName, + OptionalValue url, + OptionalValue fallbackUrl) { + this.command = command; + this.customEventName = customEventName; + this.url = url; + this.fallbackUrl = fallbackUrl; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public String getCustomEventName() { + return customEventName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_WEBHOOK_NAME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue customEventName() { + return customEventName; + } + + @JsonIgnore + public String getUrl() { + return url.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_URL) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue url() { + return url; + } + + @JsonIgnore + public String getFallbackUrl() { + return fallbackUrl.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_FALLBACK_URL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue fallbackUrl() { + return fallbackUrl; + } + + /** Return true if this CustomEventCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CustomEventCommandImpl customEventCommand = (CustomEventCommandImpl) o; + return Objects.equals(this.command, customEventCommand.command) + && Objects.equals(this.customEventName, customEventCommand.customEventName) + && Objects.equals(this.url, customEventCommand.url) + && Objects.equals(this.fallbackUrl, customEventCommand.fallbackUrl); + } + + @Override + public int hashCode() { + return Objects.hash(command, customEventName, url, fallbackUrl); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class CustomEventCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" customEventName: ").append(toIndentedString(customEventName)).append("\n"); + sb.append(" url: ").append(toIndentedString(url)).append("\n"); + sb.append(" fallbackUrl: ").append(toIndentedString(fallbackUrl)).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 CustomEventCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.WEBHOOK); + OptionalValue customEventName = OptionalValue.empty(); + OptionalValue url = OptionalValue.empty(); + OptionalValue fallbackUrl = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.WEBHOOK)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.WEBHOOK, command)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_WEBHOOK_NAME, required = true) + public Builder setCustomEventName(String customEventName) { + this.customEventName = OptionalValue.of(customEventName); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_URL, required = true) + public Builder setUrl(String url) { + this.url = OptionalValue.of(url); + return this; + } + + @JsonProperty(JSON_PROPERTY_FALLBACK_URL) + public Builder setFallbackUrl(String fallbackUrl) { + this.fallbackUrl = OptionalValue.of(fallbackUrl); + return this; + } + + public CustomEventCommand build() { + return new CustomEventCommandImpl(command, customEventName, url, fallbackUrl); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/internal/SvamlCommandInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/internal/SvamlCommandInternal.java new file mode 100644 index 000000000..8863ad872 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/internal/SvamlCommandInternal.java @@ -0,0 +1,6 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(using = SvamlCommandInternalImpl.SvamlCommandInternalImplDeserializer.class) +public interface SvamlCommandInternal {} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/internal/SvamlCommandInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/internal/SvamlCommandInternalImpl.java new file mode 100644 index 000000000..d6e1d691a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/internal/SvamlCommandInternalImpl.java @@ -0,0 +1,214 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.internal; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +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.v2.svaml.amd.AmdCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.AnswerCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.BridgeCallCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.DialCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.HangupCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.PauseCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.customevents.CustomEventCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.menu.GotoMenuCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.menu.MenuCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.StopMessagesCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.recording.StartRecordingCommandImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.recording.StopRecordingCommandImpl; +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 = SvamlCommandInternalImpl.SvamlCommandInternalImplDeserializer.class) +@JsonSerialize(using = SvamlCommandInternalImpl.SvamlCommandInternalImplSerializer.class) +public class SvamlCommandInternalImpl extends AbstractOpenApiSchema + implements SvamlCommandInternal { + private static final Logger log = Logger.getLogger(SvamlCommandInternalImpl.class.getName()); + + public static final class SvamlCommandInternalImplSerializer + extends StdSerializer { + private static final long serialVersionUID = 1L; + + public SvamlCommandInternalImplSerializer(Class t) { + super(t); + } + + public SvamlCommandInternalImplSerializer() { + this(null); + } + + @Override + public void serialize( + SvamlCommandInternalImpl value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static final class SvamlCommandInternalImplDeserializer + extends StdDeserializer { + + private static final long serialVersionUID = 1L; + + public SvamlCommandInternalImplDeserializer() { + this(SvamlCommandInternalImpl.class); + } + + public SvamlCommandInternalImplDeserializer(Class vc) { + super(vc); + } + + @Override + public SvamlCommandInternalImpl deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + JsonNode discriminator = tree.get("command"); + String discriminatorValue = null != discriminator ? discriminator.asText() : null; + Class cls = null != discriminatorValue ? mappings.get(discriminatorValue) : null; + if (null == cls) { + throw new IOException( + String.format( + "Failed deserialization for SvamlCommandInternalImpl: unknown 'command' value '%s'." + + " Possible values: %s", + discriminatorValue, mappings.keySet())); + } + Object deserialized = tree.traverse(jp.getCodec()).readValueAs(cls); + SvamlCommandInternalImpl ret = new SvamlCommandInternalImpl(); + ret.setActualInstance(deserialized); + return ret; + } + + /** Handle deserialization of the 'null' value. */ + @Override + public SvamlCommandInternalImpl getNullValue(DeserializationContext ctxt) + throws JsonMappingException { + throw new JsonMappingException(ctxt.getParser(), "SvamlCommandInternalImpl cannot be null"); + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap<>(); + + // discriminator value -> class + private static final Map> mappings = new HashMap<>(); + + public SvamlCommandInternalImpl() { + super("oneOf", Boolean.FALSE); + } + + static { + schemas.put("AmdCommandImpl", AmdCommandImpl.class); + schemas.put("AnswerCommandImpl", AnswerCommandImpl.class); + schemas.put("BridgeCallCommandImpl", BridgeCallCommandImpl.class); + schemas.put("DialCommandImpl", DialCommandImpl.class); + schemas.put("GotoMenuCommandImpl", GotoMenuCommandImpl.class); + schemas.put("HangupCommandImpl", HangupCommandImpl.class); + schemas.put("MenuCommandImpl", MenuCommandImpl.class); + schemas.put("MessagesCommandImpl", MessagesCommandImpl.class); + schemas.put("PauseCommandImpl", PauseCommandImpl.class); + schemas.put("StartRecordingCommandImpl", StartRecordingCommandImpl.class); + schemas.put("StopMessagesCommandImpl", StopMessagesCommandImpl.class); + schemas.put("StopRecordingCommandImpl", StopRecordingCommandImpl.class); + schemas.put("CustomEventCommandImpl", CustomEventCommandImpl.class); + JSONNavigator.registerDescendants( + SvamlCommandInternalImpl.class, Collections.unmodifiableMap(schemas)); + // Initialize and register the discriminator mappings. + mappings.put("amd", AmdCommandImpl.class); + mappings.put("answer", AnswerCommandImpl.class); + mappings.put("bridgeCall", BridgeCallCommandImpl.class); + mappings.put("dial", DialCommandImpl.class); + mappings.put("gotoMenu", GotoMenuCommandImpl.class); + mappings.put("hangup", HangupCommandImpl.class); + mappings.put("menu", MenuCommandImpl.class); + mappings.put("messages", MessagesCommandImpl.class); + mappings.put("pause", PauseCommandImpl.class); + mappings.put("startRecording", StartRecordingCommandImpl.class); + mappings.put("stopMessages", StopMessagesCommandImpl.class); + mappings.put("stopRecording", StopRecordingCommandImpl.class); + mappings.put("webhook", CustomEventCommandImpl.class); + JSONNavigator.registerDiscriminator(SvamlCommandInternalImpl.class, "command", mappings); + } + + @Override + public Map> getSchemas() { + return SvamlCommandInternalImpl.schemas; + } + + /** + * Set the instance that matches the oneOf child schema, check the instance parameter is valid + * against the oneOf child schemas: AmdCommandImpl, AnswerCommandImpl, BridgeCallCommandImpl, + * DialCommandImpl, GotoMenuCommandImpl, HangupCommandImpl, MenuCommandImpl, MessagesCommandImpl, + * PauseCommandImpl, StartRecordingCommandImpl, StopMessagesCommandImpl, StopRecordingCommandImpl, + * CustomEventCommandImpl + */ + @Override + public void setActualInstance(Object instance) { + for (Class schema : schemas.values()) { + if (JSONNavigator.isInstanceOf(schema, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + } + throw new RuntimeException( + "Invalid instance type. Must be AmdCommandImpl, AnswerCommandImpl, BridgeCallCommandImpl," + + " DialCommandImpl, GotoMenuCommandImpl, HangupCommandImpl, MenuCommandImpl," + + " MessagesCommandImpl, PauseCommandImpl, StartRecordingCommandImpl," + + " StopMessagesCommandImpl, StopRecordingCommandImpl, CustomEventCommandImpl"); + } + + /** + * Get the actual instance, which can be the following: AmdCommandImpl, AnswerCommandImpl, + * BridgeCallCommandImpl, DialCommandImpl, GotoMenuCommandImpl, HangupCommandImpl, + * MenuCommandImpl, MessagesCommandImpl, PauseCommandImpl, StartRecordingCommandImpl, + * StopMessagesCommandImpl, StopRecordingCommandImpl, CustomEventCommandImpl + * + * @return The actual instance + */ + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + public static class Deserializer + extends StdDeserializer { + + public Deserializer() { + this(null); + } + + public Deserializer(Class vc) { + super(vc); + } + + @Override + public com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand deserialize( + JsonParser jp, DeserializationContext ctxt) throws IOException { + + Object deserialized = jp.readValueAs(SvamlCommandInternalImpl.class).getActualInstance(); + if (null == deserialized) { + return null; + } + if (!(deserialized instanceof com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand)) { + log.log(Level.SEVERE, "Input data does not match schema ", deserialized); + return null; + } + + return (com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand) deserialized; + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/GotoMenuCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/GotoMenuCommand.java new file mode 100644 index 000000000..501e445e7 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/GotoMenuCommand.java @@ -0,0 +1,63 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; + +/** + * Switches execution to another menu within the current menu context. The menu name must be a menu + * defined in the menu command. This is a blocking command — execution waits for the + * menu to complete before proceeding to the next command. + * + *

Important: this command can only be called within a menu execution context. + */ +@JsonDeserialize(builder = GotoMenuCommandImpl.Builder.class) +public interface GotoMenuCommand extends SvamlCommand { + + /** + * Name of the target menu to execute next. Must match a key in menus. + * + *

Field is required + * + * @return menuName + */ + String getMenuName(); + + /** + * Create a gotoMenu command jumping to the given menu + * + * @param menuName see {@link #getMenuName()} + * @return A new GotoMenuCommand + */ + static GotoMenuCommand of(String menuName) { + return builder().setMenuName(menuName).build(); + } + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new GotoMenuCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param menuName see getter + * @return Current builder + * @see #getMenuName + */ + Builder setMenuName(String menuName); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + GotoMenuCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/GotoMenuCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/GotoMenuCommandImpl.java new file mode 100644 index 000000000..681ffdda9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/GotoMenuCommandImpl.java @@ -0,0 +1,151 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({ + GotoMenuCommandImpl.JSON_PROPERTY_COMMAND, + GotoMenuCommandImpl.JSON_PROPERTY_MENU_NAME +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class GotoMenuCommandImpl implements GotoMenuCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value gotoMenu. */ + public static class CommandEnum extends EnumDynamic { + /** The gotoMenu command. */ + public static final CommandEnum GOTO_MENU = new CommandEnum("gotoMenu"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CommandEnum.class, CommandEnum::new, Arrays.asList(GOTO_MENU)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_MENU_NAME = "menuName"; + + private OptionalValue menuName; + + public GotoMenuCommandImpl() {} + + protected GotoMenuCommandImpl( + OptionalValue command, OptionalValue menuName) { + this.command = command; + this.menuName = menuName; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public String getMenuName() { + return menuName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MENU_NAME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue menuName() { + return menuName; + } + + /** Return true if this GotoMenuCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GotoMenuCommandImpl gotoMenuCommand = (GotoMenuCommandImpl) o; + return Objects.equals(this.command, gotoMenuCommand.command) + && Objects.equals(this.menuName, gotoMenuCommand.menuName); + } + + @Override + public int hashCode() { + return Objects.hash(command, menuName); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GotoMenuCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" menuName: ").append(toIndentedString(menuName)).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 GotoMenuCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.GOTO_MENU); + OptionalValue menuName = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.GOTO_MENU)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.GOTO_MENU, command)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_MENU_NAME, required = true) + public Builder setMenuName(String menuName) { + this.menuName = OptionalValue.of(menuName); + return this; + } + + public GotoMenuCommand build() { + return new GotoMenuCommandImpl(command, menuName); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuCommand.java new file mode 100644 index 000000000..4d5c524e5 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuCommand.java @@ -0,0 +1,71 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Map; + +/** + * Defines a set of named menus and executes them starting from startMenu. This is a blocking + * command — execution waits for the menu to complete before proceeding to the next command. + * + *

Each menu item configures prompts, input collection, timeout handling, and repeat behavior. + */ +@JsonDeserialize(builder = MenuCommandImpl.Builder.class) +public interface MenuCommand extends SvamlCommand { + + /** + * Name of the menu to execute first. Must match a key in menus. + * + *

Field is required + * + * @return startMenu + */ + String getStartMenu(); + + /** + * Map of menu definitions keyed by menu name. + * + *

Field is required + * + * @return menus + */ + Map getMenus(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new MenuCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param startMenu see getter + * @return Current builder + * @see #getStartMenu + */ + Builder setStartMenu(String startMenu); + + /** + * see getter + * + * @param menus see getter + * @return Current builder + * @see #getMenus + */ + Builder setMenus(Map menus); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + MenuCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuCommandImpl.java new file mode 100644 index 000000000..6a6149628 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuCommandImpl.java @@ -0,0 +1,180 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +@JsonPropertyOrder({ + MenuCommandImpl.JSON_PROPERTY_COMMAND, + MenuCommandImpl.JSON_PROPERTY_START_MENU, + MenuCommandImpl.JSON_PROPERTY_MENUS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class MenuCommandImpl implements MenuCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value menu. */ + public static class CommandEnum extends EnumDynamic { + /** The menu command. */ + public static final CommandEnum MENU = new CommandEnum("menu"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CommandEnum.class, CommandEnum::new, Arrays.asList(MENU)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_START_MENU = "startMenu"; + + private OptionalValue startMenu; + + public static final String JSON_PROPERTY_MENUS = "menus"; + + private OptionalValue> menus; + + public MenuCommandImpl() {} + + protected MenuCommandImpl( + OptionalValue command, + OptionalValue startMenu, + OptionalValue> menus) { + this.command = command; + this.startMenu = startMenu; + this.menus = menus; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public String getStartMenu() { + return startMenu.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_START_MENU) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue startMenu() { + return startMenu; + } + + @JsonIgnore + public Map getMenus() { + return menus.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MENUS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> menus() { + return menus; + } + + /** Return true if this MenuCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MenuCommandImpl menuCommand = (MenuCommandImpl) o; + return Objects.equals(this.command, menuCommand.command) + && Objects.equals(this.startMenu, menuCommand.startMenu) + && Objects.equals(this.menus, menuCommand.menus); + } + + @Override + public int hashCode() { + return Objects.hash(command, startMenu, menus); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class MenuCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" startMenu: ").append(toIndentedString(startMenu)).append("\n"); + sb.append(" menus: ").append(toIndentedString(menus)).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 MenuCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.MENU); + OptionalValue startMenu = OptionalValue.empty(); + OptionalValue> menus = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.MENU)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.MENU, command)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_START_MENU, required = true) + public Builder setStartMenu(String startMenu) { + this.startMenu = OptionalValue.of(startMenu); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_MENUS, required = true) + public Builder setMenus(Map menus) { + this.menus = OptionalValue.of(menus); + return this; + } + + public MenuCommand build() { + return new MenuCommandImpl(command, startMenu, menus); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuItem.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuItem.java new file mode 100644 index 000000000..9c28ba335 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuItem.java @@ -0,0 +1,251 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +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.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Defines a single menu step, including prompts, input handling rules, input-to-command matches, + * and failure handling. + * + *

Each collected input is matched against the values in the matches property. If no + * match succeeds, the onFailure commands are executed. + * + *

If no match succeeds and onFailure is not defined, a call.menu event + * including the collected input is sent to the event destination when the service call behavior is + * {@link com.sinch.sdk.domains.voice.models.v2.services.EventDestinationCallBehavior}. + */ +@JsonDeserialize(builder = MenuItemImpl.Builder.class) +public interface MenuItem { + + /** Gets or Sets inputMethods */ + public class InputMethodsEnum extends EnumDynamic { + public static final InputMethodsEnum DTMF = new InputMethodsEnum("DTMF"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + InputMethodsEnum.class, InputMethodsEnum::new, Arrays.asList(DTMF)); + + private InputMethodsEnum(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static InputMethodsEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(InputMethodsEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + /** + * Prompt played when this menu starts. + * + *

This prompt is also used as the repeat prompt when repeatPrompt is not defined for the menu. + * + * @return prompt + */ + MenuPrompt getPrompt(); + + /** + * Prompt played when the menu is repeated. + * + *

Repeats occur when input times out or when the provided input does not match any menu match + * item. + * + * @return repeatPrompt + */ + MenuPrompt getRepeatPrompt(); + + /** + * Maximum number of seconds to wait for user input before the input attempt times out. + * + * @return inputTimeoutDurationSeconds + */ + Integer getInputTimeoutDurationSeconds(); + + /** + * Maximum number of times the menu is repeated. + * + *

A repeat occurs when input times out or when the provided input does not match any menu + * match item. + * + * @return repeatCount + */ + Integer getRepeatCount(); + + /** + * Minimum number of input characters required before the menu evaluates the collected input. + * + * @return minimumInputLength + */ + Integer getMinimumInputLength(); + + /** + * Maximum number of input characters that triggers the menu to evaluate the collected input. + * + * @return maximumInputLength + */ + Integer getMaximumInputLength(); + + /** + * Character sequence that signals the end of input and triggers immediate evaluation. + * + *

Useful when variable-length input is allowed and shorter valid options should be submitted + * without waiting for timeout or maximum length. + * + *

The terminating sequence value is included in the evaluated input. + * + * @return terminatingSequence + */ + String getTerminatingSequence(); + + /** + * Input methods accepted for this menu when collecting user input. + * + * @return inputMethods + */ + List getInputMethods(); + + /** + * Items matched against the collected input. Maximum number of allowed match expressions is 50. + * + *

Defined as a dictionary where each property name is a literal or a regular expression + * string. + * + *

Values are evaluated in the order they are defined. + * + * @return matches + */ + Map> getMatches(); + + /** + * SVAML commands executed when the menu fails to collect a matching input. + * + *

This handler runs after the repeat limit is reached without any input matching a menu match + * item. + * + * @return onFailure + */ + List getOnFailure(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new MenuItemImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param prompt see getter + * @return Current builder + * @see #getPrompt + */ + Builder setPrompt(MenuPrompt prompt); + + /** + * see getter + * + * @param repeatPrompt see getter + * @return Current builder + * @see #getRepeatPrompt + */ + Builder setRepeatPrompt(MenuPrompt repeatPrompt); + + /** + * see getter + * + * @param inputTimeoutDurationSeconds see getter + * @return Current builder + * @see #getInputTimeoutDurationSeconds + */ + Builder setInputTimeoutDurationSeconds(Integer inputTimeoutDurationSeconds); + + /** + * see getter + * + * @param repeatCount see getter + * @return Current builder + * @see #getRepeatCount + */ + Builder setRepeatCount(Integer repeatCount); + + /** + * see getter + * + * @param minimumInputLength see getter + * @return Current builder + * @see #getMinimumInputLength + */ + Builder setMinimumInputLength(Integer minimumInputLength); + + /** + * see getter + * + * @param maximumInputLength see getter + * @return Current builder + * @see #getMaximumInputLength + */ + Builder setMaximumInputLength(Integer maximumInputLength); + + /** + * see getter + * + * @param terminatingSequence see getter + * @return Current builder + * @see #getTerminatingSequence + */ + Builder setTerminatingSequence(String terminatingSequence); + + /** + * see getter + * + * @param inputMethods see getter + * @return Current builder + * @see #getInputMethods + */ + Builder setInputMethods(List inputMethods); + + /** + * see getter + * + * @param matches see getter + * @return Current builder + * @see #getMatches + */ + Builder setMatches(Map> matches); + + /** + * see getter + * + * @param onFailure see getter + * @return Current builder + * @see #getOnFailure + */ + Builder setOnFailure(List onFailure); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + MenuItem build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuItemImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuItemImpl.java new file mode 100644 index 000000000..03df931a8 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuItemImpl.java @@ -0,0 +1,364 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +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.v2.svaml.SvamlCommand; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@JsonPropertyOrder({ + MenuItemImpl.JSON_PROPERTY_PROMPT, + MenuItemImpl.JSON_PROPERTY_REPEAT_PROMPT, + MenuItemImpl.JSON_PROPERTY_INPUT_TIMEOUT_DURATION_SECONDS, + MenuItemImpl.JSON_PROPERTY_REPEAT_COUNT, + MenuItemImpl.JSON_PROPERTY_MINIMUM_INPUT_LENGTH, + MenuItemImpl.JSON_PROPERTY_MAXIMUM_INPUT_LENGTH, + MenuItemImpl.JSON_PROPERTY_TERMINATING_SEQUENCE, + MenuItemImpl.JSON_PROPERTY_INPUT_METHODS, + MenuItemImpl.JSON_PROPERTY_MATCHES, + MenuItemImpl.JSON_PROPERTY_ON_FAILURE +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class MenuItemImpl implements MenuItem { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_PROMPT = "prompt"; + + private OptionalValue prompt; + + public static final String JSON_PROPERTY_REPEAT_PROMPT = "repeatPrompt"; + + private OptionalValue repeatPrompt; + + public static final String JSON_PROPERTY_INPUT_TIMEOUT_DURATION_SECONDS = + "inputTimeoutDurationSeconds"; + + private OptionalValue inputTimeoutDurationSeconds; + + public static final String JSON_PROPERTY_REPEAT_COUNT = "repeatCount"; + + private OptionalValue repeatCount; + + public static final String JSON_PROPERTY_MINIMUM_INPUT_LENGTH = "minimumInputLength"; + + private OptionalValue minimumInputLength; + + public static final String JSON_PROPERTY_MAXIMUM_INPUT_LENGTH = "maximumInputLength"; + + private OptionalValue maximumInputLength; + + public static final String JSON_PROPERTY_TERMINATING_SEQUENCE = "terminatingSequence"; + + private OptionalValue terminatingSequence; + + public static final String JSON_PROPERTY_INPUT_METHODS = "inputMethods"; + + private OptionalValue> inputMethods; + + public static final String JSON_PROPERTY_MATCHES = "matches"; + + private OptionalValue>> matches; + + public static final String JSON_PROPERTY_ON_FAILURE = "onFailure"; + + private OptionalValue> onFailure; + + public MenuItemImpl() {} + + protected MenuItemImpl( + OptionalValue prompt, + OptionalValue repeatPrompt, + OptionalValue inputTimeoutDurationSeconds, + OptionalValue repeatCount, + OptionalValue minimumInputLength, + OptionalValue maximumInputLength, + OptionalValue terminatingSequence, + OptionalValue> inputMethods, + OptionalValue>> matches, + OptionalValue> onFailure) { + this.prompt = prompt; + this.repeatPrompt = repeatPrompt; + this.inputTimeoutDurationSeconds = inputTimeoutDurationSeconds; + this.repeatCount = repeatCount; + this.minimumInputLength = minimumInputLength; + this.maximumInputLength = maximumInputLength; + this.terminatingSequence = terminatingSequence; + this.inputMethods = inputMethods; + this.matches = matches; + this.onFailure = onFailure; + } + + @JsonIgnore + public MenuPrompt getPrompt() { + return prompt.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_PROMPT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue prompt() { + return prompt; + } + + @JsonIgnore + public MenuPrompt getRepeatPrompt() { + return repeatPrompt.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_REPEAT_PROMPT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue repeatPrompt() { + return repeatPrompt; + } + + @JsonIgnore + public Integer getInputTimeoutDurationSeconds() { + return inputTimeoutDurationSeconds.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_INPUT_TIMEOUT_DURATION_SECONDS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue inputTimeoutDurationSeconds() { + return inputTimeoutDurationSeconds; + } + + @JsonIgnore + public Integer getRepeatCount() { + return repeatCount.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_REPEAT_COUNT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue repeatCount() { + return repeatCount; + } + + @JsonIgnore + public Integer getMinimumInputLength() { + return minimumInputLength.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MINIMUM_INPUT_LENGTH) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue minimumInputLength() { + return minimumInputLength; + } + + @JsonIgnore + public Integer getMaximumInputLength() { + return maximumInputLength.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MAXIMUM_INPUT_LENGTH) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue maximumInputLength() { + return maximumInputLength; + } + + @JsonIgnore + public String getTerminatingSequence() { + return terminatingSequence.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TERMINATING_SEQUENCE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue terminatingSequence() { + return terminatingSequence; + } + + @JsonIgnore + public List getInputMethods() { + return inputMethods.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_INPUT_METHODS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> inputMethods() { + return inputMethods; + } + + @JsonIgnore + public Map> getMatches() { + return matches.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MATCHES) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue>> matches() { + return matches; + } + + @JsonIgnore + public List getOnFailure() { + return onFailure.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_FAILURE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onFailure() { + return onFailure; + } + + /** Return true if this MenuItem object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MenuItemImpl menuItem = (MenuItemImpl) o; + return Objects.equals(this.prompt, menuItem.prompt) + && Objects.equals(this.repeatPrompt, menuItem.repeatPrompt) + && Objects.equals(this.inputTimeoutDurationSeconds, menuItem.inputTimeoutDurationSeconds) + && Objects.equals(this.repeatCount, menuItem.repeatCount) + && Objects.equals(this.minimumInputLength, menuItem.minimumInputLength) + && Objects.equals(this.maximumInputLength, menuItem.maximumInputLength) + && Objects.equals(this.terminatingSequence, menuItem.terminatingSequence) + && Objects.equals(this.inputMethods, menuItem.inputMethods) + && Objects.equals(this.matches, menuItem.matches) + && Objects.equals(this.onFailure, menuItem.onFailure); + } + + @Override + public int hashCode() { + return Objects.hash( + prompt, + repeatPrompt, + inputTimeoutDurationSeconds, + repeatCount, + minimumInputLength, + maximumInputLength, + terminatingSequence, + inputMethods, + matches, + onFailure); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class MenuItemImpl {\n"); + sb.append(" prompt: ").append(toIndentedString(prompt)).append("\n"); + sb.append(" repeatPrompt: ").append(toIndentedString(repeatPrompt)).append("\n"); + sb.append(" inputTimeoutDurationSeconds: ") + .append(toIndentedString(inputTimeoutDurationSeconds)) + .append("\n"); + sb.append(" repeatCount: ").append(toIndentedString(repeatCount)).append("\n"); + sb.append(" minimumInputLength: ").append(toIndentedString(minimumInputLength)).append("\n"); + sb.append(" maximumInputLength: ").append(toIndentedString(maximumInputLength)).append("\n"); + sb.append(" terminatingSequence: ") + .append(toIndentedString(terminatingSequence)) + .append("\n"); + sb.append(" inputMethods: ").append(toIndentedString(inputMethods)).append("\n"); + sb.append(" matches: ").append(toIndentedString(matches)).append("\n"); + sb.append(" onFailure: ").append(toIndentedString(onFailure)).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 MenuItem.Builder { + OptionalValue prompt = OptionalValue.empty(); + OptionalValue repeatPrompt = OptionalValue.empty(); + OptionalValue inputTimeoutDurationSeconds = OptionalValue.empty(); + OptionalValue repeatCount = OptionalValue.empty(); + OptionalValue minimumInputLength = OptionalValue.empty(); + OptionalValue maximumInputLength = OptionalValue.empty(); + OptionalValue terminatingSequence = OptionalValue.empty(); + OptionalValue> inputMethods = OptionalValue.empty(); + OptionalValue>> matches = OptionalValue.empty(); + OptionalValue> onFailure = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_PROMPT) + public Builder setPrompt(MenuPrompt prompt) { + this.prompt = OptionalValue.of(prompt); + return this; + } + + @JsonProperty(JSON_PROPERTY_REPEAT_PROMPT) + public Builder setRepeatPrompt(MenuPrompt repeatPrompt) { + this.repeatPrompt = OptionalValue.of(repeatPrompt); + return this; + } + + @JsonProperty(JSON_PROPERTY_INPUT_TIMEOUT_DURATION_SECONDS) + public Builder setInputTimeoutDurationSeconds(Integer inputTimeoutDurationSeconds) { + this.inputTimeoutDurationSeconds = OptionalValue.of(inputTimeoutDurationSeconds); + return this; + } + + @JsonProperty(JSON_PROPERTY_REPEAT_COUNT) + public Builder setRepeatCount(Integer repeatCount) { + this.repeatCount = OptionalValue.of(repeatCount); + return this; + } + + @JsonProperty(JSON_PROPERTY_MINIMUM_INPUT_LENGTH) + public Builder setMinimumInputLength(Integer minimumInputLength) { + this.minimumInputLength = OptionalValue.of(minimumInputLength); + return this; + } + + @JsonProperty(JSON_PROPERTY_MAXIMUM_INPUT_LENGTH) + public Builder setMaximumInputLength(Integer maximumInputLength) { + this.maximumInputLength = OptionalValue.of(maximumInputLength); + return this; + } + + @JsonProperty(JSON_PROPERTY_TERMINATING_SEQUENCE) + public Builder setTerminatingSequence(String terminatingSequence) { + this.terminatingSequence = OptionalValue.of(terminatingSequence); + return this; + } + + @JsonProperty(JSON_PROPERTY_INPUT_METHODS) + public Builder setInputMethods(List inputMethods) { + this.inputMethods = OptionalValue.of(inputMethods); + return this; + } + + @JsonProperty(JSON_PROPERTY_MATCHES) + public Builder setMatches(Map> matches) { + this.matches = OptionalValue.of(matches); + return this; + } + + @JsonProperty(JSON_PROPERTY_ON_FAILURE) + public Builder setOnFailure(List onFailure) { + this.onFailure = OptionalValue.of(onFailure); + return this; + } + + public MenuItem build() { + return new MenuItemImpl( + prompt, + repeatPrompt, + inputTimeoutDurationSeconds, + repeatCount, + minimumInputLength, + maximumInputLength, + terminatingSequence, + inputMethods, + matches, + onFailure); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuPrompt.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuPrompt.java new file mode 100644 index 000000000..3ecf2fda9 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuPrompt.java @@ -0,0 +1,70 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.Message; +import java.util.List; + +/** Prompt configuration for menu playback, including prompt messages and barge-in behavior. */ +@JsonDeserialize(builder = MenuPromptImpl.Builder.class) +public interface MenuPrompt { + + /** + * Controls whether input can interrupt prompt playback. + * + *

When enabled, playback stops as soon as input is detected and the input is evaluated + * immediately if matching conditions are met. + * + *

When disabled, input is still collected during playback and evaluated after playback + * finishes. + * + * @return allowBargeIn + */ + Boolean getAllowBargeIn(); + + /** + * Ordered list of messages to play. + * + *

Field is required + * + * @return messages + */ + List getMessages(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new MenuPromptImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param allowBargeIn see getter + * @return Current builder + * @see #getAllowBargeIn + */ + Builder setAllowBargeIn(Boolean allowBargeIn); + + /** + * see getter + * + * @param messages see getter + * @return Current builder + * @see #getMessages + */ + Builder setMessages(List messages); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + MenuPrompt build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuPromptImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuPromptImpl.java new file mode 100644 index 000000000..2a1e36382 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuPromptImpl.java @@ -0,0 +1,121 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +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.v2.svaml.playback.Message; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + MenuPromptImpl.JSON_PROPERTY_ALLOW_BARGE_IN, + MenuPromptImpl.JSON_PROPERTY_MESSAGES +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class MenuPromptImpl implements MenuPrompt { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ALLOW_BARGE_IN = "allowBargeIn"; + + private OptionalValue allowBargeIn; + + public static final String JSON_PROPERTY_MESSAGES = "messages"; + + private OptionalValue> messages; + + public MenuPromptImpl() {} + + protected MenuPromptImpl( + OptionalValue allowBargeIn, OptionalValue> messages) { + this.allowBargeIn = allowBargeIn; + this.messages = messages; + } + + @JsonIgnore + public Boolean getAllowBargeIn() { + return allowBargeIn.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ALLOW_BARGE_IN) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue allowBargeIn() { + return allowBargeIn; + } + + @JsonIgnore + public List getMessages() { + return messages.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MESSAGES) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> messages() { + return messages; + } + + /** Return true if this MenuPrompt object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MenuPromptImpl menuPrompt = (MenuPromptImpl) o; + return Objects.equals(this.allowBargeIn, menuPrompt.allowBargeIn) + && Objects.equals(this.messages, menuPrompt.messages); + } + + @Override + public int hashCode() { + return Objects.hash(allowBargeIn, messages); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class MenuPromptImpl {\n"); + sb.append(" allowBargeIn: ").append(toIndentedString(allowBargeIn)).append("\n"); + sb.append(" messages: ").append(toIndentedString(messages)).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 MenuPrompt.Builder { + OptionalValue allowBargeIn = OptionalValue.empty(); + OptionalValue> messages = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_ALLOW_BARGE_IN) + public Builder setAllowBargeIn(Boolean allowBargeIn) { + this.allowBargeIn = OptionalValue.of(allowBargeIn); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_MESSAGES, required = true) + public Builder setMessages(List messages) { + this.messages = OptionalValue.of(messages); + return this; + } + + public MenuPrompt build() { + return new MenuPromptImpl(allowBargeIn, messages); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessageEvents.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessageEvents.java new file mode 100644 index 000000000..43e98ec2f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessageEvents.java @@ -0,0 +1,46 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.List; + +/** SVAML commands to execute based on message playback outcomes. */ +@JsonDeserialize(builder = MessageEventsImpl.Builder.class) +public interface MessageEvents { + + /** + * Commands to execute when all messages in the sequence have finished playing. + * + * @return onFinish + */ + List getOnFinish(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new MessageEventsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param onFinish see getter + * @return Current builder + * @see #getOnFinish + */ + Builder setOnFinish(List onFinish); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + MessageEvents build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessageEventsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessageEventsImpl.java new file mode 100644 index 000000000..61593c416 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessageEventsImpl.java @@ -0,0 +1,92 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +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.v2.svaml.SvamlCommand; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({MessageEventsImpl.JSON_PROPERTY_ON_FINISH}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class MessageEventsImpl implements MessageEvents { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ON_FINISH = "onFinish"; + + private OptionalValue> onFinish; + + public MessageEventsImpl() {} + + protected MessageEventsImpl(OptionalValue> onFinish) { + this.onFinish = onFinish; + } + + @JsonIgnore + public List getOnFinish() { + return onFinish.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_FINISH) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onFinish() { + return onFinish; + } + + /** Return true if this MessageEvents object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MessageEventsImpl messageEvents = (MessageEventsImpl) o; + return Objects.equals(this.onFinish, messageEvents.onFinish); + } + + @Override + public int hashCode() { + return Objects.hash(onFinish); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class MessageEventsImpl {\n"); + sb.append(" onFinish: ").append(toIndentedString(onFinish)).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 MessageEvents.Builder { + OptionalValue> onFinish = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_ON_FINISH) + public Builder setOnFinish(List onFinish) { + this.onFinish = OptionalValue.of(onFinish); + return this; + } + + public MessageEvents build() { + return new MessageEventsImpl(onFinish); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessagesCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessagesCommand.java new file mode 100644 index 000000000..6da635cc3 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessagesCommand.java @@ -0,0 +1,91 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.List; + +/** + * Plays one or more messages on the call. Multiple messages in the array are played sequentially in + * order. + * + *

This is a non-blocking command — the next command in the sequence executes immediately while + * messages play in parallel. Playback outcomes are handled via the events property. + * The onFinish event can be used to run a command after all messages complete. + */ +@JsonDeserialize(builder = MessagesCommandImpl.Builder.class) +public interface MessagesCommand extends SvamlCommand { + + /** + * Name of the message for identification and reference within the call session. + * + *

This name is used to uniquely identify the message and must be unique within the current + * call session. This name can be referenced in other commands (e.g., stopMessages) + * to control this specific message. + * + * @return messagesName + */ + String getMessagesName(); + + /** + * Ordered list of messages to play. + * + *

Field is required + * + * @return messages + */ + List getMessages(); + + /** + * SVAML commands to execute based on message playback outcomes. + * + * @return events + */ + MessageEvents getEvents(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new MessagesCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param messagesName see getter + * @return Current builder + * @see #getMessagesName + */ + Builder setMessagesName(String messagesName); + + /** + * see getter + * + * @param messages see getter + * @return Current builder + * @see #getMessages + */ + Builder setMessages(List messages); + + /** + * see getter + * + * @param events see getter + * @return Current builder + * @see #getEvents + */ + Builder setEvents(MessageEvents events); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + MessagesCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessagesCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessagesCommandImpl.java new file mode 100644 index 000000000..68d7020ca --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessagesCommandImpl.java @@ -0,0 +1,207 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + MessagesCommandImpl.JSON_PROPERTY_COMMAND, + MessagesCommandImpl.JSON_PROPERTY_MESSAGES_NAME, + MessagesCommandImpl.JSON_PROPERTY_MESSAGES, + MessagesCommandImpl.JSON_PROPERTY_EVENTS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class MessagesCommandImpl implements MessagesCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value messages. */ + public static class CommandEnum extends EnumDynamic { + /** The messages command. */ + public static final CommandEnum MESSAGES = new CommandEnum("messages"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CommandEnum.class, CommandEnum::new, Arrays.asList(MESSAGES)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_MESSAGES_NAME = "messagesName"; + + private OptionalValue messagesName; + + public static final String JSON_PROPERTY_MESSAGES = "messages"; + + private OptionalValue> messages; + + public static final String JSON_PROPERTY_EVENTS = "events"; + + private OptionalValue events; + + public MessagesCommandImpl() {} + + protected MessagesCommandImpl( + OptionalValue command, + OptionalValue messagesName, + OptionalValue> messages, + OptionalValue events) { + this.command = command; + this.messagesName = messagesName; + this.messages = messages; + this.events = events; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public String getMessagesName() { + return messagesName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MESSAGES_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue messagesName() { + return messagesName; + } + + @JsonIgnore + public List getMessages() { + return messages.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MESSAGES) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue> messages() { + return messages; + } + + @JsonIgnore + public MessageEvents getEvents() { + return events.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue events() { + return events; + } + + /** Return true if this MessagesCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MessagesCommandImpl messagesCommand = (MessagesCommandImpl) o; + return Objects.equals(this.command, messagesCommand.command) + && Objects.equals(this.messagesName, messagesCommand.messagesName) + && Objects.equals(this.messages, messagesCommand.messages) + && Objects.equals(this.events, messagesCommand.events); + } + + @Override + public int hashCode() { + return Objects.hash(command, messagesName, messages, events); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class MessagesCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" messagesName: ").append(toIndentedString(messagesName)).append("\n"); + sb.append(" messages: ").append(toIndentedString(messages)).append("\n"); + sb.append(" events: ").append(toIndentedString(events)).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 MessagesCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.MESSAGES); + OptionalValue messagesName = OptionalValue.empty(); + OptionalValue> messages = OptionalValue.empty(); + OptionalValue events = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.MESSAGES)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.MESSAGES, command)); + } + return this; + } + + @JsonProperty(JSON_PROPERTY_MESSAGES_NAME) + public Builder setMessagesName(String messagesName) { + this.messagesName = OptionalValue.of(messagesName); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_MESSAGES, required = true) + public Builder setMessages(List messages) { + this.messages = OptionalValue.of(messages); + return this; + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + public Builder setEvents(MessageEvents events) { + this.events = OptionalValue.of(events); + return this; + } + + public MessagesCommand build() { + return new MessagesCommandImpl(command, messagesName, messages, events); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/PlayMessage.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/PlayMessage.java new file mode 100644 index 000000000..01495a761 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/PlayMessage.java @@ -0,0 +1,49 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + * An audio file playback message item. The platform fetches and plays the audio file at the + * provided URL on the call. + */ +@JsonDeserialize(builder = PlayMessageImpl.Builder.class) +public interface PlayMessage extends Message { + + /** + * URL of the media to send + * + *

Field is required + * + * @return url + */ + String getUrl(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new PlayMessageImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param url see getter + * @return Current builder + * @see #getUrl + */ + Builder setUrl(String url); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + PlayMessage build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/PlayMessageImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/PlayMessageImpl.java new file mode 100644 index 000000000..b44f7ac22 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/PlayMessageImpl.java @@ -0,0 +1,174 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.internal.PlayMessageInternal; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({PlayMessageImpl.JSON_PROPERTY_TYPE, PlayMessageImpl.JSON_PROPERTY_PLAY}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class PlayMessageImpl implements PlayMessage, Message { + private static final long serialVersionUID = 1L; + + /** The type property. Must have the value PLAY. */ + public static class TypeEnum extends EnumDynamic { + /** The PLAY type. */ + public static final TypeEnum PLAY = new TypeEnum("PLAY"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(TypeEnum.class, TypeEnum::new, Arrays.asList(PLAY)); + + private TypeEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static TypeEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(TypeEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public static final String JSON_PROPERTY_PLAY = "play"; + + private OptionalValue play; + + public PlayMessageImpl() {} + + protected PlayMessageImpl(OptionalValue type, OptionalValue play) { + this.type = type; + this.play = play; + } + + @JsonIgnore + public TypeEnum getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue type() { + return type; + } + + @JsonIgnore + public PlayMessageInternal getPlay() { + return play.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_PLAY) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue play() { + return play; + } + + @JsonIgnore + public String getUrl() { + if (null == play || !play.isPresent() || null == play.get().getUrl()) { + return null; + } + return play.get().getUrl(); + } + + /** Return true if this PlayMessage object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlayMessageImpl playMessage = (PlayMessageImpl) o; + return Objects.equals(this.type, playMessage.type) + && Objects.equals(this.play, playMessage.play); + } + + @Override + public int hashCode() { + return Objects.hash(type, play); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class PlayMessageImpl {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" play: ").append(toIndentedString(play)).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 PlayMessage.Builder { + OptionalValue type = OptionalValue.of(TypeEnum.PLAY); + OptionalValue play = OptionalValue.empty(); + + PlayMessageInternal.Builder _delegatedBuilder = null; + + @JsonProperty(value = JSON_PROPERTY_TYPE, required = true) + Builder setType(TypeEnum type) { + if (!Objects.equals(type, TypeEnum.PLAY)) { + throw new IllegalArgumentException( + String.format("'type' must be '%s' (is '%s')", TypeEnum.PLAY, type)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_PLAY, required = true) + public Builder setPlay(PlayMessageInternal play) { + this.play = OptionalValue.of(play); + return this; + } + + @JsonIgnore + public Builder setUrl(String url) { + getDelegatedBuilder().setUrl(url); + return this; + } + + private PlayMessageInternal.Builder getDelegatedBuilder() { + if (null == _delegatedBuilder) { + this._delegatedBuilder = PlayMessageInternal.builder(); + } + return this._delegatedBuilder; + } + + public PlayMessage build() { + // delegated builder was used: filling the related source of delegation field + if (null != this._delegatedBuilder) { + this.play = OptionalValue.of(this._delegatedBuilder.build()); + } + return new PlayMessageImpl(type, play); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/SayMessage.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/SayMessage.java new file mode 100644 index 000000000..1c646cfd7 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/SayMessage.java @@ -0,0 +1,120 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * A text-to-speech (TTS) message item. The platform synthesizes the provided text into speech and + * plays it on the call. + */ +@JsonDeserialize(builder = SayMessageImpl.Builder.class) +public interface SayMessage extends Message { + + /** Gets or Sets format */ + public class FormatEnum extends EnumDynamic { + public static final FormatEnum TEXT = new FormatEnum("TEXT"); + + public static final FormatEnum SSML = new FormatEnum("SSML"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(FormatEnum.class, FormatEnum::new, Arrays.asList(TEXT, SSML)); + + private FormatEnum(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static FormatEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(FormatEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + /** + * The text to be synthesized into speech. + * + *

If format is TEXT (default), provide plain text. If format + * is SSML, provide a valid SSML document (for example, + * <speak>...</speak>). + * + *

Field is required + * + * @return text + */ + String getText(); + + /** + * Format of the message + * + * @return format + */ + FormatEnum getFormat(); + + /** + * The name of the voice to use for text-to-speech synthesis. + * + *

Supported voices include: Emma, Brian, and others. For a complete list of available voices + * and their characteristics, see the Text-to-Speech Voices documentation. + * + *

Field is required + * + * @return voiceName + */ + String getVoiceName(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SayMessageImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param text see getter + * @return Current builder + * @see #getText + */ + Builder setText(String text); + + /** + * see getter + * + * @param format see getter + * @return Current builder + * @see #getFormat + */ + Builder setFormat(FormatEnum format); + + /** + * see getter + * + * @param voiceName see getter + * @return Current builder + * @see #getVoiceName + */ + Builder setVoiceName(String voiceName); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + SayMessage build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/SayMessageImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/SayMessageImpl.java new file mode 100644 index 000000000..2ebe1478a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/SayMessageImpl.java @@ -0,0 +1,201 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.internal.SayMessageInternal; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({SayMessageImpl.JSON_PROPERTY_TYPE, SayMessageImpl.JSON_PROPERTY_SAY}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SayMessageImpl implements SayMessage, Message { + private static final long serialVersionUID = 1L; + + /** The type property. Must have the value SAY. */ + public static class TypeEnum extends EnumDynamic { + /** The SAY type. */ + public static final TypeEnum SAY = new TypeEnum("SAY"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(TypeEnum.class, TypeEnum::new, Arrays.asList(SAY)); + + private TypeEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static TypeEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(TypeEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + + private OptionalValue type; + + public static final String JSON_PROPERTY_SAY = "say"; + + private OptionalValue say; + + public SayMessageImpl() {} + + protected SayMessageImpl(OptionalValue type, OptionalValue say) { + this.type = type; + this.say = say; + } + + @JsonIgnore + public TypeEnum getType() { + return type.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue type() { + return type; + } + + @JsonIgnore + public SayMessageInternal getSay() { + return say.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SAY) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue say() { + return say; + } + + @JsonIgnore + public String getText() { + if (null == say || !say.isPresent() || null == say.get().getText()) { + return null; + } + return say.get().getText(); + } + + @JsonIgnore + public FormatEnum getFormat() { + if (null == say || !say.isPresent() || null == say.get().getFormat()) { + return null; + } + return say.get().getFormat(); + } + + @JsonIgnore + public String getVoiceName() { + if (null == say || !say.isPresent() || null == say.get().getVoiceName()) { + return null; + } + return say.get().getVoiceName(); + } + + /** Return true if this SayMessage object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SayMessageImpl sayMessage = (SayMessageImpl) o; + return Objects.equals(this.type, sayMessage.type) && Objects.equals(this.say, sayMessage.say); + } + + @Override + public int hashCode() { + return Objects.hash(type, say); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SayMessageImpl {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" say: ").append(toIndentedString(say)).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 SayMessage.Builder { + OptionalValue type = OptionalValue.of(TypeEnum.SAY); + OptionalValue say = OptionalValue.empty(); + + SayMessageInternal.Builder _delegatedBuilder = null; + + @JsonProperty(value = JSON_PROPERTY_TYPE, required = true) + Builder setType(TypeEnum type) { + if (!Objects.equals(type, TypeEnum.SAY)) { + throw new IllegalArgumentException( + String.format("'type' must be '%s' (is '%s')", TypeEnum.SAY, type)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_SAY, required = true) + public Builder setSay(SayMessageInternal say) { + this.say = OptionalValue.of(say); + return this; + } + + @JsonIgnore + public Builder setText(String text) { + getDelegatedBuilder().setText(text); + return this; + } + + @JsonIgnore + public Builder setFormat(FormatEnum format) { + getDelegatedBuilder().setFormat(format); + return this; + } + + @JsonIgnore + public Builder setVoiceName(String voiceName) { + getDelegatedBuilder().setVoiceName(voiceName); + return this; + } + + private SayMessageInternal.Builder getDelegatedBuilder() { + if (null == _delegatedBuilder) { + this._delegatedBuilder = SayMessageInternal.builder(); + } + return this._delegatedBuilder; + } + + public SayMessage build() { + // delegated builder was used: filling the related source of delegation field + if (null != this._delegatedBuilder) { + this.say = OptionalValue.of(this._delegatedBuilder.build()); + } + return new SayMessageImpl(type, say); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/StopMessagesCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/StopMessagesCommand.java new file mode 100644 index 000000000..08a00d5b0 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/StopMessagesCommand.java @@ -0,0 +1,64 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; + +/** + * Stops a message sequence started by a messages command. It will also cancel any + * messages with specified messagesName that will appear in the call session in the + * future. + * + *

This is a non-blocking command. + */ +@JsonDeserialize(builder = StopMessagesCommandImpl.Builder.class) +public interface StopMessagesCommand extends SvamlCommand { + + /** + * Name of the message sequence to stop, as set by messagesName in the messages + * command. + * + *

Field is required + * + * @return messagesName + */ + String getMessagesName(); + + /** + * Create a stopMessages command stopping the given message sequence + * + * @param messagesName see {@link #getMessagesName()} + * @return A new StopMessagesCommand + */ + static StopMessagesCommand of(String messagesName) { + return builder().setMessagesName(messagesName).build(); + } + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StopMessagesCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param messagesName see getter + * @return Current builder + * @see #getMessagesName + */ + Builder setMessagesName(String messagesName); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StopMessagesCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/StopMessagesCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/StopMessagesCommandImpl.java new file mode 100644 index 000000000..3919753ad --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/StopMessagesCommandImpl.java @@ -0,0 +1,151 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({ + StopMessagesCommandImpl.JSON_PROPERTY_COMMAND, + StopMessagesCommandImpl.JSON_PROPERTY_MESSAGES_NAME +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class StopMessagesCommandImpl implements StopMessagesCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value stopMessages. */ + public static class CommandEnum extends EnumDynamic { + /** The stopMessages command. */ + public static final CommandEnum STOP_MESSAGES = new CommandEnum("stopMessages"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>(CommandEnum.class, CommandEnum::new, Arrays.asList(STOP_MESSAGES)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_MESSAGES_NAME = "messagesName"; + + private OptionalValue messagesName; + + public StopMessagesCommandImpl() {} + + protected StopMessagesCommandImpl( + OptionalValue command, OptionalValue messagesName) { + this.command = command; + this.messagesName = messagesName; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public String getMessagesName() { + return messagesName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_MESSAGES_NAME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue messagesName() { + return messagesName; + } + + /** Return true if this StopMessagesCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StopMessagesCommandImpl stopMessagesCommand = (StopMessagesCommandImpl) o; + return Objects.equals(this.command, stopMessagesCommand.command) + && Objects.equals(this.messagesName, stopMessagesCommand.messagesName); + } + + @Override + public int hashCode() { + return Objects.hash(command, messagesName); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StopMessagesCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" messagesName: ").append(toIndentedString(messagesName)).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 StopMessagesCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.STOP_MESSAGES); + OptionalValue messagesName = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.STOP_MESSAGES)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.STOP_MESSAGES, command)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_MESSAGES_NAME, required = true) + public Builder setMessagesName(String messagesName) { + this.messagesName = OptionalValue.of(messagesName); + return this; + } + + public StopMessagesCommand build() { + return new StopMessagesCommandImpl(command, messagesName); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/MessageInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/MessageInternal.java new file mode 100644 index 000000000..5f6e0696d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/MessageInternal.java @@ -0,0 +1,6 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(using = MessageInternalImpl.MessageInternalImplDeserializer.class) +public interface MessageInternal {} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/MessageInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/MessageInternalImpl.java new file mode 100644 index 000000000..480288e5b --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/MessageInternalImpl.java @@ -0,0 +1,170 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback.internal; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +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.v2.svaml.playback.PlayMessageImpl; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.SayMessageImpl; +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 = MessageInternalImpl.MessageInternalImplDeserializer.class) +@JsonSerialize(using = MessageInternalImpl.MessageInternalImplSerializer.class) +public class MessageInternalImpl extends AbstractOpenApiSchema implements MessageInternal { + private static final Logger log = Logger.getLogger(MessageInternalImpl.class.getName()); + + public static final class MessageInternalImplSerializer + extends StdSerializer { + private static final long serialVersionUID = 1L; + + public MessageInternalImplSerializer(Class t) { + super(t); + } + + public MessageInternalImplSerializer() { + this(null); + } + + @Override + public void serialize( + MessageInternalImpl value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static final class MessageInternalImplDeserializer + extends StdDeserializer { + + private static final long serialVersionUID = 1L; + + public MessageInternalImplDeserializer() { + this(MessageInternalImpl.class); + } + + public MessageInternalImplDeserializer(Class vc) { + super(vc); + } + + @Override + public MessageInternalImpl deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + JsonNode discriminator = tree.get("type"); + String discriminatorValue = null != discriminator ? discriminator.asText() : null; + Class cls = null != discriminatorValue ? mappings.get(discriminatorValue) : null; + if (null == cls) { + throw new IOException( + String.format( + "Failed deserialization for MessageInternalImpl: unknown 'type' value '%s'." + + " Possible values: %s", + discriminatorValue, mappings.keySet())); + } + Object deserialized = tree.traverse(jp.getCodec()).readValueAs(cls); + MessageInternalImpl ret = new MessageInternalImpl(); + ret.setActualInstance(deserialized); + return ret; + } + + /** Handle deserialization of the 'null' value. */ + @Override + public MessageInternalImpl getNullValue(DeserializationContext ctxt) + throws JsonMappingException { + throw new JsonMappingException(ctxt.getParser(), "MessageInternalImpl cannot be null"); + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap<>(); + + // discriminator value -> class + private static final Map> mappings = new HashMap<>(); + + public MessageInternalImpl() { + super("oneOf", Boolean.FALSE); + } + + static { + schemas.put("PlayMessageImpl", PlayMessageImpl.class); + schemas.put("SayMessageImpl", SayMessageImpl.class); + JSONNavigator.registerDescendants( + MessageInternalImpl.class, Collections.unmodifiableMap(schemas)); + // Initialize and register the discriminator mappings. + mappings.put("PLAY", PlayMessageImpl.class); + mappings.put("SAY", SayMessageImpl.class); + JSONNavigator.registerDiscriminator(MessageInternalImpl.class, "type", mappings); + } + + @Override + public Map> getSchemas() { + return MessageInternalImpl.schemas; + } + + /** + * Set the instance that matches the oneOf child schema, check the instance parameter is valid + * against the oneOf child schemas: PlayMessageImpl, SayMessageImpl + */ + @Override + public void setActualInstance(Object instance) { + for (Class schema : schemas.values()) { + if (JSONNavigator.isInstanceOf(schema, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + } + throw new RuntimeException("Invalid instance type. Must be PlayMessageImpl, SayMessageImpl"); + } + + /** + * Get the actual instance, which can be the following: PlayMessageImpl, SayMessageImpl + * + * @return The actual instance + */ + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + public static class Deserializer + extends StdDeserializer { + + public Deserializer() { + this(null); + } + + public Deserializer(Class vc) { + super(vc); + } + + @Override + public com.sinch.sdk.domains.voice.models.v2.svaml.playback.Message deserialize( + JsonParser jp, DeserializationContext ctxt) throws IOException { + + Object deserialized = jp.readValueAs(MessageInternalImpl.class).getActualInstance(); + if (null == deserialized) { + return null; + } + if (!(deserialized instanceof com.sinch.sdk.domains.voice.models.v2.svaml.playback.Message)) { + log.log(Level.SEVERE, "Input data does not match schema ", deserialized); + return null; + } + + return (com.sinch.sdk.domains.voice.models.v2.svaml.playback.Message) deserialized; + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/PlayMessageInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/PlayMessageInternal.java new file mode 100644 index 000000000..7f76ef2c7 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/PlayMessageInternal.java @@ -0,0 +1,45 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(builder = PlayMessageInternalImpl.Builder.class) +public interface PlayMessageInternal { + + /** + * URL of the media to send + * + *

Field is required + * + * @return url + */ + String getUrl(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new PlayMessageInternalImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param url see getter + * @return Current builder + * @see #getUrl + */ + Builder setUrl(String url); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + PlayMessageInternal build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/PlayMessageInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/PlayMessageInternalImpl.java new file mode 100644 index 000000000..b3f9eb6a7 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/PlayMessageInternalImpl.java @@ -0,0 +1,90 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback.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 java.util.Objects; + +@JsonPropertyOrder({PlayMessageInternalImpl.JSON_PROPERTY_URL}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class PlayMessageInternalImpl implements PlayMessageInternal { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_URL = "url"; + + private OptionalValue url; + + public PlayMessageInternalImpl() {} + + protected PlayMessageInternalImpl(OptionalValue url) { + this.url = url; + } + + @JsonIgnore + public String getUrl() { + return url.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_URL) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue url() { + return url; + } + + /** Return true if this PlayMessageInternal object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlayMessageInternalImpl play = (PlayMessageInternalImpl) o; + return Objects.equals(this.url, play.url); + } + + @Override + public int hashCode() { + return Objects.hash(url); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class PlayMessageInternalImpl {\n"); + sb.append(" url: ").append(toIndentedString(url)).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 PlayMessageInternal.Builder { + OptionalValue url = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_URL, required = true) + public Builder setUrl(String url) { + this.url = OptionalValue.of(url); + return this; + } + + public PlayMessageInternal build() { + return new PlayMessageInternalImpl(url); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/SayMessageInternal.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/SayMessageInternal.java new file mode 100644 index 000000000..7f202043a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/SayMessageInternal.java @@ -0,0 +1,87 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.SayMessage.FormatEnum; + +@JsonDeserialize(builder = SayMessageInternalImpl.Builder.class) +public interface SayMessageInternal { + + /** + * The text to be synthesized into speech. + * + *

If format is TEXT (default), provide plain text. If format + * is SSML, provide a valid SSML document (for example, + * <speak>...</speak>). + * + *

Field is required + * + * @return text + */ + String getText(); + + /** + * Format of the message + * + * @return format + */ + FormatEnum getFormat(); + + /** + * The name of the voice to use for text-to-speech synthesis. + * + *

Supported voices include: Emma, Brian, and others. For a complete list of available voices + * and their characteristics, see the Text-to-Speech Voices documentation. + * + *

Field is required + * + * @return voiceName + */ + String getVoiceName(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SayMessageInternalImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param text see getter + * @return Current builder + * @see #getText + */ + Builder setText(String text); + + /** + * see getter + * + * @param format see getter + * @return Current builder + * @see #getFormat + */ + Builder setFormat(FormatEnum format); + + /** + * see getter + * + * @param voiceName see getter + * @return Current builder + * @see #getVoiceName + */ + Builder setVoiceName(String voiceName); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + SayMessageInternal build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/SayMessageInternalImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/SayMessageInternalImpl.java new file mode 100644 index 000000000..d76f74aef --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/playback/internal/SayMessageInternalImpl.java @@ -0,0 +1,148 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback.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.voice.models.v2.svaml.playback.SayMessage.FormatEnum; +import java.util.Objects; + +@JsonPropertyOrder({ + SayMessageInternalImpl.JSON_PROPERTY_TEXT, + SayMessageInternalImpl.JSON_PROPERTY_FORMAT, + SayMessageInternalImpl.JSON_PROPERTY_VOICE_NAME +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SayMessageInternalImpl implements SayMessageInternal { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_TEXT = "text"; + + private OptionalValue text; + + public static final String JSON_PROPERTY_FORMAT = "format"; + + private OptionalValue format; + + public static final String JSON_PROPERTY_VOICE_NAME = "voiceName"; + + private OptionalValue voiceName; + + public SayMessageInternalImpl() {} + + protected SayMessageInternalImpl( + OptionalValue text, + OptionalValue format, + OptionalValue voiceName) { + this.text = text; + this.format = format; + this.voiceName = voiceName; + } + + @JsonIgnore + public String getText() { + return text.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TEXT) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue text() { + return text; + } + + @JsonIgnore + public FormatEnum getFormat() { + return format.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_FORMAT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue format() { + return format; + } + + @JsonIgnore + public String getVoiceName() { + return voiceName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_VOICE_NAME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue voiceName() { + return voiceName; + } + + /** Return true if this SayMessageInternal object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SayMessageInternalImpl say = (SayMessageInternalImpl) o; + return Objects.equals(this.text, say.text) + && Objects.equals(this.format, say.format) + && Objects.equals(this.voiceName, say.voiceName); + } + + @Override + public int hashCode() { + return Objects.hash(text, format, voiceName); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SayMessageInternalImpl {\n"); + sb.append(" text: ").append(toIndentedString(text)).append("\n"); + sb.append(" format: ").append(toIndentedString(format)).append("\n"); + sb.append(" voiceName: ").append(toIndentedString(voiceName)).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 SayMessageInternal.Builder { + OptionalValue text = OptionalValue.empty(); + OptionalValue format = OptionalValue.empty(); + OptionalValue voiceName = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_TEXT, required = true) + public Builder setText(String text) { + this.text = OptionalValue.of(text); + return this; + } + + @JsonProperty(JSON_PROPERTY_FORMAT) + public Builder setFormat(FormatEnum format) { + this.format = OptionalValue.of(format); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_VOICE_NAME, required = true) + public Builder setVoiceName(String voiceName) { + this.voiceName = OptionalValue.of(voiceName); + return this; + } + + public SayMessageInternal build() { + return new SayMessageInternalImpl(text, format, voiceName); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingDestinationType.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingDestinationType.java new file mode 100644 index 000000000..886772df3 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingDestinationType.java @@ -0,0 +1,46 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** Select target service to receive recorded and transcribed files */ +public class RecordingDestinationType extends EnumDynamic { + /** + * Store recordings in Amazon Web Services S3 bucket. Use s3 as schema for the URL. + */ + public static final RecordingDestinationType AWS = new RecordingDestinationType("AWS"); + + /** + * Store recordings in Google Cloud Platform Storage. Use gs as schema for the URL. + */ + public static final RecordingDestinationType GCP = new RecordingDestinationType("GCP"); + + /** + * Store recordings in Microsoft Azure Blob Storage. Use azure as schema for the URL. + */ + public static final RecordingDestinationType AZURE = new RecordingDestinationType("AZURE"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + RecordingDestinationType.class, + RecordingDestinationType::new, + Arrays.asList(AWS, GCP, AZURE)); + + private RecordingDestinationType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static RecordingDestinationType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(RecordingDestinationType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingEvents.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingEvents.java new file mode 100644 index 000000000..5367837ac --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingEvents.java @@ -0,0 +1,64 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.List; + +/** SVAML commands to execute based on recording lifecycle outcomes. */ +@JsonDeserialize(builder = RecordingEventsImpl.Builder.class) +public interface RecordingEvents { + + /** + * Commands to execute when the recording is successfully stopped. Note that this does not mean + * that the file is delivered to the configured destination yet. + * + * @return onFinish + */ + List getOnFinish(); + + /** + * Commands to execute if the recording fails to start. If omitted, failures are silently ignored + * and the call flow continues. + * + * @return onFailure + */ + List getOnFailure(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new RecordingEventsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param onFinish see getter + * @return Current builder + * @see #getOnFinish + */ + Builder setOnFinish(List onFinish); + + /** + * see getter + * + * @param onFailure see getter + * @return Current builder + * @see #getOnFailure + */ + Builder setOnFailure(List onFailure); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + RecordingEvents build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingEventsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingEventsImpl.java new file mode 100644 index 000000000..31cef9a44 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingEventsImpl.java @@ -0,0 +1,121 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +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.v2.svaml.SvamlCommand; +import java.util.List; +import java.util.Objects; + +@JsonPropertyOrder({ + RecordingEventsImpl.JSON_PROPERTY_ON_FINISH, + RecordingEventsImpl.JSON_PROPERTY_ON_FAILURE +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class RecordingEventsImpl implements RecordingEvents { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ON_FINISH = "onFinish"; + + private OptionalValue> onFinish; + + public static final String JSON_PROPERTY_ON_FAILURE = "onFailure"; + + private OptionalValue> onFailure; + + public RecordingEventsImpl() {} + + protected RecordingEventsImpl( + OptionalValue> onFinish, OptionalValue> onFailure) { + this.onFinish = onFinish; + this.onFailure = onFailure; + } + + @JsonIgnore + public List getOnFinish() { + return onFinish.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_FINISH) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onFinish() { + return onFinish; + } + + @JsonIgnore + public List getOnFailure() { + return onFailure.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ON_FAILURE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> onFailure() { + return onFailure; + } + + /** Return true if this RecordingEvents object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RecordingEventsImpl recordingEvents = (RecordingEventsImpl) o; + return Objects.equals(this.onFinish, recordingEvents.onFinish) + && Objects.equals(this.onFailure, recordingEvents.onFailure); + } + + @Override + public int hashCode() { + return Objects.hash(onFinish, onFailure); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class RecordingEventsImpl {\n"); + sb.append(" onFinish: ").append(toIndentedString(onFinish)).append("\n"); + sb.append(" onFailure: ").append(toIndentedString(onFailure)).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 RecordingEvents.Builder { + OptionalValue> onFinish = OptionalValue.empty(); + OptionalValue> onFailure = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_ON_FINISH) + public Builder setOnFinish(List onFinish) { + this.onFinish = OptionalValue.of(onFinish); + return this; + } + + @JsonProperty(JSON_PROPERTY_ON_FAILURE) + public Builder setOnFailure(List onFailure) { + this.onFailure = OptionalValue.of(onFailure); + return this; + } + + public RecordingEvents build() { + return new RecordingEventsImpl(onFinish, onFailure); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingFormatType.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingFormatType.java new file mode 100644 index 000000000..3e5de94b4 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingFormatType.java @@ -0,0 +1,35 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** Audio format for this recording. */ +public class RecordingFormatType extends EnumDynamic { + /** MPEG Audio Layer III compressed audio format. */ + public static final RecordingFormatType MP3 = new RecordingFormatType("MP3"); + + /** Waveform Audio File Format, uncompressed audio. */ + public static final RecordingFormatType WAV = new RecordingFormatType("WAV"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + RecordingFormatType.class, RecordingFormatType::new, Arrays.asList(MP3, WAV)); + + private RecordingFormatType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static RecordingFormatType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(RecordingFormatType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingOptions.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingOptions.java new file mode 100644 index 000000000..37e956d2a --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingOptions.java @@ -0,0 +1,130 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** Recording options for this recording. */ +@JsonDeserialize(builder = RecordingOptionsImpl.Builder.class) +public interface RecordingOptions { + + /** + * Audio format for this recording. + * + * @return format + */ + RecordingFormatType getFormat(); + + /** + * The type of recording to perform. + * + * @return recordingType + */ + RecordingType getRecordingType(); + + /** + * Select target service to receive recorded and transcribed files + * + *

Field is required + * + * @return destination + */ + RecordingDestinationType getDestination(); + + /** + * Destination URL for the recording. + * + *

Field is required + * + * @return destinationUrl + */ + String getDestinationUrl(); + + /** + * Credentials to third party storage. + * + *

Field is required + * + * @return credentials + */ + String getCredentials(); + + /** + * Configuration for automatic speech-to-text transcription of the recording. + * + * @return transcriptionOptions + */ + TranscriptionOptions getTranscriptionOptions(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new RecordingOptionsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param format see getter + * @return Current builder + * @see #getFormat + */ + Builder setFormat(RecordingFormatType format); + + /** + * see getter + * + * @param recordingType see getter + * @return Current builder + * @see #getRecordingType + */ + Builder setRecordingType(RecordingType recordingType); + + /** + * see getter + * + * @param destination see getter + * @return Current builder + * @see #getDestination + */ + Builder setDestination(RecordingDestinationType destination); + + /** + * see getter + * + * @param destinationUrl see getter + * @return Current builder + * @see #getDestinationUrl + */ + Builder setDestinationUrl(String destinationUrl); + + /** + * see getter + * + * @param credentials see getter + * @return Current builder + * @see #getCredentials + */ + Builder setCredentials(String credentials); + + /** + * see getter + * + * @param transcriptionOptions see getter + * @return Current builder + * @see #getTranscriptionOptions + */ + Builder setTranscriptionOptions(TranscriptionOptions transcriptionOptions); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + RecordingOptions build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingOptionsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingOptionsImpl.java new file mode 100644 index 000000000..764501d8d --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingOptionsImpl.java @@ -0,0 +1,232 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +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({ + RecordingOptionsImpl.JSON_PROPERTY_FORMAT, + RecordingOptionsImpl.JSON_PROPERTY_RECORDING_TYPE, + RecordingOptionsImpl.JSON_PROPERTY_DESTINATION, + RecordingOptionsImpl.JSON_PROPERTY_DESTINATION_URL, + RecordingOptionsImpl.JSON_PROPERTY_CREDENTIALS, + RecordingOptionsImpl.JSON_PROPERTY_TRANSCRIPTION_OPTIONS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class RecordingOptionsImpl implements RecordingOptions { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_FORMAT = "format"; + + private OptionalValue format; + + public static final String JSON_PROPERTY_RECORDING_TYPE = "recordingType"; + + private OptionalValue recordingType; + + public static final String JSON_PROPERTY_DESTINATION = "destination"; + + private OptionalValue destination; + + public static final String JSON_PROPERTY_DESTINATION_URL = "destinationUrl"; + + private OptionalValue destinationUrl; + + public static final String JSON_PROPERTY_CREDENTIALS = "credentials"; + + private OptionalValue credentials; + + public static final String JSON_PROPERTY_TRANSCRIPTION_OPTIONS = "transcriptionOptions"; + + private OptionalValue transcriptionOptions; + + public RecordingOptionsImpl() {} + + protected RecordingOptionsImpl( + OptionalValue format, + OptionalValue recordingType, + OptionalValue destination, + OptionalValue destinationUrl, + OptionalValue credentials, + OptionalValue transcriptionOptions) { + this.format = format; + this.recordingType = recordingType; + this.destination = destination; + this.destinationUrl = destinationUrl; + this.credentials = credentials; + this.transcriptionOptions = transcriptionOptions; + } + + @JsonIgnore + public RecordingFormatType getFormat() { + return format.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_FORMAT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue format() { + return format; + } + + @JsonIgnore + public RecordingType getRecordingType() { + return recordingType.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_RECORDING_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue recordingType() { + return recordingType; + } + + @JsonIgnore + public RecordingDestinationType getDestination() { + return destination.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DESTINATION) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue destination() { + return destination; + } + + @JsonIgnore + public String getDestinationUrl() { + return destinationUrl.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DESTINATION_URL) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue destinationUrl() { + return destinationUrl; + } + + @JsonIgnore + public String getCredentials() { + return credentials.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_CREDENTIALS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue credentials() { + return credentials; + } + + @JsonIgnore + public TranscriptionOptions getTranscriptionOptions() { + return transcriptionOptions.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_TRANSCRIPTION_OPTIONS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue transcriptionOptions() { + return transcriptionOptions; + } + + /** Return true if this RecordingOptions object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RecordingOptionsImpl recordingOptions = (RecordingOptionsImpl) o; + return Objects.equals(this.format, recordingOptions.format) + && Objects.equals(this.recordingType, recordingOptions.recordingType) + && Objects.equals(this.destination, recordingOptions.destination) + && Objects.equals(this.destinationUrl, recordingOptions.destinationUrl) + && Objects.equals(this.credentials, recordingOptions.credentials) + && Objects.equals(this.transcriptionOptions, recordingOptions.transcriptionOptions); + } + + @Override + public int hashCode() { + return Objects.hash( + format, recordingType, destination, destinationUrl, credentials, transcriptionOptions); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class RecordingOptionsImpl {\n"); + sb.append(" format: ").append(toIndentedString(format)).append("\n"); + sb.append(" recordingType: ").append(toIndentedString(recordingType)).append("\n"); + sb.append(" destination: ").append(toIndentedString(destination)).append("\n"); + sb.append(" destinationUrl: ").append(toIndentedString(destinationUrl)).append("\n"); + sb.append(" credentials: ").append(toIndentedString("***")).append("\n"); + sb.append(" transcriptionOptions: ") + .append(toIndentedString(transcriptionOptions)) + .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 RecordingOptions.Builder { + OptionalValue format = OptionalValue.empty(); + OptionalValue recordingType = OptionalValue.empty(); + OptionalValue destination = OptionalValue.empty(); + OptionalValue destinationUrl = OptionalValue.empty(); + OptionalValue credentials = OptionalValue.empty(); + OptionalValue transcriptionOptions = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_FORMAT) + public Builder setFormat(RecordingFormatType format) { + this.format = OptionalValue.of(format); + return this; + } + + @JsonProperty(JSON_PROPERTY_RECORDING_TYPE) + public Builder setRecordingType(RecordingType recordingType) { + this.recordingType = OptionalValue.of(recordingType); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_DESTINATION, required = true) + public Builder setDestination(RecordingDestinationType destination) { + this.destination = OptionalValue.of(destination); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_DESTINATION_URL, required = true) + public Builder setDestinationUrl(String destinationUrl) { + this.destinationUrl = OptionalValue.of(destinationUrl); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_CREDENTIALS, required = true) + public Builder setCredentials(String credentials) { + this.credentials = OptionalValue.of(credentials); + return this; + } + + @JsonProperty(JSON_PROPERTY_TRANSCRIPTION_OPTIONS) + public Builder setTranscriptionOptions(TranscriptionOptions transcriptionOptions) { + this.transcriptionOptions = OptionalValue.of(transcriptionOptions); + return this; + } + + public RecordingOptions build() { + return new RecordingOptionsImpl( + format, recordingType, destination, destinationUrl, credentials, transcriptionOptions); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingType.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingType.java new file mode 100644 index 000000000..28faed891 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingType.java @@ -0,0 +1,38 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** The type of recording to perform. */ +public class RecordingType extends EnumDynamic { + /** Record inbound and outbound voice streams. */ + public static final RecordingType COMBINED = new RecordingType("COMBINED"); + + /** Record inbound voice stream only. */ + public static final RecordingType INBOUND = new RecordingType("INBOUND"); + + /** Record outbound voice stream only. */ + public static final RecordingType OUTBOUND = new RecordingType("OUTBOUND"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + RecordingType.class, RecordingType::new, Arrays.asList(COMBINED, INBOUND, OUTBOUND)); + + private RecordingType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static RecordingType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(RecordingType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StartRecordingCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StartRecordingCommand.java new file mode 100644 index 000000000..c197a0c18 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StartRecordingCommand.java @@ -0,0 +1,90 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; + +/** + * Starts recording the call. This is a non-blocking command — execution continues to the next + * command in the sequence immediately after recording begins. + */ +@JsonDeserialize(builder = StartRecordingCommandImpl.Builder.class) +public interface StartRecordingCommand extends SvamlCommand { + + /** + * Identifier for this recording within the session. Must be unique across active recordings in + * the session. + * + *

Other commands (e.g., stopRecording) reference this name to target a specific + * recording. + * + *

Setting the recording name is useful for stopping the recording using the + * stopRecording command. If name is not set, recording can only be stopped when the call + * is disconnected. + * + * @return recordingName + */ + String getRecordingName(); + + /** + * Recording options for this recording. + * + *

Field is required + * + * @return recordingOptions + */ + RecordingOptions getRecordingOptions(); + + /** + * SVAML commands to execute based on recording lifecycle outcomes. + * + * @return events + */ + RecordingEvents getEvents(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StartRecordingCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param recordingName see getter + * @return Current builder + * @see #getRecordingName + */ + Builder setRecordingName(String recordingName); + + /** + * see getter + * + * @param recordingOptions see getter + * @return Current builder + * @see #getRecordingOptions + */ + Builder setRecordingOptions(RecordingOptions recordingOptions); + + /** + * see getter + * + * @param events see getter + * @return Current builder + * @see #getEvents + */ + Builder setEvents(RecordingEvents events); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StartRecordingCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StartRecordingCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StartRecordingCommandImpl.java new file mode 100644 index 000000000..421ed37c6 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StartRecordingCommandImpl.java @@ -0,0 +1,208 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({ + StartRecordingCommandImpl.JSON_PROPERTY_COMMAND, + StartRecordingCommandImpl.JSON_PROPERTY_RECORDING_NAME, + StartRecordingCommandImpl.JSON_PROPERTY_RECORDING_OPTIONS, + StartRecordingCommandImpl.JSON_PROPERTY_EVENTS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class StartRecordingCommandImpl implements StartRecordingCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value startRecording. */ + public static class CommandEnum extends EnumDynamic { + /** The startRecording command. */ + public static final CommandEnum START_RECORDING = new CommandEnum("startRecording"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + CommandEnum.class, CommandEnum::new, Arrays.asList(START_RECORDING)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_RECORDING_NAME = "recordingName"; + + private OptionalValue recordingName; + + public static final String JSON_PROPERTY_RECORDING_OPTIONS = "recordingOptions"; + + private OptionalValue recordingOptions; + + public static final String JSON_PROPERTY_EVENTS = "events"; + + private OptionalValue events; + + public StartRecordingCommandImpl() {} + + protected StartRecordingCommandImpl( + OptionalValue command, + OptionalValue recordingName, + OptionalValue recordingOptions, + OptionalValue events) { + this.command = command; + this.recordingName = recordingName; + this.recordingOptions = recordingOptions; + this.events = events; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public String getRecordingName() { + return recordingName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_RECORDING_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue recordingName() { + return recordingName; + } + + @JsonIgnore + public RecordingOptions getRecordingOptions() { + return recordingOptions.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_RECORDING_OPTIONS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue recordingOptions() { + return recordingOptions; + } + + @JsonIgnore + public RecordingEvents getEvents() { + return events.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue events() { + return events; + } + + /** Return true if this StartRecordingCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StartRecordingCommandImpl startRecordingCommand = (StartRecordingCommandImpl) o; + return Objects.equals(this.command, startRecordingCommand.command) + && Objects.equals(this.recordingName, startRecordingCommand.recordingName) + && Objects.equals(this.recordingOptions, startRecordingCommand.recordingOptions) + && Objects.equals(this.events, startRecordingCommand.events); + } + + @Override + public int hashCode() { + return Objects.hash(command, recordingName, recordingOptions, events); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StartRecordingCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" recordingName: ").append(toIndentedString(recordingName)).append("\n"); + sb.append(" recordingOptions: ").append(toIndentedString(recordingOptions)).append("\n"); + sb.append(" events: ").append(toIndentedString(events)).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 StartRecordingCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.START_RECORDING); + OptionalValue recordingName = OptionalValue.empty(); + OptionalValue recordingOptions = OptionalValue.empty(); + OptionalValue events = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.START_RECORDING)) { + throw new IllegalArgumentException( + String.format( + "'command' must be '%s' (is '%s')", CommandEnum.START_RECORDING, command)); + } + return this; + } + + @JsonProperty(JSON_PROPERTY_RECORDING_NAME) + public Builder setRecordingName(String recordingName) { + this.recordingName = OptionalValue.of(recordingName); + return this; + } + + @JsonProperty(value = JSON_PROPERTY_RECORDING_OPTIONS, required = true) + public Builder setRecordingOptions(RecordingOptions recordingOptions) { + this.recordingOptions = OptionalValue.of(recordingOptions); + return this; + } + + @JsonProperty(JSON_PROPERTY_EVENTS) + public Builder setEvents(RecordingEvents events) { + this.events = OptionalValue.of(events); + return this; + } + + public StartRecordingCommand build() { + return new StartRecordingCommandImpl(command, recordingName, recordingOptions, events); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StopRecordingCommand.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StopRecordingCommand.java new file mode 100644 index 000000000..60b3a8de8 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StopRecordingCommand.java @@ -0,0 +1,62 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; + +/** + * Stops a recording previously started by a startRecording command. This is a + * non-blocking command — execution continues to the next command in the sequence immediately after + * the stop is initiated. + */ +@JsonDeserialize(builder = StopRecordingCommandImpl.Builder.class) +public interface StopRecordingCommand extends SvamlCommand { + + /** + * Name of the recording to stop, as set by recordingName in the startRecording + * command. + * + *

Field is required + * + * @return recordingName + */ + String getRecordingName(); + + /** + * Create a stopRecording command stopping the given recording + * + * @param recordingName see {@link #getRecordingName()} + * @return A new StopRecordingCommand + */ + static StopRecordingCommand of(String recordingName) { + return builder().setRecordingName(recordingName).build(); + } + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new StopRecordingCommandImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param recordingName see getter + * @return Current builder + * @see #getRecordingName + */ + Builder setRecordingName(String recordingName); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + StopRecordingCommand build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StopRecordingCommandImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StopRecordingCommandImpl.java new file mode 100644 index 000000000..0b77b94a5 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StopRecordingCommandImpl.java @@ -0,0 +1,152 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +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.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlCommand; +import java.util.Arrays; +import java.util.Objects; + +@JsonPropertyOrder({ + StopRecordingCommandImpl.JSON_PROPERTY_COMMAND, + StopRecordingCommandImpl.JSON_PROPERTY_RECORDING_NAME +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class StopRecordingCommandImpl implements StopRecordingCommand, SvamlCommand { + private static final long serialVersionUID = 1L; + + /** The command property. Must have the value stopRecording. */ + public static class CommandEnum extends EnumDynamic { + /** The stopRecording command. */ + public static final CommandEnum STOP_RECORDING = new CommandEnum("stopRecording"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + CommandEnum.class, CommandEnum::new, Arrays.asList(STOP_RECORDING)); + + private CommandEnum(String value) { + super(value); + } + + public static java.util.stream.Stream values() { + return ENUM_SUPPORT.values(); + } + + public static CommandEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(CommandEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + public static final String JSON_PROPERTY_COMMAND = "command"; + + private OptionalValue command; + + public static final String JSON_PROPERTY_RECORDING_NAME = "recordingName"; + + private OptionalValue recordingName; + + public StopRecordingCommandImpl() {} + + protected StopRecordingCommandImpl( + OptionalValue command, OptionalValue recordingName) { + this.command = command; + this.recordingName = recordingName; + } + + @JsonIgnore + public CommandEnum getCommand() { + return command.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_COMMAND) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue command() { + return command; + } + + @JsonIgnore + public String getRecordingName() { + return recordingName.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_RECORDING_NAME) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue recordingName() { + return recordingName; + } + + /** Return true if this StopRecordingCommand object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StopRecordingCommandImpl stopRecordingCommand = (StopRecordingCommandImpl) o; + return Objects.equals(this.command, stopRecordingCommand.command) + && Objects.equals(this.recordingName, stopRecordingCommand.recordingName); + } + + @Override + public int hashCode() { + return Objects.hash(command, recordingName); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StopRecordingCommandImpl {\n"); + sb.append(" command: ").append(toIndentedString(command)).append("\n"); + sb.append(" recordingName: ").append(toIndentedString(recordingName)).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 StopRecordingCommand.Builder { + OptionalValue command = OptionalValue.of(CommandEnum.STOP_RECORDING); + OptionalValue recordingName = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_COMMAND, required = true) + Builder setCommand(CommandEnum command) { + if (!Objects.equals(command, CommandEnum.STOP_RECORDING)) { + throw new IllegalArgumentException( + String.format("'command' must be '%s' (is '%s')", CommandEnum.STOP_RECORDING, command)); + } + return this; + } + + @JsonProperty(value = JSON_PROPERTY_RECORDING_NAME, required = true) + public Builder setRecordingName(String recordingName) { + this.recordingName = OptionalValue.of(recordingName); + return this; + } + + public StopRecordingCommand build() { + return new StopRecordingCommandImpl(command, recordingName); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/TranscriptionOptions.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/TranscriptionOptions.java new file mode 100644 index 000000000..5748d14ad --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/TranscriptionOptions.java @@ -0,0 +1,62 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** Configuration for automatic speech-to-text transcription of the recording. */ +@JsonDeserialize(builder = TranscriptionOptionsImpl.Builder.class) +public interface TranscriptionOptions { + + /** + * If true, the recording will be transcribed to text. + * + *

Field is required + * + * @return isEnabled + */ + Boolean getIsEnabled(); + + /** + * Language code in BCP-47 format. + * + * @return locale + */ + String getLocale(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new TranscriptionOptionsImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param isEnabled see getter + * @return Current builder + * @see #getIsEnabled + */ + Builder setIsEnabled(Boolean isEnabled); + + /** + * see getter + * + * @param locale see getter + * @return Current builder + * @see #getLocale + */ + Builder setLocale(String locale); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + TranscriptionOptions build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/TranscriptionOptionsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/TranscriptionOptionsImpl.java new file mode 100644 index 000000000..a249d85d3 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/recording/TranscriptionOptionsImpl.java @@ -0,0 +1,119 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +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({ + TranscriptionOptionsImpl.JSON_PROPERTY_IS_ENABLED, + TranscriptionOptionsImpl.JSON_PROPERTY_LOCALE +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class TranscriptionOptionsImpl implements TranscriptionOptions { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_IS_ENABLED = "isEnabled"; + + private OptionalValue isEnabled; + + public static final String JSON_PROPERTY_LOCALE = "locale"; + + private OptionalValue locale; + + public TranscriptionOptionsImpl() {} + + protected TranscriptionOptionsImpl( + OptionalValue isEnabled, OptionalValue locale) { + this.isEnabled = isEnabled; + this.locale = locale; + } + + @JsonIgnore + public Boolean getIsEnabled() { + return isEnabled.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_IS_ENABLED) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue isEnabled() { + return isEnabled; + } + + @JsonIgnore + public String getLocale() { + return locale.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_LOCALE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue locale() { + return locale; + } + + /** Return true if this TranscriptionOptions object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TranscriptionOptionsImpl transcriptionOptions = (TranscriptionOptionsImpl) o; + return Objects.equals(this.isEnabled, transcriptionOptions.isEnabled) + && Objects.equals(this.locale, transcriptionOptions.locale); + } + + @Override + public int hashCode() { + return Objects.hash(isEnabled, locale); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TranscriptionOptionsImpl {\n"); + sb.append(" isEnabled: ").append(toIndentedString(isEnabled)).append("\n"); + sb.append(" locale: ").append(toIndentedString(locale)).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 TranscriptionOptions.Builder { + OptionalValue isEnabled = OptionalValue.empty(); + OptionalValue locale = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_IS_ENABLED, required = true) + public Builder setIsEnabled(Boolean isEnabled) { + this.isEnabled = OptionalValue.of(isEnabled); + return this; + } + + @JsonProperty(JSON_PROPERTY_LOCALE) + public Builder setLocale(String locale) { + this.locale = OptionalValue.of(locale); + return this; + } + + public TranscriptionOptions build() { + return new TranscriptionOptionsImpl(isEnabled, locale); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/DescribeSvamlRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/DescribeSvamlRequest.java new file mode 100644 index 000000000..e2c1eda4f --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/DescribeSvamlRequest.java @@ -0,0 +1,47 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.request; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.sinch.sdk.domains.voice.models.v2.svaml.SvamlInput; + +/** Request payload for describing a SVAML document before using it in a call. */ +@JsonDeserialize(builder = DescribeSvamlRequestImpl.Builder.class) +public interface DescribeSvamlRequest { + + /** + * The SVAML payload to describe. + * + *

Field is required + * + * @return svaml + */ + SvamlInput getSvaml(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new DescribeSvamlRequestImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param svaml see getter + * @return Current builder + * @see #getSvaml + */ + Builder setSvaml(SvamlInput svaml); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + DescribeSvamlRequest build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/DescribeSvamlRequestImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/DescribeSvamlRequestImpl.java new file mode 100644 index 000000000..07fd5cdae --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/DescribeSvamlRequestImpl.java @@ -0,0 +1,91 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.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.v2.svaml.SvamlInput; +import java.util.Objects; + +@JsonPropertyOrder({DescribeSvamlRequestImpl.JSON_PROPERTY_SVAML}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class DescribeSvamlRequestImpl implements DescribeSvamlRequest { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_SVAML = "svaml"; + + private OptionalValue svaml; + + public DescribeSvamlRequestImpl() {} + + protected DescribeSvamlRequestImpl(OptionalValue svaml) { + this.svaml = svaml; + } + + @JsonIgnore + public SvamlInput getSvaml() { + return svaml.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SVAML) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue svaml() { + return svaml; + } + + /** Return true if this DescribeSvamlRequest object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DescribeSvamlRequestImpl describeSvamlRequest = (DescribeSvamlRequestImpl) o; + return Objects.equals(this.svaml, describeSvamlRequest.svaml); + } + + @Override + public int hashCode() { + return Objects.hash(svaml); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class DescribeSvamlRequestImpl {\n"); + sb.append(" svaml: ").append(toIndentedString(svaml)).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 DescribeSvamlRequest.Builder { + OptionalValue svaml = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_SVAML, required = true) + public Builder setSvaml(SvamlInput svaml) { + this.svaml = OptionalValue.of(svaml); + return this; + } + + public DescribeSvamlRequest build() { + return new DescribeSvamlRequestImpl(svaml); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/ValidateSvamlRequest.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/ValidateSvamlRequest.java new file mode 100644 index 000000000..3bd81004c --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/ValidateSvamlRequest.java @@ -0,0 +1,106 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.request; + +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.voice.models.v2.svaml.SvamlInput; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * Request payload for validating a SVAML document before using it in a call. + * + *

Use validationType to control how strictly the payload is checked. + */ +@JsonDeserialize(builder = ValidateSvamlRequestImpl.Builder.class) +public interface ValidateSvamlRequest { + + /** Controls how strictly the SVAML payload is validated. */ + public class ValidationTypeEnum extends EnumDynamic { + public static final ValidationTypeEnum NORMAL = new ValidationTypeEnum("NORMAL"); + + public static final ValidationTypeEnum STRICT = new ValidationTypeEnum("STRICT"); + + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + ValidationTypeEnum.class, ValidationTypeEnum::new, Arrays.asList(NORMAL, STRICT)); + + private ValidationTypeEnum(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static ValidationTypeEnum from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(ValidationTypeEnum e) { + return ENUM_SUPPORT.valueOf(e); + } + } + + /** + * The SVAML payload to validate. + * + *

Field is required + * + * @return svaml + */ + SvamlInput getSvaml(); + + /** + * Controls how strictly the SVAML payload is validated. + * + *

NORMAL: applies the same validation rules used when initiating a call. Unknown + * or extra properties are ignored. + * + *

STRICT: applies stricter validation rules. Returns errors for any properties + * that are not recognised or not expected in the given context. + * + *

If omitted, the server applies NORMAL. + * + * @return validationType + */ + ValidationTypeEnum getValidationType(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new ValidateSvamlRequestImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param svaml see getter + * @return Current builder + * @see #getSvaml + */ + Builder setSvaml(SvamlInput svaml); + + /** + * see getter + * + * @param validationType see getter + * @return Current builder + * @see #getValidationType + */ + Builder setValidationType(ValidationTypeEnum validationType); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + ValidateSvamlRequest build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/ValidateSvamlRequestImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/ValidateSvamlRequestImpl.java new file mode 100644 index 000000000..249dff845 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/request/ValidateSvamlRequestImpl.java @@ -0,0 +1,120 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.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.v2.svaml.SvamlInput; +import java.util.Objects; + +@JsonPropertyOrder({ + ValidateSvamlRequestImpl.JSON_PROPERTY_SVAML, + ValidateSvamlRequestImpl.JSON_PROPERTY_VALIDATION_TYPE +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class ValidateSvamlRequestImpl implements ValidateSvamlRequest { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_SVAML = "svaml"; + + private OptionalValue svaml; + + public static final String JSON_PROPERTY_VALIDATION_TYPE = "validationType"; + + private OptionalValue validationType; + + public ValidateSvamlRequestImpl() {} + + protected ValidateSvamlRequestImpl( + OptionalValue svaml, OptionalValue validationType) { + this.svaml = svaml; + this.validationType = validationType; + } + + @JsonIgnore + public SvamlInput getSvaml() { + return svaml.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_SVAML) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue svaml() { + return svaml; + } + + @JsonIgnore + public ValidationTypeEnum getValidationType() { + return validationType.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_VALIDATION_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue validationType() { + return validationType; + } + + /** Return true if this ValidateSvamlRequest object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ValidateSvamlRequestImpl validateSvamlRequest = (ValidateSvamlRequestImpl) o; + return Objects.equals(this.svaml, validateSvamlRequest.svaml) + && Objects.equals(this.validationType, validateSvamlRequest.validationType); + } + + @Override + public int hashCode() { + return Objects.hash(svaml, validationType); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ValidateSvamlRequestImpl {\n"); + sb.append(" svaml: ").append(toIndentedString(svaml)).append("\n"); + sb.append(" validationType: ").append(toIndentedString(validationType)).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 ValidateSvamlRequest.Builder { + OptionalValue svaml = OptionalValue.empty(); + OptionalValue validationType = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_SVAML, required = true) + public Builder setSvaml(SvamlInput svaml) { + this.svaml = OptionalValue.of(svaml); + return this; + } + + @JsonProperty(JSON_PROPERTY_VALIDATION_TYPE) + public Builder setValidationType(ValidationTypeEnum validationType) { + this.validationType = OptionalValue.of(validationType); + return this; + } + + public ValidateSvamlRequest build() { + return new ValidateSvamlRequestImpl(svaml, validationType); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/SvamlDescriptionResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/SvamlDescriptionResponse.java new file mode 100644 index 000000000..be1b7bc31 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/SvamlDescriptionResponse.java @@ -0,0 +1,44 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** Response containing a human-readable description of the SVAML call flow. */ +@JsonDeserialize(builder = SvamlDescriptionResponseImpl.Builder.class) +public interface SvamlDescriptionResponse { + + /** + * Human-readable description of the call flow. + * + * @return description + */ + String getDescription(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new SvamlDescriptionResponseImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param description see getter + * @return Current builder + * @see #getDescription + */ + Builder setDescription(String description); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + SvamlDescriptionResponse build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/SvamlDescriptionResponseImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/SvamlDescriptionResponseImpl.java new file mode 100644 index 000000000..3f0d3e355 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/SvamlDescriptionResponseImpl.java @@ -0,0 +1,90 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.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({SvamlDescriptionResponseImpl.JSON_PROPERTY_DESCRIPTION}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class SvamlDescriptionResponseImpl implements SvamlDescriptionResponse { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_DESCRIPTION = "description"; + + private OptionalValue description; + + public SvamlDescriptionResponseImpl() {} + + protected SvamlDescriptionResponseImpl(OptionalValue description) { + this.description = description; + } + + @JsonIgnore + public String getDescription() { + return description.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_DESCRIPTION) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue description() { + return description; + } + + /** Return true if this SvamlDescriptionResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SvamlDescriptionResponseImpl svamlDescriptionResponse = (SvamlDescriptionResponseImpl) o; + return Objects.equals(this.description, svamlDescriptionResponse.description); + } + + @Override + public int hashCode() { + return Objects.hash(description); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SvamlDescriptionResponseImpl {\n"); + sb.append(" description: ").append(toIndentedString(description)).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 SvamlDescriptionResponse.Builder { + OptionalValue description = OptionalValue.empty(); + + @JsonProperty(JSON_PROPERTY_DESCRIPTION) + public Builder setDescription(String description) { + this.description = OptionalValue.of(description); + return this; + } + + public SvamlDescriptionResponse build() { + return new SvamlDescriptionResponseImpl(description); + } + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/ValidateSvamlResponse.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/ValidateSvamlResponse.java new file mode 100644 index 000000000..608afda37 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/ValidateSvamlResponse.java @@ -0,0 +1,72 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.List; + +/** + * Result of a SVAML validation request. + * + *

The HTTP 200 status indicates the validation was successfully performed — not + * that the payload is valid. Inspect isValid to determine the outcome. + */ +@JsonDeserialize(builder = ValidateSvamlResponseImpl.Builder.class) +public interface ValidateSvamlResponse { + + /** + * true if the submitted SVAML payload passed validation; false if one + * or more errors were found. + * + *

Field is required + * + * @return isValid + */ + Boolean getIsValid(); + + /** + * Validation error messages describing why the SVAML payload is invalid. + * + *

Present only when isValid is false. Each entry identifies a + * specific problem, including the affected field or command where applicable. + * + * @return errors + */ + List getErrors(); + + /** + * Getting builder + * + * @return New Builder instance + */ + static Builder builder() { + return new ValidateSvamlResponseImpl.Builder(); + } + + /** Dedicated Builder */ + interface Builder { + + /** + * see getter + * + * @param isValid see getter + * @return Current builder + * @see #getIsValid + */ + Builder setIsValid(Boolean isValid); + + /** + * see getter + * + * @param errors see getter + * @return Current builder + * @see #getErrors + */ + Builder setErrors(List errors); + + /** + * Create instance + * + * @return The instance build with current builder values + */ + ValidateSvamlResponse build(); + } +} diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/ValidateSvamlResponseImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/ValidateSvamlResponseImpl.java new file mode 100644 index 000000000..613125ce4 --- /dev/null +++ b/openapi-contracts/src/main/com/sinch/sdk/domains/voice/models/v2/svaml/response/ValidateSvamlResponseImpl.java @@ -0,0 +1,120 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.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.List; +import java.util.Objects; + +@JsonPropertyOrder({ + ValidateSvamlResponseImpl.JSON_PROPERTY_IS_VALID, + ValidateSvamlResponseImpl.JSON_PROPERTY_ERRORS +}) +@JsonFilter("uninitializedFilter") +@JsonInclude(value = JsonInclude.Include.CUSTOM) +public class ValidateSvamlResponseImpl implements ValidateSvamlResponse { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_IS_VALID = "isValid"; + + private OptionalValue isValid; + + public static final String JSON_PROPERTY_ERRORS = "errors"; + + private OptionalValue> errors; + + public ValidateSvamlResponseImpl() {} + + protected ValidateSvamlResponseImpl( + OptionalValue isValid, OptionalValue> errors) { + this.isValid = isValid; + this.errors = errors; + } + + @JsonIgnore + public Boolean getIsValid() { + return isValid.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_IS_VALID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public OptionalValue isValid() { + return isValid; + } + + @JsonIgnore + public List getErrors() { + return errors.orElse(null); + } + + @JsonProperty(JSON_PROPERTY_ERRORS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public OptionalValue> errors() { + return errors; + } + + /** Return true if this ValidateSvamlResponse object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ValidateSvamlResponseImpl validateSvamlResponse = (ValidateSvamlResponseImpl) o; + return Objects.equals(this.isValid, validateSvamlResponse.isValid) + && Objects.equals(this.errors, validateSvamlResponse.errors); + } + + @Override + public int hashCode() { + return Objects.hash(isValid, errors); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ValidateSvamlResponseImpl {\n"); + sb.append(" isValid: ").append(toIndentedString(isValid)).append("\n"); + sb.append(" errors: ").append(toIndentedString(errors)).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 ValidateSvamlResponse.Builder { + OptionalValue isValid = OptionalValue.empty(); + OptionalValue> errors = OptionalValue.empty(); + + @JsonProperty(value = JSON_PROPERTY_IS_VALID, required = true) + public Builder setIsValid(Boolean isValid) { + this.isValid = OptionalValue.of(isValid); + return this; + } + + @JsonProperty(JSON_PROPERTY_ERRORS) + public Builder setErrors(List errors) { + this.errors = OptionalValue.of(errors); + return this; + } + + public ValidateSvamlResponse build() { + return new ValidateSvamlResponseImpl(isValid, errors); + } + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/CallDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/CallDtoTest.java new file mode 100644 index 000000000..e84c5ba04 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/CallDtoTest.java @@ -0,0 +1,77 @@ +package com.sinch.sdk.domains.voice.models.v2; + +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.voice.models.v2.destination.PhoneDtoTest; +import com.sinch.sdk.domains.voice.models.v2.destination.SipFromDtoTest; +import java.time.Instant; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class CallDtoTest extends BaseTest { + + public static final Call expectedCall = + Call.builder() + .setCallId("01ARZ3NDEKTSV4RRFFQ69G5FAA") + .setProjectId("5c5bf2b1-35ae-4825-ab89-457e07bb60e6") + .setServiceId("6e124178-c29d-46a5-943c-5c2ae544aade") + .setSessionId("01BX5ZZKBKACTAV9WEVGEMMVRB") + .setCallName("origin") + .setBridgeName("my-bridge") + .setBatchId("01BX5ZZKBKACTAV9WEVGEMMVRC") + .setFrom(SipFromDtoTest.expectedSipFrom) + .setTo(PhoneDtoTest.expectedPhone) + .setStartTime(Instant.parse("2025-02-10T09:00:00Z")) + .setUpdateTime(Instant.parse("2025-02-10T09:00:47Z")) + .setCallType(CallType.PHONE) + .setDirection(CallDirection.INBOUND) + .setAnswerTime(Instant.parse("2025-02-10T09:00:05Z")) + .setEndTime(Instant.parse("2025-02-10T09:00:47Z")) + .setCallDurationSeconds(42) + .setCallResult(CallResult.COMPLETED) + .setCallReason(CallReason.CALLEE_HANGUP) + .setOriginationType(OriginationType.SIP) + .setCallRate(MoneyDtoTest.expectedMoney) + .setCallResourceUrl( + "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls/01ARZ3NDEKTSV4RRFFQ69G5FAA") + .build(); + + public static final Call expectedCallMinimal = + Call.builder() + .setCallId("01ARZ3NDEKTSV4RRFFQ69G5FAA") + .setProjectId("5c5bf2b1-35ae-4825-ab89-457e07bb60e6") + .setServiceId("6e124178-c29d-46a5-943c-5c2ae544aade") + .setSessionId("01BX5ZZKBKACTAV9WEVGEMMVRB") + .setStartTime(Instant.parse("2025-02-10T09:00:00Z")) + .setCallType(CallType.PHONE) + .setDirection(CallDirection.OUTBOUND) + .setCallResult(CallResult.QUEUED) + .setOriginationType(OriginationType.SERVER) + .setCallRate(MoneyDtoTest.expectedMoney) + .setCallResourceUrl( + "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls/01ARZ3NDEKTSV4RRFFQ69G5FAA") + .build(); + + @GivenTextResource("/domains/voice/v2/CallDto.json") + String jsonCall; + + @GivenTextResource("/domains/voice/v2/CallMinimalDto.json") + String jsonCallMinimal; + + @Test + void deserialize() throws JsonProcessingException { + Call deserialized = objectMapper.readValue(jsonCall, Call.class); + + TestHelpers.recursiveEquals(deserialized, expectedCall); + } + + @Test + void deserializeMinimalDto() throws JsonProcessingException { + Call deserialized = objectMapper.readValue(jsonCallMinimal, Call.class); + + TestHelpers.recursiveEquals(deserialized, expectedCallMinimal); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/MoneyDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/MoneyDtoTest.java new file mode 100644 index 000000000..64f91460b --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/MoneyDtoTest.java @@ -0,0 +1,25 @@ +package com.sinch.sdk.domains.voice.models.v2; + +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 MoneyDtoTest extends BaseTest { + + public static final Money expectedMoney = + Money.builder().setCurrencyCode("USD").setAmount("0.0123").build(); + + @GivenTextResource("/domains/voice/v2/MoneyDto.json") + String jsonMoney; + + @Test + void deserialize() throws JsonProcessingException { + Money deserialized = objectMapper.readValue(jsonMoney, Money.class); + + TestHelpers.recursiveEquals(deserialized, expectedMoney); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/PaginationLinksDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/PaginationLinksDtoTest.java new file mode 100644 index 000000000..5a830c985 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/PaginationLinksDtoTest.java @@ -0,0 +1,35 @@ +package com.sinch.sdk.domains.voice.models.v2; + +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 PaginationLinksDtoTest extends BaseTest { + + static final String BASE_URL = + "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls"; + + public static final PaginationLinks expectedPaginationLinks = + PaginationLinks.builder() + .setFirst(BASE_URL + "?page=1&pageSize=2") + .setLast(BASE_URL + "?page=2&pageSize=2") + .setNext(BASE_URL + "?page=3&pageSize=2") + .setPrev(BASE_URL + "?page=1&pageSize=2") + .setSelf(BASE_URL + "?page=2&pageSize=2") + .build(); + + @GivenTextResource("/domains/voice/v2/PaginationLinksDto.json") + String jsonPaginationLinks; + + @Test + void deserialize() throws JsonProcessingException { + PaginationLinks deserialized = + objectMapper.readValue(jsonPaginationLinks, PaginationLinks.class); + + TestHelpers.recursiveEquals(deserialized, expectedPaginationLinks); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/PaginationMetaDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/PaginationMetaDtoTest.java new file mode 100644 index 000000000..aa39393f8 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/PaginationMetaDtoTest.java @@ -0,0 +1,25 @@ +package com.sinch.sdk.domains.voice.models.v2; + +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 PaginationMetaDtoTest extends BaseTest { + + public static final PaginationMeta expectedPaginationMeta = + PaginationMeta.builder().setTotalCount(3).setPageCount(2).build(); + + @GivenTextResource("/domains/voice/v2/PaginationMetaDto.json") + String jsonPaginationMeta; + + @Test + void deserialize() throws JsonProcessingException { + PaginationMeta deserialized = objectMapper.readValue(jsonPaginationMeta, PaginationMeta.class); + + TestHelpers.recursiveEquals(deserialized, expectedPaginationMeta); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/request/BatchOptionsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/request/BatchOptionsDtoTest.java new file mode 100644 index 000000000..3c02b31b4 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/request/BatchOptionsDtoTest.java @@ -0,0 +1,26 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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 org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class BatchOptionsDtoTest extends BaseTest { + + public static final BatchOptions expectedBatchOptions = + BatchOptions.builder().setMaxCps(10).setTtlSeconds(3600).build(); + + @GivenTextResource("/domains/voice/v2/batches/request/BatchOptionsDto.json") + String jsonBatchOptions; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedBatchOptions); + + JSONAssert.assertEquals(jsonBatchOptions, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchRequestDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchRequestDtoTest.java new file mode 100644 index 000000000..325a5cbb7 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/request/StartBatchRequestDtoTest.java @@ -0,0 +1,45 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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.v2.svaml.calls.DialCommandDtoTest; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class StartBatchRequestDtoTest extends BaseTest { + + static final Map secondCallParameters = new HashMap<>(); + + static { + secondCallParameters.put("numberB", "+15559876544"); + secondCallParameters.put("name", "Alice"); + } + + public static final StartBatchRequest expectedStartBatchRequest = + StartBatchRequest.builder() + .setCommands(Arrays.asList(DialCommandDtoTest.expectedDialCommand)) + .setParameters( + Arrays.asList( + Collections.singletonMap("numberB", "+15559876543"), secondCallParameters)) + .setBatchOptions(BatchOptionsDtoTest.expectedBatchOptions) + .setIdempotencyKey("3f1c7a52-8d3e-4b9a-9c0e-2f6d1b7a4e21") + .build(); + + @GivenTextResource("/domains/voice/v2/batches/request/StartBatchRequestDto.json") + String jsonStartBatchRequest; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedStartBatchRequest); + + JSONAssert.assertEquals(jsonStartBatchRequest, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchDetailsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchDetailsDtoTest.java new file mode 100644 index 000000000..9243b563a --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchDetailsDtoTest.java @@ -0,0 +1,41 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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.voice.models.v2.SessionState; +import java.util.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class BatchDetailsDtoTest extends BaseTest { + + public static final BatchDetails expectedBatchDetails = + BatchDetails.builder() + .setSessions( + Arrays.asList( + BatchSessionSummaryDtoTest.expectedBatchSessionSummary, + BatchSessionSummary.builder() + .setId("01F8Z5J4X2G9Y3J4X2G9Y3J4X5YJ") + .setState(SessionState.IN_PROGRESS) + .setParameters(Collections.singletonMap("numberB", "+15559876544")) + .build(), + BatchSessionSummary.builder() + .setId("01F8Z5J4X2G9Y3J4X2G9Y3J4X9QK") + .setState(SessionState.QUEUED) + .build())) + .build(); + + @GivenTextResource("/domains/voice/v2/batches/response/BatchDetailsDto.json") + String jsonBatchDetails; + + @Test + void deserialize() throws JsonProcessingException { + BatchDetails deserialized = objectMapper.readValue(jsonBatchDetails, BatchDetails.class); + + TestHelpers.recursiveEquals(deserialized, expectedBatchDetails); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSessionSummaryDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSessionSummaryDtoTest.java new file mode 100644 index 000000000..180b314e6 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSessionSummaryDtoTest.java @@ -0,0 +1,32 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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.voice.models.v2.SessionState; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class BatchSessionSummaryDtoTest extends BaseTest { + + public static final BatchSessionSummary expectedBatchSessionSummary = + BatchSessionSummary.builder() + .setId("01F8Z5J4X2G9Y3J4X2G9Y3J4X2G9") + .setState(SessionState.COMPLETED) + .setParameters(Collections.singletonMap("numberB", "+15559876543")) + .build(); + + @GivenTextResource("/domains/voice/v2/batches/response/BatchSessionSummaryDto.json") + String jsonBatchSessionSummary; + + @Test + void deserialize() throws JsonProcessingException { + BatchSessionSummary deserialized = + objectMapper.readValue(jsonBatchSessionSummary, BatchSessionSummary.class); + + TestHelpers.recursiveEquals(deserialized, expectedBatchSessionSummary); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchStopResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchStopResponseDtoTest.java new file mode 100644 index 000000000..bced282f5 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchStopResponseDtoTest.java @@ -0,0 +1,26 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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.junit.jupiter.api.Test; + +@TestWithResources +public class BatchStopResponseDtoTest extends BaseTest { + + public static final BatchStopResponse expectedBatchStopResponse = + BatchStopResponse.builder().setResult(BatchStopResponse.ResultEnum.STOP_REQUESTED).build(); + + @GivenTextResource("/domains/voice/v2/batches/response/BatchStopResponseDto.json") + String jsonBatchStopResponse; + + @Test + void deserialize() throws JsonProcessingException { + BatchStopResponse deserialized = + objectMapper.readValue(jsonBatchStopResponse, BatchStopResponse.class); + + TestHelpers.recursiveEquals(deserialized, expectedBatchStopResponse); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSummaryDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSummaryDtoTest.java new file mode 100644 index 000000000..5dee722e9 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/BatchSummaryDtoTest.java @@ -0,0 +1,36 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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 BatchSummaryDtoTest extends BaseTest { + + public static final BatchSummary expectedBatchSummary = + BatchSummary.builder() + .setBatchId("01BX5ZZKBKACTAV9WEVGEMMVRC") + .setSessionCount(3) + .setEndTime(Instant.parse("2026-08-28T12:17:49Z")) + .setQueued(1) + .setInProgress(1) + .setCompleted(1) + .setExpired(0) + .setTtlSeconds(3600) + .setRequestedCps(10) + .build(); + + @GivenTextResource("/domains/voice/v2/batches/response/BatchSummaryDto.json") + String jsonBatchSummary; + + @Test + void deserialize() throws JsonProcessingException { + BatchSummary deserialized = objectMapper.readValue(jsonBatchSummary, BatchSummary.class); + + TestHelpers.recursiveEquals(deserialized, expectedBatchSummary); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/StartBatchResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/StartBatchResponseDtoTest.java new file mode 100644 index 000000000..3d40ae9bc --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/batches/response/StartBatchResponseDtoTest.java @@ -0,0 +1,30 @@ +package com.sinch.sdk.domains.voice.models.v2.batches.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.junit.jupiter.api.Test; + +@TestWithResources +public class StartBatchResponseDtoTest extends BaseTest { + + public static final StartBatchResponse expectedStartBatchResponse = + StartBatchResponse.builder() + .setProjectId("5c5bf2b1-35ae-4825-ab89-457e07bb60e6") + .setServiceId("6e124178-c29d-46a5-943c-5c2ae544aade") + .setBatchId("01BX5ZZKBKACTAV9WEVGEMMVRC") + .build(); + + @GivenTextResource("/domains/voice/v2/batches/response/StartBatchResponseDto.json") + String jsonStartBatchResponse; + + @Test + void deserialize() throws JsonProcessingException { + StartBatchResponse deserialized = + objectMapper.readValue(jsonStartBatchResponse, StartBatchResponse.class); + + TestHelpers.recursiveEquals(deserialized, expectedStartBatchResponse); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/request/CallPatchRequestDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/request/CallPatchRequestDtoTest.java new file mode 100644 index 000000000..dc6be0fe9 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/request/CallPatchRequestDtoTest.java @@ -0,0 +1,35 @@ +package com.sinch.sdk.domains.voice.models.v2.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.v2.svaml.calls.HangupCommandDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommandDtoTest; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class CallPatchRequestDtoTest extends BaseTest { + + public static final CallPatchRequest expectedCallPatchRequest = + CallPatchRequest.builder() + .setCommands( + Arrays.asList( + MessagesCommandDtoTest.expectedMessagesCommand, + HangupCommandDtoTest.expectedHangupCommand)) + .setIdempotencyKey("7b2e9d41-5c8a-4f3e-a1d6-0e4b8c2f9a17") + .build(); + + @GivenTextResource("/domains/voice/v2/calls/request/CallPatchRequestDto.json") + String jsonCallPatchRequest; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedCallPatchRequest); + + JSONAssert.assertEquals(jsonCallPatchRequest, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallRequestDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallRequestDtoTest.java new file mode 100644 index 000000000..dd56cfddf --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/request/StartCallRequestDtoTest.java @@ -0,0 +1,31 @@ +package com.sinch.sdk.domains.voice.models.v2.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.v2.svaml.calls.DialCommandDtoTest; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class StartCallRequestDtoTest extends BaseTest { + + public static final StartCallRequest expectedStartCallRequest = + StartCallRequest.builder() + .setCommands(Arrays.asList(DialCommandDtoTest.expectedDialCommand)) + .setIdempotencyKey("3f1c7a52-8d3e-4b9a-9c0e-2f6d1b7a4e21") + .build(); + + @GivenTextResource("/domains/voice/v2/calls/request/StartCallRequestDto.json") + String jsonStartCallRequest; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedStartCallRequest); + + JSONAssert.assertEquals(jsonStartCallRequest, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/response/StartCallResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/response/StartCallResponseDtoTest.java new file mode 100644 index 000000000..29c696e8f --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/response/StartCallResponseDtoTest.java @@ -0,0 +1,30 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.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.junit.jupiter.api.Test; + +@TestWithResources +public class StartCallResponseDtoTest extends BaseTest { + + public static final StartCallResponse expectedStartCallResponse = + StartCallResponse.builder() + .setProjectId("5c5bf2b1-35ae-4825-ab89-457e07bb60e6") + .setServiceId("6e124178-c29d-46a5-943c-5c2ae544aade") + .setSessionId("01BX5ZZKBKACTAV9WEVGEMMVRB") + .build(); + + @GivenTextResource("/domains/voice/v2/calls/response/StartCallResponseDto.json") + String jsonStartCallResponse; + + @Test + void deserialize() throws JsonProcessingException { + StartCallResponse deserialized = + objectMapper.readValue(jsonStartCallResponse, StartCallResponse.class); + + TestHelpers.recursiveEquals(deserialized, expectedStartCallResponse); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/response/internal/CallsListResponseInternalDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/response/internal/CallsListResponseInternalDtoTest.java new file mode 100644 index 000000000..981f1df3d --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/calls/response/internal/CallsListResponseInternalDtoTest.java @@ -0,0 +1,69 @@ +package com.sinch.sdk.domains.voice.models.v2.calls.response.internal; + +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.voice.models.v2.CallDtoTest; +import com.sinch.sdk.domains.voice.models.v2.PaginationLinks; +import com.sinch.sdk.domains.voice.models.v2.PaginationMetaDtoTest; +import java.util.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class CallsListResponseInternalDtoTest extends BaseTest { + + static final String BASE_URL = + "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls"; + + public static final CallsListResponseInternal expectedCallsListResponse = + CallsListResponseInternal.builder() + .setCalls(Arrays.asList(CallDtoTest.expectedCall, CallDtoTest.expectedCallMinimal)) + .setLinks( + PaginationLinks.builder() + .setFirst(BASE_URL + "?page=1&pageSize=2") + .setLast(BASE_URL + "?page=2&pageSize=2") + .setNext(BASE_URL + "?page=2&pageSize=2") + .setSelf(BASE_URL + "?page=1&pageSize=2") + .build()) + .setMeta(PaginationMetaDtoTest.expectedPaginationMeta) + .build(); + + public static final CallsListResponseInternal expectedCallsListLastPageResponse = + CallsListResponseInternal.builder() + .setCalls(Collections.singletonList(CallDtoTest.expectedCallMinimal)) + .setLinks( + PaginationLinks.builder() + .setFirst(BASE_URL + "?page=1&pageSize=2") + .setLast(BASE_URL + "?page=2&pageSize=2") + .setPrev(BASE_URL + "?page=1&pageSize=2") + .setSelf(BASE_URL + "?page=2&pageSize=2") + .build()) + .setMeta(PaginationMetaDtoTest.expectedPaginationMeta) + .build(); + + @GivenTextResource("/domains/voice/v2/calls/response/internal/CallsListResponseInternalDto.json") + String jsonCallsListResponse; + + @GivenTextResource( + "/domains/voice/v2/calls/response/internal/CallsListResponseInternalLastPageDto.json") + String jsonCallsListLastPageResponse; + + @Test + void deserialize() throws JsonProcessingException { + CallsListResponseInternal deserialized = + objectMapper.readValue(jsonCallsListResponse, CallsListResponseInternal.class); + + TestHelpers.recursiveEquals(deserialized, expectedCallsListResponse); + } + + @Test + void deserializeLastPage() throws JsonProcessingException { + CallsListResponseInternal deserialized = + objectMapper.readValue(jsonCallsListLastPageResponse, CallsListResponseInternal.class); + + TestHelpers.recursiveEquals(deserialized, expectedCallsListLastPageResponse); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/CallHeaderDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/CallHeaderDtoTest.java new file mode 100644 index 000000000..1ee03d2d4 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/CallHeaderDtoTest.java @@ -0,0 +1,34 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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 CallHeaderDtoTest extends BaseTest { + + public static final CallHeader expectedCallHeader = + CallHeader.builder().setKey("customer-id").setValue("12345").build(); + + @GivenTextResource("/domains/voice/v2/destination/CallHeaderDto.json") + String jsonCallHeader; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedCallHeader); + + JSONAssert.assertEquals(jsonCallHeader, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + CallHeader deserialized = objectMapper.readValue(jsonCallHeader, CallHeader.class); + + TestHelpers.recursiveEquals(deserialized, expectedCallHeader); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/PhoneDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/PhoneDtoTest.java new file mode 100644 index 000000000..8b1b287ce --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/PhoneDtoTest.java @@ -0,0 +1,33 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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 PhoneDtoTest extends BaseTest { + + public static final Phone expectedPhone = Phone.builder().setNumber("+15551234567").build(); + + @GivenTextResource("/domains/voice/v2/destination/PhoneDto.json") + String jsonPhone; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedPhone); + + JSONAssert.assertEquals(jsonPhone, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + Phone deserialized = objectMapper.readValue(jsonPhone, Phone.class); + + TestHelpers.recursiveEquals(deserialized, expectedPhone); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/SipCallHeaderDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/SipCallHeaderDtoTest.java new file mode 100644 index 000000000..1ab1a4ef3 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/SipCallHeaderDtoTest.java @@ -0,0 +1,34 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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 SipCallHeaderDtoTest extends BaseTest { + + public static final SipCallHeader expectedSipCallHeader = + SipCallHeader.builder().setKey("X-Custom-Header").setValue("custom-value").build(); + + @GivenTextResource("/domains/voice/v2/destination/SipCallHeaderDto.json") + String jsonSipCallHeader; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedSipCallHeader); + + JSONAssert.assertEquals(jsonSipCallHeader, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + SipCallHeader deserialized = objectMapper.readValue(jsonSipCallHeader, SipCallHeader.class); + + TestHelpers.recursiveEquals(deserialized, expectedSipCallHeader); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/SipDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/SipDtoTest.java new file mode 100644 index 000000000..d5f0fe877 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/SipDtoTest.java @@ -0,0 +1,39 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class SipDtoTest extends BaseTest { + + public static final Sip expectedSip = + Sip.builder() + .setEndpoint("sips:bob@sip.example.com") + .setTransport(Sip.TransportEnum.TLS) + .setCallHeaders(Arrays.asList(SipCallHeaderDtoTest.expectedSipCallHeader)) + .build(); + + @GivenTextResource("/domains/voice/v2/destination/SipDto.json") + String jsonSip; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedSip); + + JSONAssert.assertEquals(jsonSip, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + Sip deserialized = objectMapper.readValue(jsonSip, Sip.class); + + TestHelpers.recursiveEquals(deserialized, expectedSip); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/SipFromDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/SipFromDtoTest.java new file mode 100644 index 000000000..480b38b99 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/SipFromDtoTest.java @@ -0,0 +1,34 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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 SipFromDtoTest extends BaseTest { + + public static final SipFrom expectedSipFrom = + SipFrom.builder().setEndpoint("sip:alice@sip.example.com").setDisplayName("Alice").build(); + + @GivenTextResource("/domains/voice/v2/destination/SipFromDto.json") + String jsonSipFrom; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedSipFrom); + + JSONAssert.assertEquals(jsonSipFrom, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + SipFrom deserialized = objectMapper.readValue(jsonSipFrom, SipFrom.class); + + TestHelpers.recursiveEquals(deserialized, expectedSipFrom); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/StreamDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/StreamDtoTest.java new file mode 100644 index 000000000..22e45743e --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/StreamDtoTest.java @@ -0,0 +1,39 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class StreamDtoTest extends BaseTest { + + public static final Stream expectedStream = + Stream.builder() + .setEndpoint("wss://myapp.example.com/audio") + .setStreamOptions(StreamOptionsDtoTest.expectedStreamOptions) + .setCallHeaders(Arrays.asList(CallHeaderDtoTest.expectedCallHeader)) + .build(); + + @GivenTextResource("/domains/voice/v2/destination/StreamDto.json") + String jsonStream; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedStream); + + JSONAssert.assertEquals(jsonStream, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + Stream deserialized = objectMapper.readValue(jsonStream, Stream.class); + + TestHelpers.recursiveEquals(deserialized, expectedStream); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/StreamOptionsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/StreamOptionsDtoTest.java new file mode 100644 index 000000000..d11eec14f --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/StreamOptionsDtoTest.java @@ -0,0 +1,38 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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 StreamOptionsDtoTest extends BaseTest { + + public static final StreamOptions expectedStreamOptions = + StreamOptions.builder() + .setVersion(1) + .setCodec(StreamOptions.CodecEnum.PCM) + .setSampleRate(StreamOptions.SampleRateEnum.HZ_16000) + .build(); + + @GivenTextResource("/domains/voice/v2/destination/StreamOptionsDto.json") + String jsonStreamOptions; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedStreamOptions); + + JSONAssert.assertEquals(jsonStreamOptions, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + StreamOptions deserialized = objectMapper.readValue(jsonStreamOptions, StreamOptions.class); + + TestHelpers.recursiveEquals(deserialized, expectedStreamOptions); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/VoiceRelayDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/VoiceRelayDtoTest.java new file mode 100644 index 000000000..593b1a0b8 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/destination/VoiceRelayDtoTest.java @@ -0,0 +1,41 @@ +package com.sinch.sdk.domains.voice.models.v2.destination; + +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.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class VoiceRelayDtoTest extends BaseTest { + + public static final VoiceRelay expectedVoiceRelay = + VoiceRelay.builder() + .setEndpoint("wss://relay.example.com/agent") + .setEnableInterruptions(false) + .setTtsVoice("Emma") + .setSttLanguage("en-US") + .setCallHeaders(Arrays.asList(CallHeaderDtoTest.expectedCallHeader)) + .build(); + + @GivenTextResource("/domains/voice/v2/destination/VoiceRelayDto.json") + String jsonVoiceRelay; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedVoiceRelay); + + JSONAssert.assertEquals(jsonVoiceRelay, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + VoiceRelay deserialized = objectMapper.readValue(jsonVoiceRelay, VoiceRelay.class); + + TestHelpers.recursiveEquals(deserialized, expectedVoiceRelay); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationCallBehaviorDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationCallBehaviorDtoTest.java new file mode 100644 index 000000000..2cb9a4c0e --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationCallBehaviorDtoTest.java @@ -0,0 +1,47 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +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 EventDestinationCallBehaviorDtoTest extends BaseTest { + + public static final EventDestinationCallBehavior expectedEventDestinationCallBehavior = + EventDestinationCallBehavior.builder() + .setEventDestination( + EventDestinationConfigurationDtoTest.expectedEventDestinationConfiguration) + .build(); + + @GivenTextResource("/domains/voice/v2/services/EventDestinationCallBehaviorDto.json") + String jsonEventDestinationCallBehavior; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedEventDestinationCallBehavior); + + JSONAssert.assertEquals(jsonEventDestinationCallBehavior, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + EventDestinationCallBehavior deserialized = + objectMapper.readValue( + jsonEventDestinationCallBehavior, EventDestinationCallBehavior.class); + + TestHelpers.recursiveEquals(deserialized, expectedEventDestinationCallBehavior); + } + + @Test + void deserializeAsCallBehavior() throws JsonProcessingException { + CallBehavior deserialized = + objectMapper.readValue(jsonEventDestinationCallBehavior, CallBehavior.class); + + TestHelpers.recursiveEquals(deserialized, expectedEventDestinationCallBehavior); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationConfigurationDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationConfigurationDtoTest.java new file mode 100644 index 000000000..c1894b2cc --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/EventDestinationConfigurationDtoTest.java @@ -0,0 +1,40 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +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 EventDestinationConfigurationDtoTest extends BaseTest { + + public static final EventDestinationConfiguration expectedEventDestinationConfiguration = + EventDestinationConfiguration.builder() + .setUrl("https://example.com/webhook") + .setFallbackUrl("https://example.com/fallback") + .build(); + + @GivenTextResource("/domains/voice/v2/services/EventDestinationConfigurationDto.json") + String jsonEventDestinationConfiguration; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = + objectMapper.writeValueAsString(expectedEventDestinationConfiguration); + + JSONAssert.assertEquals(jsonEventDestinationConfiguration, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + EventDestinationConfiguration deserialized = + objectMapper.readValue( + jsonEventDestinationConfiguration, EventDestinationConfiguration.class); + + TestHelpers.recursiveEquals(deserialized, expectedEventDestinationConfiguration); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/NoneCallBehaviorDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/NoneCallBehaviorDtoTest.java new file mode 100644 index 000000000..3dd84c3dc --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/NoneCallBehaviorDtoTest.java @@ -0,0 +1,42 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +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 NoneCallBehaviorDtoTest extends BaseTest { + + public static final NoneCallBehavior expectedNoneCallBehavior = + NoneCallBehavior.NONE_CALL_BEHAVIOR; + + @GivenTextResource("/domains/voice/v2/services/NoneCallBehaviorDto.json") + String jsonNoneCallBehavior; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedNoneCallBehavior); + + JSONAssert.assertEquals(jsonNoneCallBehavior, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + NoneCallBehavior deserialized = + objectMapper.readValue(jsonNoneCallBehavior, NoneCallBehavior.class); + + TestHelpers.recursiveEquals(deserialized, expectedNoneCallBehavior); + } + + @Test + void deserializeAsCallBehavior() throws JsonProcessingException { + CallBehavior deserialized = objectMapper.readValue(jsonNoneCallBehavior, CallBehavior.class); + + TestHelpers.recursiveEquals(deserialized, expectedNoneCallBehavior); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/StaticCallBehaviorDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/StaticCallBehaviorDtoTest.java new file mode 100644 index 000000000..bbf8dee29 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/StaticCallBehaviorDtoTest.java @@ -0,0 +1,43 @@ +package com.sinch.sdk.domains.voice.models.v2.services; + +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.voice.models.v2.svaml.SvamlInputDtoTest; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class StaticCallBehaviorDtoTest extends BaseTest { + + public static final StaticCallBehavior expectedStaticCallBehavior = + StaticCallBehavior.builder().setStatic(SvamlInputDtoTest.expectedSvamlInput).build(); + + @GivenTextResource("/domains/voice/v2/services/StaticCallBehaviorDto.json") + String jsonStaticCallBehavior; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedStaticCallBehavior); + + JSONAssert.assertEquals(jsonStaticCallBehavior, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + StaticCallBehavior deserialized = + objectMapper.readValue(jsonStaticCallBehavior, StaticCallBehavior.class); + + TestHelpers.recursiveEquals(deserialized, expectedStaticCallBehavior); + } + + @Test + void deserializeAsCallBehavior() throws JsonProcessingException { + CallBehavior deserialized = objectMapper.readValue(jsonStaticCallBehavior, CallBehavior.class); + + TestHelpers.recursiveEquals(deserialized, expectedStaticCallBehavior); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/request/CreateServiceRequestDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/request/CreateServiceRequestDtoTest.java new file mode 100644 index 000000000..8905ff802 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/request/CreateServiceRequestDtoTest.java @@ -0,0 +1,33 @@ +package com.sinch.sdk.domains.voice.models.v2.services.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.v2.services.EventDestinationCallBehaviorDtoTest; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class CreateServiceRequestDtoTest extends BaseTest { + + public static final CreateServiceRequest expectedCreateServiceRequest = + CreateServiceRequest.builder() + .setName("Example service") + .setDescription("Service with webhooks") + .setIsDefault(true) + .setCallBehavior(EventDestinationCallBehaviorDtoTest.expectedEventDestinationCallBehavior) + .setIdempotencyKey("3f9c2a7e-8b1d-4e6f-9a2c-5d7e1b4f8c30") + .build(); + + @GivenTextResource("/domains/voice/v2/services/request/CreateServiceRequestDto.json") + String jsonCreateServiceRequest; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedCreateServiceRequest); + + JSONAssert.assertEquals(jsonCreateServiceRequest, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/request/UpdateServiceRequestDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/request/UpdateServiceRequestDtoTest.java new file mode 100644 index 000000000..7ec544505 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/request/UpdateServiceRequestDtoTest.java @@ -0,0 +1,31 @@ +package com.sinch.sdk.domains.voice.models.v2.services.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.v2.services.EventDestinationCallBehaviorDtoTest; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class UpdateServiceRequestDtoTest extends BaseTest { + + public static final UpdateServiceRequest expectedUpdateServiceRequest = + UpdateServiceRequest.builder() + .setDescription("Service with webhooks") + .setCallBehavior(EventDestinationCallBehaviorDtoTest.expectedEventDestinationCallBehavior) + .setIdempotencyKey("7b2e9f4a-3c8d-4a1e-b6f5-0d9c2a7e4b13") + .build(); + + @GivenTextResource("/domains/voice/v2/services/request/UpdateServiceRequestDto.json") + String jsonUpdateServiceRequest; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedUpdateServiceRequest); + + JSONAssert.assertEquals(jsonUpdateServiceRequest, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceResponseDtoTest.java new file mode 100644 index 000000000..ee535bc36 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceResponseDtoTest.java @@ -0,0 +1,59 @@ +package com.sinch.sdk.domains.voice.models.v2.services.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.voice.models.v2.services.EventDestinationCallBehaviorDtoTest; +import java.time.Instant; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class ServiceResponseDtoTest extends BaseTest { + + public static final ServiceResponse expectedServiceResponse = + ServiceResponse.builder() + .setServiceId("6e124178-c29d-46a5-943c-5c2ae544aade") + .setServiceSecret("9+MXX5u6dky5aATA7ZfIRg==") + .setProjectId("5c5bf2b1-35ae-4825-ab89-457e07bb60e6") + .setCreateTime(Instant.parse("2025-01-01T00:00:00Z")) + .setUpdateTime(Instant.parse("2025-02-15T10:30:00Z")) + .setName("Example service") + .setDescription("Service with webhooks") + .setIsDefault(false) + .setCallBehavior(EventDestinationCallBehaviorDtoTest.expectedEventDestinationCallBehavior) + .build(); + + public static final ServiceResponse expectedServiceResponseMinimal = + ServiceResponse.builder() + .setServiceId("7f235289-d3ae-57b6-a54d-6d3bf655bbdf") + .setServiceSecret("Jq4Rz2s8VnYkL0pT7wXc1A==") + .setProjectId("5c5bf2b1-35ae-4825-ab89-457e07bb60e6") + .setCreateTime(Instant.parse("2025-02-15T10:30:00Z")) + .setName("Secondary Service") + .setIsDefault(false) + .build(); + + @GivenTextResource("/domains/voice/v2/services/response/ServiceResponseDto.json") + String jsonServiceResponse; + + @GivenTextResource("/domains/voice/v2/services/response/ServiceResponseMinimalDto.json") + String jsonServiceResponseMinimal; + + @Test + void deserialize() throws JsonProcessingException { + ServiceResponse deserialized = + objectMapper.readValue(jsonServiceResponse, ServiceResponse.class); + + TestHelpers.recursiveEquals(deserialized, expectedServiceResponse); + } + + @Test + void deserializeMinimalDto() throws JsonProcessingException { + ServiceResponse deserialized = + objectMapper.readValue(jsonServiceResponseMinimal, ServiceResponse.class); + + TestHelpers.recursiveEquals(deserialized, expectedServiceResponseMinimal); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceShortResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceShortResponseDtoTest.java new file mode 100644 index 000000000..6b3da027f --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/response/ServiceShortResponseDtoTest.java @@ -0,0 +1,55 @@ +package com.sinch.sdk.domains.voice.models.v2.services.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 ServiceShortResponseDtoTest extends BaseTest { + + public static final ServiceShortResponse expectedServiceShortResponse = + ServiceShortResponse.builder() + .setServiceId("6e124178-c29d-46a5-943c-5c2ae544aade") + .setProjectId("5c5bf2b1-35ae-4825-ab89-457e07bb60e6") + .setCreateTime(Instant.parse("2025-01-01T00:00:00Z")) + .setUpdateTime(Instant.parse("2025-02-15T10:30:00Z")) + .setName("Primary Service") + .setDescription("Main service with webhook call behavior") + .setIsDefault(true) + .build(); + + public static final ServiceShortResponse expectedServiceShortResponseMinimal = + ServiceShortResponse.builder() + .setServiceId("7f235289-d3ae-57b6-a54d-6d3bf655bbdf") + .setProjectId("5c5bf2b1-35ae-4825-ab89-457e07bb60e6") + .setCreateTime(Instant.parse("2025-02-15T10:30:00Z")) + .setName("Secondary Service") + .setIsDefault(false) + .build(); + + @GivenTextResource("/domains/voice/v2/services/response/ServiceShortResponseDto.json") + String jsonServiceShortResponse; + + @GivenTextResource("/domains/voice/v2/services/response/ServiceShortResponseMinimalDto.json") + String jsonServiceShortResponseMinimal; + + @Test + void deserialize() throws JsonProcessingException { + ServiceShortResponse deserialized = + objectMapper.readValue(jsonServiceShortResponse, ServiceShortResponse.class); + + TestHelpers.recursiveEquals(deserialized, expectedServiceShortResponse); + } + + @Test + void deserializeMinimalDto() throws JsonProcessingException { + ServiceShortResponse deserialized = + objectMapper.readValue(jsonServiceShortResponseMinimal, ServiceShortResponse.class); + + TestHelpers.recursiveEquals(deserialized, expectedServiceShortResponseMinimal); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/response/internal/ServicesListResponseInternalDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/response/internal/ServicesListResponseInternalDtoTest.java new file mode 100644 index 000000000..f49c65a51 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/services/response/internal/ServicesListResponseInternalDtoTest.java @@ -0,0 +1,76 @@ +package com.sinch.sdk.domains.voice.models.v2.services.response.internal; + +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.voice.models.v2.PaginationLinks; +import com.sinch.sdk.domains.voice.models.v2.PaginationMetaDtoTest; +import com.sinch.sdk.domains.voice.models.v2.services.response.ServiceShortResponseDtoTest; +import java.util.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class ServicesListResponseInternalDtoTest extends BaseTest { + + static final String BASE_URL = + "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/services"; + + public static final ServicesListResponseInternal expectedServicesListResponse = + ServicesListResponseInternal.builder() + .setServices( + Arrays.asList( + ServiceShortResponseDtoTest.expectedServiceShortResponse, + ServiceShortResponseDtoTest.expectedServiceShortResponseMinimal)) + .setLinks( + PaginationLinks.builder() + .setFirst(BASE_URL + "?page=1&pageSize=2") + .setLast(BASE_URL + "?page=2&pageSize=2") + .setNext(BASE_URL + "?page=2&pageSize=2") + .setSelf(BASE_URL + "?page=1&pageSize=2") + .build()) + .setMeta(PaginationMetaDtoTest.expectedPaginationMeta) + .build(); + + public static final ServicesListResponseInternal expectedServicesListLastPageResponse = + ServicesListResponseInternal.builder() + .setServices( + Collections.singletonList( + ServiceShortResponseDtoTest.expectedServiceShortResponseMinimal)) + .setLinks( + PaginationLinks.builder() + .setFirst(BASE_URL + "?page=1&pageSize=2") + .setLast(BASE_URL + "?page=2&pageSize=2") + .setPrev(BASE_URL + "?page=1&pageSize=2") + .setSelf(BASE_URL + "?page=2&pageSize=2") + .build()) + .setMeta(PaginationMetaDtoTest.expectedPaginationMeta) + .build(); + + @GivenTextResource( + "/domains/voice/v2/services/response/internal/ServicesListResponseInternalDto.json") + String jsonServicesListResponse; + + @GivenTextResource( + "/domains/voice/v2/services/response/internal/ServicesListResponseInternalLastPageDto.json") + String jsonServicesListLastPageResponse; + + @Test + void deserialize() throws JsonProcessingException { + ServicesListResponseInternal deserialized = + objectMapper.readValue(jsonServicesListResponse, ServicesListResponseInternal.class); + + TestHelpers.recursiveEquals(deserialized, expectedServicesListResponse); + } + + @Test + void deserializeLastPage() throws JsonProcessingException { + ServicesListResponseInternal deserialized = + objectMapper.readValue( + jsonServicesListLastPageResponse, ServicesListResponseInternal.class); + + TestHelpers.recursiveEquals(deserialized, expectedServicesListLastPageResponse); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sessions/response/SessionDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sessions/response/SessionDtoTest.java new file mode 100644 index 000000000..b0097fd9c --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sessions/response/SessionDtoTest.java @@ -0,0 +1,58 @@ +package com.sinch.sdk.domains.voice.models.v2.sessions.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.voice.models.v2.CallDtoTest; +import com.sinch.sdk.domains.voice.models.v2.SessionState; +import java.time.Instant; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class SessionDtoTest extends BaseTest { + + public static final Session expectedSession = + Session.builder() + .setSessionId("01BX5ZZKBKACTAV9WEVGEMMVRB") + .setProjectId("5c5bf2b1-35ae-4825-ab89-457e07bb60e6") + .setServiceId("6e124178-c29d-46a5-943c-5c2ae544aade") + .setCalls(Collections.singletonList(CallDtoTest.expectedCall)) + .setCreateTime(Instant.parse("2025-02-10T09:00:00Z")) + .setUpdateTime(Instant.parse("2025-02-10T09:00:47Z")) + .setEndTime(Instant.parse("2025-02-10T09:00:47Z")) + .setState(SessionState.COMPLETED) + .build(); + + public static final Session expectedSessionMinimal = + Session.builder() + .setSessionId("01BX5ZZKBKACTAV9WEVGEMMVRB") + .setProjectId("5c5bf2b1-35ae-4825-ab89-457e07bb60e6") + .setServiceId("6e124178-c29d-46a5-943c-5c2ae544aade") + .setCalls(Collections.singletonList(CallDtoTest.expectedCallMinimal)) + .setCreateTime(Instant.parse("2025-02-10T09:00:00Z")) + .setState(SessionState.QUEUED) + .build(); + + @GivenTextResource("/domains/voice/v2/sessions/response/SessionDto.json") + String jsonSession; + + @GivenTextResource("/domains/voice/v2/sessions/response/SessionMinimalDto.json") + String jsonSessionMinimal; + + @Test + void deserialize() throws JsonProcessingException { + Session deserialized = objectMapper.readValue(jsonSession, Session.class); + + TestHelpers.recursiveEquals(deserialized, expectedSession); + } + + @Test + void deserializeMinimalDto() throws JsonProcessingException { + Session deserialized = objectMapper.readValue(jsonSessionMinimal, Session.class); + + TestHelpers.recursiveEquals(deserialized, expectedSessionMinimal); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sinchevents/MenuInputDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sinchevents/MenuInputDtoTest.java new file mode 100644 index 000000000..9e6e6a3e3 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sinchevents/MenuInputDtoTest.java @@ -0,0 +1,25 @@ +package com.sinch.sdk.domains.voice.models.v2.sinchevents; + +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 MenuInputDtoTest extends BaseTest { + + public static final MenuInput expectedMenuInput = + MenuInput.builder().setMenuName("main").setInput("1234").build(); + + @GivenTextResource("/domains/voice/v2/sinchevents/MenuInputDto.json") + String jsonMenuInput; + + @Test + void deserialize() throws JsonProcessingException { + MenuInput deserialized = objectMapper.readValue(jsonMenuInput, MenuInput.class); + + TestHelpers.recursiveEquals(deserialized, expectedMenuInput); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventDtoTest.java new file mode 100644 index 000000000..412d1d681 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventDtoTest.java @@ -0,0 +1,51 @@ +package com.sinch.sdk.domains.voice.models.v2.sinchevents; + +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.voice.models.v2.CallDtoTest; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class VoiceSinchEventDtoTest extends BaseTest { + + public static final VoiceSinchEvent expectedVoiceSinchEvent = + VoiceSinchEvent.builder() + .setEvent(SinchEventType.CALL_MENU) + .setCall(CallDtoTest.expectedCall) + .setMenu(MenuInputDtoTest.expectedMenuInput) + .build(); + + public static final VoiceSinchEvent expectedVoiceSinchEventCustomEvent = + VoiceSinchEvent.builder() + .setEvent(SinchEventType.from("call.webhook.on-answer")) + .setCall(CallDtoTest.expectedCall) + .build(); + + @GivenTextResource("/domains/voice/v2/sinchevents/VoiceSinchEventDto.json") + String jsonVoiceSinchEvent; + + @GivenTextResource("/domains/voice/v2/sinchevents/VoiceSinchEventCustomEventDto.json") + String jsonVoiceSinchEventCustomEvent; + + @Test + void deserialize() throws JsonProcessingException { + VoiceSinchEvent deserialized = + objectMapper.readValue(jsonVoiceSinchEvent, VoiceSinchEvent.class); + + TestHelpers.recursiveEquals(deserialized, expectedVoiceSinchEvent); + } + + @Test + void deserializeCustomEvent() throws JsonProcessingException { + VoiceSinchEvent deserialized = + objectMapper.readValue(jsonVoiceSinchEventCustomEvent, VoiceSinchEvent.class); + + TestHelpers.recursiveEquals(deserialized, expectedVoiceSinchEventCustomEvent); + Assertions.assertThat(SinchEventType.valueOf(deserialized.getEvent())) + .isEqualTo("call.webhook.on-answer"); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventResponseDtoTest.java new file mode 100644 index 000000000..f93ed1f8c --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/sinchevents/VoiceSinchEventResponseDtoTest.java @@ -0,0 +1,33 @@ +package com.sinch.sdk.domains.voice.models.v2.sinchevents; + +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.v2.svaml.IncomingCallResponseEventsDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.HangupCommandDtoTest; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class VoiceSinchEventResponseDtoTest extends BaseTest { + + public static final VoiceSinchEventResponse expectedVoiceSinchEventResponse = + VoiceSinchEventResponse.builder() + .setCommands(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .setCallName("incoming") + .setEvents(IncomingCallResponseEventsDtoTest.expectedIncomingCallResponseEvents) + .build(); + + @GivenTextResource("/domains/voice/v2/sinchevents/VoiceSinchEventResponseDto.json") + String jsonVoiceSinchEventResponse; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedVoiceSinchEventResponse); + + JSONAssert.assertEquals(jsonVoiceSinchEventResponse, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/IncomingCallResponseEventsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/IncomingCallResponseEventsDtoTest.java new file mode 100644 index 000000000..217716093 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/IncomingCallResponseEventsDtoTest.java @@ -0,0 +1,30 @@ +package com.sinch.sdk.domains.voice.models.v2.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.v2.svaml.calls.HangupCommandDtoTest; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class IncomingCallResponseEventsDtoTest extends BaseTest { + + public static final IncomingCallResponseEvents expectedIncomingCallResponseEvents = + IncomingCallResponseEvents.builder() + .setOnHangup(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/IncomingCallResponseEventsDto.json") + String jsonIncomingCallResponseEvents; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedIncomingCallResponseEvents); + + JSONAssert.assertEquals(jsonIncomingCallResponseEvents, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlCommandTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlCommandTest.java new file mode 100644 index 000000000..80c50f736 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlCommandTest.java @@ -0,0 +1,29 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.sinch.sdk.BaseTest; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +public class SvamlCommandTest extends BaseTest { + + @Test + void deserializeUnknownCommandFails() { + IOException exception = + assertThrows( + IOException.class, + () -> objectMapper.readValue("{\"command\":\"unknown\"}", SvamlCommand.class)); + assertTrue(exception.getMessage().contains("unknown 'command' value 'unknown'")); + } + + @Test + void deserializeMissingCommandFails() { + IOException exception = + assertThrows( + IOException.class, + () -> objectMapper.readValue("{\"callName\":\"origin\"}", SvamlCommand.class)); + assertTrue(exception.getMessage().contains("unknown 'command' value 'null'")); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlInputDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlInputDtoTest.java new file mode 100644 index 000000000..b939844b8 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/SvamlInputDtoTest.java @@ -0,0 +1,32 @@ +package com.sinch.sdk.domains.voice.models.v2.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.v2.svaml.calls.HangupCommandDtoTest; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class SvamlInputDtoTest extends BaseTest { + + public static final SvamlInput expectedSvamlInput = + SvamlInput.builder() + .setCommands(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .setCallName("incoming") + .setEvents(IncomingCallResponseEventsDtoTest.expectedIncomingCallResponseEvents) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/SvamlInputDto.json") + String jsonSvamlInput; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedSvamlInput); + + JSONAssert.assertEquals(jsonSvamlInput, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdCommandDtoTest.java new file mode 100644 index 000000000..b0f19e2f8 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdCommandDtoTest.java @@ -0,0 +1,34 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.amd; + +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 AmdCommandDtoTest extends BaseTest { + + public static final AmdCommand expectedAmdCommand = + AmdCommand.builder().setEvents(AmdEventsDtoTest.expectedAmdEvents).build(); + + @GivenTextResource("/domains/voice/v2/svaml/amd/AmdCommandDto.json") + String jsonAmdCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedAmdCommand); + + JSONAssert.assertEquals(jsonAmdCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + AmdCommand deserialized = objectMapper.readValue(jsonAmdCommand, AmdCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedAmdCommand); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdEventsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdEventsDtoTest.java new file mode 100644 index 000000000..282112ea2 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/amd/AmdEventsDtoTest.java @@ -0,0 +1,43 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.amd; + +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.voice.models.v2.svaml.calls.HangupCommandDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.calls.PauseCommandDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.MessagesCommandDtoTest; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class AmdEventsDtoTest extends BaseTest { + + public static final AmdEvents expectedAmdEvents = + AmdEvents.builder() + .setOnHuman(Arrays.asList(MessagesCommandDtoTest.expectedMessagesCommand)) + .setOnMachine(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .setOnBeep(Arrays.asList(PauseCommandDtoTest.expectedPauseCommand)) + .setOnUnknown(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/amd/AmdEventsDto.json") + String jsonAmdEvents; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedAmdEvents); + + JSONAssert.assertEquals(jsonAmdEvents, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + AmdEvents deserialized = objectMapper.readValue(jsonAmdEvents, AmdEvents.class); + + TestHelpers.recursiveEquals(deserialized, expectedAmdEvents); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/AnswerCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/AnswerCommandDtoTest.java new file mode 100644 index 000000000..d020aac9c --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/AnswerCommandDtoTest.java @@ -0,0 +1,33 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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 AnswerCommandDtoTest extends BaseTest { + + public static final AnswerCommand expectedAnswerCommand = AnswerCommand.ANSWER_COMMAND; + + @GivenTextResource("/domains/voice/v2/svaml/calls/AnswerCommandDto.json") + String jsonAnswerCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedAnswerCommand); + + JSONAssert.assertEquals(jsonAnswerCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + AnswerCommand deserialized = objectMapper.readValue(jsonAnswerCommand, AnswerCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedAnswerCommand); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/BridgeCallCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/BridgeCallCommandDtoTest.java new file mode 100644 index 000000000..e90e3635e --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/BridgeCallCommandDtoTest.java @@ -0,0 +1,40 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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 BridgeCallCommandDtoTest extends BaseTest { + + public static final BridgeCallCommand expectedBridgeCallCommand = + BridgeCallCommand.builder().setBridgeName("bridge").build(); + + @GivenTextResource("/domains/voice/v2/svaml/calls/BridgeCallCommandDto.json") + String jsonBridgeCallCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedBridgeCallCommand); + + JSONAssert.assertEquals(jsonBridgeCallCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + BridgeCallCommand deserialized = + objectMapper.readValue(jsonBridgeCallCommand, BridgeCallCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedBridgeCallCommand); + } + + @Test + void of() { + TestHelpers.recursiveEquals(BridgeCallCommand.of("bridge"), expectedBridgeCallCommand); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/CallEventsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/CallEventsDtoTest.java new file mode 100644 index 000000000..2894a078a --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/CallEventsDtoTest.java @@ -0,0 +1,59 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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.voice.models.v2.svaml.amd.AmdCommandDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.customevents.CustomEventCommandDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.menu.MenuCommandDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.playback.StopMessagesCommandDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.recording.StartRecordingCommandDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.recording.StopRecordingCommandDtoTest; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class CallEventsDtoTest extends BaseTest { + + public static final CallEvents expectedCallEvents = + CallEvents.builder() + .setOnAnswer( + Arrays.asList( + AnswerCommandDtoTest.expectedAnswerCommand, + AmdCommandDtoTest.expectedAmdCommand, + StartRecordingCommandDtoTest.expectedStartRecordingCommand, + MenuCommandDtoTest.expectedMenuCommand, + BridgeCallCommandDtoTest.expectedBridgeCallCommand, + CustomEventCommandDtoTest.expectedCustomEventCommand, + StopMessagesCommandDtoTest.expectedStopMessagesCommand)) + .setOnBusy(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .setOnReject(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .setOnTimeout(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .setOnHangup( + Arrays.asList( + StopRecordingCommandDtoTest.expectedStopRecordingCommand, + HangupCommandDtoTest.expectedHangupCommand)) + .setOnFailure(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/calls/CallEventsDto.json") + String jsonCallEvents; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedCallEvents); + + JSONAssert.assertEquals(jsonCallEvents, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + CallEvents deserialized = objectMapper.readValue(jsonCallEvents, CallEvents.class); + + TestHelpers.recursiveEquals(deserialized, expectedCallEvents); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/DialCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/DialCommandDtoTest.java new file mode 100644 index 000000000..dbb85798f --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/DialCommandDtoTest.java @@ -0,0 +1,110 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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.voice.models.v2.destination.PhoneDtoTest; +import com.sinch.sdk.domains.voice.models.v2.destination.SipDtoTest; +import com.sinch.sdk.domains.voice.models.v2.destination.SipFromDtoTest; +import com.sinch.sdk.domains.voice.models.v2.destination.StreamDtoTest; +import com.sinch.sdk.domains.voice.models.v2.destination.VoiceRelayDtoTest; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class DialCommandDtoTest extends BaseTest { + + public static final DialCommand expectedDialCommand = + DialCommand.builder() + .setCallName("origin") + .setFrom(PhoneDtoTest.expectedPhone) + .setTo(PhoneDtoTest.expectedPhone) + .setDialTimeoutDurationSeconds(30) + .setMaxCallDurationSeconds(3600) + .setEvents(CallEventsDtoTest.expectedCallEvents) + .build(); + + public static final DialCommand expectedDialCommandSip = + DialCommand.builder() + .setCallName("origin") + .setFrom(SipFromDtoTest.expectedSipFrom) + .setTo(SipDtoTest.expectedSip) + .build(); + + public static final DialCommand expectedDialCommandStream = + DialCommand.builder().setTo(StreamDtoTest.expectedStream).build(); + + public static final DialCommand expectedDialCommandVoiceRelay = + DialCommand.builder().setTo(VoiceRelayDtoTest.expectedVoiceRelay).build(); + + @GivenTextResource("/domains/voice/v2/svaml/calls/DialCommandDto.json") + String jsonDialCommand; + + @GivenTextResource("/domains/voice/v2/svaml/calls/DialCommandSipDto.json") + String jsonDialCommandSip; + + @GivenTextResource("/domains/voice/v2/svaml/calls/DialCommandStreamDto.json") + String jsonDialCommandStream; + + @GivenTextResource("/domains/voice/v2/svaml/calls/DialCommandVoiceRelayDto.json") + String jsonDialCommandVoiceRelay; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedDialCommand); + + JSONAssert.assertEquals(jsonDialCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + DialCommand deserialized = objectMapper.readValue(jsonDialCommand, DialCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedDialCommand); + } + + @Test + void serializeSip() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedDialCommandSip); + + JSONAssert.assertEquals(jsonDialCommandSip, serializedString, true); + } + + @Test + void deserializeSip() throws JsonProcessingException { + DialCommand deserialized = objectMapper.readValue(jsonDialCommandSip, DialCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedDialCommandSip); + } + + @Test + void serializeStream() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedDialCommandStream); + + JSONAssert.assertEquals(jsonDialCommandStream, serializedString, true); + } + + @Test + void deserializeStream() throws JsonProcessingException { + DialCommand deserialized = objectMapper.readValue(jsonDialCommandStream, DialCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedDialCommandStream); + } + + @Test + void serializeVoiceRelay() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedDialCommandVoiceRelay); + + JSONAssert.assertEquals(jsonDialCommandVoiceRelay, serializedString, true); + } + + @Test + void deserializeVoiceRelay() throws JsonProcessingException { + DialCommand deserialized = objectMapper.readValue(jsonDialCommandVoiceRelay, DialCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedDialCommandVoiceRelay); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/HangupCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/HangupCommandDtoTest.java new file mode 100644 index 000000000..99d858ebc --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/HangupCommandDtoTest.java @@ -0,0 +1,46 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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 HangupCommandDtoTest extends BaseTest { + + public static final HangupCommand expectedHangupCommand = + HangupCommand.builder().setCallName("origin").build(); + + @GivenTextResource("/domains/voice/v2/svaml/calls/HangupCommandDto.json") + String jsonHangupCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedHangupCommand); + + JSONAssert.assertEquals(jsonHangupCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + HangupCommand deserialized = objectMapper.readValue(jsonHangupCommand, HangupCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedHangupCommand); + } + + @Test + void of() { + TestHelpers.recursiveEquals(HangupCommand.of("origin"), expectedHangupCommand); + } + + @Test + void serializeHangupCommandConstant() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(HangupCommand.HANGUP_COMMAND); + + JSONAssert.assertEquals("{\"command\": \"hangup\"}", serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/PauseCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/PauseCommandDtoTest.java new file mode 100644 index 000000000..80cc88a24 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/calls/PauseCommandDtoTest.java @@ -0,0 +1,39 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.calls; + +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 PauseCommandDtoTest extends BaseTest { + + public static final PauseCommand expectedPauseCommand = + PauseCommand.builder().setDurationMilliseconds(1500).build(); + + @GivenTextResource("/domains/voice/v2/svaml/calls/PauseCommandDto.json") + String jsonPauseCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedPauseCommand); + + JSONAssert.assertEquals(jsonPauseCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + PauseCommand deserialized = objectMapper.readValue(jsonPauseCommand, PauseCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedPauseCommand); + } + + @Test + void of() { + TestHelpers.recursiveEquals(PauseCommand.of(1500), expectedPauseCommand); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/CustomEventCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/CustomEventCommandDtoTest.java new file mode 100644 index 000000000..793dfab40 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/customevents/CustomEventCommandDtoTest.java @@ -0,0 +1,39 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.customevents; + +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 CustomEventCommandDtoTest extends BaseTest { + + public static final CustomEventCommand expectedCustomEventCommand = + CustomEventCommand.builder() + .setCustomEventName("my.custom.event") + .setUrl("https://example.com/events") + .setFallbackUrl("https://fallback.example.com/events") + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/customevents/CustomEventCommandDto.json") + String jsonCustomEventCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedCustomEventCommand); + + JSONAssert.assertEquals(jsonCustomEventCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + CustomEventCommand deserialized = + objectMapper.readValue(jsonCustomEventCommand, CustomEventCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedCustomEventCommand); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/GotoMenuCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/GotoMenuCommandDtoTest.java new file mode 100644 index 000000000..298bd74b1 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/GotoMenuCommandDtoTest.java @@ -0,0 +1,40 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +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 GotoMenuCommandDtoTest extends BaseTest { + + public static final GotoMenuCommand expectedGotoMenuCommand = + GotoMenuCommand.builder().setMenuName("main").build(); + + @GivenTextResource("/domains/voice/v2/svaml/menu/GotoMenuCommandDto.json") + String jsonGotoMenuCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedGotoMenuCommand); + + JSONAssert.assertEquals(jsonGotoMenuCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + GotoMenuCommand deserialized = + objectMapper.readValue(jsonGotoMenuCommand, GotoMenuCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedGotoMenuCommand); + } + + @Test + void of() { + TestHelpers.recursiveEquals(GotoMenuCommand.of("main"), expectedGotoMenuCommand); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuCommandDtoTest.java new file mode 100644 index 000000000..3398eab43 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuCommandDtoTest.java @@ -0,0 +1,38 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +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.util.Collections; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class MenuCommandDtoTest extends BaseTest { + + public static final MenuCommand expectedMenuCommand = + MenuCommand.builder() + .setStartMenu("main") + .setMenus(Collections.singletonMap("main", MenuItemDtoTest.expectedMenuItem)) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/menu/MenuCommandDto.json") + String jsonMenuCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedMenuCommand); + + JSONAssert.assertEquals(jsonMenuCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + MenuCommand deserialized = objectMapper.readValue(jsonMenuCommand, MenuCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedMenuCommand); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuItemDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuItemDtoTest.java new file mode 100644 index 000000000..7419a20e9 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuItemDtoTest.java @@ -0,0 +1,50 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +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.voice.models.v2.svaml.calls.HangupCommandDtoTest; +import java.util.Arrays; +import java.util.Collections; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class MenuItemDtoTest extends BaseTest { + + public static final MenuItem expectedMenuItem = + MenuItem.builder() + .setPrompt(MenuPromptDtoTest.expectedMenuPrompt) + .setRepeatPrompt(MenuPromptDtoTest.expectedMenuPrompt) + .setInputTimeoutDurationSeconds(10) + .setRepeatCount(3) + .setMinimumInputLength(1) + .setMaximumInputLength(4) + .setTerminatingSequence("#") + .setInputMethods(Arrays.asList(MenuItem.InputMethodsEnum.DTMF)) + .setMatches( + Collections.singletonMap( + "1", Arrays.asList(GotoMenuCommandDtoTest.expectedGotoMenuCommand))) + .setOnFailure(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/menu/MenuItemDto.json") + String jsonMenuItem; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedMenuItem); + + JSONAssert.assertEquals(jsonMenuItem, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + MenuItem deserialized = objectMapper.readValue(jsonMenuItem, MenuItem.class); + + TestHelpers.recursiveEquals(deserialized, expectedMenuItem); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuPromptDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuPromptDtoTest.java new file mode 100644 index 000000000..027e1f907 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/menu/MenuPromptDtoTest.java @@ -0,0 +1,39 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.menu; + +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.voice.models.v2.svaml.playback.SayMessageDtoTest; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class MenuPromptDtoTest extends BaseTest { + + public static final MenuPrompt expectedMenuPrompt = + MenuPrompt.builder() + .setAllowBargeIn(false) + .setMessages(Arrays.asList(SayMessageDtoTest.expectedSayMessage)) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/menu/MenuPromptDto.json") + String jsonMenuPrompt; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedMenuPrompt); + + JSONAssert.assertEquals(jsonMenuPrompt, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + MenuPrompt deserialized = objectMapper.readValue(jsonMenuPrompt, MenuPrompt.class); + + TestHelpers.recursiveEquals(deserialized, expectedMenuPrompt); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessageEventsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessageEventsDtoTest.java new file mode 100644 index 000000000..29e3201c4 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessageEventsDtoTest.java @@ -0,0 +1,38 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +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.voice.models.v2.svaml.calls.HangupCommandDtoTest; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class MessageEventsDtoTest extends BaseTest { + + public static final MessageEvents expectedMessageEvents = + MessageEvents.builder() + .setOnFinish(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/playback/MessageEventsDto.json") + String jsonMessageEvents; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedMessageEvents); + + JSONAssert.assertEquals(jsonMessageEvents, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + MessageEvents deserialized = objectMapper.readValue(jsonMessageEvents, MessageEvents.class); + + TestHelpers.recursiveEquals(deserialized, expectedMessageEvents); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessagesCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessagesCommandDtoTest.java new file mode 100644 index 000000000..8549fefa7 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/MessagesCommandDtoTest.java @@ -0,0 +1,42 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +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.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class MessagesCommandDtoTest extends BaseTest { + + public static final MessagesCommand expectedMessagesCommand = + MessagesCommand.builder() + .setMessagesName("my-messages") + .setMessages( + Arrays.asList( + SayMessageDtoTest.expectedSayMessage, PlayMessageDtoTest.expectedPlayMessage)) + .setEvents(MessageEventsDtoTest.expectedMessageEvents) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/playback/MessagesCommandDto.json") + String jsonMessagesCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedMessagesCommand); + + JSONAssert.assertEquals(jsonMessagesCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + MessagesCommand deserialized = + objectMapper.readValue(jsonMessagesCommand, MessagesCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedMessagesCommand); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/PlayMessageDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/PlayMessageDtoTest.java new file mode 100644 index 000000000..74d94e04a --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/PlayMessageDtoTest.java @@ -0,0 +1,34 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +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 PlayMessageDtoTest extends BaseTest { + + public static final PlayMessage expectedPlayMessage = + PlayMessage.builder().setUrl("https://example.com/audio/welcome.mp3").build(); + + @GivenTextResource("/domains/voice/v2/svaml/playback/PlayMessageDto.json") + String jsonPlayMessage; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedPlayMessage); + + JSONAssert.assertEquals(jsonPlayMessage, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + PlayMessage deserialized = objectMapper.readValue(jsonPlayMessage, PlayMessage.class); + + TestHelpers.recursiveEquals(deserialized, expectedPlayMessage); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/SayMessageDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/SayMessageDtoTest.java new file mode 100644 index 000000000..c5aede36b --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/SayMessageDtoTest.java @@ -0,0 +1,38 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +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 SayMessageDtoTest extends BaseTest { + + public static final SayMessage expectedSayMessage = + SayMessage.builder() + .setText("Hello, your call is now connected.") + .setFormat(SayMessage.FormatEnum.TEXT) + .setVoiceName("Emma") + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/playback/SayMessageDto.json") + String jsonSayMessage; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedSayMessage); + + JSONAssert.assertEquals(jsonSayMessage, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + SayMessage deserialized = objectMapper.readValue(jsonSayMessage, SayMessage.class); + + TestHelpers.recursiveEquals(deserialized, expectedSayMessage); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/StopMessagesCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/StopMessagesCommandDtoTest.java new file mode 100644 index 000000000..2c925eeb8 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/playback/StopMessagesCommandDtoTest.java @@ -0,0 +1,40 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.playback; + +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 StopMessagesCommandDtoTest extends BaseTest { + + public static final StopMessagesCommand expectedStopMessagesCommand = + StopMessagesCommand.builder().setMessagesName("my-messages").build(); + + @GivenTextResource("/domains/voice/v2/svaml/playback/StopMessagesCommandDto.json") + String jsonStopMessagesCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedStopMessagesCommand); + + JSONAssert.assertEquals(jsonStopMessagesCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + StopMessagesCommand deserialized = + objectMapper.readValue(jsonStopMessagesCommand, StopMessagesCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedStopMessagesCommand); + } + + @Test + void of() { + TestHelpers.recursiveEquals(StopMessagesCommand.of("my-messages"), expectedStopMessagesCommand); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingEventsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingEventsDtoTest.java new file mode 100644 index 000000000..bb3573df3 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingEventsDtoTest.java @@ -0,0 +1,40 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +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.voice.models.v2.svaml.calls.HangupCommandDtoTest; +import java.util.Arrays; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class RecordingEventsDtoTest extends BaseTest { + + public static final RecordingEvents expectedRecordingEvents = + RecordingEvents.builder() + .setOnFinish(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .setOnFailure(Arrays.asList(HangupCommandDtoTest.expectedHangupCommand)) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/recording/RecordingEventsDto.json") + String jsonRecordingEvents; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedRecordingEvents); + + JSONAssert.assertEquals(jsonRecordingEvents, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + RecordingEvents deserialized = + objectMapper.readValue(jsonRecordingEvents, RecordingEvents.class); + + TestHelpers.recursiveEquals(deserialized, expectedRecordingEvents); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingOptionsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingOptionsDtoTest.java new file mode 100644 index 000000000..611312b1f --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/RecordingOptionsDtoTest.java @@ -0,0 +1,51 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +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.Assertions; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class RecordingOptionsDtoTest extends BaseTest { + + public static final RecordingOptions expectedRecordingOptions = + RecordingOptions.builder() + .setFormat(RecordingFormatType.MP3) + .setRecordingType(RecordingType.COMBINED) + .setDestination(RecordingDestinationType.AWS) + .setDestinationUrl("s3://my-bucket/recordings") + .setCredentials("access-key:secret-key") + .setTranscriptionOptions(TranscriptionOptionsDtoTest.expectedTranscriptionOptions) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/recording/RecordingOptionsDto.json") + String jsonRecordingOptions; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedRecordingOptions); + + JSONAssert.assertEquals(jsonRecordingOptions, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + RecordingOptions deserialized = + objectMapper.readValue(jsonRecordingOptions, RecordingOptions.class); + + TestHelpers.recursiveEquals(deserialized, expectedRecordingOptions); + } + + @Test + void toStringMasksCredentials() { + String string = expectedRecordingOptions.toString(); + + Assertions.assertFalse(string.contains("access-key:secret-key"), string); + Assertions.assertTrue(string.contains("credentials: ***"), string); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StartRecordingCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StartRecordingCommandDtoTest.java new file mode 100644 index 000000000..48a331a55 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StartRecordingCommandDtoTest.java @@ -0,0 +1,39 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +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 StartRecordingCommandDtoTest extends BaseTest { + + public static final StartRecordingCommand expectedStartRecordingCommand = + StartRecordingCommand.builder() + .setRecordingName("my-recording") + .setRecordingOptions(RecordingOptionsDtoTest.expectedRecordingOptions) + .setEvents(RecordingEventsDtoTest.expectedRecordingEvents) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/recording/StartRecordingCommandDto.json") + String jsonStartRecordingCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedStartRecordingCommand); + + JSONAssert.assertEquals(jsonStartRecordingCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + StartRecordingCommand deserialized = + objectMapper.readValue(jsonStartRecordingCommand, StartRecordingCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedStartRecordingCommand); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StopRecordingCommandDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StopRecordingCommandDtoTest.java new file mode 100644 index 000000000..81259ba28 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/StopRecordingCommandDtoTest.java @@ -0,0 +1,41 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +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 StopRecordingCommandDtoTest extends BaseTest { + + public static final StopRecordingCommand expectedStopRecordingCommand = + StopRecordingCommand.builder().setRecordingName("my-recording").build(); + + @GivenTextResource("/domains/voice/v2/svaml/recording/StopRecordingCommandDto.json") + String jsonStopRecordingCommand; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedStopRecordingCommand); + + JSONAssert.assertEquals(jsonStopRecordingCommand, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + StopRecordingCommand deserialized = + objectMapper.readValue(jsonStopRecordingCommand, StopRecordingCommand.class); + + TestHelpers.recursiveEquals(deserialized, expectedStopRecordingCommand); + } + + @Test + void of() { + TestHelpers.recursiveEquals( + StopRecordingCommand.of("my-recording"), expectedStopRecordingCommand); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/TranscriptionOptionsDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/TranscriptionOptionsDtoTest.java new file mode 100644 index 000000000..4a75b9b98 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/recording/TranscriptionOptionsDtoTest.java @@ -0,0 +1,35 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.recording; + +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 TranscriptionOptionsDtoTest extends BaseTest { + + public static final TranscriptionOptions expectedTranscriptionOptions = + TranscriptionOptions.builder().setIsEnabled(true).setLocale("en-US").build(); + + @GivenTextResource("/domains/voice/v2/svaml/recording/TranscriptionOptionsDto.json") + String jsonTranscriptionOptions; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedTranscriptionOptions); + + JSONAssert.assertEquals(jsonTranscriptionOptions, serializedString, true); + } + + @Test + void deserialize() throws JsonProcessingException { + TranscriptionOptions deserialized = + objectMapper.readValue(jsonTranscriptionOptions, TranscriptionOptions.class); + + TestHelpers.recursiveEquals(deserialized, expectedTranscriptionOptions); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/request/DescribeSvamlRequestDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/request/DescribeSvamlRequestDtoTest.java new file mode 100644 index 000000000..8e9fc053c --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/request/DescribeSvamlRequestDtoTest.java @@ -0,0 +1,27 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.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.v2.svaml.SvamlInputDtoTest; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class DescribeSvamlRequestDtoTest extends BaseTest { + + public static final DescribeSvamlRequest expectedDescribeSvamlRequest = + DescribeSvamlRequest.builder().setSvaml(SvamlInputDtoTest.expectedSvamlInput).build(); + + @GivenTextResource("/domains/voice/v2/svaml/request/DescribeSvamlRequestDto.json") + String jsonDescribeSvamlRequest; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedDescribeSvamlRequest); + + JSONAssert.assertEquals(jsonDescribeSvamlRequest, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/request/ValidateSvamlRequestDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/request/ValidateSvamlRequestDtoTest.java new file mode 100644 index 000000000..48446499a --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/request/ValidateSvamlRequestDtoTest.java @@ -0,0 +1,31 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.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.v2.svaml.SvamlInputDtoTest; +import com.sinch.sdk.domains.voice.models.v2.svaml.request.ValidateSvamlRequest.ValidationTypeEnum; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +public class ValidateSvamlRequestDtoTest extends BaseTest { + + public static final ValidateSvamlRequest expectedValidateSvamlRequest = + ValidateSvamlRequest.builder() + .setSvaml(SvamlInputDtoTest.expectedSvamlInput) + .setValidationType(ValidationTypeEnum.STRICT) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/request/ValidateSvamlRequestDto.json") + String jsonValidateSvamlRequest; + + @Test + void serialize() throws JsonProcessingException, JSONException { + String serializedString = objectMapper.writeValueAsString(expectedValidateSvamlRequest); + + JSONAssert.assertEquals(jsonValidateSvamlRequest, serializedString, true); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/response/SvamlDescriptionResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/response/SvamlDescriptionResponseDtoTest.java new file mode 100644 index 000000000..ba579ec34 --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/response/SvamlDescriptionResponseDtoTest.java @@ -0,0 +1,28 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.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.junit.jupiter.api.Test; + +@TestWithResources +public class SvamlDescriptionResponseDtoTest extends BaseTest { + + public static final SvamlDescriptionResponse expectedSvamlDescriptionResponse = + SvamlDescriptionResponse.builder() + .setDescription("The call is answered and a TTS message is played using the voice Emma.") + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/response/SvamlDescriptionResponseDto.json") + String jsonSvamlDescriptionResponse; + + @Test + void deserialize() throws JsonProcessingException { + SvamlDescriptionResponse deserialized = + objectMapper.readValue(jsonSvamlDescriptionResponse, SvamlDescriptionResponse.class); + + TestHelpers.recursiveEquals(deserialized, expectedSvamlDescriptionResponse); + } +} diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/response/ValidateSvamlResponseDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/response/ValidateSvamlResponseDtoTest.java new file mode 100644 index 000000000..5d2429d2e --- /dev/null +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/voice/models/v2/svaml/response/ValidateSvamlResponseDtoTest.java @@ -0,0 +1,33 @@ +package com.sinch.sdk.domains.voice.models.v2.svaml.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.util.Arrays; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class ValidateSvamlResponseDtoTest extends BaseTest { + + public static final ValidateSvamlResponse expectedValidateSvamlResponse = + ValidateSvamlResponse.builder() + .setIsValid(false) + .setErrors( + Arrays.asList( + "Invalid dial command. Missing destination.", + "Invalid say command. Missing voice name.")) + .build(); + + @GivenTextResource("/domains/voice/v2/svaml/response/ValidateSvamlResponseDto.json") + String jsonValidateSvamlResponse; + + @Test + void deserialize() throws JsonProcessingException { + ValidateSvamlResponse deserialized = + objectMapper.readValue(jsonValidateSvamlResponse, ValidateSvamlResponse.class); + + TestHelpers.recursiveEquals(deserialized, expectedValidateSvamlResponse); + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/CallDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/CallDto.json new file mode 100644 index 000000000..0114f2001 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/CallDto.json @@ -0,0 +1,37 @@ +{ + "callId": "01ARZ3NDEKTSV4RRFFQ69G5FAA", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB", + "callName": "origin", + "bridgeName": "my-bridge", + "batchId": "01BX5ZZKBKACTAV9WEVGEMMVRC", + "from": { + "type": "SIP", + "sip": { + "endpoint": "sip:alice@sip.example.com", + "displayName": "Alice" + } + }, + "to": { + "type": "PHONE", + "phone": { + "number": "+15551234567" + } + }, + "startTime": "2025-02-10T09:00:00Z", + "updateTime": "2025-02-10T09:00:47Z", + "callType": "PHONE", + "direction": "INBOUND", + "answerTime": "2025-02-10T09:00:05Z", + "endTime": "2025-02-10T09:00:47Z", + "callDurationSeconds": 42, + "callResult": "COMPLETED", + "callReason": "CALLEE_HANGUP", + "originationType": "SIP", + "callRate": { + "currencyCode": "USD", + "amount": "0.0123" + }, + "callResourceUrl": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls/01ARZ3NDEKTSV4RRFFQ69G5FAA" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/CallMinimalDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/CallMinimalDto.json new file mode 100644 index 000000000..1f5843e3d --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/CallMinimalDto.json @@ -0,0 +1,16 @@ +{ + "callId": "01ARZ3NDEKTSV4RRFFQ69G5FAA", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB", + "startTime": "2025-02-10T09:00:00Z", + "callType": "PHONE", + "direction": "OUTBOUND", + "callResult": "QUEUED", + "originationType": "SERVER", + "callRate": { + "currencyCode": "USD", + "amount": "0.0123" + }, + "callResourceUrl": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls/01ARZ3NDEKTSV4RRFFQ69G5FAA" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/MoneyDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/MoneyDto.json new file mode 100644 index 000000000..7b08cf3fd --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/MoneyDto.json @@ -0,0 +1,4 @@ +{ + "currencyCode": "USD", + "amount": "0.0123" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/PaginationLinksDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/PaginationLinksDto.json new file mode 100644 index 000000000..057f11fd4 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/PaginationLinksDto.json @@ -0,0 +1,7 @@ +{ + "first": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=1&pageSize=2", + "last": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=2&pageSize=2", + "next": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=3&pageSize=2", + "prev": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=1&pageSize=2", + "self": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=2&pageSize=2" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/PaginationMetaDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/PaginationMetaDto.json new file mode 100644 index 000000000..d6679aa5b --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/PaginationMetaDto.json @@ -0,0 +1,4 @@ +{ + "totalCount": 3, + "pageCount": 2 +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/batches/request/BatchOptionsDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/batches/request/BatchOptionsDto.json new file mode 100644 index 000000000..36beb756d --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/batches/request/BatchOptionsDto.json @@ -0,0 +1,4 @@ +{ + "maxCps": 10, + "ttlSeconds": 3600 +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/batches/request/StartBatchRequestDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/batches/request/StartBatchRequestDto.json new file mode 100644 index 000000000..0e081ac5c --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/batches/request/StartBatchRequestDto.json @@ -0,0 +1,228 @@ +{ + "commands": [ + { + "command": "dial", + "callName": "origin", + "from": { + "type": "PHONE", + "phone": { + "number": "+15551234567" + } + }, + "to": { + "type": "PHONE", + "phone": { + "number": "+15551234567" + } + }, + "dialTimeoutDurationSeconds": 30, + "maxCallDurationSeconds": 3600, + "events": { + "onAnswer": [ + { + "command": "answer" + }, + { + "command": "amd", + "events": { + "onHuman": [ + { + "command": "messages", + "messagesName": "my-messages", + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + }, + { + "type": "PLAY", + "play": { + "url": "https://example.com/audio/welcome.mp3" + } + } + ], + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + ], + "onMachine": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onBeep": [ + { + "command": "pause", + "durationMilliseconds": 1500 + } + ], + "onUnknown": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + }, + { + "command": "startRecording", + "recordingName": "my-recording", + "recordingOptions": { + "format": "MP3", + "recordingType": "COMBINED", + "destination": "AWS", + "destinationUrl": "s3://my-bucket/recordings", + "credentials": "access-key:secret-key", + "transcriptionOptions": { + "isEnabled": true, + "locale": "en-US" + } + }, + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + }, + { + "command": "menu", + "startMenu": "main", + "menus": { + "main": { + "prompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "repeatPrompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "inputTimeoutDurationSeconds": 10, + "repeatCount": 3, + "minimumInputLength": 1, + "maximumInputLength": 4, + "terminatingSequence": "#", + "inputMethods": [ + "DTMF" + ], + "matches": { + "1": [ + { + "command": "gotoMenu", + "menuName": "main" + } + ] + }, + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + }, + { + "command": "bridgeCall", + "bridgeName": "bridge" + }, + { + "command": "webhook", + "webhookName": "my.custom.event", + "url": "https://example.com/events", + "fallbackUrl": "https://fallback.example.com/events" + }, + { + "command": "stopMessages", + "messagesName": "my-messages" + } + ], + "onBusy": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onReject": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onTimeout": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onHangup": [ + { + "command": "stopRecording", + "recordingName": "my-recording" + }, + { + "command": "hangup", + "callName": "origin" + } + ], + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + ], + "parameters": [ + { + "numberB": "+15559876543" + }, + { + "numberB": "+15559876544", + "name": "Alice" + } + ], + "batchOptions": { + "maxCps": 10, + "ttlSeconds": 3600 + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchDetailsDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchDetailsDto.json new file mode 100644 index 000000000..c4fbbf73a --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchDetailsDto.json @@ -0,0 +1,22 @@ +{ + "sessions": [ + { + "id": "01F8Z5J4X2G9Y3J4X2G9Y3J4X2G9", + "state": "COMPLETED", + "parameters": { + "numberB": "+15559876543" + } + }, + { + "id": "01F8Z5J4X2G9Y3J4X2G9Y3J4X5YJ", + "state": "IN_PROGRESS", + "parameters": { + "numberB": "+15559876544" + } + }, + { + "id": "01F8Z5J4X2G9Y3J4X2G9Y3J4X9QK", + "state": "QUEUED" + } + ] +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchSessionSummaryDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchSessionSummaryDto.json new file mode 100644 index 000000000..661aaf66e --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchSessionSummaryDto.json @@ -0,0 +1,7 @@ +{ + "id": "01F8Z5J4X2G9Y3J4X2G9Y3J4X2G9", + "state": "COMPLETED", + "parameters": { + "numberB": "+15559876543" + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchStopResponseDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchStopResponseDto.json new file mode 100644 index 000000000..7b83e8207 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchStopResponseDto.json @@ -0,0 +1,3 @@ +{ + "result": "STOP_REQUESTED" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchSummaryDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchSummaryDto.json new file mode 100644 index 000000000..fa589a742 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/BatchSummaryDto.json @@ -0,0 +1,11 @@ +{ + "batchId": "01BX5ZZKBKACTAV9WEVGEMMVRC", + "sessionCount": 3, + "endTime": "2026-08-28T12:17:49Z", + "queued": 1, + "inProgress": 1, + "completed": 1, + "expired": 0, + "ttlSeconds": 3600, + "requestedCps": 10 +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/StartBatchResponseDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/StartBatchResponseDto.json new file mode 100644 index 000000000..b2c86ddec --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/batches/response/StartBatchResponseDto.json @@ -0,0 +1,5 @@ +{ + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "batchId": "01BX5ZZKBKACTAV9WEVGEMMVRC" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/calls/request/CallPatchRequestDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/calls/request/CallPatchRequestDto.json new file mode 100644 index 000000000..d0d7b16f8 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/calls/request/CallPatchRequestDto.json @@ -0,0 +1,36 @@ +{ + "commands": [ + { + "command": "messages", + "messagesName": "my-messages", + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + }, + { + "type": "PLAY", + "play": { + "url": "https://example.com/audio/welcome.mp3" + } + } + ], + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + }, + { + "command": "hangup", + "callName": "origin" + } + ] +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/calls/request/StartCallRequestDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/calls/request/StartCallRequestDto.json new file mode 100644 index 000000000..0de2dd010 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/calls/request/StartCallRequestDto.json @@ -0,0 +1,215 @@ +{ + "commands": [ + { + "command": "dial", + "callName": "origin", + "from": { + "type": "PHONE", + "phone": { + "number": "+15551234567" + } + }, + "to": { + "type": "PHONE", + "phone": { + "number": "+15551234567" + } + }, + "dialTimeoutDurationSeconds": 30, + "maxCallDurationSeconds": 3600, + "events": { + "onAnswer": [ + { + "command": "answer" + }, + { + "command": "amd", + "events": { + "onHuman": [ + { + "command": "messages", + "messagesName": "my-messages", + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + }, + { + "type": "PLAY", + "play": { + "url": "https://example.com/audio/welcome.mp3" + } + } + ], + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + ], + "onMachine": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onBeep": [ + { + "command": "pause", + "durationMilliseconds": 1500 + } + ], + "onUnknown": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + }, + { + "command": "startRecording", + "recordingName": "my-recording", + "recordingOptions": { + "format": "MP3", + "recordingType": "COMBINED", + "destination": "AWS", + "destinationUrl": "s3://my-bucket/recordings", + "credentials": "access-key:secret-key", + "transcriptionOptions": { + "isEnabled": true, + "locale": "en-US" + } + }, + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + }, + { + "command": "menu", + "startMenu": "main", + "menus": { + "main": { + "prompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "repeatPrompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "inputTimeoutDurationSeconds": 10, + "repeatCount": 3, + "minimumInputLength": 1, + "maximumInputLength": 4, + "terminatingSequence": "#", + "inputMethods": [ + "DTMF" + ], + "matches": { + "1": [ + { + "command": "gotoMenu", + "menuName": "main" + } + ] + }, + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + }, + { + "command": "bridgeCall", + "bridgeName": "bridge" + }, + { + "command": "webhook", + "webhookName": "my.custom.event", + "url": "https://example.com/events", + "fallbackUrl": "https://fallback.example.com/events" + }, + { + "command": "stopMessages", + "messagesName": "my-messages" + } + ], + "onBusy": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onReject": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onTimeout": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onHangup": [ + { + "command": "stopRecording", + "recordingName": "my-recording" + }, + { + "command": "hangup", + "callName": "origin" + } + ], + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + ] +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/calls/response/StartCallResponseDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/calls/response/StartCallResponseDto.json new file mode 100644 index 000000000..85a5be881 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/calls/response/StartCallResponseDto.json @@ -0,0 +1,5 @@ +{ + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/calls/response/internal/CallsListResponseInternalDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/calls/response/internal/CallsListResponseInternalDto.json new file mode 100644 index 000000000..2e1fc50e9 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/calls/response/internal/CallsListResponseInternalDto.json @@ -0,0 +1,67 @@ +{ + "calls": [ + { + "callId": "01ARZ3NDEKTSV4RRFFQ69G5FAA", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB", + "callName": "origin", + "bridgeName": "my-bridge", + "batchId": "01BX5ZZKBKACTAV9WEVGEMMVRC", + "from": { + "type": "SIP", + "sip": { + "endpoint": "sip:alice@sip.example.com", + "displayName": "Alice" + } + }, + "to": { + "type": "PHONE", + "phone": { + "number": "+15551234567" + } + }, + "startTime": "2025-02-10T09:00:00Z", + "updateTime": "2025-02-10T09:00:47Z", + "callType": "PHONE", + "direction": "INBOUND", + "answerTime": "2025-02-10T09:00:05Z", + "endTime": "2025-02-10T09:00:47Z", + "callDurationSeconds": 42, + "callResult": "COMPLETED", + "callReason": "CALLEE_HANGUP", + "originationType": "SIP", + "callRate": { + "currencyCode": "USD", + "amount": "0.0123" + }, + "callResourceUrl": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls/01ARZ3NDEKTSV4RRFFQ69G5FAA" + }, + { + "callId": "01ARZ3NDEKTSV4RRFFQ69G5FAA", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB", + "startTime": "2025-02-10T09:00:00Z", + "callType": "PHONE", + "direction": "OUTBOUND", + "callResult": "QUEUED", + "originationType": "SERVER", + "callRate": { + "currencyCode": "USD", + "amount": "0.0123" + }, + "callResourceUrl": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls/01ARZ3NDEKTSV4RRFFQ69G5FAA" + } + ], + "links": { + "first": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=1&pageSize=2", + "last": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=2&pageSize=2", + "next": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=2&pageSize=2", + "self": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=1&pageSize=2" + }, + "meta": { + "totalCount": 3, + "pageCount": 2 + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/calls/response/internal/CallsListResponseInternalLastPageDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/calls/response/internal/CallsListResponseInternalLastPageDto.json new file mode 100644 index 000000000..e618a532c --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/calls/response/internal/CallsListResponseInternalLastPageDto.json @@ -0,0 +1,30 @@ +{ + "calls": [ + { + "callId": "01ARZ3NDEKTSV4RRFFQ69G5FAA", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB", + "startTime": "2025-02-10T09:00:00Z", + "callType": "PHONE", + "direction": "OUTBOUND", + "callResult": "QUEUED", + "originationType": "SERVER", + "callRate": { + "currencyCode": "USD", + "amount": "0.0123" + }, + "callResourceUrl": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls/01ARZ3NDEKTSV4RRFFQ69G5FAA" + } + ], + "links": { + "first": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=1&pageSize=2", + "last": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=2&pageSize=2", + "prev": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=1&pageSize=2", + "self": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls?page=2&pageSize=2" + }, + "meta": { + "totalCount": 3, + "pageCount": 2 + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/destination/CallHeaderDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/destination/CallHeaderDto.json new file mode 100644 index 000000000..40fb7109b --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/destination/CallHeaderDto.json @@ -0,0 +1,4 @@ +{ + "key": "customer-id", + "value": "12345" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/destination/PhoneDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/destination/PhoneDto.json new file mode 100644 index 000000000..5ed464e5e --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/destination/PhoneDto.json @@ -0,0 +1,6 @@ +{ + "type": "PHONE", + "phone": { + "number": "+15551234567" + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/destination/SipCallHeaderDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/destination/SipCallHeaderDto.json new file mode 100644 index 000000000..fe23a5f96 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/destination/SipCallHeaderDto.json @@ -0,0 +1,4 @@ +{ + "key": "X-Custom-Header", + "value": "custom-value" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/destination/SipDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/destination/SipDto.json new file mode 100644 index 000000000..820c20220 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/destination/SipDto.json @@ -0,0 +1,13 @@ +{ + "type": "SIP", + "sip": { + "endpoint": "sips:bob@sip.example.com", + "transport": "TLS", + "callHeaders": [ + { + "key": "X-Custom-Header", + "value": "custom-value" + } + ] + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/destination/SipFromDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/destination/SipFromDto.json new file mode 100644 index 000000000..abd9de4e2 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/destination/SipFromDto.json @@ -0,0 +1,7 @@ +{ + "type": "SIP", + "sip": { + "endpoint": "sip:alice@sip.example.com", + "displayName": "Alice" + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/destination/StreamDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/destination/StreamDto.json new file mode 100644 index 000000000..337e308ae --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/destination/StreamDto.json @@ -0,0 +1,17 @@ +{ + "type": "STREAM", + "stream": { + "endpoint": "wss://myapp.example.com/audio", + "streamOptions": { + "version": 1, + "codec": "PCM", + "sampleRate": 16000 + }, + "callHeaders": [ + { + "key": "customer-id", + "value": "12345" + } + ] + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/destination/StreamOptionsDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/destination/StreamOptionsDto.json new file mode 100644 index 000000000..03e091154 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/destination/StreamOptionsDto.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "codec": "PCM", + "sampleRate": 16000 +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/destination/VoiceRelayDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/destination/VoiceRelayDto.json new file mode 100644 index 000000000..efe3045fb --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/destination/VoiceRelayDto.json @@ -0,0 +1,15 @@ +{ + "type": "VOICE_RELAY", + "voiceRelay": { + "endpoint": "wss://relay.example.com/agent", + "enableInterruptions": false, + "ttsVoice": "Emma", + "sttLanguage": "en-US", + "callHeaders": [ + { + "key": "customer-id", + "value": "12345" + } + ] + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/error-response.json b/openapi-contracts/src/test/resources/domains/voice/v2/error-response.json new file mode 100644 index 000000000..169f187cd --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/error-response.json @@ -0,0 +1,6 @@ +{ + "type": "https://api.sinch.com/docs/errors/bad-request", + "title": "Bad Request", + "detail": "The request body is invalid or malformed.", + "instance": "/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/EventDestinationCallBehaviorDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/EventDestinationCallBehaviorDto.json new file mode 100644 index 000000000..1a36b7937 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/EventDestinationCallBehaviorDto.json @@ -0,0 +1,7 @@ +{ + "type": "WEBHOOK", + "webhook": { + "url": "https://example.com/webhook", + "fallbackUrl": "https://example.com/fallback" + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/EventDestinationConfigurationDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/EventDestinationConfigurationDto.json new file mode 100644 index 000000000..817b70728 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/EventDestinationConfigurationDto.json @@ -0,0 +1,4 @@ +{ + "url": "https://example.com/webhook", + "fallbackUrl": "https://example.com/fallback" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/NoneCallBehaviorDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/NoneCallBehaviorDto.json new file mode 100644 index 000000000..462a309f6 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/NoneCallBehaviorDto.json @@ -0,0 +1,3 @@ +{ + "type": "NONE" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/StaticCallBehaviorDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/StaticCallBehaviorDto.json new file mode 100644 index 000000000..8322599a6 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/StaticCallBehaviorDto.json @@ -0,0 +1,20 @@ +{ + "type": "STATIC", + "static": { + "commands": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "callName": "incoming", + "events": { + "onHangup": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/request/CreateServiceRequestDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/request/CreateServiceRequestDto.json new file mode 100644 index 000000000..ad0a0e816 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/request/CreateServiceRequestDto.json @@ -0,0 +1,12 @@ +{ + "name": "Example service", + "description": "Service with webhooks", + "isDefault": true, + "callBehavior": { + "type": "WEBHOOK", + "webhook": { + "url": "https://example.com/webhook", + "fallbackUrl": "https://example.com/fallback" + } + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/request/UpdateServiceRequestDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/request/UpdateServiceRequestDto.json new file mode 100644 index 000000000..a3991d977 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/request/UpdateServiceRequestDto.json @@ -0,0 +1,10 @@ +{ + "description": "Service with webhooks", + "callBehavior": { + "type": "WEBHOOK", + "webhook": { + "url": "https://example.com/webhook", + "fallbackUrl": "https://example.com/fallback" + } + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceResponseDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceResponseDto.json new file mode 100644 index 000000000..427eccdd6 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceResponseDto.json @@ -0,0 +1,17 @@ +{ + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "serviceSecret": "9+MXX5u6dky5aATA7ZfIRg==", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "createTime": "2025-01-01T00:00:00Z", + "updateTime": "2025-02-15T10:30:00Z", + "name": "Example service", + "description": "Service with webhooks", + "isDefault": false, + "callBehavior": { + "type": "WEBHOOK", + "webhook": { + "url": "https://example.com/webhook", + "fallbackUrl": "https://example.com/fallback" + } + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceResponseMinimalDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceResponseMinimalDto.json new file mode 100644 index 000000000..47d2b5a0b --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceResponseMinimalDto.json @@ -0,0 +1,8 @@ +{ + "serviceId": "7f235289-d3ae-57b6-a54d-6d3bf655bbdf", + "serviceSecret": "Jq4Rz2s8VnYkL0pT7wXc1A==", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "createTime": "2025-02-15T10:30:00Z", + "name": "Secondary Service", + "isDefault": false +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceShortResponseDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceShortResponseDto.json new file mode 100644 index 000000000..7213476e7 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceShortResponseDto.json @@ -0,0 +1,9 @@ +{ + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "createTime": "2025-01-01T00:00:00Z", + "updateTime": "2025-02-15T10:30:00Z", + "name": "Primary Service", + "description": "Main service with webhook call behavior", + "isDefault": true +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceShortResponseMinimalDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceShortResponseMinimalDto.json new file mode 100644 index 000000000..913100347 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/ServiceShortResponseMinimalDto.json @@ -0,0 +1,7 @@ +{ + "serviceId": "7f235289-d3ae-57b6-a54d-6d3bf655bbdf", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "createTime": "2025-02-15T10:30:00Z", + "name": "Secondary Service", + "isDefault": false +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/response/internal/ServicesListResponseInternalDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/internal/ServicesListResponseInternalDto.json new file mode 100644 index 000000000..88a61f1be --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/internal/ServicesListResponseInternalDto.json @@ -0,0 +1,30 @@ +{ + "services": [ + { + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "createTime": "2025-01-01T00:00:00Z", + "updateTime": "2025-02-15T10:30:00Z", + "name": "Primary Service", + "description": "Main service with webhook call behavior", + "isDefault": true + }, + { + "serviceId": "7f235289-d3ae-57b6-a54d-6d3bf655bbdf", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "createTime": "2025-02-15T10:30:00Z", + "name": "Secondary Service", + "isDefault": false + } + ], + "links": { + "first": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/services?page=1&pageSize=2", + "last": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/services?page=2&pageSize=2", + "next": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/services?page=2&pageSize=2", + "self": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/services?page=1&pageSize=2" + }, + "meta": { + "totalCount": 3, + "pageCount": 2 + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/services/response/internal/ServicesListResponseInternalLastPageDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/internal/ServicesListResponseInternalLastPageDto.json new file mode 100644 index 000000000..54b830e02 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/services/response/internal/ServicesListResponseInternalLastPageDto.json @@ -0,0 +1,21 @@ +{ + "services": [ + { + "serviceId": "7f235289-d3ae-57b6-a54d-6d3bf655bbdf", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "createTime": "2025-02-15T10:30:00Z", + "name": "Secondary Service", + "isDefault": false + } + ], + "links": { + "first": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/services?page=1&pageSize=2", + "last": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/services?page=2&pageSize=2", + "prev": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/services?page=1&pageSize=2", + "self": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/services?page=2&pageSize=2" + }, + "meta": { + "totalCount": 3, + "pageCount": 2 + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/sessions/response/SessionDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/sessions/response/SessionDto.json new file mode 100644 index 000000000..cea02a135 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/sessions/response/SessionDto.json @@ -0,0 +1,48 @@ +{ + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "calls": [ + { + "callId": "01ARZ3NDEKTSV4RRFFQ69G5FAA", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB", + "callName": "origin", + "bridgeName": "my-bridge", + "batchId": "01BX5ZZKBKACTAV9WEVGEMMVRC", + "from": { + "type": "SIP", + "sip": { + "endpoint": "sip:alice@sip.example.com", + "displayName": "Alice" + } + }, + "to": { + "type": "PHONE", + "phone": { + "number": "+15551234567" + } + }, + "startTime": "2025-02-10T09:00:00Z", + "updateTime": "2025-02-10T09:00:47Z", + "callType": "PHONE", + "direction": "INBOUND", + "answerTime": "2025-02-10T09:00:05Z", + "endTime": "2025-02-10T09:00:47Z", + "callDurationSeconds": 42, + "callResult": "COMPLETED", + "callReason": "CALLEE_HANGUP", + "originationType": "SIP", + "callRate": { + "currencyCode": "USD", + "amount": "0.0123" + }, + "callResourceUrl": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls/01ARZ3NDEKTSV4RRFFQ69G5FAA" + } + ], + "createTime": "2025-02-10T09:00:00Z", + "updateTime": "2025-02-10T09:00:47Z", + "endTime": "2025-02-10T09:00:47Z", + "state": "COMPLETED" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/sessions/response/SessionMinimalDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/sessions/response/SessionMinimalDto.json new file mode 100644 index 000000000..bee95389e --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/sessions/response/SessionMinimalDto.json @@ -0,0 +1,25 @@ +{ + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "calls": [ + { + "callId": "01ARZ3NDEKTSV4RRFFQ69G5FAA", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB", + "startTime": "2025-02-10T09:00:00Z", + "callType": "PHONE", + "direction": "OUTBOUND", + "callResult": "QUEUED", + "originationType": "SERVER", + "callRate": { + "currencyCode": "USD", + "amount": "0.0123" + }, + "callResourceUrl": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls/01ARZ3NDEKTSV4RRFFQ69G5FAA" + } + ], + "createTime": "2025-02-10T09:00:00Z", + "state": "QUEUED" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/MenuInputDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/MenuInputDto.json new file mode 100644 index 000000000..483f86b8d --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/MenuInputDto.json @@ -0,0 +1,4 @@ +{ + "menuName": "main", + "input": "1234" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/VoiceSinchEventCustomEventDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/VoiceSinchEventCustomEventDto.json new file mode 100644 index 000000000..9e6b4a42f --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/VoiceSinchEventCustomEventDto.json @@ -0,0 +1,40 @@ +{ + "event": "call.webhook.on-answer", + "call": { + "callId": "01ARZ3NDEKTSV4RRFFQ69G5FAA", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB", + "callName": "origin", + "bridgeName": "my-bridge", + "batchId": "01BX5ZZKBKACTAV9WEVGEMMVRC", + "from": { + "type": "SIP", + "sip": { + "endpoint": "sip:alice@sip.example.com", + "displayName": "Alice" + } + }, + "to": { + "type": "PHONE", + "phone": { + "number": "+15551234567" + } + }, + "startTime": "2025-02-10T09:00:00Z", + "updateTime": "2025-02-10T09:00:47Z", + "callType": "PHONE", + "direction": "INBOUND", + "answerTime": "2025-02-10T09:00:05Z", + "endTime": "2025-02-10T09:00:47Z", + "callDurationSeconds": 42, + "callResult": "COMPLETED", + "callReason": "CALLEE_HANGUP", + "originationType": "SIP", + "callRate": { + "currencyCode": "USD", + "amount": "0.0123" + }, + "callResourceUrl": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls/01ARZ3NDEKTSV4RRFFQ69G5FAA" + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/VoiceSinchEventDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/VoiceSinchEventDto.json new file mode 100644 index 000000000..70982cb35 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/VoiceSinchEventDto.json @@ -0,0 +1,44 @@ +{ + "event": "call.menu", + "call": { + "callId": "01ARZ3NDEKTSV4RRFFQ69G5FAA", + "projectId": "5c5bf2b1-35ae-4825-ab89-457e07bb60e6", + "serviceId": "6e124178-c29d-46a5-943c-5c2ae544aade", + "sessionId": "01BX5ZZKBKACTAV9WEVGEMMVRB", + "callName": "origin", + "bridgeName": "my-bridge", + "batchId": "01BX5ZZKBKACTAV9WEVGEMMVRC", + "from": { + "type": "SIP", + "sip": { + "endpoint": "sip:alice@sip.example.com", + "displayName": "Alice" + } + }, + "to": { + "type": "PHONE", + "phone": { + "number": "+15551234567" + } + }, + "startTime": "2025-02-10T09:00:00Z", + "updateTime": "2025-02-10T09:00:47Z", + "callType": "PHONE", + "direction": "INBOUND", + "answerTime": "2025-02-10T09:00:05Z", + "endTime": "2025-02-10T09:00:47Z", + "callDurationSeconds": 42, + "callResult": "COMPLETED", + "callReason": "CALLEE_HANGUP", + "originationType": "SIP", + "callRate": { + "currencyCode": "USD", + "amount": "0.0123" + }, + "callResourceUrl": "https://voice.api.sinch.com/v2/projects/5c5bf2b1-35ae-4825-ab89-457e07bb60e6/calls/01ARZ3NDEKTSV4RRFFQ69G5FAA" + }, + "menu": { + "menuName": "main", + "input": "1234" + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/VoiceSinchEventResponseDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/VoiceSinchEventResponseDto.json new file mode 100644 index 000000000..7f6fd301e --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/sinchevents/VoiceSinchEventResponseDto.json @@ -0,0 +1,17 @@ +{ + "commands": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "callName": "incoming", + "events": { + "onHangup": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/IncomingCallResponseEventsDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/IncomingCallResponseEventsDto.json new file mode 100644 index 000000000..236ffa0f4 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/IncomingCallResponseEventsDto.json @@ -0,0 +1,8 @@ +{ + "onHangup": [ + { + "command": "hangup", + "callName": "origin" + } + ] +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/SvamlInputDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/SvamlInputDto.json new file mode 100644 index 000000000..7f6fd301e --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/SvamlInputDto.json @@ -0,0 +1,17 @@ +{ + "commands": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "callName": "incoming", + "events": { + "onHangup": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/amd/AmdCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/amd/AmdCommandDto.json new file mode 100644 index 000000000..8e7e16b0e --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/amd/AmdCommandDto.json @@ -0,0 +1,53 @@ +{ + "command": "amd", + "events": { + "onHuman": [ + { + "command": "messages", + "messagesName": "my-messages", + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + }, + { + "type": "PLAY", + "play": { + "url": "https://example.com/audio/welcome.mp3" + } + } + ], + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + ], + "onMachine": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onBeep": [ + { + "command": "pause", + "durationMilliseconds": 1500 + } + ], + "onUnknown": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/amd/AmdEventsDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/amd/AmdEventsDto.json new file mode 100644 index 000000000..3e2fbf090 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/amd/AmdEventsDto.json @@ -0,0 +1,50 @@ +{ + "onHuman": [ + { + "command": "messages", + "messagesName": "my-messages", + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + }, + { + "type": "PLAY", + "play": { + "url": "https://example.com/audio/welcome.mp3" + } + } + ], + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + ], + "onMachine": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onBeep": [ + { + "command": "pause", + "durationMilliseconds": 1500 + } + ], + "onUnknown": [ + { + "command": "hangup", + "callName": "origin" + } + ] +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/AnswerCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/AnswerCommandDto.json new file mode 100644 index 000000000..0cce78131 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/AnswerCommandDto.json @@ -0,0 +1,3 @@ +{ + "command": "answer" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/BridgeCallCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/BridgeCallCommandDto.json new file mode 100644 index 000000000..98342420b --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/BridgeCallCommandDto.json @@ -0,0 +1,4 @@ +{ + "command": "bridgeCall", + "bridgeName": "bridge" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/CallEventsDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/CallEventsDto.json new file mode 100644 index 000000000..abff109e2 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/CallEventsDto.json @@ -0,0 +1,193 @@ +{ + "onAnswer": [ + { + "command": "answer" + }, + { + "command": "amd", + "events": { + "onHuman": [ + { + "command": "messages", + "messagesName": "my-messages", + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + }, + { + "type": "PLAY", + "play": { + "url": "https://example.com/audio/welcome.mp3" + } + } + ], + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + ], + "onMachine": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onBeep": [ + { + "command": "pause", + "durationMilliseconds": 1500 + } + ], + "onUnknown": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + }, + { + "command": "startRecording", + "recordingName": "my-recording", + "recordingOptions": { + "format": "MP3", + "recordingType": "COMBINED", + "destination": "AWS", + "destinationUrl": "s3://my-bucket/recordings", + "credentials": "access-key:secret-key", + "transcriptionOptions": { + "isEnabled": true, + "locale": "en-US" + } + }, + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + }, + { + "command": "menu", + "startMenu": "main", + "menus": { + "main": { + "prompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "repeatPrompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "inputTimeoutDurationSeconds": 10, + "repeatCount": 3, + "minimumInputLength": 1, + "maximumInputLength": 4, + "terminatingSequence": "#", + "inputMethods": [ + "DTMF" + ], + "matches": { + "1": [ + { + "command": "gotoMenu", + "menuName": "main" + } + ] + }, + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + }, + { + "command": "bridgeCall", + "bridgeName": "bridge" + }, + { + "command": "webhook", + "webhookName": "my.custom.event", + "url": "https://example.com/events", + "fallbackUrl": "https://fallback.example.com/events" + }, + { + "command": "stopMessages", + "messagesName": "my-messages" + } + ], + "onBusy": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onReject": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onTimeout": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onHangup": [ + { + "command": "stopRecording", + "recordingName": "my-recording" + }, + { + "command": "hangup", + "callName": "origin" + } + ], + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandDto.json new file mode 100644 index 000000000..6f6986902 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandDto.json @@ -0,0 +1,211 @@ +{ + "command": "dial", + "callName": "origin", + "from": { + "type": "PHONE", + "phone": { + "number": "+15551234567" + } + }, + "to": { + "type": "PHONE", + "phone": { + "number": "+15551234567" + } + }, + "dialTimeoutDurationSeconds": 30, + "maxCallDurationSeconds": 3600, + "events": { + "onAnswer": [ + { + "command": "answer" + }, + { + "command": "amd", + "events": { + "onHuman": [ + { + "command": "messages", + "messagesName": "my-messages", + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + }, + { + "type": "PLAY", + "play": { + "url": "https://example.com/audio/welcome.mp3" + } + } + ], + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + ], + "onMachine": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onBeep": [ + { + "command": "pause", + "durationMilliseconds": 1500 + } + ], + "onUnknown": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + }, + { + "command": "startRecording", + "recordingName": "my-recording", + "recordingOptions": { + "format": "MP3", + "recordingType": "COMBINED", + "destination": "AWS", + "destinationUrl": "s3://my-bucket/recordings", + "credentials": "access-key:secret-key", + "transcriptionOptions": { + "isEnabled": true, + "locale": "en-US" + } + }, + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + }, + { + "command": "menu", + "startMenu": "main", + "menus": { + "main": { + "prompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "repeatPrompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "inputTimeoutDurationSeconds": 10, + "repeatCount": 3, + "minimumInputLength": 1, + "maximumInputLength": 4, + "terminatingSequence": "#", + "inputMethods": [ + "DTMF" + ], + "matches": { + "1": [ + { + "command": "gotoMenu", + "menuName": "main" + } + ] + }, + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } + }, + { + "command": "bridgeCall", + "bridgeName": "bridge" + }, + { + "command": "webhook", + "webhookName": "my.custom.event", + "url": "https://example.com/events", + "fallbackUrl": "https://fallback.example.com/events" + }, + { + "command": "stopMessages", + "messagesName": "my-messages" + } + ], + "onBusy": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onReject": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onTimeout": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onHangup": [ + { + "command": "stopRecording", + "recordingName": "my-recording" + }, + { + "command": "hangup", + "callName": "origin" + } + ], + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandSipDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandSipDto.json new file mode 100644 index 000000000..2f316bb2e --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandSipDto.json @@ -0,0 +1,24 @@ +{ + "command": "dial", + "callName": "origin", + "from": { + "type": "SIP", + "sip": { + "endpoint": "sip:alice@sip.example.com", + "displayName": "Alice" + } + }, + "to": { + "type": "SIP", + "sip": { + "endpoint": "sips:bob@sip.example.com", + "transport": "TLS", + "callHeaders": [ + { + "key": "X-Custom-Header", + "value": "custom-value" + } + ] + } + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandStreamDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandStreamDto.json new file mode 100644 index 000000000..db9cfc202 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandStreamDto.json @@ -0,0 +1,20 @@ +{ + "command": "dial", + "to": { + "type": "STREAM", + "stream": { + "endpoint": "wss://myapp.example.com/audio", + "streamOptions": { + "version": 1, + "codec": "PCM", + "sampleRate": 16000 + }, + "callHeaders": [ + { + "key": "customer-id", + "value": "12345" + } + ] + } + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandVoiceRelayDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandVoiceRelayDto.json new file mode 100644 index 000000000..c54ef6fd1 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/DialCommandVoiceRelayDto.json @@ -0,0 +1,18 @@ +{ + "command": "dial", + "to": { + "type": "VOICE_RELAY", + "voiceRelay": { + "endpoint": "wss://relay.example.com/agent", + "enableInterruptions": false, + "ttsVoice": "Emma", + "sttLanguage": "en-US", + "callHeaders": [ + { + "key": "customer-id", + "value": "12345" + } + ] + } + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/HangupCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/HangupCommandDto.json new file mode 100644 index 000000000..39c7fc5d1 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/HangupCommandDto.json @@ -0,0 +1,4 @@ +{ + "command": "hangup", + "callName": "origin" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/PauseCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/PauseCommandDto.json new file mode 100644 index 000000000..f3132842a --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/calls/PauseCommandDto.json @@ -0,0 +1,4 @@ +{ + "command": "pause", + "durationMilliseconds": 1500 +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/customevents/CustomEventCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/customevents/CustomEventCommandDto.json new file mode 100644 index 000000000..3d206d2d5 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/customevents/CustomEventCommandDto.json @@ -0,0 +1,6 @@ +{ + "command": "webhook", + "webhookName": "my.custom.event", + "url": "https://example.com/events", + "fallbackUrl": "https://fallback.example.com/events" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/GotoMenuCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/GotoMenuCommandDto.json new file mode 100644 index 000000000..6c7fc5a84 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/GotoMenuCommandDto.json @@ -0,0 +1,4 @@ +{ + "command": "gotoMenu", + "menuName": "main" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/MenuCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/MenuCommandDto.json new file mode 100644 index 000000000..0dd79ff2b --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/MenuCommandDto.json @@ -0,0 +1,56 @@ +{ + "command": "menu", + "startMenu": "main", + "menus": { + "main": { + "prompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "repeatPrompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "inputTimeoutDurationSeconds": 10, + "repeatCount": 3, + "minimumInputLength": 1, + "maximumInputLength": 4, + "terminatingSequence": "#", + "inputMethods": [ + "DTMF" + ], + "matches": { + "1": [ + { + "command": "gotoMenu", + "menuName": "main" + } + ] + }, + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/MenuItemDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/MenuItemDto.json new file mode 100644 index 000000000..2ed5318ee --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/MenuItemDto.json @@ -0,0 +1,50 @@ +{ + "prompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "repeatPrompt": { + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] + }, + "inputTimeoutDurationSeconds": 10, + "repeatCount": 3, + "minimumInputLength": 1, + "maximumInputLength": 4, + "terminatingSequence": "#", + "inputMethods": [ + "DTMF" + ], + "matches": { + "1": [ + { + "command": "gotoMenu", + "menuName": "main" + } + ] + }, + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/MenuPromptDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/MenuPromptDto.json new file mode 100644 index 000000000..ec40fd303 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/menu/MenuPromptDto.json @@ -0,0 +1,13 @@ +{ + "allowBargeIn": false, + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + } + ] +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/MessageEventsDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/MessageEventsDto.json new file mode 100644 index 000000000..7438c67be --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/MessageEventsDto.json @@ -0,0 +1,8 @@ +{ + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ] +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/MessagesCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/MessagesCommandDto.json new file mode 100644 index 000000000..db7f63ea5 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/MessagesCommandDto.json @@ -0,0 +1,28 @@ +{ + "command": "messages", + "messagesName": "my-messages", + "messages": [ + { + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } + }, + { + "type": "PLAY", + "play": { + "url": "https://example.com/audio/welcome.mp3" + } + } + ], + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/PlayMessageDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/PlayMessageDto.json new file mode 100644 index 000000000..d60f1767c --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/PlayMessageDto.json @@ -0,0 +1,6 @@ +{ + "type": "PLAY", + "play": { + "url": "https://example.com/audio/welcome.mp3" + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/SayMessageDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/SayMessageDto.json new file mode 100644 index 000000000..5ac574e4b --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/SayMessageDto.json @@ -0,0 +1,8 @@ +{ + "type": "SAY", + "say": { + "text": "Hello, your call is now connected.", + "format": "TEXT", + "voiceName": "Emma" + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/StopMessagesCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/StopMessagesCommandDto.json new file mode 100644 index 000000000..f70f3c940 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/playback/StopMessagesCommandDto.json @@ -0,0 +1,4 @@ +{ + "command": "stopMessages", + "messagesName": "my-messages" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/RecordingEventsDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/RecordingEventsDto.json new file mode 100644 index 000000000..86727a803 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/RecordingEventsDto.json @@ -0,0 +1,14 @@ +{ + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/RecordingOptionsDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/RecordingOptionsDto.json new file mode 100644 index 000000000..81111531b --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/RecordingOptionsDto.json @@ -0,0 +1,11 @@ +{ + "format": "MP3", + "recordingType": "COMBINED", + "destination": "AWS", + "destinationUrl": "s3://my-bucket/recordings", + "credentials": "access-key:secret-key", + "transcriptionOptions": { + "isEnabled": true, + "locale": "en-US" + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/StartRecordingCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/StartRecordingCommandDto.json new file mode 100644 index 000000000..6f4c23146 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/StartRecordingCommandDto.json @@ -0,0 +1,29 @@ +{ + "command": "startRecording", + "recordingName": "my-recording", + "recordingOptions": { + "format": "MP3", + "recordingType": "COMBINED", + "destination": "AWS", + "destinationUrl": "s3://my-bucket/recordings", + "credentials": "access-key:secret-key", + "transcriptionOptions": { + "isEnabled": true, + "locale": "en-US" + } + }, + "events": { + "onFinish": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "onFailure": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/StopRecordingCommandDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/StopRecordingCommandDto.json new file mode 100644 index 000000000..e0dd46e1d --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/StopRecordingCommandDto.json @@ -0,0 +1,4 @@ +{ + "command": "stopRecording", + "recordingName": "my-recording" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/TranscriptionOptionsDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/TranscriptionOptionsDto.json new file mode 100644 index 000000000..0e6225682 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/recording/TranscriptionOptionsDto.json @@ -0,0 +1,4 @@ +{ + "isEnabled": true, + "locale": "en-US" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/request/DescribeSvamlRequestDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/request/DescribeSvamlRequestDto.json new file mode 100644 index 000000000..cb4558478 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/request/DescribeSvamlRequestDto.json @@ -0,0 +1,19 @@ +{ + "svaml": { + "commands": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "callName": "incoming", + "events": { + "onHangup": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + } +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/request/ValidateSvamlRequestDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/request/ValidateSvamlRequestDto.json new file mode 100644 index 000000000..65895b3bb --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/request/ValidateSvamlRequestDto.json @@ -0,0 +1,20 @@ +{ + "svaml": { + "commands": [ + { + "command": "hangup", + "callName": "origin" + } + ], + "callName": "incoming", + "events": { + "onHangup": [ + { + "command": "hangup", + "callName": "origin" + } + ] + } + }, + "validationType": "STRICT" +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/response/SvamlDescriptionResponseDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/response/SvamlDescriptionResponseDto.json new file mode 100644 index 000000000..0977a210a --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/response/SvamlDescriptionResponseDto.json @@ -0,0 +1,3 @@ +{ + "description": "The call is answered and a TTS message is played using the voice Emma." +} diff --git a/openapi-contracts/src/test/resources/domains/voice/v2/svaml/response/ValidateSvamlResponseDto.json b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/response/ValidateSvamlResponseDto.json new file mode 100644 index 000000000..fd3e01e68 --- /dev/null +++ b/openapi-contracts/src/test/resources/domains/voice/v2/svaml/response/ValidateSvamlResponseDto.json @@ -0,0 +1,7 @@ +{ + "isValid": false, + "errors": [ + "Invalid dial command. Missing destination.", + "Invalid say command. Missing voice name." + ] +} diff --git a/pom.xml b/pom.xml index 29cd923ff..87a92996e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.sinch.sdk sinch-sdk-java - 2.3.0-SNAPSHOT + 2.4.0-SNAPSHOT Sinch Java SDK @@ -60,7 +60,7 @@ UTF-8 - 2.21.5 + 2.21.7 5.6.3 @@ -74,7 +74,7 @@ 3.6.0 3.0.1 1.6 - 0.11.0 + 0.12.0 @@ -295,6 +295,7 @@ com.sinch.sdk.e2e.domains.conversation.ConversationIT com.sinch.sdk.e2e.domains.sms.v1.SmsIT com.sinch.sdk.e2e.domains.voice.v1.VoiceIT + com.sinch.sdk.e2e.domains.voice.v2.VoiceIT com.sinch.sdk.e2e.domains.verification.v1.VerificationIT com.sinch.sdk.e2e.domains.numberlookup.v2.NumberLookupIT