scrubbr
Advanced tools
Comparing version 0.0.1-alpha.9 to 0.0.1-alpha.10
@@ -11,8 +11,11 @@ /** | ||
* | ||
* @param typeName - The type that this node should be serialized with. | ||
* @param serializedData - Serialized/transformed data for this node. | ||
* @public | ||
*/ | ||
export declare function useType(typeName: string): UseType; | ||
export declare function useType(typeName: string, serializedData?: unknown): UseType; | ||
export declare class UseType { | ||
typeName: string; | ||
constructor(typeName: string); | ||
data: unknown; | ||
constructor(typeName: string, data?: unknown); | ||
} |
@@ -14,11 +14,14 @@ "use strict"; | ||
* | ||
* @param typeName - The type that this node should be serialized with. | ||
* @param serializedData - Serialized/transformed data for this node. | ||
* @public | ||
*/ | ||
function useType(typeName) { | ||
return new UseType(typeName); | ||
function useType(typeName, serializedData) { | ||
return new UseType(typeName, serializedData); | ||
} | ||
exports.useType = useType; | ||
var UseType = /** @class */ (function () { | ||
function UseType(typeName) { | ||
function UseType(typeName, data) { | ||
this.typeName = typeName; | ||
this.data = data; | ||
} | ||
@@ -25,0 +28,0 @@ return UseType; |
import { JSONSchema7 } from 'json-schema'; | ||
import { ScrubbrOptions, JSONSchemaDefinitions, TypeSerializer, GenericSerializer } from '.'; | ||
declare type ContextObject = Record<string, unknown>; | ||
export default class Scrubbr { | ||
@@ -72,7 +73,7 @@ options: ScrubbrOptions; | ||
*/ | ||
setGlobalContext(context: object, merge?: boolean): void; | ||
setGlobalContext(context: ContextObject, merge?: boolean): ContextObject; | ||
/** | ||
* Retrieve the global context object. | ||
*/ | ||
getGlobalContext(): object; | ||
getGlobalContext(): ContextObject; | ||
/** | ||
@@ -94,3 +95,3 @@ * Serialize data based on a TypeScript type. | ||
*/ | ||
serialize<Type = any>(schemaType: string, data: object, context?: object, options?: ScrubbrOptions): Promise<Type>; | ||
serialize<Type = any>(schemaType: string, data: unknown, context?: ContextObject, options?: ScrubbrOptions): Promise<Type>; | ||
/** | ||
@@ -145,1 +146,2 @@ * Traverse into a node of data on an object to serialize. | ||
} | ||
export {}; |
@@ -76,3 +76,3 @@ "use strict"; | ||
var defaultOptions = { | ||
logLevel: Logger_1.LogLevel.NONE, | ||
logLevel: Logger_1.LogLevel.WARN, | ||
logNesting: false, | ||
@@ -186,3 +186,3 @@ logPrefix: '', | ||
var _this = this; | ||
var typeNames = (Array.isArray(typeName)) ? typeName : [typeName]; | ||
var typeNames = Array.isArray(typeName) ? typeName : [typeName]; | ||
typeNames.forEach(function (name) { | ||
@@ -218,2 +218,3 @@ _this.logger.debug("Adding custom serializer for type: " + name); | ||
} | ||
return this.globalContext; | ||
}; | ||
@@ -484,3 +485,3 @@ /** | ||
var others = typeNames.filter(function (n) { return n != chosenType; }); | ||
state.logger.warn("Guessing at type '" + chosenType + "' over " + others + " because it has the fewest properties (you can explicitly override this selection with the 'useType()' function.)."); | ||
state.logger.warn("Guessing: Using type '" + chosenType + "', instead of '" + others + "', because it has the fewest properties at object path: '" + state.path + "'.\n(You can explicitly override this selection with the 'useType()' function in a custom serializer)."); | ||
} | ||
@@ -530,22 +531,41 @@ else { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var _this = this; | ||
var serialized, i, serializerFn, result; | ||
return __generator(this, function (_a) { | ||
if (!this.genericSerializers.length) { | ||
return [2 /*return*/, dataNode]; | ||
switch (_a.label) { | ||
case 0: | ||
if (!this.genericSerializers.length) { | ||
return [2 /*return*/, dataNode]; | ||
} | ||
state.logger.debug("Running " + this.genericSerializers.length + " generic serializers"); | ||
serialized = dataNode; | ||
i = 0; | ||
_a.label = 1; | ||
case 1: | ||
if (!(i < this.genericSerializers.length)) return [3 /*break*/, 4]; | ||
serializerFn = this.genericSerializers[i]; | ||
return [4 /*yield*/, serializerFn.call(null, serialized, state)]; | ||
case 2: | ||
result = _a.sent(); | ||
if (typeof result === 'undefined') { | ||
state.logger.warn("Generic serializer returned 'undefined' at object path: " + state.path); | ||
} | ||
if (result instanceof helpers_1.UseType) { | ||
state.logger.debug("Overriding type: '" + result.typeName + "'"); | ||
if (result.typeName === state.schemaType) { | ||
state.logger.warn("Trying to override type with the same type ('" + result.typeName + "') at object path: " + state.path); | ||
} | ||
if (typeof result.data !== 'undefined') { | ||
serialized = result.data; | ||
} | ||
state = this.setStateSchemaDefinition(result.typeName, state); | ||
} | ||
else { | ||
serialized = result; | ||
} | ||
_a.label = 3; | ||
case 3: | ||
i++; | ||
return [3 /*break*/, 1]; | ||
case 4: return [2 /*return*/, serialized]; | ||
} | ||
state.logger.debug("Running " + this.genericSerializers.length + " generic serializers"); | ||
return [2 /*return*/, this.genericSerializers.reduce(function (promise, serializerFn) { | ||
return promise.then(function (data) { | ||
var serialized = serializerFn(data, state); | ||
if (serialized instanceof helpers_1.UseType) { | ||
state.logger.debug("Overriding type: '" + serialized.typeName + "'"); | ||
if (serialized.typeName === state.schemaType) { | ||
state.logger.warn("Trying to override type with the same type ('" + serialized.typeName + "') at object path: " + state.path); | ||
} | ||
state = _this.setStateSchemaDefinition(serialized.typeName, state); | ||
return data; | ||
} | ||
return serialized; | ||
}); | ||
}, Promise.resolve(dataNode))]; | ||
}); | ||
@@ -559,3 +579,3 @@ }); | ||
return __awaiter(this, void 0, void 0, function () { | ||
var typeName, serializerFns, i, serialized, typeName_1, circularRef; | ||
var typeName, serializerFns, serialized, i, result, circularRef; | ||
return __generator(this, function (_a) { | ||
@@ -577,28 +597,36 @@ switch (_a.label) { | ||
state.logger.debug("Running " + serializerFns.length + " serializers for type '" + typeName + "'"); | ||
serialized = dataNode; | ||
i = 0; | ||
_a.label = 1; | ||
case 1: | ||
if (!(i < serializerFns.length)) return [3 /*break*/, 7]; | ||
if (!(i < serializerFns.length)) return [3 /*break*/, 8]; | ||
return [4 /*yield*/, serializerFns[i].call(null, dataNode, state)]; | ||
case 2: | ||
serialized = _a.sent(); | ||
if (!(serialized instanceof helpers_1.UseType && serialized.typeName !== typeName)) return [3 /*break*/, 5]; | ||
typeName_1 = serialized.typeName; | ||
state.logger.debug("Overriding type: '" + typeName_1 + "'"); | ||
if (!(serialized.typeName === state.schemaType)) return [3 /*break*/, 3]; | ||
state.logger.warn("Trying to override type with the same type ('" + serialized.typeName + "') at object path: " + state.path); | ||
result = _a.sent(); | ||
if (typeof result === 'undefined') { | ||
state.logger.warn("Serializer for type '" + typeName + "' returned 'undefined' at object path: " + state.path); | ||
} | ||
if (!(result instanceof helpers_1.UseType)) return [3 /*break*/, 6]; | ||
state.logger.debug("Overriding type: '" + typeName + "'"); | ||
// Serialized data | ||
if (typeof result.data !== 'undefined') { | ||
serialized = result.data; | ||
} | ||
if (!(result.typeName === state.schemaType)) return [3 /*break*/, 3]; | ||
state.logger.warn("Trying to override type with the same type ('" + result.typeName + "') at object path: " + state.path); | ||
return [3 /*break*/, 5]; | ||
case 3: | ||
circularRef = this.isCircularReference(typeName_1, state); | ||
circularRef = this.isCircularReference(result.typeName, state); | ||
if (!!circularRef) return [3 /*break*/, 5]; | ||
state = this.setStateSchemaDefinition(typeName_1, state); | ||
return [4 /*yield*/, this.runTypeSerializers(dataNode, state)]; | ||
state = this.setStateSchemaDefinition(result.typeName, state); | ||
return [4 /*yield*/, this.runTypeSerializers(serialized, state)]; | ||
case 4: return [2 /*return*/, _a.sent()]; | ||
case 5: | ||
dataNode = serialized; | ||
_a.label = 6; | ||
case 5: return [3 /*break*/, 7]; | ||
case 6: | ||
serialized = result; | ||
_a.label = 7; | ||
case 7: | ||
i++; | ||
return [3 /*break*/, 1]; | ||
case 7: return [2 /*return*/, dataNode]; | ||
case 8: return [2 /*return*/, serialized]; | ||
} | ||
@@ -623,3 +651,5 @@ }); | ||
var _a; | ||
var throwOnError = (state) ? (_a = state === null || state === void 0 ? void 0 : state.options) === null || _a === void 0 ? void 0 : _a.throwOnError : this.options.throwOnError; | ||
var throwOnError = state | ||
? (_a = state === null || state === void 0 ? void 0 : state.options) === null || _a === void 0 ? void 0 : _a.throwOnError | ||
: this.options.throwOnError; | ||
if (throwOnError) { | ||
@@ -626,0 +656,0 @@ throw new Error(message); |
@@ -8,3 +8,3 @@ import { JSONSchema7 } from 'json-schema'; | ||
*/ | ||
export declare type TypeSerializer = (data: any, state: ScrubbrState) => any | Promise<any>; | ||
export declare type TypeSerializer = (data: unknown, state: ScrubbrState) => unknown | Promise<unknown>; | ||
/** | ||
@@ -14,3 +14,3 @@ * Serializer called for each node within the data object that is being serialized. | ||
*/ | ||
export declare type GenericSerializer = (data: any, state: ScrubbrState) => any | Promise<any>; | ||
export declare type GenericSerializer = (data: unknown, state: ScrubbrState) => unknown | Promise<unknown>; | ||
/** | ||
@@ -22,3 +22,3 @@ * Options passed to the Scrubbr constructor | ||
* Set the logger level: LogLevel.NONE, LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO, LogLevel.DEBUG | ||
* @defaultValue LogLevel.NONE | ||
* @defaultValue LogLevel.WARN | ||
* @public | ||
@@ -25,0 +25,0 @@ */ |
@@ -10,2 +10,3 @@ # Troubleshooting | ||
``` | ||
This is the [JSON schema](https://json-schema.org/understanding-json-schema/) that was created from your TypeScript file. | ||
@@ -39,1 +40,36 @@ | ||
``` | ||
## Understanding the warning: _"Guessing: Using type 'xyz', instead of 'abc' , because it has the fewest properties"_ | ||
``` | ||
Guessing: Using type 'UserPublic', instead of 'UserRestricted', because it has the fewest properties at object path: 'user'. | ||
(You can explicitly override this selection with the 'useType()' function in a custom serializer). | ||
``` | ||
This warning happens when you have a type like this: | ||
```typescript | ||
type Payload = { | ||
user: UserPublic | UserRestricted; | ||
}; | ||
``` | ||
When Scrubbr gets to the `user` property, it doesn't know exactly how to choose between `UserPublic` and `UserRestricted` and so it makes a guess and chooses the type that defines the fewest properties (most restrictive). In the interest of performance, Scrubbr avoids doing deep introspection and type checking. | ||
To avoid this warning, use a type alias return the `useType` function in a serializer: | ||
```typescript | ||
type Payload = { | ||
user: User; | ||
}; | ||
type User = UserPublic | UserRestricted; | ||
``` | ||
```typescript | ||
scrubbr.addTypeSerializer('User', (data, state) => { | ||
if (/* type choosing logic here */) { | ||
return useType('UserRestricted'); | ||
} | ||
return useType('UserPublic'); | ||
} | ||
``` |
{ | ||
"name": "scrubbr", | ||
"version": "0.0.1-alpha.9", | ||
"version": "0.0.1-alpha.10", | ||
"description": "Serialize and sanitize JSON data using TypeScript.", | ||
@@ -5,0 +5,0 @@ "repository": "https://github.com/jgillick/scrubbr", |
@@ -11,6 +11,8 @@ /** | ||
* | ||
* @param typeName - The type that this node should be serialized with. | ||
* @param serializedData - Serialized/transformed data for this node. | ||
* @public | ||
*/ | ||
export function useType(typeName: string): UseType { | ||
return new UseType(typeName); | ||
export function useType(typeName: string, serializedData?: unknown): UseType { | ||
return new UseType(typeName, serializedData); | ||
} | ||
@@ -20,5 +22,8 @@ | ||
typeName: string; | ||
constructor(typeName: string) { | ||
data: unknown; | ||
constructor(typeName: string, data?: unknown) { | ||
this.typeName = typeName; | ||
this.data = data; | ||
} | ||
} |
@@ -16,3 +16,3 @@ import * as fs from 'fs'; | ||
const defaultOptions = { | ||
logLevel: LogLevel.NONE, | ||
logLevel: LogLevel.WARN, | ||
logNesting: false, | ||
@@ -23,3 +23,4 @@ logPrefix: '', | ||
type ObjectNode = Record<string, any>; | ||
type ObjectNode = Record<string, unknown>; | ||
type ContextObject = Record<string, unknown>; | ||
@@ -32,3 +33,3 @@ export default class Scrubbr { | ||
private genericSerializers: GenericSerializer[] = []; | ||
private globalContext: object = {}; | ||
private globalContext: ContextObject = {}; | ||
@@ -97,3 +98,3 @@ /** | ||
*/ | ||
loadSchema(schema: string | JSONSchemaDefinitions) { | ||
loadSchema(schema: string | JSONSchemaDefinitions): void { | ||
// Load typescript file | ||
@@ -136,3 +137,3 @@ if (typeof schema == 'string') { | ||
*/ | ||
getSchemaFor(typeName: string): JSONSchema7 | null { | ||
getSchemaFor(typeName: string): JSONSchema7 | null { | ||
const definitions = this.schema?.definitions || {}; | ||
@@ -150,4 +151,7 @@ if (typeof definitions[typeName] === 'undefined') { | ||
*/ | ||
addTypeSerializer(typeName: string | string[], serializer: TypeSerializer) { | ||
const typeNames = (Array.isArray(typeName)) ? typeName : [typeName]; | ||
addTypeSerializer( | ||
typeName: string | string[], | ||
serializer: TypeSerializer | ||
): void { | ||
const typeNames = Array.isArray(typeName) ? typeName : [typeName]; | ||
typeNames.forEach((name) => { | ||
@@ -165,3 +169,3 @@ this.logger.debug(`Adding custom serializer for type: ${name}`); | ||
*/ | ||
addGenericSerializer(serializer: GenericSerializer) { | ||
addGenericSerializer(serializer: GenericSerializer): void { | ||
this.logger.debug(`Adding generic serializer`); | ||
@@ -178,3 +182,3 @@ this.genericSerializers.push(serializer); | ||
*/ | ||
setGlobalContext(context: object, merge: boolean = false) { | ||
setGlobalContext(context: ContextObject, merge = false): ContextObject { | ||
if (merge) { | ||
@@ -188,2 +192,3 @@ this.globalContext = { | ||
} | ||
return this.globalContext; | ||
} | ||
@@ -194,3 +199,3 @@ | ||
*/ | ||
getGlobalContext() { | ||
getGlobalContext(): ContextObject { | ||
return this.globalContext; | ||
@@ -217,4 +222,4 @@ } | ||
schemaType: string, | ||
data: object, | ||
context: object = {}, | ||
data: unknown, | ||
context: ContextObject = {}, | ||
options: ScrubbrOptions = {} | ||
@@ -232,4 +237,6 @@ ): Promise<Type> { | ||
...context, | ||
} | ||
this.logger.debug(`Using context: '${JSON.stringify(context, null, ' ')}'`); | ||
}; | ||
this.logger.debug( | ||
`Using context: '${JSON.stringify(context, null, ' ')}'` | ||
); | ||
@@ -261,3 +268,3 @@ // Create state | ||
*/ | ||
private async walkData(node: Object, state: ScrubbrState): Promise<Object> { | ||
private async walkData(node: unknown, state: ScrubbrState): Promise<unknown> { | ||
const serializedNode = await this.serializeNode(node, state); | ||
@@ -270,3 +277,3 @@ | ||
} else if (typeof serializedNode === 'object') { | ||
return await this.walkObjectNode(serializedNode, state); | ||
return await this.walkObjectNode(serializedNode as ObjectNode, state); | ||
} | ||
@@ -284,3 +291,3 @@ return serializedNode; | ||
state: ScrubbrState | ||
): Promise<Object> { | ||
): Promise<unknown> { | ||
const nodeProps = Object.entries(node); | ||
@@ -292,8 +299,13 @@ const schemaProps = state.schemaDef.properties || {}; | ||
for (let i = 0; i < nodeProps.length; i++) { | ||
let [name, value] = nodeProps[i]; | ||
let propSchema = schemaProps[name] as JSONSchema7; | ||
const [name, value] = nodeProps[i]; | ||
const propSchema = schemaProps[name] as JSONSchema7; | ||
const propPath = `${pathPrefix}${name}`; | ||
state.logger.debug(`[PATH] ${propPath}`); | ||
const propState = state.createNodeState(value, name, propPath, propSchema); | ||
const propState = state.createNodeState( | ||
value, | ||
name, | ||
propPath, | ||
propSchema | ||
); | ||
@@ -318,3 +330,6 @@ // Property not defined in the schema, do not serialize property | ||
*/ | ||
private async walkArrayNode(node: any[], state: ScrubbrState): Promise<any> { | ||
private async walkArrayNode( | ||
node: unknown[], | ||
state: ScrubbrState | ||
): Promise<unknown> { | ||
const schema = state.schemaDef; | ||
@@ -340,3 +355,3 @@ const listSchema = schema.items as JSONSchema7 | JSONSchema7[]; | ||
itemPath, | ||
itemSchema as JSONSchema7, | ||
itemSchema as JSONSchema7 | ||
); | ||
@@ -361,3 +376,6 @@ | ||
*/ | ||
private async serializeNode(data: any, state: ScrubbrState): Promise<Object> { | ||
private async serializeNode( | ||
data: unknown, | ||
state: ScrubbrState | ||
): Promise<unknown> { | ||
const originalDef = state.schemaDef; | ||
@@ -404,3 +422,3 @@ | ||
let schemaList = (schema?.allOf || | ||
const schemaList = (schema?.allOf || | ||
schema?.anyOf || | ||
@@ -415,3 +433,3 @@ schema?.oneOf || | ||
// Get all the types we have definitions for | ||
let foundTypes = new Map<string, JSONSchema7>(); | ||
const foundTypes = new Map<string, JSONSchema7>(); | ||
schemaList.forEach((schemaRef) => { | ||
@@ -456,3 +474,3 @@ const refPath = schemaRef.$ref; | ||
state.logger.warn( | ||
`Guessing at type '${chosenType}' over ${others} because it has the fewest properties (you can explicitly override this selection with the 'useType()' function.).` | ||
`Guessing: Using type '${chosenType}', instead of '${others}', because it has the fewest properties at object path: '${state.path}'.\n(You can explicitly override this selection with the 'useType()' function in a custom serializer).` | ||
); | ||
@@ -512,5 +530,5 @@ } else { | ||
private async runGenericSerializers( | ||
dataNode: Object, | ||
dataNode: unknown, | ||
state: ScrubbrState | ||
): Promise<Object> { | ||
): Promise<unknown> { | ||
if (!this.genericSerializers.length) { | ||
@@ -524,17 +542,31 @@ return dataNode; | ||
return this.genericSerializers.reduce((promise, serializerFn) => { | ||
return promise.then((data) => { | ||
const serialized = serializerFn(data, state); | ||
if (serialized instanceof UseType) { | ||
state.logger.debug(`Overriding type: '${serialized.typeName}'`); | ||
if (serialized.typeName === state.schemaType) { | ||
state.logger.warn(`Trying to override type with the same type ('${serialized.typeName}') at object path: ${state.path}`); | ||
} | ||
let serialized = dataNode; | ||
for (let i = 0; i < this.genericSerializers.length; i++) { | ||
const serializerFn = this.genericSerializers[i]; | ||
const result = await serializerFn.call(null, serialized, state); | ||
state = this.setStateSchemaDefinition(serialized.typeName, state); | ||
return data; | ||
if (typeof result === 'undefined') { | ||
state.logger.warn( | ||
`Generic serializer returned 'undefined' at object path: ${state.path}` | ||
); | ||
} | ||
if (result instanceof UseType) { | ||
state.logger.debug(`Overriding type: '${result.typeName}'`); | ||
if (result.typeName === state.schemaType) { | ||
state.logger.warn( | ||
`Trying to override type with the same type ('${result.typeName}') at object path: ${state.path}` | ||
); | ||
} | ||
return serialized; | ||
}); | ||
}, Promise.resolve(dataNode)); | ||
if (typeof result.data !== 'undefined') { | ||
serialized = result.data; | ||
} | ||
state = this.setStateSchemaDefinition(result.typeName, state); | ||
} else { | ||
serialized = result; | ||
} | ||
} | ||
return serialized; | ||
} | ||
@@ -546,5 +578,5 @@ | ||
private async runTypeSerializers( | ||
dataNode: Object, | ||
dataNode: unknown, | ||
state: ScrubbrState | ||
): Promise<Object> { | ||
): Promise<unknown> { | ||
const typeName = state.schemaType; | ||
@@ -569,25 +601,40 @@ if (!typeName) { | ||
let serialized = dataNode; | ||
for (let i = 0; i < serializerFns.length; i++) { | ||
const serialized = await serializerFns[i].call(null, dataNode, state); | ||
const result = await serializerFns[i].call(null, dataNode, state); | ||
if (typeof result === 'undefined') { | ||
state.logger.warn( | ||
`Serializer for type '${typeName}' returned 'undefined' at object path: ${state.path}` | ||
); | ||
} | ||
// Change type | ||
if (serialized instanceof UseType && serialized.typeName !== typeName) { | ||
const { typeName } = serialized; | ||
if (result instanceof UseType) { | ||
state.logger.debug(`Overriding type: '${typeName}'`); | ||
if (serialized.typeName === state.schemaType) { | ||
state.logger.warn(`Trying to override type with the same type ('${serialized.typeName}') at object path: ${state.path}`); | ||
// Serialized data | ||
if (typeof result.data !== 'undefined') { | ||
serialized = result.data; | ||
} | ||
// No type change | ||
if (result.typeName === state.schemaType) { | ||
state.logger.warn( | ||
`Trying to override type with the same type ('${result.typeName}') at object path: ${state.path}` | ||
); | ||
} else { | ||
// Check for circular reference | ||
const circularRef = this.isCircularReference(typeName, state); | ||
// Stop serializing this type and switch to the new type, if it's not a circular reference | ||
const circularRef = this.isCircularReference(result.typeName, state); | ||
if (!circularRef) { | ||
state = this.setStateSchemaDefinition(typeName, state); | ||
return await this.runTypeSerializers(dataNode, state); | ||
state = this.setStateSchemaDefinition(result.typeName, state); | ||
return await this.runTypeSerializers(serialized, state); | ||
} | ||
} | ||
} else { | ||
serialized = result; | ||
} | ||
dataNode = serialized; | ||
} | ||
return dataNode; | ||
return serialized; | ||
} | ||
@@ -613,3 +660,5 @@ | ||
private error(message: string, state?: ScrubbrState) { | ||
const throwOnError = (state) ? state?.options?.throwOnError : this.options.throwOnError; | ||
const throwOnError = state | ||
? state?.options?.throwOnError | ||
: this.options.throwOnError; | ||
if (throwOnError) { | ||
@@ -616,0 +665,0 @@ throw new Error(message); |
@@ -10,5 +10,5 @@ import { JSONSchema7 } from 'json-schema'; | ||
export type TypeSerializer = ( | ||
data: any, | ||
data: unknown, | ||
state: ScrubbrState | ||
) => any | Promise<any>; | ||
) => unknown | Promise<unknown>; | ||
@@ -20,5 +20,5 @@ /** | ||
export type GenericSerializer = ( | ||
data: any, | ||
data: unknown, | ||
state: ScrubbrState | ||
) => any | Promise<any>; | ||
) => unknown | Promise<unknown>; | ||
@@ -31,3 +31,3 @@ /** | ||
* Set the logger level: LogLevel.NONE, LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO, LogLevel.DEBUG | ||
* @defaultValue LogLevel.NONE | ||
* @defaultValue LogLevel.WARN | ||
* @public | ||
@@ -34,0 +34,0 @@ */ |
@@ -104,2 +104,24 @@ import 'jest'; | ||
}); | ||
test('override type and transform node', async () => { | ||
serializerFn.mockImplementation((data, state) => { | ||
if (state.path == 'child') { | ||
const newData = { | ||
node: 'boo', | ||
extra: 'woo', | ||
}; | ||
return useType('GenericSerializerExtended', newData); | ||
} | ||
return data; | ||
}); | ||
const serialized = await scrubbr.serialize('GenericSerializerTest', { | ||
child: { | ||
node: 'foo', | ||
extra: 'bar', | ||
}, | ||
}); | ||
expect(serialized.child.node).toBe('boo'); | ||
expect(serialized.child.extra).toBe('woo'); | ||
}); | ||
}); |
@@ -45,2 +45,17 @@ import 'jest'; | ||
test('override type and transform node', async () => { | ||
scrubbr.addTypeSerializer('TargetTypeA', () => { | ||
const newData = { | ||
value: 'boo', | ||
}; | ||
return useType('TargetTypeB', newData); | ||
}); | ||
const data = { | ||
node: { value: 'foo' }, | ||
}; | ||
const serialized = await scrubbr.serialize('TypeSerializerTest', data); | ||
expect(serialized.node.value).toBe('boo'); | ||
}); | ||
test('union type', async () => { | ||
@@ -82,3 +97,3 @@ const typeASerializerFn = jest.fn((data, _state) => data); | ||
expect(Array.from(receivedTypes)).toEqual(['TargetTypeA', 'TargetTypeB']); | ||
}) | ||
}); | ||
}); |
@@ -1,18 +0,18 @@ | ||
type SimpleTypeUnion = { | ||
value: OptionOne | OptionTwo; | ||
type UnionTypeTestSimple = { | ||
value: UnionTypeTestType1 | UnionTypeTestType2; | ||
}; | ||
type PrimitiveUnion = { | ||
type UnionTypeTestPrimitive = { | ||
value: string | boolean; | ||
}; | ||
type MixedUnion = { | ||
value: OptionOne | null; | ||
type UnionTypeTestMixed = { | ||
value: UnionTypeTestType1 | null; | ||
}; | ||
type UnionAlias = { | ||
type UnionTypeTestAlias = { | ||
value: AliasedUnion; | ||
}; | ||
type OptionOne = { | ||
type UnionTypeTestType1 = { | ||
nodeA: string; | ||
@@ -22,6 +22,6 @@ nodeB: string; | ||
type OptionTwo = { | ||
type UnionTypeTestType2 = { | ||
nodeA: string; | ||
}; | ||
type AliasedUnion = OptionOne | OptionTwo; | ||
type AliasedUnion = UnionTypeTestType1 | UnionTypeTestType2; |
import 'jest'; | ||
import Scrubbr, { useType, ScrubbrState } from '../src/'; | ||
import Scrubbr, { useType, ScrubbrState, LogLevel } from '../src/'; | ||
describe('Union types', () => { | ||
const pathTypes = new Map<string, string | null>(); | ||
let scrubbr: Scrubbr; | ||
let pathTypes = new Map<string, string | null>(); | ||
let firstSerializerFn: jest.Mock; | ||
@@ -12,6 +12,8 @@ | ||
// Override this later | ||
firstSerializerFn = jest.fn((data: any) => data); | ||
scrubbr.addGenericSerializer(firstSerializerFn); | ||
// Track what type is chosen for ever path node | ||
firstSerializerFn = jest.fn((data: any, _state: ScrubbrState) => data); | ||
scrubbr.addGenericSerializer(firstSerializerFn); | ||
scrubbr.addGenericSerializer((data: any, state: ScrubbrState) => { | ||
scrubbr.addGenericSerializer((data: unknown, state: ScrubbrState) => { | ||
pathTypes.set(state.path, state.schemaType); | ||
@@ -23,3 +25,3 @@ return data; | ||
test('automatically choose between two types', async () => { | ||
await scrubbr.serialize('SimpleTypeUnion', { | ||
await scrubbr.serialize('UnionTypeTestSimple', { | ||
value: { | ||
@@ -32,7 +34,7 @@ nodeA: 'foo', | ||
// OptionTwo has fewer properties | ||
expect(pathTypes.get('value')).toBe('OptionTwo'); | ||
expect(pathTypes.get('value')).toBe('UnionTypeTestType2'); | ||
}); | ||
test('choose between primitive types', async () => { | ||
await scrubbr.serialize('PrimitiveUnion', { value: 'test' }); | ||
await scrubbr.serialize('UnionTypeTestPrimitive', { value: 'test' }); | ||
expect(pathTypes.get('value')).toBe(null); | ||
@@ -42,8 +44,8 @@ }); | ||
test('union between TypeScript type and primitive', async () => { | ||
await scrubbr.serialize('MixedUnion', { value: 'test' }); | ||
expect(pathTypes.get('value')).toBe('OptionOne'); | ||
await scrubbr.serialize('UnionTypeTestMixed', { value: 'test' }); | ||
expect(pathTypes.get('value')).toBe('UnionTypeTestType1'); | ||
}); | ||
test('Aliased union type', async () => { | ||
await scrubbr.serialize('UnionAlias', { | ||
await scrubbr.serialize('UnionTypeTestAlias', { | ||
value: { | ||
@@ -54,3 +56,3 @@ nodeA: 'foo', | ||
}); | ||
expect(pathTypes.get('value')).toBe('OptionTwo'); | ||
expect(pathTypes.get('value')).toBe('UnionTypeTestType2'); | ||
}); | ||
@@ -61,7 +63,8 @@ | ||
if (state.path == 'value') { | ||
return useType('OptionOne'); | ||
return useType('UnionTypeTestType1'); | ||
} | ||
return data; | ||
}); | ||
const output = await scrubbr.serialize('SimpleTypeUnion', { | ||
const output = await scrubbr.serialize('UnionTypeTestSimple', { | ||
value: { | ||
@@ -72,6 +75,29 @@ nodeA: 'foo', | ||
}); | ||
expect(pathTypes.get('value')).toBe('OptionOne'); | ||
expect(pathTypes.get('value')).toBe('UnionTypeTestType1'); | ||
expect(output.value.nodeA).toBe('foo'); | ||
expect(output.value.nodeB).toBe('bar'); | ||
}); | ||
// test('change type and override value', async () => { | ||
// firstSerializerFn.mockImplementation((data, state) => { | ||
// if (state.path == 'value') { | ||
// const newNodeData = { | ||
// nodeA: 'baz', | ||
// nodeB: 'boo', | ||
// }; | ||
// return useType('UnionTypeTestType1', newNodeData); | ||
// } | ||
// return data; | ||
// }); | ||
// const output = await scrubbr.serialize('UnionTypeTestSimple', { | ||
// value: { | ||
// nodeA: 'foo', | ||
// nodeB: 'bar', | ||
// }, | ||
// }); | ||
// expect(pathTypes.get('value')).toBe('UnionTypeTestType1'); | ||
// expect(output.value.nodeA).toBe('baz'); | ||
// expect(output.value.nodeB).toBe('boo'); | ||
// }); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1143060
4819