Skip to content

Commit

Permalink
Support object value assertions, and improve code quality
Browse files Browse the repository at this point in the history
  • Loading branch information
chuigda committed Sep 20, 2022
1 parent dfd70f1 commit 7d3fcd3
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 18 deletions.
18 changes: 16 additions & 2 deletions test.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// noinspection JSUnresolvedFunction

const { typeAssert, enableChainAPI, preventErrTrace } = require('./typeAssert.cjs')

preventErrTrace(true)
Expand Down Expand Up @@ -85,10 +87,22 @@ typeAssert({
]
}, compoundAssertion)

typeAssert(5, 'number'.chainWith(x => x > 0 ? true : 'no negative numbers'))
expectFailure(() => typeAssert(-1, 'number'.chainWith(x => x > 0 ? true : 'no negative numbers')))
const nonNegativeAssertion = 'number'.chainWith(x => x > 0 || 'no negative numbers')
typeAssert(5, nonNegativeAssertion)
expectFailure(() => typeAssert(-1, nonNegativeAssertion))

typeAssert(5, 'number'.assertValue(5))
expectFailure(() => typeAssert(5, 'number'.assertValue(114514)))

const objectValueAssertion = {}.assertObjectValue('number')
typeAssert({ a: 114, b: 514 }, objectValueAssertion)
expectFailure(() => typeAssert({ a: 114, b: '514' }, objectValueAssertion))

const objectValueAssertion2 = 'object?'.assertObjectValue('number'.chainWith(x => x >= 100 || 'no less than 100'))
typeAssert({ a: 114, b: 514 }, objectValueAssertion2)
typeAssert(null, objectValueAssertion2)
typeAssert(undefined, objectValueAssertion2)
expectFailure(() => typeAssert({ a: 114, b: '514' }, objectValueAssertion2))
expectFailure(() => typeAssert({ a: 114, b: 90 }, objectValueAssertion2))

console.info('mission accomplished')
18 changes: 16 additions & 2 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// noinspection JSUnresolvedFunction

import { typeAssert, enableChainAPI, preventErrTrace } from './typeAssert.js'

preventErrTrace(true)
Expand Down Expand Up @@ -85,10 +87,22 @@ typeAssert({
]
}, compoundAssertion)

typeAssert(5, 'number'.chainWith(x => x > 0 ? true : 'no negative numbers'))
expectFailure(() => typeAssert(-1, 'number'.chainWith(x => x > 0 ? true : 'no negative numbers')))
const nonNegativeAssertion = 'number'.chainWith(x => x > 0 || 'no negative numbers')
typeAssert(5, nonNegativeAssertion)
expectFailure(() => typeAssert(-1, nonNegativeAssertion))

typeAssert(5, 'number'.assertValue(5))
expectFailure(() => typeAssert(5, 'number'.assertValue(114514)))

const objectValueAssertion = {}.assertObjectValue('number')
typeAssert({ a: 114, b: 514 }, objectValueAssertion)
expectFailure(() => typeAssert({ a: 114, b: '514' }, objectValueAssertion))

const objectValueAssertion2 = 'object?'.assertObjectValue('number'.chainWith(x => x >= 100 || 'no less than 100'))
typeAssert({ a: 114, b: 514 }, objectValueAssertion2)
typeAssert(null, objectValueAssertion2)
typeAssert(undefined, objectValueAssertion2)
expectFailure(() => typeAssert({ a: 114, b: '514' }, objectValueAssertion2))
expectFailure(() => typeAssert({ a: 114, b: 90 }, objectValueAssertion2))

