Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions API_DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ getLoaders(resources[, options])

- **`errorHandler`**

(Optional) Provide a function to wrap the underlying resource call. Useful if you want to handle 'expected' errors (e.g. 4xxs, 5xxs) before handing over to the resolver method.
(Optional) Provide a function to wrap the underlying resource call. Useful if you want to handle 'expected' errors or rejected promises from the resource function (e.g. 4xxs, 5xxs) before handing over to the resolver method.

Must return an Error object.

**Interface:**

```js
errorHandler(resourcePath: Array<string>, error: Error): Promise<Error>
(resourcePath: $ReadOnlyArray<string>, error: any): Promise<Error>,
```

- **`resourceMiddleware`**
Expand All @@ -58,7 +60,7 @@ getLoaders(resources[, options])
**Interface**:

```js
before(resourcePath: Array<string>, resourceArgs: T): Promise<T>
(resourcePath: $ReadOnlyArray<string>, resourceArgs: T): Promise<T>
```

- **`after`**
Expand All @@ -68,7 +70,7 @@ getLoaders(resources[, options])
**Interface**:

```js
after(resourcePath: Array<string>, response: T): Promise<T>
(resourcePath: $ReadOnlyArray<string>, response: T): Promise<T>
```

### Example
Expand Down
230 changes: 164 additions & 66 deletions __tests__/implementation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,69 +292,6 @@ test('batch endpoint (multiple requests)', async () => {
});
});

