Skip to content

Commit 3b250da

Browse files
committed
v2 / Errors: improve impl. and tests.
1 parent 5f08b5f commit 3b250da

File tree

4 files changed

+77
-44
lines changed

4 files changed

+77
-44
lines changed

API_v2.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,11 +305,11 @@ using `Error` subclasses. Importantly:
305305

306306
- Special care is taken to provide `message` and `stack` properties rich in
307307
info, so that no special serialization is needed to debug production errors.
308+
That is, simply logging the `stack` (as most error handlers tend to do)
309+
should get you meaningful and useful data.
308310

309311
- Structured info returned by Neo4j is also available on the `Error` instances
310312
under a `neo4j` property, for deeper introspection and analysis if desired.
311-
In addition, if this error is associated with a full HTTP response, the HTTP
312-
`statusCode`, `headers`, and `body` are available via an `http` property.
313313

314314
```coffee
315315
class Error {name, message, stack, http, neo4j}

lib-new/GraphDatabase.coffee

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
$ = require 'underscore'
2-
errors = require './errors'
2+
{Error} = require './errors'
33
lib = require '../package.json'
44
Node = require './Node'
55
Relationship = require './Relationship'
@@ -55,20 +55,10 @@ module.exports = class GraphDatabase
5555

5656
{body, headers, statusCode} = resp
5757

58-
if statusCode >= 500
59-
# TODO: Parse errors, and differentiate w/ TransientErrors.
60-
err = new errors.DatabaseError 'TODO',
61-
http: {body, headers, statusCode}
58+
if err = Error._fromResponse resp
6259
return cb err
6360

64-
if statusCode >= 400
65-
# TODO: Parse errors.
66-
err = new errors.ClientError 'TODO',
67-
http: {body, headers, statusCode}
68-
return cb err
69-
70-
# Parse nodes and relationships in the body, and return:
71-
return cb null, _transform body
61+
cb null, _transform body
7262

7363

7464
## HELPERS

lib-new/errors.coffee

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
11
$ = require 'underscore'
2+
http = require 'http'
23

34
class @Error extends Error
45

5-
Object.defineProperties @::,
6-
name: {get: -> 'neo4j.' + @constructor.name}
7-
8-
constructor: (@message='Unknown error', {@http, @neo4j}={}) ->
6+
constructor: (@message='Unknown error', @neo4j={}) ->
7+
@name = 'neo4j.' + @constructor.name
98
Error.captureStackTrace @, @constructor
109

10+
#
11+
# Accepts the given HTTP client response, and if it represents an error,
12+
# creates and returns the appropriate Error instance from it.
13+
# If the response doesn't represent an error, returns null.
14+
#
15+
@_fromResponse: (resp) ->
16+
{body, headers, statusCode} = resp
17+
18+
return null if statusCode < 400
19+
20+
ErrorType = if statusCode >= 500 then 'Database' else 'Client'
21+
ErrorClass = exports["#{ErrorType}Error"]
22+
23+
message = "#{statusCode} "
24+
logBody = statusCode >= 500 # TODO: Config to always log body?
25+
26+
if body?.exception
27+
message += "#{body.exception}: #{body.message or '(no message)'}"
28+
else
29+
statusText = http.STATUS_CODES[statusCode] # E.g. "Not Found"
30+
reqText = "#{resp.request.method} #{resp.request.path}"
31+
message += "#{statusText} response for #{reqText}"
32+
logBody = true # always log body if non-error returned
33+
34+
if logBody and body?
35+
message += ": #{JSON.stringify body, null, 4}"
36+
37+
new ErrorClass message, body
38+
1139
# TODO: Helper to rethrow native/inner errors? Not sure if we need one.
1240

1341
class @ClientError extends @Error

test-new/http._coffee

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ expectNeo4jRoot = (body) ->
4545
expect(body).to.be.an 'object'
4646
expect(body).to.have.keys 'data', 'management'
4747

48+
#
49+
# Asserts that the given object is a proper instance of the given Neo4j Error
50+
# subclass, including with the given message.
51+
# Additional checks, e.g. of the `neo4j` property's contents, are up to you.
52+
#
53+
expectError = (err, ErrorClass, message) ->
54+
expect(err).to.be.an.instanceOf ErrorClass
55+
expect(err.name).to.equal "neo4j.#{ErrorClass.name}"
56+
expect(err.neo4j).to.be.an 'object'
57+
expect(err.message).to.equal message
58+
expect(err.stack).to.contain '\n'
59+
expect(err.stack.split('\n')[0]).to.equal "#{err.name}: #{err.message}"
60+
4861

4962
## TESTS
5063

@@ -64,21 +77,19 @@ describe 'GraphDatabase::http', ->
6477

6578
expectNeo4jRoot body
6679

67-
it 'should throw errors for 4xx responses by default', (_) ->
68-
try
69-
thrown = false
70-
DB.http
71-
method: 'POST'
72-
path: '/'
73-
, _
74-
catch err
75-
thrown = true
76-
expect(err).to.be.an.instanceOf neo4j.ClientError
77-
expect(err.name).to.equal 'neo4j.ClientError'
78-
expect(err.http).to.be.an 'object'
79-
expect(err.http.statusCode).to.equal 405
80-
81-
expect(thrown).to.be.true()
80+
it 'should throw errors for 4xx responses by default', (done) ->
81+
DB.http
82+
method: 'POST'
83+
path: '/'
84+
, (err, body) ->
85+
expect(err).to.exist()
86+
expect(body).to.not.exist()
87+
88+
expectError err, neo4j.ClientError,
89+
'405 Method Not Allowed response for POST /'
90+
expect(err.neo4j).to.be.empty()
91+
92+
done()
8293

8394
it 'should support returning raw responses', (_) ->
8495
resp = DB.http
@@ -100,23 +111,27 @@ describe 'GraphDatabase::http', ->
100111

101112
expectResponse resp, 405 # Method Not Allowed
102113

103-
it 'should throw native errors always', (_) ->
114+
it 'should throw native errors always', (done) ->
104115
db = new neo4j.GraphDatabase 'http://idontexist.foobarbaz.nodeneo4j'
116+
db.http
117+
path: '/'
118+
raw: true
119+
, (err, resp) ->
120+
expect(err).to.exist()
121+
expect(resp).to.not.exist()
105122

106-
try
107-
thrown = false
108-
db.http
109-
path: '/'
110-
raw: true
111-
, _
112-
catch err
113-
thrown = true
123+
# NOTE: *Not* using `expectError` here, because we explicitly don't
124+
# wrap native (non-Neo4j) errors.
114125
expect(err).to.be.an.instanceOf Error
115126
expect(err.name).to.equal 'Error'
116127
expect(err.code).to.equal 'ENOTFOUND'
117128
expect(err.syscall).to.equal 'getaddrinfo'
129+
expect(err.message).to.contain "#{err.syscall} #{err.code}"
130+
# NOTE: Node 0.11 adds the hostname to the message.
131+
expect(err.stack).to.contain '\n'
132+
expect(err.stack.split('\n')[0]).to.equal "#{err.name}: #{err.message}"
118133

119-
expect(thrown).to.be.true()
134+
done()
120135

121136
it 'should support streaming'
122137
# Test that it immediately returns a duplex HTTP stream.

0 commit comments

Comments
 (0)