Skip to content

Commit a4346a7

Browse files
authored
fix(clients): make POST body related to the endpoint, assert body in tests (#849)
1 parent 3b0ddfc commit a4346a7

File tree

19 files changed

+142
-150
lines changed

19 files changed

+142
-150
lines changed

clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/ApiClient.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,17 @@ public RequestBody serialize(Object obj) throws AlgoliaRuntimeException {
239239

240240
if (obj != null) {
241241
try {
242-
content = json.writeValueAsString(obj);
242+
if (obj.getClass().getName().equals("java.lang.Object")) {
243+
content = "{}";
244+
} else {
245+
content = json.writeValueAsString(obj);
246+
}
243247
} catch (JsonProcessingException e) {
244248
throw new AlgoliaRuntimeException(e);
245249
}
246250
} else {
247-
content = null;
251+
// We can't send a null body with okhttp, so we default it to an empty string
252+
content = "";
248253
}
249254

250255
return RequestBody.create(content, MediaType.parse(this.contentType));
@@ -343,16 +348,10 @@ public Request buildRequest(
343348
processHeaderParams(headerParams, hasRequestOptions ? requestOptions.getExtraHeaders() : null, reqBuilder);
344349

345350
RequestBody reqBody;
346-
if (!HttpMethod.permitsRequestBody(method)) {
351+
// We rely on `permitsRequestBody` to tell us if we should provide a body
352+
// but also set it for DELETE methods
353+
if (!HttpMethod.permitsRequestBody(method) || (method.equals("DELETE") && body == null)) {
347354
reqBody = null;
348-
} else if (body == null) {
349-
if ("DELETE".equals(method)) {
350-
// allow calling DELETE without sending a request body
351-
reqBody = null;
352-
} else {
353-
// use an empty request body (for POST, PUT and PATCH)
354-
reqBody = RequestBody.create("", MediaType.parse(this.contentType));
355-
}
356355
} else {
357356
reqBody = serialize(body);
358357
}

clients/algoliasearch-client-javascript/bundlesize.config.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
"files": [
33
{
44
"path": "packages/algoliasearch/dist/algoliasearch.umd.js",
5-
"maxSize": "8.35KB"
5+
"maxSize": "8.30KB"
66
},
77
{
88
"path": "packages/algoliasearch/dist/lite/lite.umd.js",
9-
"maxSize": "3.90KB"
9+
"maxSize": "3.85KB"
1010
},
1111
{
1212
"path": "packages/client-abtesting/dist/client-abtesting.umd.js",
@@ -18,15 +18,15 @@
1818
},
1919
{
2020
"path": "packages/client-insights/dist/client-insights.umd.js",
21-
"maxSize": "3.85KB"
21+
"maxSize": "3.80KB"
2222
},
2323
{
2424
"path": "packages/client-personalization/dist/client-personalization.umd.js",
25-
"maxSize": "4.00KB"
25+
"maxSize": "3.95KB"
2626
},
2727
{
2828
"path": "packages/client-query-suggestions/dist/client-query-suggestions.umd.js",
29-
"maxSize": "4.05KB"
29+
"maxSize": "4.00KB"
3030
},
3131
{
3232
"path": "packages/client-search/dist/client-search.umd.js",
@@ -38,7 +38,7 @@
3838
},
3939
{
4040
"path": "packages/predict/dist/predict.umd.js",
41-
"maxSize": "3.90KB"
41+
"maxSize": "3.85KB"
4242
},
4343
{
4444
"path": "packages/recommend/dist/recommend.umd.js",
Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { EchoResponse, EndRequest, Request, Response } from './types';
1+
import type { EchoResponse, EndRequest, Requester, Response } from './types';
22

33
export type EchoRequesterParams = {
44
getURL: (url: string) => URL;
@@ -8,7 +8,8 @@ export type EchoRequesterParams = {
88
function getUrlParams({
99
host,
1010
searchParams: urlSearchParams,
11-
}: URL): Pick<EchoResponse, 'algoliaAgent' | 'host' | 'searchParams'> {
11+
pathname,
12+
}: URL): Pick<EchoResponse, 'algoliaAgent' | 'host' | 'path' | 'searchParams'> {
1213
const algoliaAgent = urlSearchParams.get('x-algolia-agent') || '';
1314
const searchParams = {};
1415

@@ -25,39 +26,34 @@ function getUrlParams({
2526
algoliaAgent,
2627
searchParams:
2728
Object.keys(searchParams).length === 0 ? undefined : searchParams,
29+
path: pathname,
2830
};
2931
}
3032

31-
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
3233
export function createEchoRequester({
3334
getURL,
3435
status = 200,
35-
}: EchoRequesterParams) {
36-
function send(
37-
{ headers, url, connectTimeout, responseTimeout }: EndRequest,
38-
{ data, ...originalRequest }: Request
39-
): Promise<Response> {
40-
const { host, searchParams, algoliaAgent } = getUrlParams(getURL(url));
41-
const originalData =
42-
data && Object.keys(data).length > 0 ? data : undefined;
36+
}: EchoRequesterParams): Requester {
37+
function send(request: EndRequest): Promise<Response> {
38+
const { host, searchParams, algoliaAgent, path } = getUrlParams(
39+
getURL(request.url)
40+
);
41+
42+
const content: EchoResponse = {
43+
...request,
44+
data: request.data ? JSON.parse(request.data) : undefined,
45+
path,
46+
host,
47+
algoliaAgent: encodeURI(algoliaAgent),
48+
searchParams,
49+
};
4350

4451
return Promise.resolve({
45-
content: JSON.stringify({
46-
...originalRequest,
47-
host,
48-
headers,
49-
connectTimeout,
50-
responseTimeout,
51-
algoliaAgent: encodeURI(algoliaAgent),
52-
searchParams,
53-
data: originalData,
54-
}),
52+
content: JSON.stringify(content),
5553
isTimedOut: false,
5654
status,
5755
});
5856
}
5957

6058
return { send };
6159
}
62-
63-
export type EchoRequester = ReturnType<typeof createEchoRequester>;

clients/algoliasearch-client-javascript/packages/client-common/src/transporter/createTransporter.ts

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,20 @@ export function createTransporter({
8585

8686
async function retryableRequest<TResponse>(
8787
request: Request,
88-
requestOptions: RequestOptions
88+
requestOptions: RequestOptions,
89+
isRead = true
8990
): Promise<TResponse> {
9091
const stackTrace: StackFrame[] = [];
91-
const isRead = request.useReadTransporter || request.method === 'GET';
9292

9393
/**
9494
* First we prepare the payload that do not depend from hosts.
9595
*/
9696
const data = serializeData(request, requestOptions);
97-
const headers = serializeHeaders(baseHeaders, requestOptions);
98-
const method = request.method;
97+
const headers = serializeHeaders(
98+
baseHeaders,
99+
request.headers,
100+
requestOptions.headers
101+
);
99102

100103
// On `GET`, the data is proxied to query parameters.
101104
const dataQueryParameters: QueryParameters =
@@ -109,6 +112,7 @@ export function createTransporter({
109112
const queryParameters: QueryParameters = {
110113
'x-algolia-agent': algoliaAgent.value,
111114
...baseQueryParameters,
115+
...request.queryParameters,
112116
...dataQueryParameters,
113117
};
114118

@@ -152,7 +156,7 @@ export function createTransporter({
152156
const payload: EndRequest = {
153157
data,
154158
headers,
155-
method,
159+
method: request.method,
156160
url: serializeUrl(host, request.path, queryParameters),
157161
connectTimeout: getTimeout(timeoutsCount, timeouts.connect),
158162
responseTimeout: getTimeout(timeoutsCount, responseTimeout),
@@ -236,41 +240,20 @@ export function createTransporter({
236240
}
237241

238242
function createRequest<TResponse>(
239-
baseRequest: Request,
240-
baseRequestOptions: RequestOptions = {}
243+
request: Request,
244+
requestOptions: RequestOptions = {}
241245
): Promise<TResponse> {
242-
const mergedData: Request['data'] = Array.isArray(baseRequest.data)
243-
? baseRequest.data
244-
: {
245-
...baseRequest.data,
246-
...baseRequestOptions.data,
247-
};
248-
const request: Request = {
249-
...baseRequest,
250-
data: mergedData,
251-
};
252-
const requestOptions: RequestOptions = {
253-
cacheable: baseRequestOptions.cacheable,
254-
timeout: baseRequestOptions.timeout,
255-
queryParameters: {
256-
...baseRequest.queryParameters,
257-
...baseRequestOptions.queryParameters,
258-
},
259-
headers: {
260-
Accept: 'application/json',
261-
...baseRequest.headers,
262-
...baseRequestOptions.headers,
263-
},
264-
};
265-
246+
/**
247+
* A read request is either a `GET` request, or a request that we make
248+
* via the `read` transporter (e.g. `search`).
249+
*/
266250
const isRead = request.useReadTransporter || request.method === 'GET';
267-
268251
if (!isRead) {
269252
/**
270253
* On write requests, no cache mechanisms are applied, and we
271254
* proxy the request immediately to the requester.
272255
*/
273-
return retryableRequest<TResponse>(request, requestOptions);
256+
return retryableRequest<TResponse>(request, requestOptions, isRead);
274257
}
275258

276259
const createRetryableRequest = (): Promise<TResponse> => {
@@ -287,7 +270,7 @@ export function createTransporter({
287270
* request is "cacheable" - should be cached. Note that, once again,
288271
* the user can force this option.
289272
*/
290-
const cacheable = Boolean(requestOptions.cacheable || request.cacheable);
273+
const cacheable = requestOptions.cacheable || request.cacheable;
291274

292275
/**
293276
* If is not "cacheable", we immediately trigger the retryable request, no
@@ -306,8 +289,8 @@ export function createTransporter({
306289
request,
307290
requestOptions,
308291
transporter: {
309-
queryParameters: requestOptions.queryParameters,
310-
headers: requestOptions.headers,
292+
queryParameters: baseQueryParameters,
293+
headers: baseHeaders,
311294
},
312295
};
313296

clients/algoliasearch-client-javascript/packages/client-common/src/transporter/helpers.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,14 @@ export function serializeData(
7878

7979
export function serializeHeaders(
8080
baseHeaders: Headers,
81-
requestOptions: RequestOptions
81+
requestHeaders: Headers,
82+
requestOptionsHeaders?: Headers
8283
): Headers {
8384
const headers: Headers = {
85+
Accept: 'application/json',
8486
...baseHeaders,
85-
...requestOptions.headers,
87+
...requestHeaders,
88+
...requestOptionsHeaders,
8689
};
8790
const serializedHeaders: Headers = {};
8891

clients/algoliasearch-client-javascript/packages/client-common/src/types/Requester.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,9 @@ export type Requester = {
4242
send: (request: EndRequest, originalRequest: Request) => Promise<Response>;
4343
};
4444

45-
export type EchoResponse = Request & {
46-
connectTimeout: number;
47-
host: string;
48-
headers: Headers;
49-
responseTimeout: number;
50-
algoliaAgent: string;
51-
searchParams?: Record<string, string>;
52-
};
45+
export type EchoResponse = Omit<EndRequest, 'data'> &
46+
Pick<Request, 'data' | 'path'> & {
47+
host: string;
48+
algoliaAgent: string;
49+
searchParams?: Record<string, string>;
50+
};
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createEchoRequester } from '@algolia/client-common';
2-
import type { EchoRequester } from '@algolia/client-common';
2+
import type { Requester } from '@algolia/client-common';
33

4-
export function echoRequester(status: number = 200): EchoRequester {
4+
export function echoRequester(status: number = 200): Requester {
55
return createEchoRequester({ getURL: (url: string) => new URL(url), status });
66
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { URL } from 'url';
22

33
import { createEchoRequester } from '@algolia/client-common';
4-
import type { EchoRequester } from '@algolia/client-common';
4+
import type { Requester } from '@algolia/client-common';
55

6-
export function echoRequester(status: number = 200): EchoRequester {
6+
export function echoRequester(status: number = 200): Requester {
77
return createEchoRequester({ getURL: (url: string) => new URL(url), status });
88
}

clients/algoliasearch-client-php/lib/RequestOptions/RequestOptionsFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ private function normalize($options)
5050
'headers' => [
5151
'x-algolia-application-id' => $this->config->getAppId(),
5252
'x-algolia-api-key' => $this->config->getAlgoliaApiKey(),
53-
'User-Agent' => $this->config->getAlgoliaAgent() !== null
53+
'User-Agent' =>
54+
$this->config->getAlgoliaAgent() !== null
5455
? $this->config->getAlgoliaAgent()
5556
: AlgoliaAgent::get($this->config->getClientName()),
5657
'Content-Type' => 'application/json',

clients/algoliasearch-client-php/lib/RetryStrategy/ApiWrapper.php

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,10 @@ public function sendRequest(
7979
*/
8080
$isRead = $useReadTransporter || 'GET' === mb_strtoupper($method);
8181

82-
if ($isRead) {
82+
if ($isRead || 'DELETE' === mb_strtoupper($method)) {
8383
$requestOptions = $this->requestOptionsFactory->createBodyLess(
8484
$requestOptions
8585
);
86-
} elseif ('DELETE' === mb_strtoupper($method)) {
87-
$requestOptions = $this->requestOptionsFactory->createBodyLess(
88-
$requestOptions
89-
);
90-
$data = [];
9186
} else {
9287
$requestOptions = $this->requestOptionsFactory->create(
9388
$requestOptions
@@ -139,7 +134,9 @@ private function request(
139134
->withQuery($requestOptions->getBuiltQueryParameters())
140135
->withScheme('https');
141136

142-
$body = array_merge($data, $requestOptions->getBody());
137+
$body = isset($data)
138+
? array_merge($data, $requestOptions->getBody())
139+
: $data;
143140

144141
$logParams = [
145142
'body' => $body,
@@ -277,10 +274,9 @@ private function createRequest(
277274
$protocolVersion = '1.1'
278275
) {
279276
if (is_array($body)) {
280-
// Send an empty body instead of "[]" in case there are
281-
// no content/params to send
277+
// Send an empty valid JSON object
282278
if (empty($body)) {
283-
$body = '';
279+
$body = '{}';
284280
} else {
285281
$body = \json_encode($body, $this->jsonOptions);
286282
if (JSON_ERROR_NONE !== json_last_error()) {

0 commit comments

Comments
 (0)