test('batch endpoint that throws errors', async () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this since it's the same test as

test('batch endpoint (multiple requests, default error handling)', async () => {

const config = {
resources: {
foo: {
isBatchResource: true,
docsLink: 'example.com/docs/bar',
batchKey: 'foo_ids',
newKey: 'foo_id',
},
},
};

const resources = {
foo: ({ foo_ids, include_extra_info }) => {
if (_.isEqual(foo_ids, [1, 3])) {
expect(include_extra_info).toBe(false);
throw new Error('yikes');
}

if (_.isEqual(foo_ids, [2, 4, 5])) {
expect(include_extra_info).toBe(true);
return Promise.resolve([
{
foo_id: 2,
foo_value: 'greetings',
extra_stuff: 'lorem ipsum',
},
{
foo_id: 4,
foo_value: 'greetings',
extra_stuff: 'lorem ipsum',
},
{
foo_id: 5,
foo_value: 'greetings',
extra_stuff: 'lorem ipsum',
},
]);
}
},
};

await createDataLoaders(config, async getLoaders => {
const loaders = getLoaders(resources);

const results = await loaders.foo.loadMany([
{ foo_id: 1, include_extra_info: false },
{ foo_id: 2, include_extra_info: true },
{ foo_id: 3, include_extra_info: false },
{ foo_id: 4, include_extra_info: true },
{ foo_id: 5, include_extra_info: true },
]);

expect(results).toMatchObject([
expect.toBeError(/yikes/),
{ foo_id: 2, foo_value: 'greetings', extra_stuff: 'lorem ipsum' },
expect.toBeError(/yikes/),
{ foo_id: 4, foo_value: 'greetings', extra_stuff: 'lorem ipsum' },
{ foo_id: 5, foo_value: 'greetings', extra_stuff: 'lorem ipsum' },
]);
});
});

test('batch endpoint that rejects', async () => {
const config = {
resources: {
Expand Down Expand Up @@ -408,6 +345,7 @@ test('batch endpoint that rejects', async () => {
{ foo_id: 5, include_extra_info: true },
]);

// NonError comes from the default error handler which uses ensure-error
expect(results).toMatchObject([
expect.toBeError(/yikes/, 'NonError'),
{ foo_id: 2, foo_value: 'greetings', extra_stuff: 'lorem ipsum' },
Expand All @@ -434,7 +372,7 @@ test('batch endpoint (multiple requests, default error handling)', async () => {
foo: ({ foo_ids, include_extra_info }) => {
if (_.isEqual(foo_ids, [1, 3])) {
expect(include_extra_info).toBe(false);
return new Error('yikes');
throw new Error('yikes');
}

if (_.isEqual(foo_ids, [2, 4, 5])) {
Expand Down Expand Up @@ -482,8 +420,9 @@ test('batch endpoint (multiple requests, default error handling)', async () => {
});

test('batch endpoint (multiple requests, custom error handling)', async () => {
function errorHandler(resourcePath, error) {
async function errorHandler(resourcePath, error) {
expect(resourcePath).toEqual(['foo']);
expect(error.message).toBe('yikes');
return new Error('hello from custom error handler');
}

Expand All @@ -502,7 +441,7 @@ test('batch endpoint (multiple requests, custom error handling)', async () => {
foo: ({ foo_ids, include_extra_info }) => {
if (_.isEqual(foo_ids, [1, 3])) {
expect(include_extra_info).toBe(false);
return new Error('yikes');
throw new Error('yikes');
}

if (_.isEqual(foo_ids, [2, 4, 5])) {
Expand Down Expand Up @@ -999,3 +938,162 @@ test('middleware can transform the request args and the resource response', asyn
]);
});
});

test('returning custom errors from error handler is supported', async () => {
class MyCustomError extends Error {
constructor(...args) {
super(...args);
this.name = this.constructor.name;
this.foo = 'bar';
Error.captureStackTrace(this, MyCustomError);
}
}

function errorHandler(resourcePath, error) {
expect(resourcePath).toEqual(['foo']);
expect(error.message).toBe('yikes');
return new MyCustomError('hello from custom error object');
}

const config = {
resources: {
foo: {
isBatchResource: true,
docsLink: 'example.com/docs/bar',
batchKey: 'foo_ids',
newKey: 'foo_id',
},
},
};

const resources = {
foo: ({ foo_ids, include_extra_info }) => {
if (_.isEqual(foo_ids, [1, 3])) {
expect(include_extra_info).toBe(false);
throw new Error('yikes');
}

if (_.isEqual(foo_ids, [2, 4, 5])) {
expect(include_extra_info).toBe(true);
return Promise.resolve([
{
foo_id: 2,
foo_value: 'greetings',
extra_stuff: 'lorem ipsum',
},
{
foo_id: 4,
foo_value: 'greetings',
extra_stuff: 'lorem ipsum',
},
{
foo_id: 5,
foo_value: 'greetings',
extra_stuff: 'lorem ipsum',
},
]);
}
},
};

await createDataLoaders(config, async getLoaders => {
const loaders = getLoaders(resources, { errorHandler });

const results = await loaders.foo.loadMany([
{ foo_id: 1, include_extra_info: false },
{ foo_id: 2, include_extra_info: true },
{ foo_id: 3, include_extra_info: false },
{ foo_id: 4, include_extra_info: true },
{ foo_id: 5, include_extra_info: true },
]);

expect(results).toMatchObject([
expect.toBeError(/hello from custom error object/, 'MyCustomError'),
{ foo_id: 2, foo_value: 'greetings', extra_stuff: 'lorem ipsum' },
expect.toBeError(/hello from custom error object/, 'MyCustomError'),
{ foo_id: 4, foo_value: 'greetings', extra_stuff: 'lorem ipsum' },
{ foo_id: 5, foo_value: 'greetings', extra_stuff: 'lorem ipsum' },
]);

expect(results[0]).toHaveProperty('foo', 'bar');
expect(results[2]).toHaveProperty('foo', 'bar');
});
});

test('bail if errorHandler does not return an error', async () => {
class MyCustomError extends Error {
constructor(...args) {
super(...args);
this.name = this.constructor.name;
this.foo = 'bar';
Error.captureStackTrace(this, MyCustomError);
}
}

function errorHandler(resourcePath, error) {
expect(resourcePath).toEqual(['foo']);
expect(error.message).toBe('yikes');
return 'not an Error object';
}

const config = {
resources: {
foo: {
isBatchResource: true,
docsLink: 'example.com/docs/bar',
batchKey: 'foo_ids',
newKey: 'foo_id',
},
},
};

const resources = {
foo: ({ foo_ids, include_extra_info }) => {
if (_.isEqual(foo_ids, [1, 3])) {
expect(include_extra_info).toBe(false);
throw new Error('yikes');
}

if (_.isEqual(foo_ids, [2, 4, 5])) {
expect(include_extra_info).toBe(true);
return Promise.resolve([
{
foo_id: 2,
foo_value: 'greetings',
extra_stuff: 'lorem ipsum',
},
{
foo_id: 4,
foo_value: 'greetings',
extra_stuff: 'lorem ipsum',
},
{
foo_id: 5,
foo_value: 'greetings',
extra_stuff: 'lorem ipsum',
},
]);
}
},
};

await createDataLoaders(config, async getLoaders => {
const loaders = getLoaders(resources, { errorHandler });

const results = await loaders.foo.loadMany([
{ foo_id: 1, include_extra_info: false },
{ foo_id: 2, include_extra_info: true },
{ foo_id: 3, include_extra_info: false },
{ foo_id: 4, include_extra_info: true },
{ foo_id: 5, include_extra_info: true },
]);

expect(results).toMatchObject([
expect.toBeError(/errorHandler did not return an Error object. Instead, got string: 'not an Error object'/),
{ foo_id: 2, foo_value: 'greetings', extra_stuff: 'lorem ipsum' },
expect.toBeError(/errorHandler did not return an Error object. Instead, got string: 'not an Error object'/),
{ foo_id: 4, foo_value: 'greetings', extra_stuff: 'lorem ipsum' },
{ foo_id: 5, foo_value: 'greetings', extra_stuff: 'lorem ipsum' },
]);
});
});
Loading