Skip to content

Commit

Permalink
Propagate axios errors to stream client
Browse files Browse the repository at this point in the history
Fixes issue apache#236.

When calling an *AsStream method a client needs to handle errors
raised by couch. These errors can include retrieving an attachment
that doesn't exist or querying a deleted view.
  • Loading branch information
Byron Murgatroyd committed Dec 27, 2020
1 parent 64ba7c4 commit 60c8f24
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 4 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Note the minimum required version of Node.js is 10.
- [db.fetch(docnames, [params], [callback])](#dbfetchdocnames-params-callback)
- [db.fetchRevs(docnames, [params], [callback])](#dbfetchrevsdocnames-params-callback)
- [db.createIndex(indexDef, [callback])](#dbcreateindexindexdef-callback)
- [db.changesReader...](#reading-changes-feed)
- [db.changesReader...](##reading-changes-feed)
- [Partitioned database functions](#partition-functions)
- [db.partitionInfo(partitionKey, [callback])](#dbpartitioninfopartitionkey-callback))
- [db.partitionedList(partitionKey, [params], [callback])](#dbpartitionedlistpartitionkey-params-callback)
Expand Down Expand Up @@ -1031,7 +1031,9 @@ alice.attachment.get('rabbit', 'rabbit.png').then((body) => {
```js
const fs = require('fs');

alice.attachment.getAsStream('rabbit', 'rabbit.png').pipe(fs.createWriteStream('rabbit.png'));
alice.attachment.getAsStream('rabbit', 'rabbit.png')
.on('error', (e) => console.error(e))
.pipe(fs.createWriteStream('rabbit.png'));
```

### db.attachment.destroy(docname, attname, [params], [callback])
Expand Down
37 changes: 35 additions & 2 deletions lib/nano.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { URL } = require('url')
const assert = require('assert')
const querystring = require('qs')
const axios = require('axios').default
const INVALID_PARAMETERS_ERROR = new Error('Invalid parameters')
const stream = require('stream')
const http = require('http')
const https = require('https')
Expand Down Expand Up @@ -46,7 +47,7 @@ function missing (...params) {
}

// calls the supplied callback or if not supplied, returns a rejected promise
function callbackOrRejectError (callback, error = new Error('Invalid parameters')) {
function callbackOrRejectError (callback, error = INVALID_PARAMETERS_ERROR) {
if (callback) {
return callback(error, null)
} else {
Expand Down Expand Up @@ -189,6 +190,33 @@ module.exports = exports = function dbScope (cfg) {
}
}

function streamResponseHandler (response, req, stream) {
const statusCode = response.status || (response.response && response.response.status) || 500
if (response.isAxiosError && response.response) {
response = response.response
}
const message = response.statusText

const responseHeaders = Object.assign({
uri: req.url,
statusCode: statusCode
}, response.headers)

const error = new Error(message)
error.scope = 'couch'
error.statusCode = statusCode
error.request = req
error.headers = responseHeaders
error.errid = 'non_200'
error.name = 'Error'
error.description = message
error.reason = message

log({ err: 'couch', body: message, headers: responseHeaders })

stream.emit('error', error)
}

function relax (opts, callback) {
if (typeof opts === 'function') {
callback = opts
Expand Down Expand Up @@ -339,7 +367,12 @@ module.exports = exports = function dbScope (cfg) {
if (opts.stream) {
// return the Request object for streaming
const outStream = new stream.PassThrough()
axios(req).then((response) => { response.data.pipe(outStream) })
axios(req)
.then((response) => {
response.data.pipe(outStream)
}).catch(e => {
streamResponseHandler(e, req, outStream)
})
return outStream
} else {
if (typeof callback === 'function') {
Expand Down
16 changes: 16 additions & 0 deletions test/attachment.getAsStream.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,19 @@ test('should be able to get an attachment as a stream - GET /db/id/attname - db.
})
})
})

test('should emit an error when stream attachment does not exist - GET /db/id/attname - db.attachment.getAsStream', () => {
// test GET /db/id/attname
nock(COUCH_URL)
.get('/db/id/notexists.gif')
.reply(404, 'Object Not Found', { 'content-type': 'application/json' })

return new Promise((resolve, reject) => {
const db = nano.db.use('db')
db.attachment.getAsStream('id', 'notexist.gif')
.on('error', (e) => {
expect(e.statusCode).toStrictEqual(404)
resolve()
})
})
})

0 comments on commit 60c8f24

Please sign in to comment.