This is an automated email from the ASF dual-hosted git repository.

ycai pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 9d61f356 CASSANDRASC-118: Support sending json request (#112)
9d61f356 is described below

commit 9d61f3568b7961de3f95e567d461e5103441a161
Author: Yifan Cai <52585731+yifa...@users.noreply.github.com>
AuthorDate: Sat Apr 13 07:04:52 2024 -0700

    CASSANDRASC-118: Support sending json request (#112)
    
    Patch by Yifan Cai; Reviewed by Francisco Guerrero for CASSANDRASC-118
---
 CHANGES.txt                                        |  1 +
 client/build.gradle                                |  1 +
 .../cassandra/sidecar/client/RequestExecutor.java  |  8 +-
 .../client/request/CassandraJmxHealthRequest.java  |  2 +-
 .../request/CassandraNativeHealthRequest.java      |  2 +-
 .../client/request/CreateRestoreJobRequest.java    |  5 +-
 .../request/CreateRestoreJobSliceRequest.java      |  4 +-
 .../sidecar/client/request/GossipInfoRequest.java  |  2 +-
 .../client/request/ImportSSTableRequest.java       |  2 +-
 .../{DecodableRequest.java => JsonRequest.java}    | 37 +++-------
 ...hRequest.java => JsonResponseBytesDecoder.java} | 35 +++++----
 .../client/request/NodeSettingsRequest.java        |  2 +-
 .../cassandra/sidecar/client/request/Request.java  | 16 ++++
 ...yloadRequest.java => ResponseBytesDecoder.java} | 15 +++-
 .../client/request/RestoreJobSummaryRequest.java   |  2 +-
 .../sidecar/client/request/RingRequest.java        |  2 +-
 .../sidecar/client/request/SchemaRequest.java      |  2 +-
 .../client/request/SidecarHealthRequest.java       |  2 +-
 .../sidecar/client/request/SnapshotRequest.java    |  2 +-
 .../sidecar/client/request/TimeSkewRequest.java    |  2 +-
 .../client/request/TokenRangeReplicasRequest.java  |  2 +-
 .../client/request/UpdateRestoreJobRequest.java    |  4 +-
 .../sidecar/client/request/JsonRequestTest.java}   | 36 ++++-----
 ...Test.java => JsonResponseBytesDecoderTest.java} | 29 +-------
 .../sidecar/client/SidecarClientTest.java          | 85 ++++++++++++++++------
 .../cassandra/sidecar/client/VertxHttpClient.java  | 31 +++++---
 26 files changed, 183 insertions(+), 148 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 43873923..76b571a8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 1.0.0
 -----
+ * Support sending json request (CASSANDRASC-118)
  * Add FilteringMetricRegistry to allow filtering of metrics (CASSANDRASC-115)
  * Allow for JmxClient to be extensible (CASSANDRASC-116)
  * Improve observability in Sidecar (CASSANDRASC-111)
diff --git a/client/build.gradle b/client/build.gradle
index 202a3a16..718b585e 100644
--- a/client/build.gradle
+++ b/client/build.gradle
@@ -86,6 +86,7 @@ dependencies {
     testFixturesImplementation("org.assertj:assertj-core:3.24.2")
     testFixturesImplementation('org.mockito:mockito-core:4.10.0')
     testFixturesImplementation('com.squareup.okhttp3:mockwebserver:4.10.0')
+    testFixturesImplementation(group: 'com.fasterxml.jackson.core', name: 
'jackson-databind', version: "${project.jacksonVersion}")
     testFixturesCompileOnly(group: 'io.netty', name: 'netty-codec-http', 
version: '4.1.69.Final')
 }
 
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/RequestExecutor.java 
b/client/src/main/java/org/apache/cassandra/sidecar/client/RequestExecutor.java
index 3480dca0..d80225f3 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/RequestExecutor.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/RequestExecutor.java
@@ -32,8 +32,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import io.netty.handler.codec.http.HttpResponseStatus;
-import org.apache.cassandra.sidecar.client.request.DecodableRequest;
 import org.apache.cassandra.sidecar.client.request.Request;
+import org.apache.cassandra.sidecar.client.request.ResponseBytesDecoder;
 
 import static java.util.Objects.requireNonNull;
 
