lib/index.js
const STATUS_CODES = require('http').STATUS_CODES
const BASE_URI = ''
const CONTENT_TYPE = 'application/problem+json'
const DEFAULT_TYPE = 'about:blank'
const ERR_STATUS = '"status" must be a valid HTTP Error Status Code ([RFC7231], Section 6)'
const ERR_TITLE = 'missing "title". a short, human-readable summary of the problem type'
const STATUS_CODES_WEB = 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/'
// polyfill
require('./polyfill')
module.exports = class Problem extends Error {
constructor () {
const args = Array.from(arguments)
const status = args.shift()
let base = Problem.BASE_URI || BASE_URI
let members
let title
let type
let i = 3
while (i--) {
if (typeof args[i] === 'object') {
members = args.pop()
title = args.shift()
type = args.pop()
}
}
if (args.length) {
title = args.shift()
type = args.shift()
}
if (!type) {
type = Problem.DEFAULT_TYPE || DEFAULT_TYPE
}
if (!status) {
throw new Error(ERR_STATUS)
}
const statusNumber = Number(status)
if ((statusNumber >= 600 || statusNumber < 400) && statusNumber !== 207) {
throw new Error(ERR_STATUS)
}
if (!title && STATUS_CODES.hasOwnProperty(status)) {
title = STATUS_CODES[status]
}
if (type === DEFAULT_TYPE) {
type = STATUS_CODES_WEB + status
// reset base_uri
base = ''
}
if (!title) {
throw new Error(ERR_TITLE)
}
super(`[${String(status)}] ${String(title)} (${base + String(type)})`)
this.type = base + String(type)
this.title = String(title)
this.status = Number(status)
if (members && members.instance) {
members.instance = base + members.instance
}
if (members) {
Object.assign(this, members)
}
}
toString () {
return `[${this.status}] ${this.title} (${this.type})`
}
toObject () {
// escape the Error class
return Object.fromEntries(new Map(Object.entries(this)))
}
send (response, space) {
response.writeHead(this.status, { 'Content-Type': CONTENT_TYPE })
response.end(JSON.stringify(this, null, space))
}
}