Skip to content

Commit 46983a6

Browse files
authored
Propagate axios errors 236 (#246)
* Propagate axios errors to stream client Fixes issue #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. * Removing extra blank line * Correcting README stream function names The examples of several stream functions we not named *AsStream. Co-authored-by: Byron Murgatroyd <byron@omanom.com>
1 parent 2e20d07 commit 46983a6

File tree

3 files changed

+82
-12
lines changed

3 files changed

+82
-12
lines changed

README.md

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,9 @@ nano.db.list().then((body) => {
355355
Lists all the CouchDB databases as a stream:
356356

357357
```js
358-
nano.db.list().pipe(process.stdout);
358+
nano.db.listAsStream()
359+
.on('error', (e) => console.error('error', e))
360+
.pipe(process.stdout);
359361
```
360362

361363
### nano.db.compact(name, [designname], [callback])
@@ -623,7 +625,9 @@ alice.list({include_docs: true}).then((body) => {
623625
List all the docs in the database as a stream.
624626

625627
```js
626-
alice.list().pipe(process.stdout)
628+
alice.listAsStream()
629+
.on('error', (e) => console.error('error', e))
630+
.pipe(process.stdout)
627631
```
628632

629633
### db.fetch(docnames, [params], [callback])
@@ -881,10 +885,14 @@ Fetch documents from a partition as a stream:
881885

882886
```js
883887
// fetch document id/revs from a partition
884-
nano.db.partitionedListAsStream('canidae').pipe(process.stdout)
888+
nano.db.partitionedListAsStream('canidae')
889+
.on('error', (e) => console.error('error', e))
890+
.pipe(process.stdout)
885891

886892
// add document bodies but limit size of response
887-
nano.db.partitionedListAsStream('canidae', { include_docs: true, limit: 5 }).pipe(process.stdout)
893+
nano.db.partitionedListAsStream('canidae', { include_docs: true, limit: 5 })
894+
.on('error', (e) => console.error('error', e))
895+
.pipe(process.stdout)
888896
```
889897

890898
### db.partitionedFind(partitionKey, query, [params])
@@ -902,7 +910,9 @@ Query documents from a partition by supplying a Mango selector as a stream:
902910

903911
```js
904912
// find document whose name is 'wolf' in the 'canidae' partition
905-
db.partitionedFindAsStream('canidae', { 'selector' : { 'name': 'Wolf' }}).pipe(process.stdout)
913+
db.partitionedFindAsStream('canidae', { 'selector' : { 'name': 'Wolf' }})
914+
.on('error', (e) => console.error('error', e))
915+
.pipe(process.stdout)
906916
```
907917

908918
### db.partitionedSearch(partitionKey, designName, searchName, params, [callback])
@@ -925,7 +935,9 @@ Search documents from a partition by supplying a Lucene query as a stream:
925935
const params = {
926936
q: 'name:\'Wolf\''
927937
}
928-
db.partitionedSearchAsStream('canidae', 'search-ddoc', 'search-index', params).pipe(process.stdout)
938+
db.partitionedSearchAsStream('canidae', 'search-ddoc', 'search-index', params)
939+
.on('error', (e) => console.error('error', e))
940+
.pipe(process.stdout)
929941
// { total_rows: ... , bookmark: ..., rows: [ ...] }
930942
```
931943

@@ -953,7 +965,9 @@ const params = {
953965
endkey: 'b',
954966
limit: 1
955967
}
956-
db.partitionedView('canidae', 'view-ddoc', 'view-name', params).pipe(process.stdout)
968+
db.partitionedViewAsStream('canidae', 'view-ddoc', 'view-name', params)
969+
.on('error', (e) => console.error('error', e))
970+
.pipe(process.stdout)
957971
// { rows: [ { key: ... , value: [Object] } ] }
958972
```
959973

@@ -1031,7 +1045,9 @@ alice.attachment.get('rabbit', 'rabbit.png').then((body) => {
10311045
```js
10321046
const fs = require('fs');
10331047

1034-
alice.attachment.getAsStream('rabbit', 'rabbit.png').pipe(fs.createWriteStream('rabbit.png'));
1048+
alice.attachment.getAsStream('rabbit', 'rabbit.png')
1049+
.on('error', (e) => console.error('error', e))
1050+
.pipe(fs.createWriteStream('rabbit.png'));
10351051
```
10361052

10371053
### db.attachment.destroy(docname, attname, [params], [callback])
@@ -1100,7 +1116,9 @@ alice.view('characters', 'happy_ones', { include_docs: true }).then((body) => {
11001116
Same as `db.view` but returns a stream:
11011117

11021118
```js
1103-
alice.view('characters', 'happy_ones', {reduce: false}).pipe(process.stdout);
1119+
alice.viewAsStream('characters', 'happy_ones', {reduce: false})
1120+
.on('error', (e) => console.error('error', e))
1121+
.pipe(process.stdout);
11041122
```
11051123

11061124
### db.viewWithList(designname, viewname, listname, [params], [callback])
@@ -1227,7 +1245,9 @@ const q = {
12271245
fields: [ "name", "age", "tags", "url" ],
12281246
limit:50
12291247
};
1230-
alice.findAsStream(q).pipe(process.stdout);
1248+
alice.findAsStream(q)
1249+
.on('error', (e) => console.error('error', e))
1250+
.pipe(process.stdout);
12311251
```
12321252

12331253
## using cookie authentication
@@ -1307,7 +1327,9 @@ You can pipe the return values of certain nano functions like other stream. For
13071327
const fs = require('fs');
13081328
const nano = require('nano')('http://127.0.0.1:5984/');
13091329
const alice = nano.use('alice');
1310-
alice.attachment.getAsStream('rabbit', 'picture.png').pipe(fs.createWriteStream('/tmp/rabbit.png'));
1330+
alice.attachment.getAsStream('rabbit', 'picture.png')
1331+
.on('error', (e) => console.error('error', e))
1332+
.pipe(fs.createWriteStream('/tmp/rabbit.png'));
13111333
```
13121334
13131335
then open `/tmp/rabbit.png` and you will see the rabbit picture.

lib/nano.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,33 @@ module.exports = exports = function dbScope (cfg) {
193193
}
194194
}
195195

196+
const streamResponseHandler = function (response, req, stream) {
197+
const statusCode = response.status || (response.response && response.response.status) || 500
198+
if (response.isAxiosError && response.response) {
199+
response = response.response
200+
}
201+
const message = response.statusText
202+
203+
const responseHeaders = Object.assign({
204+
uri: req.url,
205+
statusCode: statusCode
206+
}, response.headers)
207+
208+
const error = new Error(message)
209+
error.scope = 'couch'
210+
error.statusCode = statusCode
211+
error.request = req
212+
error.headers = responseHeaders
213+
error.errid = 'non_200'
214+
error.name = 'Error'
215+
error.description = message
216+
error.reason = message
217+
218+
log({ err: 'couch', body: message, headers: responseHeaders })
219+
220+
stream.emit('error', error)
221+
}
222+
196223
function relax (opts, callback) {
197224
if (typeof opts === 'function') {
198225
callback = opts
@@ -345,7 +372,12 @@ module.exports = exports = function dbScope (cfg) {
345372
if (opts.stream) {
346373
// return the Request object for streaming
347374
const outStream = new stream.PassThrough()
348-
axios(req).then((response) => { response.data.pipe(outStream) })
375+
axios(req)
376+
.then((response) => {
377+
response.data.pipe(outStream)
378+
}).catch(e => {
379+
streamResponseHandler(e, req, outStream)
380+
})
349381
return outStream
350382
} else {
351383
if (typeof callback === 'function') {

test/attachment.getAsStream.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,19 @@ test('should be able to get an attachment as a stream - GET /db/id/attname - db.
4141
})
4242
})
4343
})
44+
45+
test('should emit an error when stream attachment does not exist - GET /db/id/attname - db.attachment.getAsStream', () => {
46+
// test GET /db/id/attname
47+
nock(COUCH_URL)
48+
.get('/db/id/notexists.gif')
49+
.reply(404, 'Object Not Found', { 'content-type': 'application/json' })
50+
51+
return new Promise((resolve, reject) => {
52+
const db = nano.db.use('db')
53+
db.attachment.getAsStream('id', 'notexist.gif')
54+
.on('error', (e) => {
55+
expect(e.statusCode).toStrictEqual(404)
56+
resolve()
57+
})
58+
})
59+
})

0 commit comments

Comments
 (0)