Skip to content

Commit

Permalink
Don't wrap existing errors (#37)
Browse files Browse the repository at this point in the history
So that when `abstract-leveldown` starts using `level-errors`, and
such a db is wrapped with `levelup`, `levelup` will not rewrap
errors and lose stack trace information in the process. I.e.:

```
// Error created by abstract-leveldown
const err = new ReadError('example')

// Error created by levelup
const wrapped = new ReadError(err)

assert(wrapped === err)
```

Ref Level/community#58
  • Loading branch information
vweevers committed Sep 24, 2021
1 parent ba60a4f commit 189f2b1
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 12 deletions.
43 changes: 37 additions & 6 deletions errors.js
@@ -1,18 +1,49 @@
'use strict'

const createError = require('errno').create
const LevelUPError = createError('LevelUPError')
const NotFoundError = createError('NotFoundError', LevelUPError)
function createError (type, Proto) {
const Err = function (message, cause) {
if (typeof message === 'object' && message !== null) {
// Can be passed just a cause
cause = cause || message
message = message.message || message.name
}

NotFoundError.prototype.notFound = true
NotFoundError.prototype.status = 404
message = message || ''
cause = cause || undefined

// If input is already of type, return as-is to keep its stack trace.
// Avoid instanceof, for when node_modules has multiple copies of level-errors.
if (typeof cause === 'object' && cause.type === type && cause.message === message) {
return cause
}

Object.defineProperty(this, 'type', { value: type, enumerable: false, writable: true, configurable: true })
Object.defineProperty(this, 'name', { value: type, enumerable: false, writable: true, configurable: true })
Object.defineProperty(this, 'cause', { value: cause, enumerable: false, writable: true, configurable: true })
Object.defineProperty(this, 'message', { value: message, enumerable: false, writable: true, configurable: true })

Error.call(this)

if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, Err)
}
}

Err.prototype = new Proto()
return Err
}

const LevelUPError = createError('LevelUPError', Error)

module.exports = {
LevelUPError: LevelUPError,
InitializationError: createError('InitializationError', LevelUPError),
OpenError: createError('OpenError', LevelUPError),
ReadError: createError('ReadError', LevelUPError),
WriteError: createError('WriteError', LevelUPError),
NotFoundError: NotFoundError,
NotFoundError: createError('NotFoundError', LevelUPError),
EncodingError: createError('EncodingError', LevelUPError)
}

module.exports.NotFoundError.prototype.notFound = true
module.exports.NotFoundError.prototype.status = 404
4 changes: 1 addition & 3 deletions package.json
Expand Up @@ -19,9 +19,7 @@
"LICENSE.md",
"UPGRADING.md"
],
"dependencies": {
"errno": "^1.0.0"
},
"dependencies": {},
"devDependencies": {
"airtap": "^4.0.3",
"airtap-playwright": "^1.0.1",
Expand Down
86 changes: 83 additions & 3 deletions test.js
Expand Up @@ -3,20 +3,100 @@
const test = require('tape')
const errors = require('.')

test('all errors are instances of LevelUPError', function (t) {
test('all errors are instances of Error and LevelUPError', function (t) {
const LevelUPError = errors.LevelUPError
const keys = Object.keys(errors)

keys.forEach(function (key) {
t.ok(new errors[key]() instanceof Error)
t.ok(new errors[key]() instanceof LevelUPError)
})

t.end()
})

test('error with message has expected properties', function (t) {
const error = new errors.ReadError('foo')

t.is(error.type, 'ReadError')
t.is(error.name, 'ReadError')
t.is(error.cause, undefined)
t.is(error.message, 'foo')
t.end()
})

test('error without message has expected properties', function (t) {
const error = new errors.ReadError()

t.is(error.type, 'ReadError')
t.is(error.name, 'ReadError')
t.is(error.cause, undefined)
t.is(error.message, '')
t.end()
})

test('error with cause has expected properties', function (t) {
const cause = new Error('foo')
const error = new errors.ReadError(cause)

t.is(error.type, 'ReadError')
t.is(error.name, 'ReadError')
t.is(error.cause, cause)
t.is(error.message, 'foo')
t.end()
})

test('error with message and cause has expected properties', function (t) {
const cause = new Error('foo')
const error = new errors.ReadError('bar', cause)

t.is(error.type, 'ReadError')
t.is(error.name, 'ReadError')
t.is(error.cause, cause)
t.is(error.message, 'bar')
t.end()
})

test('NotFoundError has special properties', function (t) {
const error = new errors.NotFoundError()
t.equal(error.notFound, true)
t.equal(error.status, 404)
t.is(error.notFound, true)
t.is(error.status, 404)
t.end()
})

test('error message is writable to mirror node core conventions', function (t) {
const error = new errors.WriteError('foo')
error.message = 'Got error: ' + error.message
t.is(error.message, 'Got error: foo')
t.end()
})

test('returns original instance if cause is the same type', function (t) {
const cause = new errors.NotFoundError('Key not found in database [foo]')
const error = new errors.NotFoundError(cause)
t.is(cause, error, 'same instance')
t.is(error.message, 'Key not found in database [foo]')
t.end()
})

test('returns new instance if cause prototype is different', function (t) {
const cause = new errors.NotFoundError('Key not found in database [foo]')
const error = new errors.WriteError(cause)
t.isNot(cause, error, 'new instance')
t.is(error.message, 'Key not found in database [foo]')
t.end()
})

test('returns original instance if message and cause are the same', function (t) {
const cause = new errors.NotFoundError('Key not found in database [foo]')
const error = new errors.NotFoundError('Key not found in database [foo]', cause)
t.is(cause, error, 'same instance')
t.end()
})

test('returns new instance if message is different', function (t) {
const cause = new errors.NotFoundError('Key not found in database [foo]')
const error = new errors.NotFoundError('Key not found in database [bar]', cause)
t.isNot(cause, error, 'new instance')
t.end()
})

0 comments on commit 189f2b1

Please sign in to comment.