console.info('mission accomplished')
72 changes: 65 additions & 7 deletions typeAssert.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ const typeAssertImpl = (path, object, assertion, preventErr) => {
}
} else if (assertion.constructor === ValueAssertion.prototype.constructor) {
assertEquals(`${path}:value`, assertion.value, object, preventErr)
} else if (assertion.constructor === ObjectValueAssertion.prototype.constructor) {
typeAssertImpl(path, object, {}, preventErr)
for (const [key, value] of Object.entries(object)) {
typeAssertImpl(`${path}.${key}`, value, assertion.valueAssertion, preventErr)
}
} else if (object === undefined) {
typeAssertError(path, 'unexpected "undefined" value', preventErr)
} else if (object === null) {
Expand Down Expand Up @@ -192,18 +197,28 @@ const ValueAssertion = (function() {
return ValueAssertion
}())

const ObjectValueAssertion = (function() {
function ObjectValueAssertion(valueAssertion) {
this.valueAssertion = valueAssertion
}

return ObjectValueAssertion
}())

const enableChainAPI = methodNames => {
let orNullName = 'orNull'
let sumWithName = 'sumWith'
let chainWithName = 'chainWith'
let assertValueName = 'assertValue'

if (methodNames !== null && methodNames !== undefined) {
const { orNull, sumWith, chainWith, assertValue } = methodNames
orNullName = orNull ? orNull : orNullName
sumWithName = sumWith ? sumWith : sumWithName
chainWithName = chainWith ? chainWith : chainWithName
assertValueName = assertValue ? assertValue : assertValueName
let assertObjectValueName = 'assertObjectValue'

if (methodNames) {
const { orNull, sumWith, chainWith, assertValue, assertObjectValue } = methodNames
orNullName = orNull || orNullName
sumWithName = sumWith || sumWithName
chainWithName = chainWith || chainWithName
assertValueName = assertValue || assertValueName
assertObjectValueName = assertObjectValue || assertObjectValueName
}

const checkChainNotEndedByValueAssertion = types => {
Expand Down Expand Up @@ -311,6 +326,48 @@ const enableChainAPI = methodNames => {
return new ChainType([...this.types, new ValueAssertion(that)])
}
})

Object.defineProperty(Object.prototype, assertObjectValueName, {
enumerable: false,
configurable: false,
writable: false,
value(valueAssertion) {
if (this.constructor !== Object.prototype.constructor) {
typeAssertError(
'<onbuild> Object.prototype.assertObjectValue',
`cannot assert object values of "${this.constructor.name}"`
)
}
return new ObjectValueAssertion(valueAssertion)
}
})

Object.defineProperty(String.prototype, assertObjectValueName, {
enumerable: false,
configurable: false,
writable: false,
value(valueAssertion) {
let nullable = false
let self = `${this}`
if (self.endsWith('?')) {
nullable = true
self = self.slice(0, -1)
}

if (self !== 'object') {
typeAssertError(
'<onbuild> String.prototype.assertObjectValue',
`cannot assert object values of "${self}"`
)
}

let ret = new ObjectValueAssertion(valueAssertion)
if (nullable) {
ret = new NullableType(ret)
}
return ret
}
})
}

module.exports = {
Expand All @@ -320,6 +377,7 @@ module.exports = {
SumType,
ChainType,
ValueAssertion,
ObjectValueAssertion,
enableChainAPI,
preventErrTrace
}
71 changes: 64 additions & 7 deletions typeAssert.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ const typeAssertImpl = (path, object, assertion, preventErr) => {
}
} else if (assertion.constructor === ValueAssertion.prototype.constructor) {
assertEquals(`${path}:value`, assertion.value, object, preventErr)
} else if (assertion.constructor === ObjectValueAssertion.prototype.constructor) {
typeAssertImpl(path, object, {}, preventErr)
for (const [key, value] of Object.entries(object)) {
typeAssertImpl(`${path}.${key}`, value, assertion.valueAssertion, preventErr)
}
} else if (object === undefined) {
typeAssertError(path, 'unexpected "undefined" value', preventErr)
} else if (object === null) {
Expand Down Expand Up @@ -192,18 +197,28 @@ export const ValueAssertion = (function() {
return ValueAssertion
}())

export const ObjectValueAssertion = (function() {
function ObjectValueAssertion(valueAssertion) {
this.valueAssertion = valueAssertion
}

return ObjectValueAssertion
}())

export const enableChainAPI = methodNames => {
let orNullName = 'orNull'
let sumWithName = 'sumWith'
let chainWithName = 'chainWith'
let assertValueName = 'assertValue'

if (methodNames !== null && methodNames !== undefined) {
const { orNull, sumWith, chainWith, assertValue } = methodNames
orNullName = orNull ? orNull : orNullName
sumWithName = sumWith ? sumWith : sumWithName
chainWithName = chainWith ? chainWith : chainWithName
assertValueName = assertValue ? assertValue : assertValueName
let assertObjectValueName = 'assertObjectValue'

if (methodNames) {
const { orNull, sumWith, chainWith, assertValue, assertObjectValue } = methodNames
orNullName = orNull || orNullName
sumWithName = sumWith || sumWithName
chainWithName = chainWith || chainWithName
assertValueName = assertValue || assertValueName
assertObjectValueName = assertObjectValue || assertObjectValueName
}

const checkChainNotEndedByValueAssertion = types => {
Expand Down Expand Up @@ -311,4 +326,46 @@ export const enableChainAPI = methodNames => {
return new ChainType([...this.types, new ValueAssertion(that)])
}
})

Object.defineProperty(Object.prototype, assertObjectValueName, {
enumerable: false,
configurable: false,
writable: false,
value(valueAssertion) {
if (this.constructor !== Object.prototype.constructor) {
typeAssertError(
'<onbuild> Object.prototype.assertObjectValue',
`cannot assert object values of "${this.constructor.name}"`
)
}
return new ObjectValueAssertion(valueAssertion)
}
})

Object.defineProperty(String.prototype, assertObjectValueName, {
enumerable: false,
configurable: false,
writable: false,
value(valueAssertion) {
let nullable = false
let self = `${this}`
if (self.endsWith('?')) {
nullable = true
self = self.slice(0, -1)
}

if (self !== 'object') {
typeAssertError(
'<onbuild> String.prototype.assertObjectValue',
`cannot assert object values of "${self}"`
)
}

let ret = new ObjectValueAssertion(valueAssertion)
if (nullable) {
ret = new NullableType(ret)
}
return ret
}
})
}

0 comments on commit 7d3fcd3

Please sign in to comment.