Skip to content

Commit

Permalink
Fix: accept body in DELETE endpoints (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
katspaugh authored Feb 26, 2024
1 parent a723748 commit 974e2a9
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 64 deletions.
20 changes: 10 additions & 10 deletions src/endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { deleteData, fetchData, getData, insertParams, stringifyQuery } from './utils'
import { fetchData, getData, insertParams, stringifyQuery } from './utils'
import type { DeleteEndpoint, GetEndpoint, paths, PostEndpoint, Primitive, PutEndpoint } from './types/api'

function makeUrl(
Expand Down Expand Up @@ -30,6 +30,15 @@ export function putEndpoint<T extends keyof paths>(
return fetchData(url, 'PUT', params?.body, params?.headers)
}

export function deleteEndpoint<T extends keyof paths>(
baseUrl: string,
path: T,
params?: paths[T] extends DeleteEndpoint ? paths[T]['delete']['parameters'] : never,
): Promise<paths[T] extends DeleteEndpoint ? paths[T]['delete']['responses'][200]['schema'] : never> {
const url = makeUrl(baseUrl, path as string, params?.path, params?.query)
return fetchData(url, 'DELETE', params?.body, params?.headers)
}

export function getEndpoint<T extends keyof paths>(
baseUrl: string,
path: T,
Expand All @@ -42,12 +51,3 @@ export function getEndpoint<T extends keyof paths>(
const url = makeUrl(baseUrl, path as string, params?.path, params?.query)
return getData(url, params?.headers)
}

export function deleteEndpoint<T extends keyof paths>(
baseUrl: string,
path: T,
params?: paths[T] extends DeleteEndpoint ? paths[T]['delete']['parameters'] : never,
): Promise<paths[T] extends DeleteEndpoint ? paths[T]['delete']['responses'][200]['schema'] : never> {
const url = makeUrl(baseUrl, path as string, params?.path)
return deleteData(url, params?.headers)
}
39 changes: 16 additions & 23 deletions src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import type {
ChangeEmailRequestBody,
GetEmailResponse,
RegisterEmailRequestBody,
AuthorizationEmailRequestHeader,
AuthorizationEmailRequestHeaders,
VerifyEmailRequestBody,
} from './emails'

Expand All @@ -43,14 +43,11 @@ export type Primitive = string | number | boolean | null
interface Params {
path?: { [key: string]: Primitive }
headers?: Record<string, string>
}

interface GetParams extends Params {
query?: { [key: string]: Primitive }
}

interface PostParams extends GetParams {
body: string | Record<string, unknown>
interface BodyParams extends Params {
body?: string | Record<string, unknown>
}

interface Responses {
Expand All @@ -64,32 +61,28 @@ interface Endpoint {
} | null
}

interface WriteMethod {
parameters: BodyParams | null
responses: Responses
}

export interface GetEndpoint extends Endpoint {
get: {
parameters: GetParams | null
parameters: Params | null
responses: Responses
}
}

export interface PostEndpoint extends Endpoint {
post: {
parameters: PostParams | null
responses: Responses
}
post: WriteMethod
}

export interface PutEndpoint extends Endpoint {
put: {
parameters: PostParams | null
responses: Responses
}
put: WriteMethod
}

export interface DeleteEndpoint extends Endpoint {
delete: {
parameters: Params | null
responses: Responses
}
delete: WriteMethod
}

interface PathRegistry {
Expand Down Expand Up @@ -896,7 +889,7 @@ export interface operations {
safe_address: string
}
body: RegisterEmailRequestBody
headers: AuthorizationEmailRequestHeader
headers: AuthorizationEmailRequestHeaders
}
responses: {
200: {
Expand All @@ -915,7 +908,7 @@ export interface operations {
signer: string
}
body: ChangeEmailRequestBody
headers: AuthorizationEmailRequestHeader
headers: AuthorizationEmailRequestHeaders
}
responses: {
200: {
Expand All @@ -933,7 +926,7 @@ export interface operations {
safe_address: string
signer: string
}
headers: AuthorizationEmailRequestHeader
headers: AuthorizationEmailRequestHeaders
}
responses: {
200: {
Expand Down Expand Up @@ -986,7 +979,7 @@ export interface operations {
safe_address: string
signer: string
}
headers: AuthorizationEmailRequestHeader
headers: AuthorizationEmailRequestHeaders
}

responses: {
Expand Down
2 changes: 1 addition & 1 deletion src/types/emails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type ChangeEmailRequestBody = {
emailAddress: string
}

export type AuthorizationEmailRequestHeader = {
export type AuthorizationEmailRequestHeaders = {
['Safe-Wallet-Signature']: string
['Safe-Wallet-Signature-Timestamp']: string
}
Expand Down
35 changes: 10 additions & 25 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,47 +58,32 @@ async function parseResponse<T>(resp: Response): Promise<T> {

export async function fetchData<T>(
url: string,
method: 'POST' | 'PUT',
method: 'POST' | 'PUT' | 'DELETE',
body?: unknown,
headers?: Record<string, string>,
): Promise<T> {
let options: RequestInit | undefined

if (body != null) {
const requestHeaders: Record<string, string> = headers ?? {}
requestHeaders['Content-Type'] = 'application/json'
options = {
method: method ?? 'POST',
body: typeof body === 'string' ? body : JSON.stringify(body),
headers: requestHeaders,
}
const requestHeaders: Record<string, string> = {
'Content-Type': 'application/json',
...headers,
}

const resp = await fetch(url, options)

return parseResponse<T>(resp)
}

export async function getData<T>(url: string, headers?: Record<string, string>): Promise<T> {
const options: RequestInit = {
method: 'GET',
method: method ?? 'POST',
headers: requestHeaders,
}

if (headers) {
options['headers'] = {
...headers,
'Content-Type': 'application/json',
}
if (body != null) {
options.body = typeof body === 'string' ? body : JSON.stringify(body)
}

const resp = await fetch(url, options)

return parseResponse<T>(resp)
}

export async function deleteData<T>(url: string, headers?: Record<string, string>): Promise<T> {
export async function getData<T>(url: string, headers?: Record<string, string>): Promise<T> {
const options: RequestInit = {
method: 'DELETE',
method: 'GET',
}

if (headers) {
Expand Down
22 changes: 21 additions & 1 deletion tests/endpoint.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getData, fetchData } from '../src/utils'
import { getEndpoint, postEndpoint, putEndpoint } from '../src/endpoint'
import { deleteEndpoint, getEndpoint, postEndpoint, putEndpoint } from '../src/endpoint'

jest.mock('../src/utils', () => {
const originalModule = jest.requireActual('../src/utils')
Expand Down Expand Up @@ -143,4 +143,24 @@ describe('getEndpoint', () => {
headers,
)
})

it('should send a DELETE request with body', async () => {
const body = {
signature: '0x123',
}

await expect(
deleteEndpoint('https://test.test', '/v1/chains/{chainId}/transactions/{safeTxHash}', {
path: { chainId: '4', safeTxHash: '0x456' },
body,
}),
).resolves.toEqual({ success: true })

expect(fetchData).toHaveBeenCalledWith(
'https://test.test/v1/chains/4/transactions/0x456',
'DELETE',
body,
undefined,
)
})
})
13 changes: 9 additions & 4 deletions tests/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// <reference lib="dom" />
import { fetchData, getData, deleteData, insertParams, stringifyQuery } from '../src/utils'
import { fetchData, getData, insertParams, stringifyQuery } from '../src/utils'

const fetchMock = jest.spyOn(global, 'fetch') as typeof fetch & jest.Mock

Expand Down Expand Up @@ -156,7 +156,7 @@ describe('utils', () => {
})
})

describe('deleteData', () => {
describe('fetchData DELETE', () => {
it('should make a DELETE request', async () => {
fetchMock.mockImplementation(() => {
return Promise.resolve({
Expand All @@ -166,10 +166,13 @@ describe('utils', () => {
})
})

await expect(deleteData('/test/safe')).resolves.toEqual({ success: true })
await expect(fetchData('/test/safe', 'DELETE')).resolves.toEqual({ success: true })

expect(fetch).toHaveBeenCalledWith('/test/safe', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
})
})

Expand All @@ -182,7 +185,9 @@ describe('utils', () => {
})
})

await expect(deleteData('/test/safe', { TestHeader: '123456' })).resolves.toEqual({ success: true })
await expect(fetchData('/test/safe', 'DELETE', undefined, { TestHeader: '123456' })).resolves.toEqual({
success: true,
})

expect(fetch).toHaveBeenCalledWith('/test/safe', {
method: 'DELETE',
Expand Down

0 comments on commit 974e2a9

Please sign in to comment.