Skip to content

Commit fdbfd93

Browse files
authored
Merge pull request #8
clean multipart and handlers to imports
2 parents 182bb74 + a1cbf87 commit fdbfd93

20 files changed

+595
-307
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Changelog
2+
-------------
3+
- Creation of a [Multipart HTTP client](src/lib/multipart/MultipartHttpClient.ts)
4+
- Creation of a [OctetStreamType Validator](src/lib/client/FileFetchClient.ts) to handle octet stream responses
5+
- Creation of a [Raw Header Parser](src/lib/multipart/RawHeaderParser.ts) ot read headers from a xmlhttp response
6+
- The use of the MultiPartHttpClient is demonstrated in the [README.md](README.md#upload-file) file
7+
- Moving generic Reponse Handlers to a [specific package](src/lib/handler)
8+
- [ResponseArrayBufferHandler](src/lib/handler/ResponseArrayBufferHandler.ts)
9+
- [ResponseJsonHandler](src/lib/handler/ResponseJsonHandler.ts)
10+
- [ResponseTextHandler](src/lib/handler/ResponseTextHandler.ts)
11+
- Moving generic Reponse Validators to a [specific package](src/lib/handler)
12+
- [ValidateBasicStatusCodeHandler.ts](src/lib/handler/ValidateBasicStatusCodeHandler.ts)
13+
- [ValidateContentTypeHandler.ts](src/lib/handler/ValidateContentTypeHandler.ts)

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ So a handler can:
118118
Expected results should be of type `Promise` of [HttpResponse](#httpresponse).
119119
120120
Here are two samples usage:
121-
- A handler that validate the HTTP status code: <https://github.com/Coreoz/simple-http-rest-client/tree/master/src/lib/client/FetchStatusValidators.ts>
121+
- A handler that validate the HTTP status code: <https://github.com/Coreoz/simple-http-rest-client/tree/master/src/lib/handler/ValidateBasicStatusCodeHandler.ts>
122122
- A handler that return a JSON results (the function is named `toJsonResponse`): <https://github.com/Coreoz/simple-http-rest-client/tree/master/src/lib/client/JsonFetchClient.ts>
123123
124124
### HttpPromise
@@ -167,7 +167,7 @@ The goal here is to verify that the result is excepted and correct.
167167
By default, these validators are provided:
168168
- **validateBasicStatusCodes**: It raises an error if the status code is 403, and it returns an empty response if the status code is 200
169169
- **jsonContentTypeValidator**: It verifies the response content-type is JSON
170-
- **contentTypeValidator**: It is used to create response content-type validator like `jsonContentTypeValidator`
170+
- **validateContentType**: It is used to create response content-type validator like `jsonContentTypeValidator`
171171
172172
Once validators are added, our API client should look like this:
173173
```typescript

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
},
3939
"sideEffects": false,
4040
"dependencies": {
41-
"simple-http-request-builder": "^2.0.1",
41+
"simple-http-request-builder": "^2.1.0",
4242
"simple-logging-system": "^1.1.0"
4343
},
4444
"devDependencies": {

src/index.ts

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,40 @@
1-
// content type validator
2-
export { contentTypeValidator } from './lib/client/ContentTypeValidator';
3-
// fetch client
1+
// clients
42
export {
53
fetchClientExecutor,
6-
networkErrorCatcher,
74
fetchClient,
85
createHttpFetchRequest,
96
} from './lib/client/FetchClient';
107
export type {
11-
FetchResponseHandler,
128
HttpFetchClient,
139
} from './lib/client/FetchClient';
14-
// fetch status validators
15-
export { validateBasicStatusCodes } from './lib/client/FetchStatusValidators';
10+
export {
11+
multipartHttpFetchClient,
12+
multipartHttpFetchClientExecutor,
13+
createMultipartHttpFetchRequest,
14+
} from './lib/multipart/MultipartHttpClient';
15+
export type {
16+
MultipartHttpFetchClient,
17+
} from './lib/multipart/MultipartHttpClient';
18+
// handlers
19+
export {
20+
validateBasicStatusCodes,
21+
} from './lib/handler/ValidateBasicStatusCodeHandler';
22+
export { validateContentType } from './lib/handler/ValidateContentTypeHandler';
23+
export {
24+
toJsonResponse, defaultJsonErrorMapper,
25+
} from './lib/handler/ResponseJsonHandler';
26+
export type {
27+
JsonErrorMapper,
28+
} from './lib/handler/ResponseJsonHandler';
29+
export { toTextResponse } from './lib/handler/ResponseTextHandler';
30+
export { toArrayBufferResponse } from './lib/handler/ResponseArrayBufferHandler';
31+
export {
32+
processHandlers,
33+
networkErrorCatcher,
34+
} from './lib/handler/FetchResponseHandlers';
35+
export type {
36+
FetchResponseHandler,
37+
} from './lib/handler/FetchResponseHandlers';
1638
// http response
1739
export type {
1840
HttpError,
@@ -27,16 +49,15 @@ export {
2749
timeoutError,
2850
forbiddenError,
2951
} from './lib/client/HttpResponse';
30-
// json fetch client
52+
// custom fetch clients
3153
export {
3254
jsonContentTypeValidator,
33-
defaultJsonErrorMapper,
34-
toJsonResponse,
3555
defaultJsonFetchClient,
3656
} from './lib/client/JsonFetchClient';
37-
export type {
38-
JsonErrorMapper,
39-
} from './lib/client/JsonFetchClient';
57+
export {
58+
octetStreamTypeValidator,
59+
fileFetchClient,
60+
} from './lib/client/FileFetchClient';
4061
// http promise
4162
export {
4263
processHttpResponse,
@@ -52,3 +73,6 @@ export { PromiseMonitor } from './lib/promise/PromiseMonitor';
5273
export { HttpPromiseMonitor } from './lib/promise/HttpPromiseMonitor';
5374
// synchronized http promise
5475
export { SynchronizedHttpPromise } from './lib/promise/SynchronizedHttpPromise';
76+
77+
// TODO to remove in the next future major release 3.x.x
78+
export { validateContentType as contentTypeValidator } from './lib/handler/ValidateContentTypeHandler';

src/lib/client/FetchClient.ts

Lines changed: 4 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
import {
22
HttpClient, HttpMethod, HttpOptions, HttpRequest,
33
} from 'simple-http-request-builder';
4-
import { Logger } from 'simple-logging-system';
54
import { HttpPromise, unwrapHttpPromise } from '../promise/HttpPromise';
6-
import {
7-
genericError,
8-
HttpResponse,
9-
networkError,
10-
timeoutError,
11-
toErrorResponsePromise,
12-
} from './HttpResponse';
13-
14-
const logger = new Logger('FetchClient');
5+
import { processHandlers, FetchResponseHandler, networkErrorCatcher } from '../handler/FetchResponseHandlers';
6+
import { HttpResponse } from './HttpResponse';
157

168
/**
179
* A {@link HttpClient} that executes an {@link HttpRequest}
@@ -40,49 +32,10 @@ export const fetchClientExecutor: HttpClient<Promise<Response>> = (httpRequest:
4032
.finally(() => clearTimeout(timeoutHandle));
4133
};
4234

43-
/**
44-
* Map {@link fetchClientExecutor} Promise errors to {@link HttpError}.
45-
* See {@link fetchClient} for reasons why these errors may occur.
46-
*
47-
* For now `AbortError` are mapped to {@link timeoutError} whereas all the other errors are mapped
48-
* to the generic {@link networkError}.
49-
*
50-
* @param error The raw {@link fetch} Promise {@link Error}
51-
*/
52-
export const networkErrorCatcher = <T>(error: Error): HttpResponse<T> => {
53-
if (error.name === 'AbortError') {
54-
return {
55-
error: timeoutError,
56-
};
57-
}
58-
logger.warn('Cannot connect to HTTP server due to a network error', { error });
59-
return {
60-
error: networkError,
61-
};
62-
};
63-
64-
/**
65-
* Handlers are executed by {@link fetchClient} after a successful HTTP response is available:
66-
* this means an HTTP response has been received (whichever the response statut, 200, 400 or 500...).
67-
* These handlers will:
68-
* - Validate some preconditions and if necessary return an error result
69-
* - Return a result
70-
*
71-
* So a handler can:
72-
* - Either return a result (which can be a successful result or an error),
73-
* in that case following handlers **will not be executed**
74-
* - Either return `undefined`, in that case following handlers **will be executed**
75-
*
76-
* Expected results should be of type {@link Promise} of {@link HttpResponse}.
77-
*/
78-
export interface FetchResponseHandler<T = unknown> {
79-
(response: Response): Promise<HttpResponse<T>> | undefined;
80-
}
81-
8235
/**
8336
* A {@link HttpClient} that uses:
8437
* - {@link fetchClientExecutor} to execute an {@link HttpRequest}
85-
* - {@link FetchResponseHandler handlers} to validate and transform the response
38+
* - {@link FetchResponseHandler} handlers to validate and transform the response with {@link processHandlers}
8639
*
8740
* There are two outcomes for the execution of the {@link HttpRequest}:
8841
* - If the execution is successful (an HTTP response has been received),
@@ -92,29 +45,13 @@ export interface FetchResponseHandler<T = unknown> {
9245
* or that the request has been cancelled using the {@link HttpOptions#timeoutAbortController}
9346
* (see {@link HttpRequest#optionValues} for details),
9447
* or else it means there is a bug in the library...
95-
* in any case {@link networkErrorCatcher} will be executed to map the error to an {@link HttpError}
96-
*
97-
* If an {@link FetchResponseHandler handler} raises an error, a {@link genericError} will be returned
9848
*
9949
* @param httpRequest The {@link HttpRequest} to execute
10050
* @param handlers The {@link FetchResponseHandler}s to execute on an OK response (status code = 2xx)
10151
*/
10252
export const fetchClient = <T = Response>(httpRequest: HttpRequest<unknown>, ...handlers: FetchResponseHandler[])
10353
: Promise<HttpResponse<T>> => <Promise<HttpResponse<T>>>fetchClientExecutor(httpRequest)
104-
.then((response) => {
105-
for (const handler of handlers) {
106-
try {
107-
const handlerResult = handler(response);
108-
if (handlerResult !== undefined) {
109-
return handlerResult;
110-
}
111-
} catch (error) {
112-
logger.error('Error executing handler', { error });
113-
return toErrorResponsePromise(genericError);
114-
}
115-
}
116-
return { response };
117-
})
54+
.then((response: Response) => processHandlers(response, handlers))
11855
.catch(networkErrorCatcher);
11956

12057
/**

src/lib/client/FileFetchClient.ts

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,19 @@
11
import { HttpRequest } from 'simple-http-request-builder';
2-
import { Logger } from 'simple-logging-system';
3-
import { contentTypeValidator } from './ContentTypeValidator';
4-
import { fetchClient, FetchResponseHandler } from './FetchClient';
5-
import { validateBasicStatusCodes } from './FetchStatusValidators';
6-
import { genericError, HttpResponse, toErrorResponsePromise } from './HttpResponse';
7-
import { defaultJsonErrorMapper, JsonErrorMapper, toJsonResponse } from './JsonFetchClient';
8-
9-
const logger: Logger = new Logger('FileFetchClient');
10-
11-
/**
12-
* A {@link FetchResponseHandler} that tries to retrieve the array buffer of the {@link Response} body
13-
* - If the {@link Response} body is not a valid array buffer,
14-
* {@link HttpResponse#error} will contain a {@link genericError}
15-
* - If the HTTP response is successful (status code is 2xx),
16-
* {@link HttpResponse#response} will contain the array buffer
17-
* - If the HTTP response is not successful (status code is not 2xx),
18-
* the {@link JsonErrorMapper} will be executed to return a {@link HttpResponse}
19-
*
20-
* @param response The {@link Response} to parse
21-
* @param jsonErrorMapper The {@link JsonErrorMapper} that will handle the parsed JSON object in case
22-
* the HTTP response is not successful (status code is not 2xx)
23-
*/
24-
export const toArrayBufferResponse = async (
25-
response: Response,
26-
jsonErrorMapper: JsonErrorMapper = defaultJsonErrorMapper,
27-
): Promise<HttpResponse<unknown>> => {
28-
if (response.ok) {
29-
try {
30-
return {
31-
response: await response.arrayBuffer(),
32-
};
33-
} catch (error) {
34-
logger.warn('Response could not be read as an array buffer');
35-
return toErrorResponsePromise(genericError);
36-
}
37-
}
38-
39-
return toJsonResponse(response, jsonErrorMapper);
40-
};
2+
import { FetchResponseHandler } from '../handler/FetchResponseHandlers';
3+
import { toArrayBufferResponse } from '../handler/ResponseArrayBufferHandler';
4+
import {
5+
validateBasicStatusCodes,
6+
} from '../handler/ValidateBasicStatusCodeHandler';
7+
import { validateContentType } from '../handler/ValidateContentTypeHandler';
8+
import { fetchClient } from './FetchClient';
9+
import { HttpResponse } from './HttpResponse';
4110

4211
/**
4312
* Validate that the content-type header of the response is 'application/octet-stream'
4413
* @param response The {@link Response} to validate
4514
*/
4615
export const octetStreamTypeValidator: FetchResponseHandler = (response: Response) => (
47-
contentTypeValidator(response, 'application/octet-stream')
16+
validateContentType(response, 'application/octet-stream')
4817
);
4918

5019
/**

src/lib/client/JsonFetchClient.ts

Lines changed: 7 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,21 @@
1-
import { Logger } from 'simple-logging-system';
21
import { HttpRequest } from 'simple-http-request-builder';
3-
import { genericError, HttpResponse } from './HttpResponse';
2+
import { toJsonResponse } from '../handler/ResponseJsonHandler';
3+
import { validateContentType } from '../handler/ValidateContentTypeHandler';
44
import { fetchClient, FetchResponseHandler } from './FetchClient';
5-
import { validateBasicStatusCodes } from './FetchStatusValidators';
6-
import { contentTypeValidator } from './ContentTypeValidator';
7-
8-
const logger = new Logger('JsonFetchClient');
5+
import { validateBasicStatusCodes } from '../handler/ValidateBasicStatusCodeHandler';
6+
import { HttpResponse } from './HttpResponse';
97

108
/**
119
* A {@link FetchResponseHandler} that verify that the content type of a {@link Response} is JSON
1210
* using the `content-type` response HTTP header.
1311
*
14-
* See {@link contentTypeValidator} for the content type validation.
12+
* See {@link validateContentType} for the content type validation.
1513
*
1614
* @param response The {@link Response} to validate
1715
*/
18-
export const jsonContentTypeValidator:
19-
FetchResponseHandler = (response: Response) => contentTypeValidator(response, 'json');
20-
21-
/**
22-
* A mapper that will handle non-successful HTTP responses that have however a JSON body.
23-
*
24-
* This mapper generally returns an {@link HttpResponse#error} containing the matching `errorCode`.
25-
* But if necessary it can return a {@link HttpResponse#response}.
26-
*
27-
* See the default implementation: {@link defaultJsonErrorMapper}.
28-
*
29-
* @param response The non-successful HTTP {@link Response}
30-
* @param json The parsed JSON object
31-
*/
32-
// any is the returned type of the Fetch.json() Promise
33-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
34-
export type JsonErrorMapper = (response: Response, json: any) => HttpResponse<unknown>;
35-
36-
/**
37-
* The default {@link JsonErrorMapper} implementation that returns an {@link HttpResponse#error} containing:
38-
* - The HTTP response body JSON object if it contains a {@link HttpError#errorCode} attribute
39-
* - Else a {@link genericError}
40-
*/
41-
export const defaultJsonErrorMapper: JsonErrorMapper = (response: Response, json) => {
42-
if (typeof json.errorCode === 'undefined') {
43-
logger.warn('Unrecognized JSON error', { response });
44-
return { error: genericError };
45-
}
46-
return { error: json };
47-
};
48-
49-
/**
50-
* A {@link FetchResponseHandler} that tries to convert the {@link Response} JSON body
51-
* to an {@link HttpResponse}:
52-
* - If the {@link Response} body is not a valid JSON object,
53-
* {@link HttpResponse#error} will contain a {@link genericError}
54-
* - If the HTTP response is successful (status code is 2xx),
55-
* {@link HttpResponse#response} will contain the JSON parsed object
56-
* - If the HTTP response is not successful (status code is not 2xx),
57-
* the {@link JsonErrorMapper} will be executed to return a {@link HttpResponse}
58-
*
59-
* @param response The {@link Response} to parse
60-
* @param jsonErrorMapper The {@link JsonErrorMapper} that will handle the parsed JSON object in case
61-
* the HTTP response is not successful (status code is not 2xx)
62-
*/
63-
export const toJsonResponse = (
16+
export const jsonContentTypeValidator: FetchResponseHandler = (
6417
response: Response,
65-
jsonErrorMapper: JsonErrorMapper = defaultJsonErrorMapper,
66-
): Promise<HttpResponse<unknown>> => response
67-
.json()
68-
.then((json) => {
69-
if (response.ok) {
70-
return {
71-
response: json,
72-
};
73-
}
74-
return jsonErrorMapper(response, json);
75-
})
76-
.catch((error) => {
77-
logger.error('Cannot parse JSON', { error });
78-
return {
79-
error: genericError,
80-
};
81-
});
18+
) => validateContentType(response, 'json');
8219

8320
/**
8421
* A {@link HttpClient} that executes an {@link HttpRequest} that returns JSON responses.

0 commit comments

Comments
 (0)