diff --git a/.changeset/red-trains-clean.md b/.changeset/red-trains-clean.md new file mode 100644 index 00000000..acd18cc9 --- /dev/null +++ b/.changeset/red-trains-clean.md @@ -0,0 +1,5 @@ +--- +'@apollo/datasource-rest': minor +--- + +Add support for responseType option diff --git a/README.md b/README.md index 5ca40338..96b1e20e 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,9 @@ You can also throw a different error here altogether. Note that by default, erro This method is called with the HTTP response and should read the body and parse it into an appropriate format. By default, it checks to see if the `Content-Type` header starts with `application/json` or ends with `+json` (just looking at the header as a string without using a Content-Type parser) and returns `response.json()` if it does or `response.text()` if it does not. If you want to read the body in a different way, override this. This method should read the response fully; if it does not, it could cause a memory leak inside the HTTP cache. If you override this, you may want to override `cloneParsedBody` as well. +You can send option to within the request called `responseType`, which will override the logic above and return the +response with given type. Supported types are `arraybuffer`, `text` and `json`. The default value to use is `json`. + ##### `cloneParsedBody` This method is used to clone a body (for use by the request deduplication feature so that multiple callers get distinct return values that can be separately mutated). If your `parseBody` returns values other than basic JSON objects, you might want to override this method too. You can also change this method to return its argument without cloning if your code that uses this class is OK with the values returned from deduplicated requests sharing state. @@ -294,6 +297,26 @@ All of the HTTP helper functions (`get`, `put`, `post`, `patch`, `delete`, and ` Alternatively, you can use the `fetch` method. The return value of this method is a `DataSourceFetchResult`, which contains `parsedBody`, `response`, and some other fields with metadata about how the operation interacted with the cache. +### Setting response type option + +If you would like to hard set the response type fetch should treat the source response with, you can use +`responseType` option to set it. + +Example of getting the response as `arraybuffer`. Supported values are `text`, `arraybuffer` and `json`. + +```ts +class PersonalizationAPI extends RESTDataSource { + // Get ArrayBuffer + async getMoviePosterPDF(id) { + return this.get( + `movies/posters/${encodeURIComponent(id)}`, { + responseType: 'arraybuffer', + } + ); + } +} +``` + ### Intercepting fetches Data sources allow you to intercept fetches to set headers, query parameters, or make other changes to the outgoing request. This is most often used for authorization or other common concerns that apply to all requests. The `constructor` can be overridden to require additional contextual information when the class is instantiated like so: diff --git a/cspell-dict.txt b/cspell-dict.txt index 255b4b24..1779cdae 100644 --- a/cspell-dict.txt +++ b/cspell-dict.txt @@ -25,3 +25,4 @@ singletonizes unmock userland withrequired +arraybuffer \ No newline at end of file diff --git a/src/RESTDataSource.ts b/src/RESTDataSource.ts index e87a6197..1adafc3d 100644 --- a/src/RESTDataSource.ts +++ b/src/RESTDataSource.ts @@ -13,9 +13,16 @@ import isPlainObject from 'lodash.isplainobject'; import { HTTPCache } from './HTTPCache'; export type ValueOrPromise = T | Promise; +export type RequestResponseType = 'arraybuffer' | 'text' | 'json'; export type RequestOptions = FetcherRequestInit & { + /** + * `responseType` indicates the type of data that the server will respond with + * options are: 'arraybuffer', 'text', 'json' + * Defaults to JSON. + */ + responseType?: RequestResponseType; /** * URL search parameters can be provided either as a record object (in which * case keys with `undefined` values are ignored) or as an URLSearchParams @@ -299,9 +306,26 @@ export abstract class RESTDataSource { // // If you override this to return interesting new mutable data types, override // cloneParsedBody too. - protected parseBody(response: FetcherResponse): Promise { + protected parseBody( + response: FetcherResponse, + requestResponseTypeOption: RequestResponseType | undefined, + ): Promise { const contentType = response.headers.get('Content-Type'); const contentLength = response.headers.get('Content-Length'); + + if (requestResponseTypeOption) { + switch (requestResponseTypeOption) { + case 'arraybuffer': + return response.arrayBuffer(); + case 'text': + return response.text(); + case 'json': + return response.json(); + default: + return response.json(); + } + } + if ( // As one might expect, a "204 No Content" is empty! This means there // isn't enough to `JSON.parse`, and trying will result in an error. @@ -551,7 +575,10 @@ export abstract class RESTDataSource { this.catchCacheWritePromiseErrors(cacheWritePromise); } - const parsedBody = await this.parseBody(response); + const parsedBody = await this.parseBody( + response, + outgoingRequest.responseType, + ); await this.throwIfResponseIsError({ url,