Skip to content

Commit

Permalink
feat: allow custom error constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
gajus committed Apr 14, 2017
1 parent a0cc27b commit 96b6822
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 30 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ A PostgreSQL client with strict types and assertions.
* [`many`](#many)
* [`maybeOne`](#maybeone)
* [`one`](#one)
* [Overriding Errors](#overriding-errors)
* [Error handling](#error-handling)
* [Handling `NotFoundError`](#handling-notfounderror)
* [Handling `DataIntegrityError`](#handling-dataintengrityerror)
Expand Down Expand Up @@ -212,6 +213,28 @@ const row = await connection.one('SELECT foo');
> `knex` is a query builder; it does not assert the value of the result.
> Mightyql `one` adds assertions about the result of the query.
## Overriding Errors

Overriding the error constructor used by Mightyql allows you to map database layer errors to your application errors.

```js
import {
createPool
} from 'mightyql';

class NotFoundError extends Error {};

createPool('postgres://', {
errors: {
NotFoundError
}
});
```

The following error types can be overridden:

* `NotFoundError`

## Error handling

### Handling `NotFoundError`
Expand Down
47 changes: 29 additions & 18 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
normalizeNamedValuePlaceholders
} from './utilities';
import type {
ClientConfigurationType,
DatabaseConfigurationType,
DatabasePoolConnectionType,
DatabaseSingleConnectionType,
Expand Down Expand Up @@ -100,11 +101,13 @@ export const query: InternalQueryType = async (connection, sql, values) => {
* @throws NotFoundError If query returns no rows.
* @throws DataIntegrityError If query returns multiple rows.
*/
export const one: InternalQueryOneType = async (connection, sql, values) => {
export const one: InternalQueryOneType = async (connection, clientConfiguration, sql, values) => {
const {rows} = await query(connection, sql, values);

if (rows.length === 0) {
throw new NotFoundError();
const ConfigurableNotFoundError = clientConfiguration.errors && clientConfiguration.errors.NotFoundError ? clientConfiguration.errors.NotFoundError : NotFoundError;

throw new ConfigurableNotFoundError();
}

if (rows.length > 1) {
Expand All @@ -119,7 +122,7 @@ export const one: InternalQueryOneType = async (connection, sql, values) => {
*
* @throws DataIntegrityError If query returns multiple rows.
*/
export const maybeOne: InternalQueryMaybeOneType = async (connection, sql, values) => {
export const maybeOne: InternalQueryMaybeOneType = async (connection, clientConfiguration, sql, values) => {
const {
rows
} = await query(connection, sql, values);
Expand All @@ -140,11 +143,13 @@ export const maybeOne: InternalQueryMaybeOneType = async (connection, sql, value
*
* @throws NotFoundError If query returns no rows.
*/
export const many: InternalQueryManyType = async (connection, sql, values) => {
export const many: InternalQueryManyType = async (connection, clientConfiguration, sql, values) => {
const {rows} = await query(connection, sql, values);

if (rows.length === 0) {
throw new NotFoundError();
const ConfigurableNotFoundError = clientConfiguration.errors && clientConfiguration.errors.NotFoundError ? clientConfiguration.errors.NotFoundError : NotFoundError;

throw new ConfigurableNotFoundError();
}

return rows;
Expand All @@ -153,39 +158,45 @@ export const many: InternalQueryManyType = async (connection, sql, values) => {
/**
* Makes a query and expects any number of results.
*/
export const any: InternalQueryAnyType = async (connection, sql, values) => {
export const any: InternalQueryAnyType = async (connection, clientConfiguration, sql, values) => {
const {rows} = await query(connection, sql, values);

return rows;
};

const createConnection = async (configuration: DatabaseConfigurationType): Promise<DatabaseSingleConnectionType> => {
const pool = new pg.Pool(configuration);
const createConnection = async (
connectionConfiguration: DatabaseConfigurationType,
clientConfiguration: ClientConfigurationType
): Promise<DatabaseSingleConnectionType> => {
const pool = new pg.Pool(connectionConfiguration);

const connection = await pool.connect();

return {
any: any.bind(null, connection),
any: any.bind(null, connection, clientConfiguration),
end: async () => {
await connection.release();

return pool.end();
},
many: many.bind(null, connection),
maybeOne: maybeOne.bind(null, connection),
one: one.bind(null, connection),
many: many.bind(null, connection, clientConfiguration),
maybeOne: maybeOne.bind(null, connection, clientConfiguration),
one: one.bind(null, connection, clientConfiguration),
query: query.bind(null, connection)
};
};

const createPool = (configuration: DatabaseConfigurationType): DatabasePoolConnectionType => {
const pool = new pg.Pool(typeof configuration === 'string' ? parseConnectionString(configuration) : configuration);
const createPool = (
connectionConfiguration: DatabaseConfigurationType,
clientConfiguration: ClientConfigurationType
): DatabasePoolConnectionType => {
const pool = new pg.Pool(typeof connectionConfiguration === 'string' ? parseConnectionString(connectionConfiguration) : connectionConfiguration);

return {
any: any.bind(null, pool),
many: many.bind(null, pool),
maybeOne: maybeOne.bind(null, pool),
one: one.bind(null, pool),
any: any.bind(null, pool, clientConfiguration),
many: many.bind(null, pool, clientConfiguration),
maybeOne: maybeOne.bind(null, pool, clientConfiguration),
one: one.bind(null, pool, clientConfiguration),
query: query.bind(null, pool)
};
};
Expand Down
39 changes: 35 additions & 4 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ type InternalDatabaseConnectionType = any;

export type DatabaseConnectionUriType = string;

export type ClientErrorsConfigurationType = {|
+NotFoundError?: Class<Error>
|};

export type ClientConfigurationType = {|
+errors?: ClientErrorsConfigurationType
|};

export type DatabaseConfigurationType = DatabaseConnectionUriType |
{|
+database?: string,
Expand Down Expand Up @@ -60,10 +68,33 @@ export type DatabaseQueryValuesType =
AnonymouseValuePlaceholderValuesType |
NamedValuePlaceholderValuesType;

export type InternalQueryAnyType = (connection: InternalDatabaseConnectionType, sql: string, values?: DatabaseQueryValuesType) => Promise<$ReadOnlyArray<QueryResultRowType>>;
export type InternalQueryManyType = (connection: InternalDatabaseConnectionType, sql: string, values?: DatabaseQueryValuesType) => Promise<$ReadOnlyArray<QueryResultRowType>>;
export type InternalQueryMaybeOneType = (connection: InternalDatabaseConnectionType, sql: string, values?: DatabaseQueryValuesType) => Promise<QueryResultRowType | null>;
export type InternalQueryOneType = (connection: InternalDatabaseConnectionType, sql: string, values?: DatabaseQueryValuesType) => Promise<QueryResultRowType>;
export type InternalQueryAnyType = (
connection: InternalDatabaseConnectionType,
clientConfiguration: ClientConfigurationType,
sql: string,
values?: DatabaseQueryValuesType
) => Promise<$ReadOnlyArray<QueryResultRowType>>;

export type InternalQueryManyType = (
connection: InternalDatabaseConnectionType,
clientConfiguration: ClientConfigurationType,
sql: string,
values?: DatabaseQueryValuesType
) => Promise<$ReadOnlyArray<QueryResultRowType>>;

export type InternalQueryMaybeOneType = (
connection: InternalDatabaseConnectionType,
clientConfiguration: ClientConfigurationType,
sql: string,
values?: DatabaseQueryValuesType
) => Promise<QueryResultRowType | null>;

export type InternalQueryOneType = (
connection: InternalDatabaseConnectionType,
clientConfiguration: ClientConfigurationType,
sql: string,
values?: DatabaseQueryValuesType
) => Promise<QueryResultRowType>;

// eslint-disable-next-line flowtype/no-weak-types
export type InternalQueryType = (connection: InternalDatabaseConnectionType, sql: string, values?: DatabaseQueryValuesType) => Promise<any>;
Expand Down
25 changes: 23 additions & 2 deletions test/mightyql/many.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

import test from 'ava';
import sinon from 'sinon';
import ExtendableError from 'es6-error';
import {
many,
NotFoundError
} from '../../src';

class TestError extends ExtendableError {}

test('returns the query results rows', async (t) => {
const stub = sinon.stub().returns({
rows: [
Expand All @@ -25,7 +28,7 @@ test('returns the query results rows', async (t) => {
query: stub
};

const result = await many(connection, 'SELECT foo FROM bar');
const result = await many(connection, {}, 'SELECT foo FROM bar');

t.deepEqual(result, [
{
Expand All @@ -46,5 +49,23 @@ test('throws an error if no rows are returned', async (t) => {
query: stub
};

await t.throws(many(connection, 'SELECT foo FROM bar'), NotFoundError);
await t.throws(many(connection, {}, 'SELECT foo FROM bar'), NotFoundError);
});

test('throws an error if no rows are returned (user defined error constructor)', async (t) => {
const stub = sinon.stub().returns({
rows: []
});

const connection: any = {
query: stub
};

const clientConfiguration = {
errors: {
NotFoundError: TestError
}
};

await t.throws(many(connection, clientConfiguration, 'SELECT foo FROM bar'), TestError);
});
6 changes: 3 additions & 3 deletions test/mightyql/maybeOne.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test('returns the first row', async (t) => {
query: stub
};

const result = await maybeOne(connection, 'SELECT foo FROM bar');
const result = await maybeOne(connection, {}, 'SELECT foo FROM bar');

t.deepEqual(result, {
foo: 1
Expand All @@ -38,7 +38,7 @@ test('returns null if no results', async (t) => {
query: stub
};

const result = await maybeOne(connection, 'SELECT foo FROM bar');
const result = await maybeOne(connection, {}, 'SELECT foo FROM bar');

t.true(result === null);
});
Expand All @@ -59,5 +59,5 @@ test('throws an error if more than one row is returned', async (t) => {
query: stub
};

await t.throws(maybeOne(connection, 'SELECT foo FROM bar'), DataIntegrityError);
await t.throws(maybeOne(connection, {}, 'SELECT foo FROM bar'), DataIntegrityError);
});
27 changes: 24 additions & 3 deletions test/mightyql/one.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

import test from 'ava';
import sinon from 'sinon';
import ExtendableError from 'es6-error';
import {
one,
DataIntegrityError,
NotFoundError
} from '../../src';

class TestError extends ExtendableError {}

test('returns the first row', async (t) => {
const stub = sinon.stub().returns({
rows: [
Expand All @@ -23,7 +26,7 @@ test('returns the first row', async (t) => {
query: stub
};

const result = await one(connection, 'SELECT foo FROM bar');
const result = await one(connection, {}, 'SELECT foo FROM bar');

t.deepEqual(result, {
foo: 1
Expand All @@ -39,7 +42,25 @@ test('throws an error if no rows are returned', async (t) => {
query: stub
};

await t.throws(one(connection, 'SELECT foo FROM bar'), NotFoundError);
await t.throws(one(connection, {}, 'SELECT foo FROM bar'), NotFoundError);
});

test('throws an error if no rows are returned (user defined error constructor)', async (t) => {
const stub = sinon.stub().returns({
rows: []
});

const connection: any = {
query: stub
};

const clientConfiguration = {
errors: {
NotFoundError: TestError
}
};

await t.throws(one(connection, clientConfiguration, 'SELECT foo FROM bar'), TestError);
});

test('throws an error if more than one row is returned', async (t) => {
Expand All @@ -58,5 +79,5 @@ test('throws an error if more than one row is returned', async (t) => {
query: stub
};

await t.throws(one(connection, 'SELECT foo FROM bar'), DataIntegrityError);
await t.throws(one(connection, {}, 'SELECT foo FROM bar'), DataIntegrityError);
});

0 comments on commit 96b6822

Please sign in to comment.