@@ -344,10 +344,10 @@ public class RequestExecutor implements AutoCloseable
 
         try
         {
-            if (request instanceof DecodableRequest)
+            ResponseBytesDecoder<?> responseDecoder = 
request.responseBytesDecoder();
+            if (responseDecoder != null)
             {
-                DecodableRequest<T> decodableRequest = (DecodableRequest<T>) 
request;
-                future.complete(decodableRequest.decode(response.raw()));
+                future.complete((T) responseDecoder.decode(response.raw()));
             }
             else
             {
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/CassandraJmxHealthRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/CassandraJmxHealthRequest.java
index 1d5644a8..946e0a86 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/CassandraJmxHealthRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/CassandraJmxHealthRequest.java
@@ -25,7 +25,7 @@ import 
org.apache.cassandra.sidecar.common.data.HealthResponse;
 /**
  * Represents a request to retrieve the connectivity health checks performed 
against the Cassandra JMX protocol
  */
-public class CassandraJmxHealthRequest extends DecodableRequest<HealthResponse>
+public class CassandraJmxHealthRequest extends JsonRequest<HealthResponse>
 {
     /**
      * Constructs a request to retrieve the Cassandra JMX health
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/CassandraNativeHealthRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/CassandraNativeHealthRequest.java
index 35960c9d..83a9667a 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/CassandraNativeHealthRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/CassandraNativeHealthRequest.java
@@ -25,7 +25,7 @@ import 
org.apache.cassandra.sidecar.common.data.HealthResponse;
 /**
  * Represents a request to retrieve the connectivity health checks performed 
against the Cassandra native protocol
  */
-public class CassandraNativeHealthRequest extends 
DecodableRequest<HealthResponse>
+public class CassandraNativeHealthRequest extends JsonRequest<HealthResponse>
 {
     /**
      * Constructs a request to retrieve the Cassandra native health
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/CreateRestoreJobRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/CreateRestoreJobRequest.java
index b1203895..c569911a 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/CreateRestoreJobRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/CreateRestoreJobRequest.java
@@ -26,8 +26,7 @@ import 
org.apache.cassandra.sidecar.common.data.CreateRestoreJobResponsePayload;
 /**
  * Represents a request to create a restore job
  */
-public class CreateRestoreJobRequest extends 
DecodableRequest<CreateRestoreJobResponsePayload>
-implements JsonPayloadRequest
+public class CreateRestoreJobRequest extends 
JsonRequest<CreateRestoreJobResponsePayload>
 {
     private final CreateRestoreJobRequestPayload requestPayload;
 
@@ -51,7 +50,7 @@ implements JsonPayloadRequest
     }
 
     @Override
-    public Object json()
+    public Object requestBody()
     {
         return requestPayload;
     }
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/CreateRestoreJobSliceRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/CreateRestoreJobSliceRequest.java
index 7c40c658..38ede8b8 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/CreateRestoreJobSliceRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/CreateRestoreJobSliceRequest.java
@@ -27,7 +27,7 @@ import 
org.apache.cassandra.sidecar.common.data.CreateSliceRequestPayload;
 /**
  * Represents a request to create a restore job slice
  */
-public class CreateRestoreJobSliceRequest extends Request implements 
JsonPayloadRequest
+public class CreateRestoreJobSliceRequest extends Request
 {
     private final CreateSliceRequestPayload payload;
 
@@ -59,7 +59,7 @@ public class CreateRestoreJobSliceRequest extends Request 
implements JsonPayload
     }
 
     @Override
-    public Object json()
+    public Object requestBody()
     {
         return payload;
     }
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/GossipInfoRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/GossipInfoRequest.java
index 17d7cda7..7c739f43 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/GossipInfoRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/GossipInfoRequest.java
@@ -25,7 +25,7 @@ import 
org.apache.cassandra.sidecar.common.data.GossipInfoResponse;
 /**
  * Represents a request to retrieve the Cassandra gossip information
  */
-public class GossipInfoRequest extends DecodableRequest<GossipInfoResponse>
+public class GossipInfoRequest extends JsonRequest<GossipInfoResponse>
 {
     /**
      * Constructs a request to retrieve the Cassandra gossip information
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/ImportSSTableRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/ImportSSTableRequest.java
index 42e44957..09598994 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/ImportSSTableRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/ImportSSTableRequest.java
@@ -29,7 +29,7 @@ import 
org.apache.cassandra.sidecar.common.data.SSTableImportResponse;
 /**
  * Represents a request to import SSTable components previously uploaded for 
an upload identifier
  */
-public class ImportSSTableRequest extends 
DecodableRequest<SSTableImportResponse>
+public class ImportSSTableRequest extends JsonRequest<SSTableImportResponse>
 {
     /**
      * Constructs a decodable request with the provided {@code requestURI}
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/DecodableRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/JsonRequest.java
similarity index 52%
rename from 
client/src/main/java/org/apache/cassandra/sidecar/client/request/DecodableRequest.java
rename to 
client/src/main/java/org/apache/cassandra/sidecar/client/request/JsonRequest.java
index f9fec279..291adceb 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/DecodableRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/JsonRequest.java
@@ -18,53 +18,36 @@
 
 package org.apache.cassandra.sidecar.client.request;
 
-import java.io.IOException;
 import java.lang.reflect.ParameterizedType;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
 /**
- * A request that can decode to the type {@code <T>}
+ * Json request that returns json response
  *
- * @param <T> the type to decode
+ * @param <T> response type
  */
-public abstract class DecodableRequest<T> extends Request
+public abstract class JsonRequest<T> extends Request
 {
-    static final ObjectMapper MAPPER = new ObjectMapper()
-                                       // ignore all the properties that are 
not declared
-                                       
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
-    private final JavaType javaType = 
MAPPER.constructType(((ParameterizedType) 
this.getClass().getGenericSuperclass())
-                                                           
.getActualTypeArguments()[0]);
+    private final JsonResponseBytesDecoder<T> responseDecoder;
 
     /**
      * Constructs a decodable request with the provided {@code requestURI}
      *
      * @param requestURI the URI of the request
      */
-    protected DecodableRequest(String requestURI)
+    protected JsonRequest(String requestURI)
     {
         super(requestURI);
+        Class<T> type = (Class<T>) ((ParameterizedType) 
this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
+        this.responseDecoder = new JsonResponseBytesDecoder<>(type);
     }
 
-    /**
-     * Decodes the provided {@code bytes} to an instance of the type {@code 
<T>}
-     *
-     * @param bytes the raw bytes of the response
-     * @return the decoded instance for the given {@code bytes}
-     * @throws IOException when the decoder is unable to decode successfully
-     */
-    public T decode(byte[] bytes) throws IOException
+    @Override
+    public ResponseBytesDecoder<T> responseBytesDecoder()
     {
-        if (bytes == null)
-        {
-            return null;
-        }
-        return MAPPER.readValue(bytes, javaType);
+        return responseDecoder;
     }
 
     @Override
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/CassandraJmxHealthRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/JsonResponseBytesDecoder.java
similarity index 52%
copy from 
client/src/main/java/org/apache/cassandra/sidecar/client/request/CassandraJmxHealthRequest.java
copy to 
client/src/main/java/org/apache/cassandra/sidecar/client/request/JsonResponseBytesDecoder.java
index 1d5644a8..6bf14c0a 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/CassandraJmxHealthRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/JsonResponseBytesDecoder.java
@@ -18,29 +18,34 @@
 
 package org.apache.cassandra.sidecar.client.request;
 
-import io.netty.handler.codec.http.HttpMethod;
-import org.apache.cassandra.sidecar.common.ApiEndpointsV1;
-import org.apache.cassandra.sidecar.common.data.HealthResponse;
+import java.io.IOException;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
 
 /**
- * Represents a request to retrieve the connectivity health checks performed 
against the Cassandra JMX protocol
+ * Decoder for json response body bytes
+ * @param <T> expected java type
  */
-public class CassandraJmxHealthRequest extends DecodableRequest<HealthResponse>
+public class JsonResponseBytesDecoder<T> implements ResponseBytesDecoder<T>
 {
-    /**
-     * Constructs a request to retrieve the Cassandra JMX health
-     */
-    public CassandraJmxHealthRequest()
+    private static final ObjectMapper MAPPER = new ObjectMapper()
+                                       // ignore all the properties that are 
not declared
+                                       
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+    private final Class<T> type;
+
+    public JsonResponseBytesDecoder(Class<T> type)
     {
-        super(ApiEndpointsV1.CASSANDRA_JMX_HEALTH_ROUTE);
+        this.type = type;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public HttpMethod method()
+    public T decode(byte[] bytes) throws IOException
     {
-        return HttpMethod.GET;
+        if (bytes == null)
+        {
+            return null;
+        }
+        return MAPPER.readValue(bytes, type);
     }
 }
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/NodeSettingsRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/NodeSettingsRequest.java
index 0a274800..c9914673 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/NodeSettingsRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/NodeSettingsRequest.java
@@ -25,7 +25,7 @@ import org.apache.cassandra.sidecar.common.NodeSettings;
 /**
  * Represents a request to retrieve the node settings
  */
-public class NodeSettingsRequest extends DecodableRequest<NodeSettings>
+public class NodeSettingsRequest extends JsonRequest<NodeSettings>
 {
     /**
      * Constructs a request to retrieve the node settings
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/Request.java 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/Request.java
index c7cb480f..51ee5626 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/Request.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/Request.java
@@ -79,6 +79,22 @@ public abstract class Request
         return requestURI;
     }
 
+    /**
+     * @return the request body. Returns null, when there is no request body.
+     */
+    public Object requestBody()
+    {
+        return null;
+    }
+
+    /**
+     * @return the response bytes decoder if configured, or null
+     */
+    public ResponseBytesDecoder<?> responseBytesDecoder()
+    {
+        return null;
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/JsonPayloadRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/ResponseBytesDecoder.java
similarity index 63%
rename from 
client/src/main/java/org/apache/cassandra/sidecar/client/request/JsonPayloadRequest.java
rename to 
client/src/main/java/org/apache/cassandra/sidecar/client/request/ResponseBytesDecoder.java
index 5a6086ca..b5a4ae19 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/JsonPayloadRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/ResponseBytesDecoder.java
@@ -18,13 +18,20 @@
 
 package org.apache.cassandra.sidecar.client.request;
 
+import java.io.IOException;
+
 /**
- * A request that contains json payload
+ * Decode response bytes into given java type {@code <T>}
+ * @param <T> expected java type
  */
-public interface JsonPayloadRequest
+public interface ResponseBytesDecoder<T>
 {
     /**
-     * @return the JSON payload for the request
+     * Decodes the provided {@code bytes} to an instance of the type {@code 
<T>}
+     *
+     * @param bytes the raw bytes of the response
+     * @return the decoded instance for the given {@code bytes}, or null for 
null input
+     * @throws IOException when the decoder is unable to decode successfully
      */
-    Object json();
+    T decode(byte[] bytes) throws IOException;
 }
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/RestoreJobSummaryRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/RestoreJobSummaryRequest.java
index bd358a62..9126d66d 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/RestoreJobSummaryRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/RestoreJobSummaryRequest.java
@@ -27,7 +27,7 @@ import 
org.apache.cassandra.sidecar.common.data.RestoreJobSummaryResponsePayload
 /**
  * Represents a request to retrieve the summary of a restore job
  */
-public class RestoreJobSummaryRequest extends 
DecodableRequest<RestoreJobSummaryResponsePayload>
+public class RestoreJobSummaryRequest extends 
JsonRequest<RestoreJobSummaryResponsePayload>
 {
     /**
      * Constructs a Sidecar request with the given {@code requestURI}. 
Defaults to {@code ssl} enabled.
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/RingRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/RingRequest.java
index 5c835ef2..1f02a338 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/RingRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/RingRequest.java
@@ -25,7 +25,7 @@ import org.apache.cassandra.sidecar.common.data.RingResponse;
 /**
  * Represents a request to retrieve the Cassandra ring information
  */
-public class RingRequest extends DecodableRequest<RingResponse>
+public class RingRequest extends JsonRequest<RingResponse>
 {
     /**
      * Constructs a request to retrieve the Cassandra ring information
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/SchemaRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/SchemaRequest.java
index 09413020..625707bd 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/SchemaRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/SchemaRequest.java
@@ -25,7 +25,7 @@ import 
org.apache.cassandra.sidecar.common.data.SchemaResponse;
 /**
  * Represents a request to retrieve the schema
  */
-public class SchemaRequest extends DecodableRequest<SchemaResponse>
+public class SchemaRequest extends JsonRequest<SchemaResponse>
 {
     /**
      * Constructs a request to retrieve the full Cassandra schema
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/SidecarHealthRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/SidecarHealthRequest.java
index 49aa139f..e6c1a2e1 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/SidecarHealthRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/SidecarHealthRequest.java
@@ -25,7 +25,7 @@ import 
org.apache.cassandra.sidecar.common.data.HealthResponse;
 /**
  * Represents a request to retrieve the Sidecar health
  */
-public class SidecarHealthRequest extends DecodableRequest<HealthResponse>
+public class SidecarHealthRequest extends JsonRequest<HealthResponse>
 {
     /**
      * Constructs a request to retrieve the Sidecar health
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/SnapshotRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/SnapshotRequest.java
index c3cdc678..485ff543 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/SnapshotRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/SnapshotRequest.java
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.Nullable;
  *
  * @param <T> the type of the decodable request
  */
-abstract class SnapshotRequest<T> extends DecodableRequest<T>
+abstract class SnapshotRequest<T> extends JsonRequest<T>
 {
     SnapshotRequest(String keyspace, String table, String snapshotName)
     {
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/TimeSkewRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/TimeSkewRequest.java
index 586c8456..99df1914 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/TimeSkewRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/TimeSkewRequest.java
@@ -25,7 +25,7 @@ import 
org.apache.cassandra.sidecar.common.data.TimeSkewResponse;
 /**
  * Represents a request to retrieve information from the time skew endpoint
  */
-public class TimeSkewRequest extends DecodableRequest<TimeSkewResponse>
+public class TimeSkewRequest extends JsonRequest<TimeSkewResponse>
 {
     /**
      * Constructs a new request to retrieve information from the time skew 
endpoint
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/TokenRangeReplicasRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/TokenRangeReplicasRequest.java
index cc7f8034..143a3960 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/TokenRangeReplicasRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/TokenRangeReplicasRequest.java
@@ -25,7 +25,7 @@ import 
org.apache.cassandra.sidecar.common.data.TokenRangeReplicasResponse;
 /**
  * Represents a request to retrieve information from the token-range replicas 
endpoint
  */
-public class TokenRangeReplicasRequest extends 
DecodableRequest<TokenRangeReplicasResponse>
+public class TokenRangeReplicasRequest extends 
JsonRequest<TokenRangeReplicasResponse>
 {
     /**
      * Constructs a new request to retrieve information by keyspace from 
token-range replicas endpoint
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/UpdateRestoreJobRequest.java
 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/UpdateRestoreJobRequest.java
index eba2a0e5..919689ab 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/UpdateRestoreJobRequest.java
+++ 
b/client/src/main/java/org/apache/cassandra/sidecar/client/request/UpdateRestoreJobRequest.java
@@ -27,7 +27,7 @@ import 
org.apache.cassandra.sidecar.common.data.UpdateRestoreJobRequestPayload;
 /**
  * Represents a request to update a restore job
  */
-public class UpdateRestoreJobRequest extends Request implements 
JsonPayloadRequest
+public class UpdateRestoreJobRequest extends Request
 {
     private final UpdateRestoreJobRequestPayload requestPayload;
 
@@ -53,7 +53,7 @@ public class UpdateRestoreJobRequest extends Request 
implements JsonPayloadReque
     }
 
     @Override
-    public Object json()
+    public Object requestBody()
     {
         return requestPayload;
     }
diff --git 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/NodeSettingsRequest.java
 
b/client/src/test/java/org/apache/cassandra/sidecar/client/request/JsonRequestTest.java
similarity index 62%
copy from 
client/src/main/java/org/apache/cassandra/sidecar/client/request/NodeSettingsRequest.java
copy to 
client/src/test/java/org/apache/cassandra/sidecar/client/request/JsonRequestTest.java
index 0a274800..c756807c 100644
--- 
a/client/src/main/java/org/apache/cassandra/sidecar/client/request/NodeSettingsRequest.java
+++ 
b/client/src/test/java/org/apache/cassandra/sidecar/client/request/JsonRequestTest.java
@@ -18,29 +18,29 @@
 
 package org.apache.cassandra.sidecar.client.request;
 
+
+import org.junit.jupiter.api.Test;
+
 import io.netty.handler.codec.http.HttpMethod;
-import org.apache.cassandra.sidecar.common.ApiEndpointsV1;
 import org.apache.cassandra.sidecar.common.NodeSettings;
 
-/**
- * Represents a request to retrieve the node settings
- */
-public class NodeSettingsRequest extends DecodableRequest<NodeSettings>
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+class JsonRequestTest
 {
-    /**
-     * Constructs a request to retrieve the node settings
-     */
-    public NodeSettingsRequest()
+    @Test
+    void testHeadersAreImmutable()
     {
-        super(ApiEndpointsV1.NODE_SETTINGS_ROUTE);
-    }
+        JsonRequest<NodeSettings> instance = new 
JsonRequest<NodeSettings>("https://cassandra-sidecar.com/api/test";)
+        {
+            @Override
+            public HttpMethod method()
+            {
+                return HttpMethod.GET;
+            }
+        };
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public HttpMethod method()
-    {
-        return HttpMethod.GET;
+        assertThatExceptionOfType(UnsupportedOperationException.class)
+        .isThrownBy(() -> instance.headers().put("not", "allowed"));
     }
 }
diff --git 
a/client/src/test/java/org/apache/cassandra/sidecar/client/request/DecodableRequestTest.java
 
b/client/src/test/java/org/apache/cassandra/sidecar/client/request/JsonResponseBytesDecoderTest.java
similarity index 72%
rename from 
client/src/test/java/org/apache/cassandra/sidecar/client/request/DecodableRequestTest.java
rename to 
client/src/test/java/org/apache/cassandra/sidecar/client/request/JsonResponseBytesDecoderTest.java
index e37dc9ab..cb9e2e1e 100644
--- 
a/client/src/test/java/org/apache/cassandra/sidecar/client/request/DecodableRequestTest.java
+++ 
b/client/src/test/java/org/apache/cassandra/sidecar/client/request/JsonResponseBytesDecoderTest.java
@@ -21,34 +21,18 @@ package org.apache.cassandra.sidecar.client.request;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import io.netty.handler.codec.http.HttpMethod;
 import org.apache.cassandra.sidecar.common.NodeSettings;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 
 /**
- * Unit tests for the {@link DecodableRequest} class
+ * Unit tests for the {@link JsonRequest} class
  */
-class DecodableRequestTest
+class JsonResponseBytesDecoderTest
 {
-    DecodableRequest<NodeSettings> instance;
-
-    @BeforeEach
-    void setup()
-    {
-        instance = new 
DecodableRequest<NodeSettings>("https://cassandra-sidecar.com/api/test";)
-        {
-            @Override
-            public HttpMethod method()
-            {
-                return HttpMethod.GET;
-            }
-        };
-    }
+    JsonResponseBytesDecoder<NodeSettings> instance = new 
JsonResponseBytesDecoder<>(NodeSettings.class);
 
     @Test
     void testDecode() throws IOException
@@ -71,11 +55,4 @@ class DecodableRequestTest
         assertThat(nodeSettings.partitioner()).isEqualTo("partitioner-value");
         assertThat(nodeSettings.releaseVersion()).isEqualTo("1.0-TEST");
     }
-
-    @Test
-    void testHeadersAreImmutable()
-    {
-        assertThatExceptionOfType(UnsupportedOperationException.class)
-        .isThrownBy(() -> instance.headers().put("not", "allowed"));
-    }
 }
diff --git 
a/client/src/testFixtures/java/org/apache/cassandra/sidecar/client/SidecarClientTest.java
 
b/client/src/testFixtures/java/org/apache/cassandra/sidecar/client/SidecarClientTest.java
index e06b7091..1854e7aa 100644
--- 
a/client/src/testFixtures/java/org/apache/cassandra/sidecar/client/SidecarClientTest.java
+++ 
b/client/src/testFixtures/java/org/apache/cassandra/sidecar/client/SidecarClientTest.java
@@ -21,6 +21,7 @@ package org.apache.cassandra.sidecar.client;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -34,6 +35,7 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import org.junit.jupiter.api.AfterEach;
@@ -41,6 +43,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import io.netty.handler.codec.http.HttpHeaderNames;
 import io.netty.handler.codec.http.HttpHeaderValues;
 import okhttp3.mockwebserver.MockResponse;
@@ -80,6 +83,9 @@ import static 
io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
 import static 
io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
 import static io.netty.handler.codec.http.HttpResponseStatus.OK;
 import static io.netty.handler.codec.http.HttpResponseStatus.PARTIAL_CONTENT;
+import static 
org.apache.cassandra.sidecar.common.ApiEndpointsV1.JOB_ID_PATH_PARAM;
+import static 
org.apache.cassandra.sidecar.common.ApiEndpointsV1.KEYSPACE_PATH_PARAM;
+import static 
org.apache.cassandra.sidecar.common.ApiEndpointsV1.TABLE_PATH_PARAM;
 import static 
org.apache.cassandra.sidecar.common.http.SidecarHttpHeaderNames.CONTENT_XXHASH32;
 import static 
org.apache.cassandra.sidecar.common.http.SidecarHttpHeaderNames.CONTENT_XXHASH32_SEED;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -283,7 +289,7 @@ abstract class SidecarClientTest
         assertThat(result.keyspace()).isEqualTo("cycling");
         assertThat(result.schema()).isEqualTo("CREATE KEYSPACE 
sample_ks.sample_table ...");
 
-        
validateResponseServed(ApiEndpointsV1.KEYSPACE_SCHEMA_ROUTE.replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM,
+        
validateResponseServed(ApiEndpointsV1.KEYSPACE_SCHEMA_ROUTE.replaceAll(KEYSPACE_PATH_PARAM,
                                                                                
"cycling"));
     }
 
@@ -312,7 +318,7 @@ abstract class SidecarClientTest
         assertThat(entry.fqdn()).isEqualTo("local");
         assertThat(entry.hostId()).isEqualTo("000");
 
-        
validateResponseServed(ApiEndpointsV1.RING_ROUTE_PER_KEYSPACE.replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM,
+        
validateResponseServed(ApiEndpointsV1.RING_ROUTE_PER_KEYSPACE.replaceAll(KEYSPACE_PATH_PARAM,
                                                                                
  "cycling"));
     }
 
@@ -450,7 +456,7 @@ abstract class SidecarClientTest
         assertThat(instanceMetadata.datacenter()).isEqualTo("datacenter1");
 
         
validateResponseServed(ApiEndpointsV1.KEYSPACE_TOKEN_MAPPING_ROUTE.replaceAll(
-        ApiEndpointsV1.KEYSPACE_PATH_PARAM, keyspace));
+        KEYSPACE_PATH_PARAM, keyspace));
     }
 
     @Test
@@ -484,7 +490,7 @@ abstract class SidecarClientTest
         assertThat(mockWebServer.getRequestCount()).isEqualTo(1);
         RecordedRequest request = mockWebServer.takeRequest();
         assertThat(request.getPath()).isEqualTo(ApiEndpointsV1.SNAPSHOTS_ROUTE
-                                                
.replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, "cycling")
+                                                
.replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                                                 
.replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, "cyclist_name")
                                                 
.replaceAll(ApiEndpointsV1.SNAPSHOT_PATH_PARAM, "2023.04.11")
                                                 + 
"?includeSecondaryIndexFiles=true");
@@ -522,7 +528,7 @@ abstract class SidecarClientTest
         assertThat(mockWebServer.getRequestCount()).isEqualTo(1);
         RecordedRequest request = mockWebServer.takeRequest();
         assertThat(request.getPath()).isEqualTo(ApiEndpointsV1.SNAPSHOTS_ROUTE
-                                                
.replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, "cycling")
+                                                
.replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                                                 
.replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, "cyclist_name")
                                                 
.replaceAll(ApiEndpointsV1.SNAPSHOT_PATH_PARAM, "2023.04.11"));
     }
@@ -542,7 +548,7 @@ abstract class SidecarClientTest
         assertThat(mockWebServer.getRequestCount()).isEqualTo(1);
         RecordedRequest request = mockWebServer.takeRequest();
         assertThat(request.getPath()).isEqualTo(ApiEndpointsV1.SNAPSHOTS_ROUTE
-                                                
.replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, "cycling")
+                                                
.replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                                                 
.replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, "cyclist_name")
                                                 
.replaceAll(ApiEndpointsV1.SNAPSHOT_PATH_PARAM, "2023.04.11"));
         assertThat(request.getMethod()).isEqualTo("DELETE");
@@ -562,7 +568,7 @@ abstract class SidecarClientTest
         assertThat(mockWebServer.getRequestCount()).isEqualTo(1);
         RecordedRequest request = mockWebServer.takeRequest();
         assertThat(request.getPath()).isEqualTo(ApiEndpointsV1.SNAPSHOTS_ROUTE
-                                                
.replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, "cycling")
+                                                
.replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                                                 
.replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, "cyclist_name")
                                                 
.replaceAll(ApiEndpointsV1.SNAPSHOT_PATH_PARAM, "2023.04.11"));
         assertThat(request.getMethod()).isEqualTo("PUT");
@@ -582,7 +588,7 @@ abstract class SidecarClientTest
         assertThat(mockWebServer.getRequestCount()).isEqualTo(1);
         RecordedRequest request = mockWebServer.takeRequest();
         String expected = ApiEndpointsV1.SNAPSHOTS_ROUTE
-                          .replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, 
"cycling")
+                          .replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                           .replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, 
"cyclist_name")
                           .replaceAll(ApiEndpointsV1.SNAPSHOT_PATH_PARAM, 
"2023.04.11") + "?ttl=2d";
         assertThat(request.getPath()).isEqualTo(expected);
@@ -634,7 +640,7 @@ abstract class SidecarClientTest
         assertThat(mockWebServer.getRequestCount()).isEqualTo(1);
         RecordedRequest request = mockWebServer.takeRequest();
         
assertThat(request.getPath()).isEqualTo(ApiEndpointsV1.SSTABLE_IMPORT_ROUTE
-                                                
.replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, "cycling")
+                                                
.replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                                                 
.replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, "cyclist_name")
                                                 
.replaceAll(ApiEndpointsV1.UPLOAD_ID_PATH_PARAM, "0000-0000"));
         assertThat(request.getMethod()).isEqualTo("PUT");
@@ -670,7 +676,7 @@ abstract class SidecarClientTest
         assertThat(mockWebServer.getRequestCount()).isEqualTo(5);
         RecordedRequest request = mockWebServer.takeRequest();
         
assertThat(request.getPath()).isEqualTo(ApiEndpointsV1.SSTABLE_IMPORT_ROUTE
-                                                
.replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, "cycling")
+                                                
.replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                                                 
.replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, "cyclist_name")
                                                 
.replaceAll(ApiEndpointsV1.UPLOAD_ID_PATH_PARAM, "0000-0000"));
         assertThat(request.getMethod()).isEqualTo("PUT");
@@ -715,7 +721,7 @@ abstract class SidecarClientTest
             assertThat(request.getPath())
             .isEqualTo(ApiEndpointsV1.SSTABLE_UPLOAD_ROUTE
                        .replaceAll(ApiEndpointsV1.UPLOAD_ID_PATH_PARAM, 
"0000-0000")
-                       .replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, 
"cycling")
+                       .replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                        .replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, 
"cyclist_name")
                        .replaceAll(ApiEndpointsV1.COMPONENT_PATH_PARAM, 
"nb-1-big-TOC.txt"));
             assertThat(request.getMethod()).isEqualTo("PUT");
