@toryt/contracts
Version:
Design-by-Contract and Test-by-Contract for JavaScript
109 lines (87 loc) • 4.75 kB
JavaScript
/*
Copyright 2016–2025 Jan Dockx
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const is = require('./_private/is')
const stack = require('./_private/stack')
const property = require('./_private/property')
const assert = require('assert')
const { stackEOL } = require('./_private/eol')
// eslint-disable-next-line no-secrets/no-secrets
/* Custom Error types are notoriously difficult in JavaScript.
See, e.g.,
* http://stackoverflow.com/questions/1382107/whats-a-good-way-to-extend-error-in-javascript
* http://pastebin.com/aRpPr5Sd
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Custom_Error_Types
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack
* https://nodejs.org/api/errors.html
* https://msdn.microsoft.com/en-us/library/hh699850%28v=vs.94%29.aspx?f=255&MSPPError=-2147217396
The main problems are:
* Calling Error() without new doesn't initialize this. It creates a new object, and does nothing with this.
Error.call(this) does nothing with this.
* The stack trace is filled out differently per platform, or not at all (older Internet Explorer). Safari produces a
stack that skips (optimize?) stack frames.
Some platforms fill out the stack trace on throw (which seems to be correct). Other on creation of an
Error. But, when following the regular inheritance pattern, this is at type definition time, where
we want to create an Error-instance as prototype for a custom error type. That is obviously never
the stack trace we want.
Some platforms offer a method to fill out the stack trace.
this.stack = "A String" does nothing on node.
Errors support the following properties common over all platforms:
* message
* name
* toString === name + ": " + message
A file name, lineNumber and columnNumber is standard and supported on most platforms, and evolving.
There is little or no documentation about how they are filled out.
Most platforms do support a stack property, which is a multi-line string. The first line is the message.
There are libraries to deal with these complexities, but different for node and browsers.
Furthermore, the landscape is evolving.
That we cannot call Error() to initialise a new custom error, is not a big problem. The standard syntax is
new Error([message[, fileName[, lineNumber]]]). We can set these properties directly in our constructor.
For fileName and lineNumber, we have the same problem as with the stack: we need a reference to somewhere else
than where we create the custom error.
*/
const message = 'abstract type'
/**
* ContractError is the general supertype of all errors thrown by Toryt Contracts.
* ContractError itself is to be considered abstract.
*
* The main feature of a ContractError is that it provides a safe, cross-platform stack trace.
* Instances should be frozen before they are thrown.
*
* <h3>Invariants</h3>
* <ul>
* <li>`name` is a mandatory property, and refers to a string</li>
* <li>`message` refers to a string</li>
* <li>`stack` is a read-only property, that returns a string, that starts with the instances `name`, the
* string ": ", and `message`, and is followed by stack code references, that do no contain references
* to the inner workings of the Toryt Contracts library.</li>
* </ul>
*
* @constructor
*/
function ContractError(rawStack) {
assert(is.stack(rawStack), 'rawStack is a stack')
property.setAndFreeze(this, '_rawStack', rawStack)
}
ContractError.prototype = new Error()
ContractError.prototype.constructor = ContractError
property.setAndFreeze(ContractError.prototype, 'name', ContractError.name)
property.setAndFreeze(ContractError.prototype, 'message', message)
property.setAndFreeze(ContractError.prototype, '_rawStack', stack.raw())
property.configurableDerived(ContractError.prototype, 'stack', function () {
// noinspection JSUnresolvedReference
return `${this.name}: ${this.message}` + stackEOL + this._rawStack
})
ContractError.message = message
module.exports = ContractError