Skip to content

Commit 3c98e76

Browse files
authored
feat: Add support for custom requests (#39)
1 parent 871dce1 commit 3c98e76

File tree

9 files changed

+426
-17
lines changed

9 files changed

+426
-17
lines changed

.github/workflows/publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ jobs:
4242
steps:
4343
- name: Send to Slack channels
4444
uses: slackapi/slack-github-action@v2.0.0
45+
continue-on-error: true
4546
with:
4647
webhook: ${{ secrets[matrix.url] }}
4748
webhook-type: incoming-webhook

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
## [2.1.0] - 2025-04-30
8+
9+
### Added
10+
- Support for custom HTTP requests via `Custom` client
11+
12+
### Changed
13+
- Bumped Java SDK version to 9.2.0
14+
715
## [2.0.0] - 2025-04-08
816
Major release for compatibility with [Java SDK v9.0.0](https://github.com/Vonage/vonage-java-sdk/releases/tag/v9.0.0)
917

README.md

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ You'll need to have [created a Vonage account](https://dashboard.nexmo.com/sign-
2828
- [Number Insight](https://developer.vonage.com/en/number-insight/overview)
2929
- [Number Management](https://developer.vonage.com/en/numbers/overview)
3030
- [Number Verification](https://developer.vonage.com/en/number-verification/overview)
31-
- [Pricing](https://developer.vonage.com/en/api/pricing)
3231
- [Redact](https://developer.vonage.com/en/redact/overview)
3332
- [SIM Swap](https://developer.vonage.com/en/sim-swap/overview)
3433
- [SMS](https://developer.vonage.com/en/messaging/sms/overview)
@@ -53,7 +52,7 @@ See all of our SDKs and integrations on the [Vonage Developer portal](https://de
5352
## Installation
5453
Releases are published to [Maven Central](https://central.sonatype.com/artifact/com.vonage/server-sdk-kotlin).
5554
Instructions for your build system can be found in the snippets section.
56-
They're also available from [here](https://search.maven.org/artifact/com.vonage/server-sdk-kotlin/2.0.0/jar).
55+
They're also available from [here](https://search.maven.org/artifact/com.vonage/server-sdk-kotlin/2.1.0/jar).
5756
Release notes for each version can be found in the [changelog](CHANGELOG.md).
5857

5958
Here are the instructions for including the SDK in your project:
@@ -63,7 +62,7 @@ Add the following to your `build.gradle` or `build.gradle.kts` file:
6362

6463
```groovy
6564
dependencies {
66-
implementation("com.vonage:server-sdk-kotlin:2.0.0")
65+
implementation("com.vonage:server-sdk-kotlin:2.1.0")
6766
}
6867
```
6968

@@ -74,7 +73,7 @@ Add the following to the `<dependencies>` section of your `pom.xml` file:
7473
<dependency>
7574
<groupId>com.vonage</groupId>
7675
<artifactId>server-sdk-kotlin</artifactId>
77-
<version>2.0.0</version>
76+
<version>2.1.0</version>
7877
</dependency>
7978
```
8079

@@ -160,13 +159,66 @@ including [**a searchable list of snippets**](https://github.com/Vonage/vonage-k
160159

161160
The SDK is fully documented with [KDocs](https://kotlinlang.org/docs/kotlin-doc.html), so you should have complete
162161
documentation from your IDE. You may need to click "Download Sources" in IntelliJ to get the full documentation.
163-
Alternatively, you can browse the documentation using a service like [Javadoc.io](https://javadoc.io/doc/com.vonage/server-sdk-kotlin/2.0.0/index.html),
164-
which renders the documentation for you from [the artifacts on Maven Central](https://repo.maven.apache.org/maven2/com/vonage/server-sdk-kotlin/2.0.0/).
162+
Alternatively, you can browse the documentation using a service like [Javadoc.io](https://javadoc.io/doc/com.vonage/server-sdk-kotlin/2.1.0/index.html),
163+
which renders the documentation for you from [the artifacts on Maven Central](https://repo.maven.apache.org/maven2/com/vonage/server-sdk-kotlin/2.1.0/).
165164

166165
For help with any specific APIs, refer to the relevant documentation on our [developer portal](https://developer.vonage.com/en/documentation),
167166
using the links provided in the [Supported APIs](#supported-apis) section. For completeness, you can also consult the
168167
[API specifications](https://developer.vonage.com/api) if you believe there are any discrepancies.
169168

169+
### Custom Requests
170+
The [Java SDK supports custom HTTP requests](https://github.com/Vonage/vonage-java-sdk?tab=readme-ov-file#custom-requests),
171+
which this SDK builds upon. This allows you to use unsupported APIs with your own data models so long as they implement
172+
the `com.vonage.client.Jsonable` interface. Alternatively you can use `Map<String, *>` to represents the JSON structure.
173+
See [Custom.kt](src/main/kotlin/com/vonage/client/kt/Custom.kt) documentation for more details.
174+
Here are some examples for creating an application, all of which are equivalent.
175+
176+
#### Map request, Map response
177+
```kotlin
178+
val response: Map<String, *> = client.custom.post(
179+
"https://api.nexmo.com/v2/applications",
180+
mapOf("name" to "Demo Application")
181+
)
182+
```
183+
184+
#### Map request, Jsonable response
185+
```kotlin
186+
val response: Application = client.custom.post(
187+
"https://api.nexmo.com/v2/applications",
188+
mapOf("name" to "Demo Application")
189+
)
190+
```
191+
192+
#### Jsonable request, Map response
193+
```kotlin
194+
val response: Map<String, *> = client.custom.post(
195+
"https://api.nexmo.com/v2/applications",
196+
com.vonage.client.application.Application.builder()
197+
.name("Demo Application").build()
198+
)
199+
```
200+
201+
#### Jsonable request, Jsonable response
202+
```kotlin
203+
val response: Application = client.custom.post(
204+
"https://api.nexmo.com/v2/applications",
205+
com.vonage.client.application.Application.builder()
206+
.name("Demo Application").build()
207+
)
208+
```
209+
210+
#### Caveats
211+
The same principle applies for all other supported HTTP methods. You can also use the `makeRequest` method for greater
212+
flexibility on the request and response types. In any case, you should **ALWAYS** use strong typing when assigning
213+
the result of the call, otherwise the compiler will not be able to infer the correct type and you will get a runtime
214+
exception. If you'd like to ignore the result, use `Void` rather than `Unit` or `Any`. For example in `DELETE` requests:
215+
216+
```kotlin
217+
client.custom.delete<Void>("https://api.nexmo.com/accounts/:api_key/secrets/:secret_id")
218+
```
219+
220+
You can see valid usage examples in [CustomTest.kt](src/test/kotlin/com/vonage/client/kt/CustomTest.kt).
221+
170222
## Frequently Asked Questions
171223

172224
**Q: Why use this SDK instead of the [Vonage Java Server SDK](https://github.com/Vonage/vonage-java-sdk)?**

pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>com.vonage</groupId>
77
<artifactId>server-sdk-kotlin</artifactId>
8-
<version>2.0.0</version>
8+
<version>2.1.0</version>
99

1010
<name>Vonage Kotlin Server SDK</name>
1111
<description>Kotlin client for Vonage APIs</description>
@@ -55,7 +55,7 @@
5555
<dependency>
5656
<groupId>com.vonage</groupId>
5757
<artifactId>server-sdk</artifactId>
58-
<version>9.1.0</version>
58+
<version>9.2.0</version>
5959
</dependency>
6060
<dependency>
6161
<groupId>org.jetbrains.kotlin</groupId>
@@ -77,7 +77,7 @@
7777
<dependency>
7878
<groupId>org.wiremock</groupId>
7979
<artifactId>wiremock-standalone</artifactId>
80-
<version>3.12.1</version>
80+
<version>3.13.0</version>
8181
<scope>test</scope>
8282
</dependency>
8383
</dependencies>
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright 2025 Vonage
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.vonage.client.kt
17+
18+
import com.vonage.client.*
19+
import com.vonage.client.common.HttpMethod
20+
21+
/**
22+
* Custom client for making HTTP requests to APIs that are unsupported by this SDK.
23+
* This will automatically handle authentication and (de)serialisation for you.
24+
*
25+
* Requests should be JSON-based; either a Map representation of the structure or an implementation of [Jsonable].
26+
* Responses (i.e. the `R` parameter) may be one of the following:
27+
* - `Map<String, *>` representation of the JSON response.
28+
* - `Collection<*>` representation of the JSON response.
29+
* - `String` representation of the response body.
30+
* - A custom object that implements the [Jsonable] interface to parse the JSON response into.
31+
* - [ByteArray] for binary response bodies.
32+
* - [Void] for empty response bodies.
33+
*
34+
* Please note that you must ALWAYS explicitly provide the type parameters when calling methods on this client and
35+
* also provide the type in the response assignment, otherwise the compiler won't be able to infer the correct type.
36+
*
37+
* @param internalJavaSDKCustomClient The underlying Java SDK implementation which this client delegates to.
38+
*
39+
* @since 2.1.0
40+
*/
41+
class Custom internal constructor(val internalJavaSDKCustomClient: CustomClient) {
42+
43+
/**
44+
* Advanced method for making requests to APIs that are unsupported by this SDK. This is the most flexible option.
45+
*
46+
* @param requestMethod The HTTP method to use for the request as an enum.
47+
* @param url Absolute URL to send the request to as a string.
48+
* @param body The request body, typically in JSON format. See [DynamicEndpoint.makeRequest] for acceptable types.
49+
*
50+
* @return The response body, which can be a Map, Collection, String, or custom object implementing [Jsonable].
51+
* @throws VonageApiResponseException If the HTTP response code is 400 or greater.
52+
*/
53+
inline fun <reified T, reified R> makeRequest(requestMethod: HttpMethod, url: String, body: T): R =
54+
internalJavaSDKCustomClient.makeRequest<T, R>(requestMethod, url, body)
55+
56+
/**
57+
* Sends a `DELETE` request to the specified URL.
58+
*
59+
* @param url Absolute URL to send the request to as a string.
60+
*
61+
* @return The response body if present, typically as JSON. See the class documentation for acceptable types.
62+
* @throws VonageApiResponseException If the HTTP response code is 400 or greater.
63+
*/
64+
inline fun <reified R> delete(url: String): R =
65+
internalJavaSDKCustomClient.delete<R>(url)
66+
67+
/**
68+
* Sends a `GET` request to the specified URL.
69+
*
70+
* @param url Absolute URL to send the request to as a string.
71+
*
72+
* @return The response body if present, typically as JSON. See the class documentation for acceptable types.
73+
* @throws VonageApiResponseException If the HTTP response code is 400 or greater.
74+
*/
75+
inline fun <reified R> get(url: String): R =
76+
internalJavaSDKCustomClient.get<R>(url)
77+
78+
/**
79+
* Sends a `POST` request to the specified URL with a JSON body.
80+
*
81+
* @param url Absolute URL to send the request to as a string.
82+
* @param body The request body as a [Jsonable] object.
83+
*
84+
* @return The response body if present, typically as JSON. See the class documentation for acceptable types.
85+
* @throws VonageApiResponseException If the HTTP response code is 400 or greater.
86+
*/
87+
inline fun <reified R> post(url: String, body: Jsonable): R =
88+
internalJavaSDKCustomClient.post<R>(url, body)
89+
90+
/**
91+
* Sends a `POST` request to the specified URL with a JSON body.
92+
*
93+
* @param url Absolute URL to send the request to as a string.
94+
* @param body The request body in JSON format as a Map tree structure.
95+
*
96+
* @return The response body if present, typically as JSON. See the class documentation for acceptable types.
97+
* @throws VonageApiResponseException If the HTTP response code is 400 or greater.
98+
*/
99+
inline fun <reified R> post(url: String, body: Map<String, *>): R =
100+
internalJavaSDKCustomClient.post<R>(url, body)
101+
102+
/**
103+
* Sends a `PUT` request to the specified URL with a JSON body.
104+
*
105+
* @param url Absolute URL to send the request to as a string.
106+
* @param body The request body as a [Jsonable] object.
107+
*
108+
* @return The response body if present, typically as JSON. See the class documentation for acceptable types.
109+
* @throws VonageApiResponseException If the HTTP response code is 400 or greater.
110+
*/
111+
inline fun <reified R> put(url: String, body: Jsonable): R =
112+
internalJavaSDKCustomClient.put<R>(url, body)
113+
114+
/**
115+
* Sends a `PUT` request to the specified URL with a JSON body.
116+
*
117+
* @param url Absolute URL to send the request to as a string.
118+
* @param body The request body in JSON format as a Map tree structure.
119+
*
120+
* @return The response body if present, typically as JSON. See the class documentation for acceptable types.
121+
* @throws VonageApiResponseException If the HTTP response code is 400 or greater.
122+
*/
123+
inline fun <reified R> put(url: String, body: Map<String, *>): R =
124+
internalJavaSDKCustomClient.put<R>(url, body)
125+
126+
/**
127+
* Sends a `PATCH` request to the specified URL with a JSON body.
128+
*
129+
* @param url Absolute URL to send the request to as a string.
130+
* @param body The request body as a [Jsonable] object.
131+
*
132+
* @return The response body if present, typically as JSON. See the class documentation for acceptable types.
133+
* @throws VonageApiResponseException If the HTTP response code is 400 or greater.
134+
*/
135+
inline fun <reified R> patch(url: String, body: Jsonable): R =
136+
internalJavaSDKCustomClient.patch<R>(url, body)
137+
138+
/**
139+
* Sends a `PATCH` request to the specified URL with a JSON body.
140+
*
141+
* @param url Absolute URL to send the request to as a string.
142+
* @param body The request body in JSON format as a Map tree structure.
143+
*
144+
* @return The response body if present, typically as JSON. See the class documentation for acceptable types.
145+
* @throws VonageApiResponseException If the HTTP response code is 400 or greater.
146+
*/
147+
inline fun <reified R> patch(url: String, body: Map<String, *>): R =
148+
internalJavaSDKCustomClient.patch<R>(url, body)
149+
}

src/main/kotlin/com/vonage/client/kt/Vonage.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import com.vonage.client.VonageClient
2121
/**
2222
* Denotes the version of the Vonage Kotlin SDK being used, in SemVer format.
2323
*/
24-
const val VONAGE_KOTLIN_SDK_VERSION = "2.0.0"
24+
const val VONAGE_KOTLIN_SDK_VERSION = "2.1.0"
2525

2626
/**
2727
* The non-overridable user agent string used by the SDK.
@@ -68,6 +68,15 @@ class Vonage(config: VonageClient.Builder.() -> Unit) {
6868
*/
6969
val conversion = Conversion(client.conversionClient)
7070

71+
/**
72+
* Access to a client for making custom HTTP requests.
73+
*
74+
* @return The [Custom] client.
75+
*
76+
* @since 2.1.0
77+
*/
78+
val custom = Custom(client.customClient)
79+
7180
/**
7281
* Access to the Vonage Messages API.
7382
*

src/test/kotlin/com/vonage/client/kt/AbstractTest.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,15 +218,19 @@ abstract class AbstractTest {
218218
contentType: ContentType? = null,
219219
accept: ContentType? = null,
220220
authType: AuthType? = null,
221-
expectedParams: Map<String, Any>? = null): BuildingStep =
221+
expectedParams: Map<String, Any>? = null,
222+
includeMimeHeaders: Boolean = true
223+
): BuildingStep =
222224
wiremock.requestServerBuilderStep({
223225
urlPath equalTo expectedUrl
224226
headers contains "User-Agent" equalTo userAgent
225-
if (contentType != null) {
226-
headers contains contentTypeHeaderName equalTo contentType.mime
227-
}
228-
if (accept != null) {
229-
headers contains "Accept" equalTo accept.mime
227+
if (includeMimeHeaders) {
228+
if (contentType != null) {
229+
headers contains contentTypeHeaderName equalTo contentType.mime
230+
}
231+
if (accept != null) {
232+
headers contains "Accept" equalTo accept.mime
233+
}
230234
}
231235

232236
if (authType != null) {

0 commit comments

Comments
 (0)