@@ -748,7 +754,7 @@ abstract class SidecarClientTest
             assertThat(request.getPath())
             .isEqualTo(ApiEndpointsV1.SSTABLE_UPLOAD_ROUTE
                        .replaceAll(ApiEndpointsV1.UPLOAD_ID_PATH_PARAM, 
"0000-0000")
-                       .replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, 
"cycling")
+                       .replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                        .replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, 
"cyclist_name")
                        .replaceAll(ApiEndpointsV1.COMPONENT_PATH_PARAM, 
"nb-1-big-TOC.txt"));
             assertThat(request.getMethod()).isEqualTo("PUT");
@@ -782,7 +788,7 @@ abstract class SidecarClientTest
             assertThat(request.getPath())
             .isEqualTo(ApiEndpointsV1.SSTABLE_UPLOAD_ROUTE
                        .replaceAll(ApiEndpointsV1.UPLOAD_ID_PATH_PARAM, 
"0000-0000")
-                       .replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, 
"cycling")
+                       .replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                        .replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, 
"cyclist_name")
                        .replaceAll(ApiEndpointsV1.COMPONENT_PATH_PARAM, 
"nb-1-big-TOC.txt"));
             assertThat(request.getMethod()).isEqualTo("PUT");
@@ -817,7 +823,7 @@ abstract class SidecarClientTest
             assertThat(request.getPath())
             .isEqualTo(ApiEndpointsV1.SSTABLE_UPLOAD_ROUTE
                        .replaceAll(ApiEndpointsV1.UPLOAD_ID_PATH_PARAM, 
"0000-0000")
-                       .replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, 
"cycling")
+                       .replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                        .replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, 
"cyclist_name")
                        .replaceAll(ApiEndpointsV1.COMPONENT_PATH_PARAM, 
"nb-1-big-TOC.txt"));
             assertThat(request.getMethod()).isEqualTo("PUT");
