diff --git a/package-lock.json b/package-lock.json index cfaf5bb..8b5aaea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@doist/todoist-api-typescript", - "version": "2.0.2", + "version": "2.0.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@doist/todoist-api-typescript", - "version": "2.0.2", + "version": "2.0.3", "license": "MIT", "dependencies": { "axios": "^0.27.0", diff --git a/package.json b/package.json index 66b87a3..b8f3b71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@doist/todoist-api-typescript", - "version": "2.0.2", + "version": "2.0.3", "description": "A typescript wrapper for the Todoist REST API.", "author": "Doist developers", "repository": "git@github.com:doist/todoist-api-typescript.git", diff --git a/src/TodoistApi.projects.test.ts b/src/TodoistApi.projects.test.ts index 5fcd24e..cc07b69 100644 --- a/src/TodoistApi.projects.test.ts +++ b/src/TodoistApi.projects.test.ts @@ -150,6 +150,7 @@ describe('TodoistApi project endpoints', () => { getRestBaseUri(), `${ENDPOINT_REST_PROJECTS}/${projectId}`, DEFAULT_AUTH_TOKEN, + undefined, DEFAULT_REQUEST_ID, ) }) diff --git a/src/TodoistApi.ts b/src/TodoistApi.ts index ae816d1..7f232f7 100644 --- a/src/TodoistApi.ts +++ b/src/TodoistApi.ts @@ -240,6 +240,7 @@ export class TodoistApi { this.restApiBase, generatePath(ENDPOINT_REST_PROJECTS, id), this.authToken, + undefined, requestId, ) return isSuccess(response) @@ -263,7 +264,7 @@ export class TodoistApi { this.restApiBase, ENDPOINT_REST_SECTIONS, this.authToken, - projectId && { projectId }, + projectId ? { projectId } : undefined, ) return validateSectionArray(response.data) diff --git a/src/restClient.axios.test.ts b/src/restClient.axios.test.ts new file mode 100644 index 0000000..5cb1a2a --- /dev/null +++ b/src/restClient.axios.test.ts @@ -0,0 +1,18 @@ +import axios from 'axios' +import { paramsSerializer } from './restClient' + +const DEFAULT_BASE_URI = 'https://api.todoist.com/rest/v2/tasks' + +describe('axios tests without mocking', () => { + test('GET calls serialise arrays correctly', () => { + const requestUri = axios.create().getUri({ + method: 'GET', + baseURL: DEFAULT_BASE_URI, + params: { + ids: ['12345', '56789'], + }, + paramsSerializer, + }) + expect(requestUri).toEqual('https://api.todoist.com/rest/v2/tasks?ids=12345%2C56789') + }) +}) diff --git a/src/restClient.test.ts b/src/restClient.test.ts index b8f25b2..fcc87a3 100644 --- a/src/restClient.test.ts +++ b/src/restClient.test.ts @@ -1,5 +1,5 @@ import Axios, { AxiosStatic, AxiosResponse, AxiosError } from 'axios' -import { request, isSuccess } from './restClient' +import { request, isSuccess, paramsSerializer } from './restClient' import { TodoistRequestError } from './types/errors' import * as caseConverter from 'axios-case-converter' import { assertInstance } from './testUtils/asserts' @@ -120,6 +120,7 @@ describe('restClient', () => { expect(axiosMock.get).toBeCalledTimes(1) expect(axiosMock.get).toBeCalledWith(DEFAULT_ENDPOINT, { params: undefined, + paramsSerializer, }) }) @@ -135,6 +136,7 @@ describe('restClient', () => { expect(axiosMock.get).toBeCalledTimes(1) expect(axiosMock.get).toBeCalledWith(DEFAULT_ENDPOINT, { params: DEFAULT_PAYLOAD, + paramsSerializer, }) }) diff --git a/src/restClient.ts b/src/restClient.ts index 96a3b3a..ac836eb 100644 --- a/src/restClient.ts +++ b/src/restClient.ts @@ -5,6 +5,21 @@ import { HttpMethod } from './types/http' import { v4 as uuidv4 } from 'uuid' import axiosRetry from 'axios-retry' +export function paramsSerializer(params: Record) { + const qs = new URLSearchParams() + + Object.keys(params).forEach((key) => { + const value = params[key] + if (Array.isArray(value)) { + qs.append(key, value.join(',')) + } else { + qs.append(key, String(value)) + } + }) + + return qs.toString() +} + const defaultHeaders = { 'Content-Type': 'application/json', } @@ -71,7 +86,7 @@ export async function request( baseUri: string, relativePath: string, apiToken?: string, - payload?: unknown, + payload?: Record, requestId?: string, ): Promise> { // axios loses the original stack when returning errors, for the sake of better reporting @@ -88,7 +103,10 @@ export async function request( switch (httpMethod) { case 'GET': - return await axiosClient.get(relativePath, { params: payload }) + return await axiosClient.get(relativePath, { + params: payload, + paramsSerializer, + }) case 'POST': return await axiosClient.post(relativePath, payload) case 'DELETE':