object-shape-tester
Advanced tools
Comparing version 0.1.1 to 0.2.0
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defineShape = void 0; | ||
const shape_specifiers_1 = require("./shape-specifiers"); | ||
const shape_to_default_value_1 = require("./shape-to-default-value"); | ||
@@ -12,4 +13,5 @@ function defineShape(shape) { | ||
defaultValue: (0, shape_to_default_value_1.shapeToDefaultValue)(shape), | ||
[shape_specifiers_1.shapeSymbol]: true, | ||
}; | ||
} | ||
exports.defineShape = defineShape; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getShapeSpecifier = exports.matchesSpecifier = exports.isUnknownShapeSpecifier = exports.isEnumShapeSpecifier = exports.isExactShapeSpecifier = exports.isAndShapeSpecifier = exports.isOrShapeSpecifier = exports.unknownShape = exports.enumShape = exports.exact = exports.and = exports.or = void 0; | ||
exports.getShapeSpecifier = exports.matchesSpecifier = exports.isUnknownShapeSpecifier = exports.isEnumShapeSpecifier = exports.isExactShapeSpecifier = exports.isAndShapeSpecifier = exports.isOrShapeSpecifier = exports.unknownShape = exports.enumShape = exports.exact = exports.and = exports.or = exports.isShapeDefinition = exports.shapeSymbol = void 0; | ||
const common_1 = require("@augment-vir/common"); | ||
@@ -11,2 +11,7 @@ const type_equality_1 = require("./type-equality"); | ||
const unknownSymbol = Symbol('unknown'); | ||
exports.shapeSymbol = Symbol('shape-marker'); | ||
function isShapeDefinition(input) { | ||
return (0, common_1.typedHasProperty)(input, exports.shapeSymbol); | ||
} | ||
exports.isShapeDefinition = isShapeDefinition; | ||
const isShapeSpecifierSymbol = Symbol('is-shape-specifier'); | ||
@@ -75,10 +80,10 @@ const shapeSpecifiersTypes = [ | ||
if (isAndShapeSpecifier(specifier)) { | ||
return specifier.parts.every((part) => (0, type_equality_1.haveEqualTypes)(part, subject)); | ||
return specifier.parts.every((part) => matchesSpecifier(subject, part)); | ||
} | ||
else if (isOrShapeSpecifier(specifier)) { | ||
return specifier.parts.some((part) => (0, type_equality_1.haveEqualTypes)(part, subject)); | ||
return specifier.parts.some((part) => matchesSpecifier(subject, part)); | ||
} | ||
else if (isExactShapeSpecifier(specifier)) { | ||
if ((0, common_1.isObject)(subject)) { | ||
return (0, type_equality_1.haveEqualTypes)(specifier.parts[0], subject); | ||
return matchesSpecifier(subject, specifier.parts[0]); | ||
} | ||
@@ -85,0 +90,0 @@ else { |
@@ -19,3 +19,3 @@ "use strict"; | ||
return Object.assign(combined, innerShapeToDefaultValue(part)); | ||
}); | ||
}, {}); | ||
} | ||
@@ -27,11 +27,11 @@ else if ((0, shape_specifiers_1.isEnumShapeSpecifier)(specifier)) { | ||
return 'unknown'; | ||
/* c8 ignore start */ | ||
} | ||
else { | ||
// covering edge cases | ||
throw new Error(`found specifier but it matches no expected specifiers: ${String(specifier.specifierType)}`); | ||
} | ||
/* c8 ignore stop */ | ||
} | ||
if (shape instanceof RegExp) { | ||
if ((0, shape_specifiers_1.isShapeDefinition)(shape)) { | ||
return shapeToDefaultValue(shape.shape); | ||
} | ||
else if (shape instanceof RegExp) { | ||
return shape; | ||
@@ -38,0 +38,0 @@ } |
@@ -18,3 +18,6 @@ "use strict"; | ||
function assertValidShape(subject, shapeDefinition) { | ||
internalAssertValidShape(subject, shapeDefinition.shape, ['top level']); | ||
internalAssertValidShape(subject, shapeDefinition.shape, ['top level'], { | ||
exactValues: false, | ||
ignoreExtraKeys: false, | ||
}); | ||
} | ||
@@ -28,7 +31,11 @@ exports.assertValidShape = assertValidShape; | ||
} | ||
function internalAssertValidShape(subject, shape, keys) { | ||
function internalAssertValidShape(subject, shape, keys, options) { | ||
// unknown shape specifier allows anything, abort instantly | ||
if ((0, shape_specifiers_1.isUnknownShapeSpecifier)(shape)) { | ||
return; | ||
return true; | ||
} | ||
if ((0, shape_specifiers_1.isShapeDefinition)(shape)) { | ||
debugger; | ||
return internalAssertValidShape(subject, shape.shape, keys, options); | ||
} | ||
const keysString = createKeyString(keys); | ||
@@ -44,55 +51,8 @@ const subjectAsSpecifier = (0, shape_specifiers_1.getShapeSpecifier)(subject); | ||
const objectSubject = subject; | ||
const subjectKeys = new Set(Object.keys(objectSubject)); | ||
const keysPassed = Object.fromEntries(Object.keys(objectSubject).map((key) => [ | ||
key, | ||
false, | ||
])); | ||
function testKeys(shapePart, { ignoreExtraKeys = false, } = { | ||
ignoreExtraKeys: false, | ||
}) { | ||
/* c8 ignore start */ | ||
// just covering edge cases, can't actually trigger this | ||
if ((0, common_1.isObject)(shapePart)) { | ||
/* c8 ignore stop */ | ||
const shapePartKeys = new Set(Object.keys(shapePart)); | ||
if (!ignoreExtraKeys) { | ||
subjectKeys.forEach((subjectKey) => { | ||
if (!shapePartKeys.has(subjectKey)) { | ||
throw new shape_mismatch_error_1.ShapeMismatchError(`Subject has extra key '${subjectKey}' in ${keysString}`); | ||
} | ||
}); | ||
} | ||
shapePartKeys.forEach((shapePartKey) => { | ||
debugger; | ||
const shapeValue = shapePart[shapePartKey]; | ||
const orContainsUndefined = (0, shape_specifiers_1.isOrShapeSpecifier)(shapeValue) | ||
? shapeValue.parts.includes(undefined) | ||
: false; | ||
const containsUndefined = shapeValue?.includes?.(undefined) || shapeValue === undefined; | ||
if (!subjectKeys.has(shapePartKey) && | ||
!orContainsUndefined && | ||
!containsUndefined) { | ||
throw new shape_mismatch_error_1.ShapeMismatchError(`Subject missing key '${shapePartKey}' in ${keysString}`); | ||
} | ||
}); | ||
subjectKeys.forEach((key) => { | ||
const subjectChild = objectSubject[key]; | ||
if (ignoreExtraKeys && !shapePartKeys.has(key)) { | ||
return; | ||
} | ||
const shapePartChild = shapePart[key]; | ||
internalAssertValidShape(subjectChild, shapePartChild, [ | ||
...keys, | ||
key, | ||
]); | ||
keysPassed[key] = true; | ||
}); | ||
/* c8 ignore start */ | ||
} | ||
else { | ||
console.error({ shapePart, keys }); | ||
throw new shape_mismatch_error_1.ShapeMismatchError(`shape definition at ${keysString} was not an object.`); | ||
} | ||
/* c8 ignore stop */ | ||
} | ||
const keysPassed = options.ignoreExtraKeys | ||
? {} | ||
: Object.fromEntries(Object.keys(objectSubject).map((key) => [ | ||
key, | ||
false, | ||
])); | ||
const errors = []; | ||
@@ -103,13 +63,13 @@ let matched = false; | ||
try { | ||
testKeys(shapePart); | ||
const newKeysPassed = internalAssertValidShape(subject, shapePart, keys, { | ||
...options, | ||
ignoreExtraKeys: false, | ||
}); | ||
Object.assign(keysPassed, newKeysPassed); | ||
return true; | ||
} | ||
catch (error) { | ||
/* c8 ignore start */ | ||
// just covering edge cases, can't actually trigger this | ||
if (error instanceof shape_mismatch_error_1.ShapeMismatchError) { | ||
/* c8 ignore stop */ | ||
errors.push(error); | ||
return false; | ||
/* c8 ignore start */ | ||
} | ||
@@ -119,3 +79,2 @@ else { | ||
} | ||
/* c8 ignore stop */ | ||
} | ||
@@ -127,13 +86,13 @@ }); | ||
try { | ||
testKeys(shapePart, { ignoreExtraKeys: true }); | ||
const newPassedKeys = internalAssertValidShape(subject, shapePart, keys, { | ||
...options, | ||
ignoreExtraKeys: true, | ||
}); | ||
Object.assign(keysPassed, newPassedKeys); | ||
return true; | ||
} | ||
catch (error) { | ||
/* c8 ignore start */ | ||
// just covering edge cases, can't actually trigger this | ||
if (error instanceof shape_mismatch_error_1.ShapeMismatchError) { | ||
/* c8 ignore stop */ | ||
errors.push(error); | ||
return false; | ||
/* c8 ignore start */ | ||
} | ||
@@ -143,3 +102,2 @@ else { | ||
} | ||
/* c8 ignore stop */ | ||
} | ||
@@ -149,10 +107,11 @@ }); | ||
else if ((0, shape_specifiers_1.isExactShapeSpecifier)(shape)) { | ||
testKeys(shape.parts[0]); | ||
const newKeysPassed = internalAssertValidShape(subject, shape.parts[0], keys, { | ||
...options, | ||
exactValues: true, | ||
}); | ||
Object.assign(keysPassed, newKeysPassed); | ||
matched = true; | ||
/* c8 ignore start */ | ||
} | ||
else if ((0, shape_specifiers_1.isEnumShapeSpecifier)(shape)) { | ||
// just cover an edge case | ||
throw new shape_mismatch_error_1.ShapeMismatchError(`Cannot compare an enum specifier to an object at ${keysString}`); | ||
/* c8 ignore stop */ | ||
} | ||
@@ -167,13 +126,9 @@ else if ((0, common_1.isRuntimeTypeOf)(shape, 'array') && (0, common_1.isRuntimeTypeOf)(objectSubject, 'array')) { | ||
index, | ||
]); | ||
], options); | ||
return true; | ||
} | ||
catch (error) { | ||
/* c8 ignore start */ | ||
// just covering edge cases, can't actually trigger this | ||
if (error instanceof shape_mismatch_error_1.ShapeMismatchError) { | ||
/* c8 ignore stop */ | ||
errors.push(error); | ||
return false; | ||
/* c8 ignore start */ | ||
} | ||
@@ -183,3 +138,2 @@ else { | ||
} | ||
/* c8 ignore stop */ | ||
} | ||
@@ -193,16 +147,68 @@ }); | ||
// if we have no specifier, pass in the whole shape itself | ||
testKeys(shape); | ||
const newKeysPassed = isValidRawObjectShape({ | ||
keys, | ||
options, | ||
shape, | ||
subject, | ||
}); | ||
Object.assign(keysPassed, newKeysPassed); | ||
matched = true; | ||
} | ||
if (!matched) { | ||
throw new shape_mismatch_error_1.ShapeMismatchError((0, common_1.combineErrorMessages)(errors) /* c8 ignore start */ || | ||
// just covering edge cases | ||
'no error message'); | ||
throw new shape_mismatch_error_1.ShapeMismatchError('no error message'); | ||
} | ||
Object.entries(keysPassed).forEach(([key, wasTested,]) => { | ||
if (!wasTested) { | ||
throw new shape_mismatch_error_1.ShapeMismatchError(`subject as extra key '${key}' in ${keysString}.`); | ||
if (!options.ignoreExtraKeys) { | ||
Object.entries(keysPassed).forEach(([key, wasTested,]) => { | ||
if (!wasTested) { | ||
throw new shape_mismatch_error_1.ShapeMismatchError(`subject as extra key '${key}' in ${keysString}.`); | ||
} | ||
}); | ||
} | ||
return keysPassed; | ||
} | ||
else if (options.exactValues) { | ||
return subject === shape; | ||
} | ||
return true; | ||
} | ||
function isValidRawObjectShape({ keys, options, shape, subject, }) { | ||
const keysString = createKeyString(keys); | ||
const keysPassed = {}; | ||
if ((0, common_1.isObject)(shape)) { | ||
const subjectKeys = new Set((0, common_1.getObjectTypedKeys)(subject)); | ||
const shapeKeys = new Set((0, common_1.getObjectTypedKeys)(shape)); | ||
if (!options.ignoreExtraKeys) { | ||
subjectKeys.forEach((subjectKey) => { | ||
if (!shapeKeys.has(subjectKey)) { | ||
throw new shape_mismatch_error_1.ShapeMismatchError(`Subject has extra key '${String(subjectKey)}' in ${keysString}`); | ||
} | ||
}); | ||
} | ||
shapeKeys.forEach((shapePartKey) => { | ||
const shapeValue = shape[shapePartKey]; | ||
const orContainsUndefined = (0, shape_specifiers_1.isOrShapeSpecifier)(shapeValue) | ||
? shapeValue.parts.includes(undefined) | ||
: false; | ||
const containsUndefined = shapeValue?.includes?.(undefined) || shapeValue === undefined; | ||
if (!subjectKeys.has(shapePartKey) && !orContainsUndefined && !containsUndefined) { | ||
throw new shape_mismatch_error_1.ShapeMismatchError(`Subject missing key '${String(shapePartKey)}' in ${keysString}`); | ||
} | ||
}); | ||
subjectKeys.forEach((key) => { | ||
const subjectChild = subject[key]; | ||
if (options.ignoreExtraKeys && !shapeKeys.has(key)) { | ||
return; | ||
} | ||
const shapePartChild = shape[key]; | ||
internalAssertValidShape(subjectChild, shapePartChild, [ | ||
...keys, | ||
key, | ||
], options); | ||
keysPassed[key] = true; | ||
}); | ||
} | ||
else { | ||
throw new shape_mismatch_error_1.ShapeMismatchError(`shape definition at ${keysString} was not an object.`); | ||
} | ||
return keysPassed; | ||
} |
@@ -0,1 +1,2 @@ | ||
import { shapeSymbol } from './shape-specifiers'; | ||
import { shapeToDefaultValue } from './shape-to-default-value'; | ||
@@ -9,3 +10,4 @@ export function defineShape(shape) { | ||
defaultValue: shapeToDefaultValue(shape), | ||
[shapeSymbol]: true, | ||
}; | ||
} |
@@ -8,2 +8,6 @@ import { isObject, isRuntimeTypeOf, typedArrayIncludes, typedHasProperty, } from '@augment-vir/common'; | ||
const unknownSymbol = Symbol('unknown'); | ||
export const shapeSymbol = Symbol('shape-marker'); | ||
export function isShapeDefinition(input) { | ||
return typedHasProperty(input, shapeSymbol); | ||
} | ||
const isShapeSpecifierSymbol = Symbol('is-shape-specifier'); | ||
@@ -62,10 +66,10 @@ const shapeSpecifiersTypes = [ | ||
if (isAndShapeSpecifier(specifier)) { | ||
return specifier.parts.every((part) => haveEqualTypes(part, subject)); | ||
return specifier.parts.every((part) => matchesSpecifier(subject, part)); | ||
} | ||
else if (isOrShapeSpecifier(specifier)) { | ||
return specifier.parts.some((part) => haveEqualTypes(part, subject)); | ||
return specifier.parts.some((part) => matchesSpecifier(subject, part)); | ||
} | ||
else if (isExactShapeSpecifier(specifier)) { | ||
if (isObject(subject)) { | ||
return haveEqualTypes(specifier.parts[0], subject); | ||
return matchesSpecifier(subject, specifier.parts[0]); | ||
} | ||
@@ -72,0 +76,0 @@ else { |
import { isObject, isRuntimeTypeOf, mapObjectValues } from '@augment-vir/common'; | ||
import { getShapeSpecifier, isAndShapeSpecifier, isEnumShapeSpecifier, isExactShapeSpecifier, isOrShapeSpecifier, isUnknownShapeSpecifier, } from './shape-specifiers'; | ||
import { getShapeSpecifier, isAndShapeSpecifier, isEnumShapeSpecifier, isExactShapeSpecifier, isOrShapeSpecifier, isShapeDefinition, isUnknownShapeSpecifier, } from './shape-specifiers'; | ||
export function shapeToDefaultValue(shape) { | ||
@@ -15,3 +15,3 @@ return innerShapeToDefaultValue(shape); | ||
return Object.assign(combined, innerShapeToDefaultValue(part)); | ||
}); | ||
}, {}); | ||
} | ||
@@ -23,11 +23,11 @@ else if (isEnumShapeSpecifier(specifier)) { | ||
return 'unknown'; | ||
/* c8 ignore start */ | ||
} | ||
else { | ||
// covering edge cases | ||
throw new Error(`found specifier but it matches no expected specifiers: ${String(specifier.specifierType)}`); | ||
} | ||
/* c8 ignore stop */ | ||
} | ||
if (shape instanceof RegExp) { | ||
if (isShapeDefinition(shape)) { | ||
return shapeToDefaultValue(shape.shape); | ||
} | ||
else if (shape instanceof RegExp) { | ||
return shape; | ||
@@ -34,0 +34,0 @@ } |
@@ -1,3 +0,3 @@ | ||
import { combineErrorMessages, isObject, isRuntimeTypeOf } from '@augment-vir/common'; | ||
import { getShapeSpecifier, isAndShapeSpecifier, isEnumShapeSpecifier, isExactShapeSpecifier, isOrShapeSpecifier, isUnknownShapeSpecifier, matchesSpecifier, } from '../define-shape/shape-specifiers'; | ||
import { getObjectTypedKeys, isObject, isRuntimeTypeOf } from '@augment-vir/common'; | ||
import { getShapeSpecifier, isAndShapeSpecifier, isEnumShapeSpecifier, isExactShapeSpecifier, isOrShapeSpecifier, isShapeDefinition, isUnknownShapeSpecifier, matchesSpecifier, } from '../define-shape/shape-specifiers'; | ||
import { ShapeMismatchError } from '../errors/shape-mismatch.error'; | ||
@@ -14,3 +14,6 @@ export function isValidShape(subject, shapeDefinition) { | ||
export function assertValidShape(subject, shapeDefinition) { | ||
internalAssertValidShape(subject, shapeDefinition.shape, ['top level']); | ||
internalAssertValidShape(subject, shapeDefinition.shape, ['top level'], { | ||
exactValues: false, | ||
ignoreExtraKeys: false, | ||
}); | ||
} | ||
@@ -23,7 +26,11 @@ function createKeyString(keys) { | ||
} | ||
function internalAssertValidShape(subject, shape, keys) { | ||
function internalAssertValidShape(subject, shape, keys, options) { | ||
// unknown shape specifier allows anything, abort instantly | ||
if (isUnknownShapeSpecifier(shape)) { | ||
return; | ||
return true; | ||
} | ||
if (isShapeDefinition(shape)) { | ||
debugger; | ||
return internalAssertValidShape(subject, shape.shape, keys, options); | ||
} | ||
const keysString = createKeyString(keys); | ||
@@ -39,55 +46,8 @@ const subjectAsSpecifier = getShapeSpecifier(subject); | ||
const objectSubject = subject; | ||
const subjectKeys = new Set(Object.keys(objectSubject)); | ||
const keysPassed = Object.fromEntries(Object.keys(objectSubject).map((key) => [ | ||
key, | ||
false, | ||
])); | ||
function testKeys(shapePart, { ignoreExtraKeys = false, } = { | ||
ignoreExtraKeys: false, | ||
}) { | ||
/* c8 ignore start */ | ||
// just covering edge cases, can't actually trigger this | ||
if (isObject(shapePart)) { | ||
/* c8 ignore stop */ | ||
const shapePartKeys = new Set(Object.keys(shapePart)); | ||
if (!ignoreExtraKeys) { | ||
subjectKeys.forEach((subjectKey) => { | ||
if (!shapePartKeys.has(subjectKey)) { | ||
throw new ShapeMismatchError(`Subject has extra key '${subjectKey}' in ${keysString}`); | ||
} | ||
}); | ||
} | ||
shapePartKeys.forEach((shapePartKey) => { | ||
debugger; | ||
const shapeValue = shapePart[shapePartKey]; | ||
const orContainsUndefined = isOrShapeSpecifier(shapeValue) | ||
? shapeValue.parts.includes(undefined) | ||
: false; | ||
const containsUndefined = shapeValue?.includes?.(undefined) || shapeValue === undefined; | ||
if (!subjectKeys.has(shapePartKey) && | ||
!orContainsUndefined && | ||
!containsUndefined) { | ||
throw new ShapeMismatchError(`Subject missing key '${shapePartKey}' in ${keysString}`); | ||
} | ||
}); | ||
subjectKeys.forEach((key) => { | ||
const subjectChild = objectSubject[key]; | ||
if (ignoreExtraKeys && !shapePartKeys.has(key)) { | ||
return; | ||
} | ||
const shapePartChild = shapePart[key]; | ||
internalAssertValidShape(subjectChild, shapePartChild, [ | ||
...keys, | ||
key, | ||
]); | ||
keysPassed[key] = true; | ||
}); | ||
/* c8 ignore start */ | ||
} | ||
else { | ||
console.error({ shapePart, keys }); | ||
throw new ShapeMismatchError(`shape definition at ${keysString} was not an object.`); | ||
} | ||
/* c8 ignore stop */ | ||
} | ||
const keysPassed = options.ignoreExtraKeys | ||
? {} | ||
: Object.fromEntries(Object.keys(objectSubject).map((key) => [ | ||
key, | ||
false, | ||
])); | ||
const errors = []; | ||
@@ -98,13 +58,13 @@ let matched = false; | ||
try { | ||
testKeys(shapePart); | ||
const newKeysPassed = internalAssertValidShape(subject, shapePart, keys, { | ||
...options, | ||
ignoreExtraKeys: false, | ||
}); | ||
Object.assign(keysPassed, newKeysPassed); | ||
return true; | ||
} | ||
catch (error) { | ||
/* c8 ignore start */ | ||
// just covering edge cases, can't actually trigger this | ||
if (error instanceof ShapeMismatchError) { | ||
/* c8 ignore stop */ | ||
errors.push(error); | ||
return false; | ||
/* c8 ignore start */ | ||
} | ||
@@ -114,3 +74,2 @@ else { | ||
} | ||
/* c8 ignore stop */ | ||
} | ||
@@ -122,13 +81,13 @@ }); | ||
try { | ||
testKeys(shapePart, { ignoreExtraKeys: true }); | ||
const newPassedKeys = internalAssertValidShape(subject, shapePart, keys, { | ||
...options, | ||
ignoreExtraKeys: true, | ||
}); | ||
Object.assign(keysPassed, newPassedKeys); | ||
return true; | ||
} | ||
catch (error) { | ||
/* c8 ignore start */ | ||
// just covering edge cases, can't actually trigger this | ||
if (error instanceof ShapeMismatchError) { | ||
/* c8 ignore stop */ | ||
errors.push(error); | ||
return false; | ||
/* c8 ignore start */ | ||
} | ||
@@ -138,3 +97,2 @@ else { | ||
} | ||
/* c8 ignore stop */ | ||
} | ||
@@ -144,10 +102,11 @@ }); | ||
else if (isExactShapeSpecifier(shape)) { | ||
testKeys(shape.parts[0]); | ||
const newKeysPassed = internalAssertValidShape(subject, shape.parts[0], keys, { | ||
...options, | ||
exactValues: true, | ||
}); | ||
Object.assign(keysPassed, newKeysPassed); | ||
matched = true; | ||
/* c8 ignore start */ | ||
} | ||
else if (isEnumShapeSpecifier(shape)) { | ||
// just cover an edge case | ||
throw new ShapeMismatchError(`Cannot compare an enum specifier to an object at ${keysString}`); | ||
/* c8 ignore stop */ | ||
} | ||
@@ -162,13 +121,9 @@ else if (isRuntimeTypeOf(shape, 'array') && isRuntimeTypeOf(objectSubject, 'array')) { | ||
index, | ||
]); | ||
], options); | ||
return true; | ||
} | ||
catch (error) { | ||
/* c8 ignore start */ | ||
// just covering edge cases, can't actually trigger this | ||
if (error instanceof ShapeMismatchError) { | ||
/* c8 ignore stop */ | ||
errors.push(error); | ||
return false; | ||
/* c8 ignore start */ | ||
} | ||
@@ -178,3 +133,2 @@ else { | ||
} | ||
/* c8 ignore stop */ | ||
} | ||
@@ -188,16 +142,68 @@ }); | ||
// if we have no specifier, pass in the whole shape itself | ||
testKeys(shape); | ||
const newKeysPassed = isValidRawObjectShape({ | ||
keys, | ||
options, | ||
shape, | ||
subject, | ||
}); | ||
Object.assign(keysPassed, newKeysPassed); | ||
matched = true; | ||
} | ||
if (!matched) { | ||
throw new ShapeMismatchError(combineErrorMessages(errors) /* c8 ignore start */ || | ||
// just covering edge cases | ||
'no error message'); | ||
throw new ShapeMismatchError('no error message'); | ||
} | ||
Object.entries(keysPassed).forEach(([key, wasTested,]) => { | ||
if (!wasTested) { | ||
throw new ShapeMismatchError(`subject as extra key '${key}' in ${keysString}.`); | ||
if (!options.ignoreExtraKeys) { | ||
Object.entries(keysPassed).forEach(([key, wasTested,]) => { | ||
if (!wasTested) { | ||
throw new ShapeMismatchError(`subject as extra key '${key}' in ${keysString}.`); | ||
} | ||
}); | ||
} | ||
return keysPassed; | ||
} | ||
else if (options.exactValues) { | ||
return subject === shape; | ||
} | ||
return true; | ||
} | ||
function isValidRawObjectShape({ keys, options, shape, subject, }) { | ||
const keysString = createKeyString(keys); | ||
const keysPassed = {}; | ||
if (isObject(shape)) { | ||
const subjectKeys = new Set(getObjectTypedKeys(subject)); | ||
const shapeKeys = new Set(getObjectTypedKeys(shape)); | ||
if (!options.ignoreExtraKeys) { | ||
subjectKeys.forEach((subjectKey) => { | ||
if (!shapeKeys.has(subjectKey)) { | ||
throw new ShapeMismatchError(`Subject has extra key '${String(subjectKey)}' in ${keysString}`); | ||
} | ||
}); | ||
} | ||
shapeKeys.forEach((shapePartKey) => { | ||
const shapeValue = shape[shapePartKey]; | ||
const orContainsUndefined = isOrShapeSpecifier(shapeValue) | ||
? shapeValue.parts.includes(undefined) | ||
: false; | ||
const containsUndefined = shapeValue?.includes?.(undefined) || shapeValue === undefined; | ||
if (!subjectKeys.has(shapePartKey) && !orContainsUndefined && !containsUndefined) { | ||
throw new ShapeMismatchError(`Subject missing key '${String(shapePartKey)}' in ${keysString}`); | ||
} | ||
}); | ||
subjectKeys.forEach((key) => { | ||
const subjectChild = subject[key]; | ||
if (options.ignoreExtraKeys && !shapeKeys.has(key)) { | ||
return; | ||
} | ||
const shapePartChild = shape[key]; | ||
internalAssertValidShape(subjectChild, shapePartChild, [ | ||
...keys, | ||
key, | ||
], options); | ||
keysPassed[key] = true; | ||
}); | ||
} | ||
else { | ||
throw new ShapeMismatchError(`shape definition at ${keysString} was not an object.`); | ||
} | ||
return keysPassed; | ||
} |
@@ -1,7 +0,2 @@ | ||
import { ShapeToRunTimeType } from './shape-specifiers'; | ||
export type ShapeDefinition<Shape> = { | ||
shape: Shape; | ||
runTimeType: ShapeToRunTimeType<Shape>; | ||
defaultValue: Readonly<ShapeToRunTimeType<Shape>>; | ||
}; | ||
import { ShapeDefinition } from './shape-specifiers'; | ||
export declare function defineShape<Shape>(shape: Shape): ShapeDefinition<Shape>; |
@@ -8,2 +8,11 @@ import { ArrayElement, AtLeastTuple, PropertyValueType } from '@augment-vir/common'; | ||
declare const unknownSymbol: unique symbol; | ||
export declare const shapeSymbol: unique symbol; | ||
/** This definition has to be in this file because the types circularly depend on each other. */ | ||
export type ShapeDefinition<Shape> = { | ||
shape: Shape; | ||
runTimeType: ShapeToRunTimeType<Shape>; | ||
defaultValue: Readonly<ShapeToRunTimeType<Shape>>; | ||
[shapeSymbol]: true; | ||
}; | ||
export declare function isShapeDefinition(input: unknown): input is ShapeDefinition<unknown>; | ||
declare const isShapeSpecifierSymbol: unique symbol; | ||
@@ -23,3 +32,5 @@ declare const shapeSpecifiersTypes: readonly [typeof andSymbol, typeof orSymbol, typeof exactSymbol, typeof enumSymbol, typeof unknownSymbol]; | ||
export type ShapeUnknown<Parts extends Readonly<[]>> = ShapeSpecifier<Parts, typeof unknownSymbol>; | ||
export type SpecifierToRunTimeType<PossiblySpecifier, IsExact extends boolean = false> = PossiblySpecifier extends ShapeSpecifier<infer Parts, infer Type> ? Type extends typeof andSymbol ? UnionToIntersection<ArrayElement<Parts>> : Type extends typeof orSymbol ? ArrayElement<Parts> : Type extends typeof exactSymbol ? WritableDeep<ArrayElement<Parts>> : Type extends typeof enumSymbol ? WritableDeep<PropertyValueType<Parts[0]>> : Type extends typeof unknownSymbol ? unknown : 'TypeError: found not match for shape specifier type.' : PossiblySpecifier extends Primitive ? IsExact extends true ? PossiblySpecifier : LiteralToPrimitive<PossiblySpecifier> : PossiblySpecifier; | ||
type ExpandParts<Parts extends BaseParts> = Exclude<ArrayElement<Parts>, ShapeDefinition<any>> | ShapeToRunTimeType<Extract<ArrayElement<Parts>, ShapeDefinition<any>>['shape']>; | ||
type ExpandPart<Part> = Part extends ShapeDefinition<infer Shape> ? ShapeToRunTimeType<Shape> : Part; | ||
export type SpecifierToRunTimeType<PossiblySpecifier, IsExact extends boolean = false> = PossiblySpecifier extends ShapeSpecifier<infer Parts, infer Type> ? Type extends typeof andSymbol ? UnionToIntersection<ExpandParts<Parts>> : Type extends typeof orSymbol ? ExpandParts<Parts> : Type extends typeof exactSymbol ? WritableDeep<ExpandParts<Parts>> : Type extends typeof enumSymbol ? WritableDeep<PropertyValueType<ExpandPart<Parts[0]>>> : Type extends typeof unknownSymbol ? unknown : 'TypeError: found not match for shape specifier type.' : PossiblySpecifier extends Primitive ? IsExact extends true ? PossiblySpecifier : LiteralToPrimitive<PossiblySpecifier> : PossiblySpecifier; | ||
export declare function or<Parts extends AtLeastTuple<unknown, 1>>(...parts: Parts): ShapeOr<Parts>; | ||
@@ -26,0 +37,0 @@ export declare function and<Parts extends AtLeastTuple<unknown, 1>>(...parts: Parts): ShapeAnd<Parts>; |
@@ -1,3 +0,3 @@ | ||
import { ShapeDefinition } from '../define-shape/define-shape'; | ||
import { ShapeDefinition } from '../define-shape/shape-specifiers'; | ||
export declare function isValidShape<Shape>(subject: unknown, shapeDefinition: ShapeDefinition<Shape>): subject is Shape; | ||
export declare function assertValidShape<Shape>(subject: unknown, shapeDefinition: ShapeDefinition<Shape>): asserts subject is ShapeDefinition<Shape>['runTimeType']; |
{ | ||
"name": "object-shape-tester", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"description": "Test object properties and value types.", | ||
@@ -27,7 +27,6 @@ "keywords": [], | ||
"format": "virmator format", | ||
"preview": "virmator frontend preview", | ||
"publish": "virmator publish \"npm run compile && npm run test:all\"", | ||
"start": "npm install && virmator frontend", | ||
"test": "virmator test-web", | ||
"test:all": "npm run test:types && npm run test:coverage && npm run test:spelling && npm run test:format && npm run test:docs", | ||
"test:all": "concurrently \"npm run test:types\" \"npm run test:coverage\" \"npm run test:spelling\" \"npm run test:format\" \"npm run test:docs\"", | ||
"test:coverage": "npm run test coverage", | ||
@@ -40,20 +39,21 @@ "test:docs": "virmator code-in-markdown check", | ||
"dependencies": { | ||
"@augment-vir/common": "^13.2.4", | ||
"type-fest": "^3.8.0" | ||
"@augment-vir/common": "^13.3.0", | ||
"type-fest": "^3.9.0" | ||
}, | ||
"devDependencies": { | ||
"@augment-vir/browser-testing": "^13.2.4", | ||
"@augment-vir/node-js": "^13.2.3", | ||
"@open-wc/testing": "^3.1.7", | ||
"@augment-vir/browser-testing": "^13.3.0", | ||
"@augment-vir/node-js": "^13.3.0", | ||
"@open-wc/testing": "^3.1.8", | ||
"@types/mocha": "^10.0.1", | ||
"@web/dev-server-esbuild": "^0.3.6", | ||
"@web/test-runner": "^0.15.3", | ||
"@web/test-runner-commands": "^0.6.6", | ||
"@web/test-runner-playwright": "^0.9.0", | ||
"@web/test-runner-visual-regression": "^0.7.1", | ||
"@web/dev-server-esbuild": "^0.4.1", | ||
"@web/test-runner": "^0.16.1", | ||
"@web/test-runner-commands": "^0.7.0", | ||
"@web/test-runner-playwright": "^0.10.0", | ||
"@web/test-runner-visual-regression": "^0.8.0", | ||
"concurrently": "^8.0.1", | ||
"cspell": "^6.31.1", | ||
"esbuild": "^0.17.17", | ||
"esbuild": "^0.17.18", | ||
"istanbul-smart-text-reporter": "^1.1.1", | ||
"markdown-code-example-inserter": "^0.3.0", | ||
"prettier": "^2.8.7", | ||
"prettier": "^2.8.8", | ||
"prettier-plugin-interpolated-html-tags": "^0.0.3", | ||
@@ -66,6 +66,6 @@ "prettier-plugin-jsdoc": "^0.4.2", | ||
"prettier-plugin-toml": "^0.3.1", | ||
"virmator": "^6.4.2", | ||
"vite": "^4.2.1", | ||
"vite-tsconfig-paths": "^4.0.8" | ||
"virmator": "^6.4.4", | ||
"vite": "^4.3.2", | ||
"vite-tsconfig-paths": "^4.2.0" | ||
} | ||
} |
50412
855
25
Updated@augment-vir/common@^13.3.0
Updatedtype-fest@^3.9.0