@@ -895,7 +901,7 @@ abstract class SidecarClientTest
             RecordedRequest request = server.takeRequest();
             assertThat(request.getPath())
             .isEqualTo(ApiEndpointsV1.COMPONENTS_ROUTE
-                       .replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, 
"cycling")
+                       .replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                        .replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, 
"cyclist_name")
                        .replaceAll(ApiEndpointsV1.SNAPSHOT_PATH_PARAM, 
"2023.04.12")
                        .replaceAll(ApiEndpointsV1.COMPONENT_PATH_PARAM, 
"nb-203-big-Data.db"));
@@ -974,7 +980,7 @@ abstract class SidecarClientTest
             RecordedRequest request = server.takeRequest();
             assertThat(request.getPath())
             .isEqualTo(ApiEndpointsV1.COMPONENTS_ROUTE
-                       .replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, 
"cycling")
+                       .replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                        .replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, 
"cyclist_name")
                        .replaceAll(ApiEndpointsV1.SNAPSHOT_PATH_PARAM, 
"2023.04.12")
                        .replaceAll(ApiEndpointsV1.COMPONENT_PATH_PARAM, 
"nb-203-big-Data.db"));
@@ -1057,7 +1063,7 @@ abstract class SidecarClientTest
             RecordedRequest request = server.takeRequest();
             assertThat(request.getPath())
             .isEqualTo(ApiEndpointsV1.COMPONENTS_ROUTE
-                       .replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, 
"cycling")
+                       .replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                        .replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, 
"cyclist_name")
                        .replaceAll(ApiEndpointsV1.SNAPSHOT_PATH_PARAM, 
"2023.04.12")
                        .replaceAll(ApiEndpointsV1.COMPONENT_PATH_PARAM, 
"nb-203-big-Data.db"));
@@ -1126,7 +1132,7 @@ abstract class SidecarClientTest
             RecordedRequest request3 = server.takeRequest();
             assertThat(request3.getPath())
             .isEqualTo(ApiEndpointsV1.COMPONENTS_ROUTE
-                       .replaceAll(ApiEndpointsV1.KEYSPACE_PATH_PARAM, 
"cycling")
+                       .replaceAll(KEYSPACE_PATH_PARAM, "cycling")
                        .replaceAll(ApiEndpointsV1.TABLE_PATH_PARAM, 
"cyclist_name")
                        .replaceAll(ApiEndpointsV1.SNAPSHOT_PATH_PARAM, 
"2023.04.12")
                        .replaceAll(ApiEndpointsV1.COMPONENT_PATH_PARAM, 
"nb-203-big-Data.db"));
@@ -1206,13 +1212,14 @@ abstract class SidecarClientTest
     }
 
     @Test
