Skip to content
12 changes: 12 additions & 0 deletions src/resources/HttpResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export type Endpoint = string | ((id?: string) => string);

export type HttpContext<TApi extends HttpApi = HttpApi> = Context & {
readonly httpApi: TApi;
readonly options?: {
throwOnEmptyStringPaths?: boolean;
};
};

export type HttpResourceOptions = {
Expand Down Expand Up @@ -56,6 +59,15 @@ export default class HttpResource<

getPath(id?: string) {
const endpoint = this._endpoint;

// this protects against GraphQL inputs of empty string hitting list endpoints
// example: GET /users/<user_id> with an empty string user_id will request /users
if (this.context.options?.throwOnEmptyStringPaths && id === '') {
throw new TypeError(
"id can't be an empty string; pass a value or undefined",
);
}

if (typeof endpoint === 'function') {
return endpoint(id);
}
Expand Down
19 changes: 19 additions & 0 deletions test/HttpResource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,23 @@

expect(await resource.delete('5')).toEqual(null);
});

it('should throw an error if an empty string is provided and the option is enabled', async () => {
const data = [{ spicy: true }, { spicy: true }, { spicy: true }];
mockedFetch.get('https://gateway/v1/salads', {

Check failure on line 141 in test/HttpResource.test.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

Cannot find name 'mockedFetch'.

Check failure on line 141 in test/HttpResource.test.ts

View workflow job for this annotation

GitHub Actions / build (24.x)

Cannot find name 'mockedFetch'.

Check failure on line 141 in test/HttpResource.test.ts

View workflow job for this annotation

GitHub Actions / build (25.x)

Cannot find name 'mockedFetch'.
status: 200,
body: { data },
});

const resource = new HttpResource(mockContext, { endpoint: 'salads' });
expect(await resource.get('')).toEqual(data);

mockContext.options = { throwOnEmptyStringPaths: true };
expect(() => {
resource.get('');
}).toThrowErrorMatchingInlineSnapshot(
`"id can't be an empty string; pass a value or undefined"`,
);
expect(await resource.get()).toEqual(data);
});
});
Loading