'use strict'
const stream = require('stream')
const Decoder = require('./decoder')
const utils = require('./utils')
const NoFilter = require('nofilter')
const {MT, SYMS} = require('./constants')
function normalizeOptions(opts, cb) {
switch (typeof opts) {
case 'function':
return {options: {}, cb: (opts)}
case 'string':
return {options: {encoding: (opts)}, cb}
case 'object':
return {options: opts || {}, cb}
default:
throw new TypeError('Unknown option type')
}
}
class Diagnose extends stream.Transform {
constructor(options = {}) {
const {
separator = '\n',
stream_errors = false,
tags,
max_depth,
preferWeb,
encoding,
...superOpts
} = options
super({
...superOpts,
readableObjectMode: false,
writableObjectMode: false,
})
this.float_bytes = -1
this.separator = separator
this.stream_errors = stream_errors
this.parser = new Decoder({
tags,
max_depth,
preferWeb,
encoding,
})
this.parser.on('more-bytes', this._on_more.bind(this))
this.parser.on('value', this._on_value.bind(this))
this.parser.on('start', this._on_start.bind(this))
this.parser.on('stop', this._on_stop.bind(this))
this.parser.on('data', this._on_data.bind(this))
this.parser.on('error', this._on_error.bind(this))
}
_transform(fresh, encoding, cb) {
this.parser.write(fresh, encoding, cb)
}
_flush(cb) {
this.parser._flush(er => {
if (this.stream_errors) {
if (er) {
this._on_error(er)
}
return cb()
}
return cb(er)
})
}
static diagnose(input, options = {}, cb = null) {
if (input == null) {
throw new TypeError('input required')
}
({options, cb} = normalizeOptions(options, cb))
const {encoding = 'hex', ...opts} = options
const bs = new NoFilter()
const d = new Diagnose(opts)
let p = null
if (typeof cb === 'function') {
d.on('end', () => cb(null, bs.toString('utf8')))
d.on('error', cb)
} else {
p = new Promise((resolve, reject) => {
d.on('end', () => resolve(bs.toString('utf8')))
d.on('error', reject)
})
}
d.pipe(bs)
utils.guessEncoding(input, encoding).pipe(d)
return p
}
_on_error(er) {
if (this.stream_errors) {
this.push(er.toString())
} else {
this.emit('error', er)
}
}
_on_more(mt, len, parent_mt, pos) {
if (mt === MT.SIMPLE_FLOAT) {
this.float_bytes = {
2: 1,
4: 2,
8: 3,
}[len]
}
}
_fore(parent_mt, pos) {
switch (parent_mt) {
case MT.BYTE_STRING:
case MT.UTF8_STRING:
case MT.ARRAY:
if (pos > 0) {
this.push(', ')
}
break
case MT.MAP:
if (pos > 0) {
if (pos % 2) {
this.push(': ')
} else {
this.push(', ')
}
}
}
}
_on_value(val, parent_mt, pos) {
if (val === SYMS.BREAK) {
return
}
this._fore(parent_mt, pos)
const fb = this.float_bytes
this.float_bytes = -1
this.push(utils.cborValueToString(val, fb))
}
_on_start(mt, tag, parent_mt, pos) {
this._fore(parent_mt, pos)
switch (mt) {
case MT.TAG:
this.push(`${tag}(`)
break
case MT.ARRAY:
this.push('[')
break
case MT.MAP:
this.push('{')
break
case MT.BYTE_STRING:
case MT.UTF8_STRING:
this.push('(')
break
}
if (tag === SYMS.STREAM) {
this.push('_ ')
}
}
_on_stop(mt) {
switch (mt) {
case MT.TAG:
this.push(')')
break
case MT.ARRAY:
this.push(']')
break
case MT.MAP:
this.push('}')
break
case MT.BYTE_STRING:
case MT.UTF8_STRING:
this.push(')')
break
}
}
_on_data() {
this.push(this.separator)
}
}
module.exports = Diagnose