-    void testAcceptCreateRestoreJobRequest()
+    void testAcceptCreateRestoreJobRequest() throws Exception
     {
+        String jobIdStr = "8e5799a4-d277-11ed-8d85-6916bb9b8056";
         enqueue(new MockResponse()
                 .setResponseCode(OK.code())
-                
.setBody("{\"jobId\":\"8e5799a4-d277-11ed-8d85-6916bb9b8056\",\"status\":\"CREATED\"}"));
+                .setBody("{\"jobId\":\"" + jobIdStr + 
"\",\"status\":\"CREATED\"}"));
 
-        UUID jobId = UUID.fromString("8e5799a4-d277-11ed-8d85-6916bb9b8056");
+        UUID jobId = UUID.fromString(jobIdStr);
         long expireAt = System.currentTimeMillis() + 10000;
         RestoreJobSecrets secrets = 
RestoreJobSecretsGen.genRestoreJobSecrets();
         CreateRestoreJobRequestPayload requestPayload = 
CreateRestoreJobRequestPayload.builder(secrets, expireAt)
@@ -1226,17 +1233,30 @@ abstract class SidecarClientTest
         assertThat(responsePayload).isNotNull();
         assertThat(responsePayload.jobId()).isEqualTo(jobId);
         assertThat(responsePayload.status()).isEqualTo("CREATED");
+
+        ObjectMapper mapper = new ObjectMapper();
+        String expectedReqBodyString = 
mapper.writeValueAsString(requestPayload);
+        validateResponseServed(ApiEndpointsV1.CREATE_RESTORE_JOB_ROUTE
+                               .replaceAll(KEYSPACE_PATH_PARAM, "cycling")
+                               .replaceAll(TABLE_PATH_PARAM, 
"rank_by_year_and_name")
+                               .replaceAll(JOB_ID_PATH_PARAM, jobIdStr),
+                               recordedRequest -> {
+                                   String reqBodyString = 
recordedRequest.getBody()
+                                                                         
.readString(Charset.defaultCharset());
+                                   
assertThat(reqBodyString).isEqualTo(expectedReqBodyString);
+                               });
     }
 
     @Test
-    void testCreateRestoreJobShouldNotRetryOnDifferentHostWithBadRequest()
+    void testCreateRestoreJobShouldNotRetryOnDifferentHostWithBadRequest() 
throws Exception
     {
+        String jobIdStr = "8e5799a4-d277-11ed-8d85-6916bb9b8056";
         enqueue(new MockResponse()
                 .setResponseCode(BAD_REQUEST.code())
                 .setBody("{\"status\":\"Fail\"," +
                          "\"message\":\"Error while decoding values, check 
your request body\"}"));
 
-        UUID jobId = UUID.fromString("8e5799a4-d277-11ed-8d85-6916bb9b8056");
+        UUID jobId = UUID.fromString(jobIdStr);
         long expireAt = System.currentTimeMillis() + 10000;
         RestoreJobSecrets secrets = 
RestoreJobSecretsGen.genRestoreJobSecrets();
         CreateRestoreJobRequestPayload requestPayload = 
CreateRestoreJobRequestPayload.builder(secrets, expireAt)
@@ -1249,6 +1269,18 @@ abstract class SidecarClientTest
                              
.withCauseInstanceOf(RetriesExhaustedException.class)
                              .withMessageContaining("Unable to complete 
request '/api/v1/keyspaces/" +
                                                     
"badkeyspace/tables/bad_table/restore-jobs' after 1 attempt");
+
+        ObjectMapper mapper = new ObjectMapper();
+        String expectedReqBodyString = 
mapper.writeValueAsString(requestPayload);
+        validateResponseServed(ApiEndpointsV1.CREATE_RESTORE_JOB_ROUTE
+                               .replaceAll(KEYSPACE_PATH_PARAM, "badkeyspace")
+                               .replaceAll(TABLE_PATH_PARAM, "bad_table")
+                               .replaceAll(JOB_ID_PATH_PARAM, jobIdStr),
+                               recordedRequest -> {
+                                   String reqBodyString = 
recordedRequest.getBody()
+                                                                         
.readString(Charset.defaultCharset());
+                                   
assertThat(reqBodyString).isEqualTo(expectedReqBodyString);
+                               });
     }
 
     private void enqueue(MockResponse response)
@@ -1260,6 +1292,12 @@ abstract class SidecarClientTest
     }
 
     private void validateResponseServed(String expectedEndpointPath) throws 
InterruptedException
+    {
+        validateResponseServed(expectedEndpointPath, req -> { });
+    }
+
+    private void validateResponseServed(String expectedEndpointPath,
+                                        Consumer<RecordedRequest> 
serverReceivedRequestVerifier) throws InterruptedException
     {
         for (MockWebServer server : servers)
         {
@@ -1267,6 +1305,7 @@ abstract class SidecarClientTest
             {
                 assertThat(server.getRequestCount()).isEqualTo(1);
                 RecordedRequest request = server.takeRequest();
+                serverReceivedRequestVerifier.accept(request);
                 assertThat(request.getPath()).isEqualTo(expectedEndpointPath);
                 return;
             }
diff --git 
a/vertx-client/src/main/java/org/apache/cassandra/sidecar/client/VertxHttpClient.java
 
b/vertx-client/src/main/java/org/apache/cassandra/sidecar/client/VertxHttpClient.java
index 02a07c38..ecc6ebd2 100644
--- 
a/vertx-client/src/main/java/org/apache/cassandra/sidecar/client/VertxHttpClient.java
+++ 
b/vertx-client/src/main/java/org/apache/cassandra/sidecar/client/VertxHttpClient.java
@@ -130,22 +130,29 @@ public class VertxHttpClient implements HttpClient
 
     protected CompletableFuture<HttpResponse> executeInternal(SidecarInstance 
sidecarInstance, RequestContext context)
     {
-        HttpRequest<Buffer> vertxRequest = vertxRequest(sidecarInstance, 
context);
-        Promise<HttpResponse> promise = Promise.promise();
-        vertxRequest.ssl(config.ssl())
-                    .timeout(config.timeoutMillis())
-                    .send()
-                    .onFailure(promise::fail)
-                    .onSuccess(response -> {
-                        byte[] raw = response.body() != null ? 
response.body().getBytes() : null;
-                        promise.complete(new 
HttpResponseImpl(response.statusCode(),
+        Future<HttpRequest<Buffer>> future = Future.future(promise -> 
promise.complete(vertxRequest(sidecarInstance, context)
+                                                                               
        .ssl(config.ssl())
+                                                                               
        .timeout(config.timeoutMillis())));
+
+        return future
+               .compose(vertxRequest -> {
+                   Request request = context.request();
+                   if (request.requestBody() != null)
+                   {
+                       return vertxRequest.sendJson(request.requestBody());
+                   }
+                   return vertxRequest.send();
+               })
+               .map(response -> {
+                   byte[] raw = response.body() != null ? 
response.body().getBytes() : null;
+                   return (HttpResponse) new 
HttpResponseImpl(response.statusCode(),
                                                               
response.statusMessage(),
                                                               raw,
                                                               
mapHeaders(response.headers()),
                                                               sidecarInstance
-                        ));
-                    });
-        return promise.future().toCompletionStage().toCompletableFuture();
+                   );
+               })
+               .toCompletionStage().toCompletableFuture();
     }
 
     protected CompletableFuture<HttpResponse> 
executeUploadFileInternal(SidecarInstance sidecarInstance,


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to