@decaf-ts/decorator-validation
Version:
simple decorator based validation engine
594 lines • 75.5 kB
JavaScript
import { Serialization } from "./../utils/serialization.js";
import { isEqual, Reflection } from "@decaf-ts/reflection";
import { validate } from "./validation.js";
import { Hashing } from "./../utils/hashing.js";
import { ModelKeys } from "./../utils/constants.js";
import { ValidationKeys } from "./../validation/Validators/constants.js";
import { jsTypes, ReservedModels } from "./constants.js";
import { getMetadata, getModelKey } from "./utils.js";
import { ASYNC_META_KEY } from "./../constants/index.js";
let modelBuilderFunction;
let actingModelRegistry;
/**
* @description Registry manager for model constructors that enables serialization and rebuilding
* @summary The ModelRegistryManager implements the ModelRegistry interface and provides
* functionality for registering, retrieving, and building model instances. It maintains
* a cache of model constructors indexed by name, allowing for efficient lookup and instantiation.
* This class is essential for the serialization and deserialization of model objects.
*
* @param {function(Record<string, any>): boolean} [testFunction] - Function to test if an object is a model, defaults to {@link Model#isModel}
*
* @class ModelRegistryManager
* @template M Type of model that can be registered, must extend Model
* @implements ModelRegistry<M>
* @category Model
*
* @example
* ```typescript
* // Create a model registry
* const registry = new ModelRegistryManager();
*
* // Register a model class
* registry.register(User);
*
* // Retrieve a model constructor by name
* const UserClass = registry.get("User");
*
* // Build a model instance from a plain object
* const userData = { name: "John", age: 30 };
* const user = registry.build(userData, "User");
* ```
*
* @mermaid
* sequenceDiagram
* participant C as Client
* participant R as ModelRegistryManager
* participant M as Model Class
*
* C->>R: new ModelRegistryManager(testFunction)
* C->>R: register(ModelClass)
* R->>R: Store in cache
* C->>R: get("ModelName")
* R-->>C: ModelClass constructor
* C->>R: build(data, "ModelName")
* R->>R: Get constructor from cache
* R->>M: new ModelClass(data)
* M-->>R: Model instance
* R-->>C: Model instance
*/
export class ModelRegistryManager {
constructor(testFunction = Model.isModel) {
this.cache = {};
this.testFunction = testFunction;
}
/**
* @description Registers a model constructor with the registry
* @summary Adds a model constructor to the registry cache, making it available for
* later retrieval and instantiation. If no name is provided, the constructor's name
* property is used as the key in the registry.
*
* @param {ModelConstructor<M>} constructor - The model constructor to register
* @param {string} [name] - Optional name to register the constructor under, defaults to constructor.name
* @return {void}
* @throws {Error} If the constructor is not a function
*/
register(constructor, name) {
if (typeof constructor !== "function")
throw new Error("Model registering failed. Missing Class name or constructor");
name = name || constructor.name;
this.cache[name] = constructor;
}
/**
* @summary Gets a registered Model {@link ModelConstructor}
* @param {string} name
*/
get(name) {
try {
return this.cache[name];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
}
catch (e) {
return undefined;
}
}
/**
* @param {Record<string, any>} obj
* @param {string} [clazz] when provided, it will attempt to find the matching constructor
*
* @throws Error If clazz is not found, or obj is not a {@link Model} meaning it has no {@link ModelKeys.ANCHOR} property
*/
build(obj = {}, clazz) {
if (!clazz && !this.testFunction(obj))
throw new Error("Provided obj is not a Model object");
const name = clazz || Model.getMetadata(obj);
if (!(name in this.cache))
throw new Error(`Provided class ${name} is not a registered Model object`);
return new this.cache[name](obj);
}
}
/**
* @summary Bulk Registers Models
* @description Useful when using bundlers that might not evaluate all the code at once
*
* @template M extends Model
* @param {Array<Constructor<M>> | Array<{name: string, constructor: Constructor<M>}>} [models]
*
* @memberOf module:decorator-validation
* @category Model
*/
export function bulkModelRegister(...models) {
models.forEach((m) => {
const constructor = (m.constructor ? m.constructor : m);
Model.register(constructor, m.name);
});
}
/**
* @summary Abstract class representing a Validatable Model object
* @description Meant to be used as a base class for all Model classes
*
* Model objects must:
* - Have all their required properties marked with '!';
* - Have all their optional properties marked as '?':
*
* @param {ModelArg<Model>} model base object from which to populate properties from
*
* @class Model
* @category Model
* @abstract
* @implements Validatable
* @implements Serializable
*
* @example
* class ClassName {
* @required()
* requiredPropertyName!: PropertyType;
*
* optionalPropertyName?: PropertyType;
* }
*/
export class Model {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
constructor(arg = undefined) { }
isAsync() {
const self = this;
return !!(self[ASYNC_META_KEY] ?? self?.constructor[ASYNC_META_KEY]);
}
/**
* @description Validates the model object against its defined validation rules
* @summary Validates the object according to its decorated properties, returning any validation errors
*
* @param {any[]} [exceptions] - Properties in the object to be ignored for the validation. Marked as 'any' to allow for extension but expects strings
* @return {ModelErrorDefinition | undefined} - Returns a ModelErrorDefinition object if validation errors exist, otherwise undefined
*/
hasErrors(...exceptions) {
return validate(this, this.isAsync(), ...exceptions);
}
/**
* @description Determines if this model is equal to another object
* @summary Compare object equality recursively, checking all properties unless excluded
*
* @param {any} obj - Object to compare to
* @param {string[]} [exceptions] - Property names to be excluded from the comparison
* @return {boolean} - True if objects are equal, false otherwise
*/
equals(obj, ...exceptions) {
return isEqual(this, obj, ...exceptions);
}
/**
* @description Converts the model to a serialized string representation
* @summary Returns the serialized model according to the currently defined {@link Serializer}
*
* @return {string} - The serialized string representation of the model
*/
serialize() {
return Model.serialize(this);
}
/**
* @description Provides a human-readable string representation of the model
* @summary Override the implementation for js's 'toString()' to provide a more useful representation
*
* @return {string} - A string representation of the model including its class name and JSON representation
* @override
*/
toString() {
return this.constructor.name + ": " + JSON.stringify(this, undefined, 2);
}
/**
* @description Generates a hash string for the model object
* @summary Defines a default implementation for object hash, relying on a basic implementation based on Java's string hash
*
* @return {string} - A hash string representing the model
*/
hash() {
return Model.hash(this);
}
/**
* @description Converts a serialized string back into a model instance
* @summary Deserializes a Model from its string representation
*
* @param {string} str - The serialized string to convert back to a model
* @return {any} - The deserialized model instance
* @throws {Error} If it fails to parse the string, or if it fails to build the model
*/
static deserialize(str) {
const metadata = Reflect.getMetadata(Model.key(ModelKeys.SERIALIZATION), this.constructor);
if (metadata && metadata.serializer)
return Serialization.deserialize(str, metadata.serializer, ...(metadata.args || []));
return Serialization.deserialize(str);
}
/**
* @description Copies properties from a source object to a model instance
* @summary Repopulates the Object properties with the ones from the new object
*
* @template T
* @param {T} self - The target model instance to update
* @param {T | Record<string, any>} [obj] - The source object containing properties to copy
* @return {T} - The updated model instance
*/
static fromObject(self, obj) {
if (!obj)
obj = {};
for (const prop of Model.getAttributes(self)) {
self[prop] = obj[prop] || undefined;
}
return self;
}
/**
* @description Copies and rebuilds properties from a source object to a model instance, handling nested models
* @summary Repopulates the instance with properties from the new Model Object, recursively rebuilding nested models
*
* @template T
* @param {T} self - The target model instance to update
* @param {T | Record<string, any>} [obj] - The source object containing properties to copy
* @return {T} - The updated model instance with rebuilt nested models
*
* @mermaid
* sequenceDiagram
* participant C as Client
* participant M as Model.fromModel
* participant B as Model.build
* participant R as Reflection
*
* C->>M: fromModel(self, obj)
* M->>M: Get attributes from self
* loop For each property
* M->>M: Copy property from obj to self
* alt Property is a model
* M->>M: Check if property is a model
* M->>B: build(property, modelType)
* B-->>M: Return built model
* else Property is a complex type
* M->>R: Get property decorators
* R-->>M: Return decorators
* M->>M: Filter type decorators
* alt Property is Array/Set with list decorator
* M->>M: Process each item in collection
* loop For each item
* M->>B: build(item, itemModelType)
* B-->>M: Return built model
* end
* else Property is another model type
* M->>B: build(property, propertyType)
* B-->>M: Return built model
* end
* end
* end
* M-->>C: Return updated self
*/
static fromModel(self, obj) {
if (!obj)
obj = {};
let decorators, dec;
const props = Model.getAttributes(self);
for (const prop of props) {
self[prop] =
obj[prop] ?? undefined;
if (typeof self[prop] !== "object")
continue;
const propM = Model.isPropertyModel(self, prop);
if (propM) {
try {
self[prop] = Model.build(self[prop], typeof propM === "string" ? propM : undefined);
}
catch (e) {
console.log(e);
}
continue;
}
const allDecorators = Reflection.getPropertyDecorators(ValidationKeys.REFLECT, self, prop).decorators;
decorators = allDecorators.filter((d) => [ModelKeys.TYPE, ValidationKeys.TYPE].indexOf(d.key) !== -1);
if (!decorators || !decorators.length)
throw new Error(`failed to find decorators for property ${prop}`);
dec = decorators.pop();
const clazz = dec.props.name
? [dec.props.name]
: Array.isArray(dec.props.customTypes)
? dec.props.customTypes
: [dec.props.customTypes];
const reserved = Object.values(ReservedModels).map((v) => v.toLowerCase());
clazz.forEach((c) => {
if (reserved.indexOf(c.toLowerCase()) === -1)
try {
switch (c) {
case "Array":
case "Set":
if (allDecorators.length) {
const listDec = allDecorators.find((d) => d.key === ValidationKeys.LIST);
if (listDec) {
const clazzName = listDec.props.clazz.find((t) => !jsTypes.includes(t.toLowerCase()));
if (c === "Array")
self[prop] = self[prop].map((el) => {
return ["object", "function"].includes(typeof el) &&
clazzName
? Model.build(el, clazzName)
: el;
});
if (c === "Set") {
const s = new Set();
for (const v of self[prop]) {
if (["object", "function"].includes(typeof v) &&
clazzName) {
s.add(Model.build(v, clazzName));
}
else {
s.add(v);
}
}
self[prop] = s;
}
}
}
break;
default:
if (typeof self[prop] !== "undefined" &&
Model.get(c))
self[prop] = Model.build(self[prop], c);
}
}
catch (e) {
console.log(e);
// do nothing. we have no registry of this class
}
});
}
return self;
}
/**
* @description Configures the global model builder function
* @summary Sets the Global {@link ModelBuilderFunction} used for building model instances
*
* @param {ModelBuilderFunction} [builder] - The builder function to set as the global builder
* @return {void}
*/
static setBuilder(builder) {
modelBuilderFunction = builder;
}
/**
* @description Retrieves the currently configured global model builder function
* @summary Returns the current global {@link ModelBuilderFunction} used for building model instances
*
* @return {ModelBuilderFunction | undefined} - The current global builder function or undefined if not set
*/
static getBuilder() {
return modelBuilderFunction || Model.fromModel;
}
/**
* @description Provides access to the current model registry
* @summary Returns the current {@link ModelRegistryManager} instance, creating one if it doesn't exist
*
* @return {ModelRegistry<any>} - The current model registry, defaults to a new {@link ModelRegistryManager} if not set
* @private
*/
static getRegistry() {
if (!actingModelRegistry)
actingModelRegistry = new ModelRegistryManager();
return actingModelRegistry;
}
/**
* @description Configures the model registry to be used by the Model system
* @summary Sets the current model registry to a custom implementation
*
* @param {BuilderRegistry<any>} modelRegistry - The new implementation of Registry to use
* @return {void}
*/
static setRegistry(modelRegistry) {
actingModelRegistry = modelRegistry;
}
/**
* @description Registers a model constructor with the model registry
* @summary Registers new model classes to make them available for serialization and deserialization
*
* @template T
* @param {ModelConstructor<T>} constructor - The model constructor to register
* @param {string} [name] - Optional name to register the constructor under, defaults to constructor.name
* @return {void}
*
* @see ModelRegistry
*/
static register(constructor, name) {
return Model.getRegistry().register(constructor, name);
}
/**
* @description Retrieves a registered model constructor by name
* @summary Gets a registered Model {@link ModelConstructor} from the model registry
*
* @template T
* @param {string} name - The name of the model constructor to retrieve
* @return {ModelConstructor<T> | undefined} - The model constructor if found, undefined otherwise
*
* @see ModelRegistry
*/
static get(name) {
return Model.getRegistry().get(name);
}
/**
* @description Creates a model instance from a plain object
* @summary Builds a model instance using the model registry, optionally specifying the model class
*
* @template T
* @param {Record<string, any>} obj - The source object to build the model from
* @param {string} [clazz] - When provided, it will attempt to find the matching constructor by name
* @return {T} - The built model instance
* @throws {Error} If clazz is not found, or obj is not a {@link Model} meaning it has no {@link ModelKeys.ANCHOR} property
*
* @see ModelRegistry
*/
static build(obj = {}, clazz) {
return Model.getRegistry().build(obj, clazz);
}
/**
* @description Retrieves the model metadata from a model instance
* @summary Gets the metadata associated with a model instance, typically the model class name
*
* @template M
* @param {M} model - The model instance to get metadata from
* @return {string} - The model metadata (typically the class name)
*/
static getMetadata(model) {
return getMetadata(model);
}
/**
* @description Retrieves all attribute names from a model class or instance
* @summary Gets all attributes defined in a model, traversing the prototype chain to include inherited attributes
*
* @template V
* @param {Constructor<V> | V} model - The model class or instance to get attributes from
* @return {string[]} - Array of attribute names defined in the model
*/
static getAttributes(model) {
const result = [];
let prototype = model instanceof Model
? Object.getPrototypeOf(model)
: model.prototype;
while (prototype != null) {
const props = prototype[ModelKeys.ATTRIBUTE];
if (props) {
result.push(...props);
}
prototype = Object.getPrototypeOf(prototype);
}
return result;
}
/**
* @description Compares two model instances for equality
* @summary Determines if two model instances are equal by comparing their properties
*
* @template M
* @param {M} obj1 - First model instance to compare
* @param {M} obj2 - Second model instance to compare
* @param {any[]} [exceptions] - Property names to exclude from comparison
* @return {boolean} - True if the models are equal, false otherwise
*/
static equals(obj1, obj2, ...exceptions) {
return isEqual(obj1, obj2, ...exceptions);
}
/**
* @description Validates a model instance against its validation rules
* @summary Checks if a model has validation errors, optionally ignoring specified properties
*
* @template M
* @param {M} model - The model instance to validate
* @param {boolean} async - A flag indicating whether validation should be asynchronous.
* @param {string[]} [propsToIgnore] - Properties to exclude from validation
* @return {ModelErrorDefinition | undefined} - Returns validation errors if any, otherwise undefined
*/
static hasErrors(model, async, ...propsToIgnore) {
return validate(model, async, ...propsToIgnore);
}
/**
* @description Converts a model instance to a serialized string
* @summary Serializes a model instance using the configured serializer or the default one
*
* @template M
* @param {M} model - The model instance to serialize
* @return {string} - The serialized string representation of the model
*/
static serialize(model) {
const metadata = Reflect.getMetadata(Model.key(ModelKeys.SERIALIZATION), model.constructor);
if (metadata && metadata.serializer)
return Serialization.serialize(this, metadata.serializer, ...(metadata.args || []));
return Serialization.serialize(model);
}
/**
* @description Generates a hash string for a model instance
* @summary Creates a hash representation of a model using the configured algorithm or the default one
*
* @template M
* @param {M} model - The model instance to hash
* @return {string} - The hash string representing the model
*/
static hash(model) {
const metadata = Reflect.getMetadata(Model.key(ModelKeys.HASHING), model.constructor);
if (metadata && metadata.algorithm)
return Hashing.hash(model, metadata.algorithm, ...(metadata.args || []));
return Hashing.hash(model);
}
/**
* @description Creates a metadata key for use with the Reflection API
* @summary Builds the key to store as Metadata under Reflections
*
* @param {string} str - The base key to concatenate with the model reflection prefix
* @return {string} - The complete metadata key
*/
static key(str) {
return getModelKey(str);
}
/**
* @description Determines if an object is a model instance or has model metadata
* @summary Checks whether a given object is either an instance of the Model class or
* has model metadata attached to it. This function is essential for serialization and
* deserialization processes, as it helps identify model objects that need special handling.
* It safely handles potential errors during metadata retrieval.
*
* @param {Record<string, any>} target - The object to check
* @return {boolean} True if the object is a model instance or has model metadata, false otherwise
*
* @example
* ```typescript
* // Check if an object is a model
* const user = new User({ name: "John" });
* const isUserModel = isModel(user); // true
*
* // Check a plain object
* const plainObject = { name: "John" };
* const isPlainObjectModel = isModel(plainObject); // false
* ```
*/
static isModel(target) {
try {
return target instanceof Model || !!Model.getMetadata(target);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
}
catch (e) {
return false;
}
}
/**
* @description Checks if a property of a model is itself a model or has a model type
* @summary Determines whether a specific property of a model instance is either a model instance
* or has a type that is registered as a model
*
* @template M
* @param {M} target - The model instance to check
* @param {string} attribute - The property name to check
* @return {boolean | string | undefined} - Returns true if the property is a model instance,
* the model name if the property has a model type, or undefined if not a model
*/
static isPropertyModel(target, attribute) {
if (Model.isModel(target[attribute]))
return true;
const metadata = Reflect.getMetadata(ModelKeys.TYPE, target, attribute);
return Model.get(metadata.name) ? metadata.name : undefined;
}
static describe(model, key) {
const descKey = Model.key(ModelKeys.DESCRIPTION);
if (key) {
model = model instanceof Model ? model : new model();
return (Reflect.getMetadataKeys(model.constructor, key.toString())
.find((k) => k === descKey)
?.toString() || model.toString());
}
return (Reflect.getMetadata(Model.key(ModelKeys.DESCRIPTION), model instanceof Model ? model.constructor : model) || model.toString());
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTW9kZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbW9kZWwvTW9kZWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGFBQWEsRUFBRSxvQ0FBK0I7QUFhdkQsT0FBTyxFQUFxQixPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDOUUsT0FBTyxFQUFFLFFBQVEsRUFBRSx3QkFBcUI7QUFDeEMsT0FBTyxFQUFFLE9BQU8sRUFBRSw4QkFBeUI7QUFDM0MsT0FBTyxFQUFFLFNBQVMsRUFBRSxnQ0FBMkI7QUFDL0MsT0FBTyxFQUFFLGNBQWMsRUFBRSxnREFBMkM7QUFDcEUsT0FBTyxFQUFFLE9BQU8sRUFBRSxjQUFjLEVBQUUsdUJBQW9CO0FBQ3RELE9BQU8sRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLG1CQUFnQjtBQUVuRCxPQUFPLEVBQUUsY0FBYyxFQUFFLGdDQUFxQjtBQUU5QyxJQUFJLG9CQUFzRCxDQUFDO0FBQzNELElBQUksbUJBQXlDLENBQUM7QUFnQjlDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBOENHO0FBQ0gsTUFBTSxPQUFPLG9CQUFvQjtJQU0vQixZQUNFLGVBQXNELEtBQUssQ0FBQyxPQUFPO1FBSjdELFVBQUssR0FBd0MsRUFBRSxDQUFDO1FBTXRELElBQUksQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDO0lBQ25DLENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBQ0gsUUFBUSxDQUFDLFdBQWdDLEVBQUUsSUFBYTtRQUN0RCxJQUFJLE9BQU8sV0FBVyxLQUFLLFVBQVU7WUFDbkMsTUFBTSxJQUFJLEtBQUssQ0FDYiw2REFBNkQsQ0FDOUQsQ0FBQztRQUNKLElBQUksR0FBRyxJQUFJLElBQUksV0FBVyxDQUFDLElBQUksQ0FBQztRQUNoQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLFdBQVcsQ0FB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SSxLQUFLLENBQUMsUUFBUSxFQUFFLENBQ25DLENBQUM7UUFDSixDQUFDO1FBRUQsT0FBTyxDQUNMLE9BQU8sQ0FBQyxXQUFXLENBQ2pCLEtBQUssQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLFdBQVcsQ0FBQyxFQUNoQyxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQ25ELElBQUksS0FBSyxDQUFDLFFBQVEsRUFBRSxDQUN0QixDQUFDO0lBQ0osQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgU2VyaWFsaXphdGlvbiB9IGZyb20gXCIuLi91dGlscy9zZXJpYWxpemF0aW9uXCI7XG5pbXBvcnQgeyBCdWlsZGVyUmVnaXN0cnkgfSBmcm9tIFwiLi4vdXRpbHMvcmVnaXN0cnlcIjtcbmltcG9ydCB7IE1vZGVsRXJyb3JEZWZpbml0aW9uIH0gZnJvbSBcIi4vTW9kZWxFcnJvckRlZmluaXRpb25cIjtcbmltcG9ydCB7XG4gIENvbXBhcmFibGUsXG4gIENvbnN0cnVjdG9yLFxuICBIYXNoYWJsZSxcbiAgTW9kZWxBcmcsXG4gIE1vZGVsQnVpbGRlckZ1bmN0aW9uLFxuICBNb2RlbENvbnN0cnVjdG9yLFxuICBTZXJpYWxpemFibGUsXG4gIFZhbGlkYXRhYmxlLFxufSBmcm9tIFwiLi90eXBlc1wiO1xuaW1wb3J0IHsgRGVjb3JhdG9yTWV0YWRhdGEsIGlzRXF1YWwsIFJlZmxlY3Rpb24gfSBmcm9tIFwiQGRlY2FmLXRzL3JlZmxlY3Rpb25cIjtcbmltcG9ydCB7IHZhbGlkYXRlIH0gZnJvbSBcIi4vdmFsaWRhdGlvblwiO1xuaW1wb3J0IHsgSGFzaGluZyB9IGZyb20gXCIuLi91dGlscy9oYXNoaW5nXCI7XG5pbXBvcnQgeyBNb2RlbEtleXMgfSBmcm9tIFwiLi4vdXRpbHMvY29uc3RhbnRzXCI7XG5pbXBvcnQgeyBWYWxpZGF0aW9uS2V5cyB9IGZyb20gXCIuLi92YWxpZGF0aW9uL1ZhbGlkYXRvcnMvY29uc3RhbnRzXCI7XG5pbXBvcnQgeyBqc1R5cGVzLCBSZXNlcnZlZE1vZGVscyB9IGZyb20gXCIuL2NvbnN0YW50c1wiO1xuaW1wb3J0IHsgZ2V0TWV0YWRhdGEsIGdldE1vZGVsS2V5IH0gZnJvbSBcIi4vdXRpbHNcIjtcbmltcG9ydCB7IENvbmRpdGlvbmFsQXN5bmMgfSBmcm9tIFwiLi4vdHlwZXNcIjtcbmltcG9ydCB7IEFTWU5DX01FVEFfS0VZIH0gZnJvbSBcIi4uL2NvbnN0YW50c1wiO1xuXG5sZXQgbW9kZWxCdWlsZGVyRnVuY3Rpb246IE1vZGVsQnVpbGRlckZ1bmN0aW9uIHwgdW5kZWZpbmVkO1xubGV0IGFjdGluZ01vZGVsUmVnaXN0cnk6IEJ1aWxkZXJSZWdpc3RyeTxhbnk+O1xuXG4vKipcbiAqIEBkZXNjcmlwdGlvbiBSZWdpc3RyeSB0eXBlIGZvciBzdG9yaW5nIGFuZCByZXRyaWV2aW5nIG1vZGVsIGNvbnN0cnVjdG9yc1xuICogQHN1bW1hcnkgVGhlIE1vZGVsUmVnaXN0cnkgdHlwZSBkZWZpbmVzIGEgcmVnaXN0cnkgZm9yIG1vZGVsIGNvbnN0cnVjdG9ycyB0aGF0IGV4dGVuZHNcbiAqIHRoZSBCdWlsZGVyUmVnaXN0cnkgaW50ZXJmYWNlLiBJdCBwcm92aWRlcyBhIHN0YW5kYXJkaXplZCB3YXkgdG8gcmVnaXN0ZXIsIHJldHJpZXZlLFxuICogYW5kIGJ1aWxkIG1vZGVsIGluc3RhbmNlcywgZW5hYmxpbmcgdGhlIG1vZGVsIHN5c3RlbSB0byB3b3JrIHdpdGggZGlmZmVyZW50IHR5cGVzIG9mIG1vZGVscy5cbiAqXG4gKiBAaW50ZXJmYWNlIE1vZGVsUmVnaXN0cnlcbiAqIEB0ZW1wbGF0ZSBUIFR5cGUgb2YgbW9kZWwgdGhhdCBjYW4gYmUgcmVnaXN0ZXJlZCwgbXVzdCBleHRlbmQgTW9kZWxcbiAqIEBleHRlbmRzIEJ1aWxkZXJSZWdpc3RyeTxUPlxuICogQG1lbWJlck9mIG1vZHVsZTpkZWNvcmF0b3ItdmFsaWRhdGlvblxuICogQGNhdGVnb3J5IE1vZGVsXG4gKi9cbmV4cG9ydCB0eXBlIE1vZGVsUmVnaXN0cnk8VCBleHRlbmRzIE1vZGVsPiA9IEJ1aWxkZXJSZWdpc3RyeTxUPjtcblxuLyoqXG4gKiBAZGVzY3JpcHRpb24gUmVnaXN0cnkgbWFuYWdlciBmb3IgbW9kZWwgY29uc3RydWN0b3JzIHRoYXQgZW5hYmxlcyBzZXJpYWxpemF0aW9uIGFuZCByZWJ1aWxkaW5nXG4gKiBAc3VtbWFyeSBUaGUgTW9kZWxSZWdpc3RyeU1hbmFnZXIgaW1wbGVtZW50cyB0aGUgTW9kZWxSZWdpc3RyeSBpbnRlcmZhY2UgYW5kIHByb3ZpZGVzXG4gKiBmdW5jdGlvbmFsaXR5IGZvciByZWdpc3RlcmluZywgcmV0cmlldmluZywgYW5kIGJ1aWxkaW5nIG1vZGVsIGluc3RhbmNlcy4gSXQgbWFpbnRhaW5zXG4gKiBhIGNhY2hlIG9mIG1vZGVsIGNvbnN0cnVjdG9ycyBpbmRleGVkIGJ5IG5hbWUsIGFsbG93aW5nIGZvciBlZmZpY2llbnQgbG9va3VwIGFuZCBpbnN0YW50aWF0aW9uLlxuICogVGhpcyBjbGFzcyBpcyBlc3NlbnRpYWwgZm9yIHRoZSBzZXJpYWxpemF0aW9uIGFuZCBkZXNlcmlhbGl6YXRpb24gb2YgbW9kZWwgb2JqZWN0cy5cbiAqXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFJlY29yZDxzdHJpbmcsIGFueT4pOiBib29sZWFufSBbdGVzdEZ1bmN0aW9uXSAtIEZ1bmN0aW9uIHRvIHRlc3QgaWYgYW4gb2JqZWN0IGlzIGEgbW9kZWwsIGRlZmF1bHRzIHRvIHtAbGluayBNb2RlbCNpc01vZGVsfVxuICpcbiAqIEBjbGFzcyBNb2RlbFJlZ2lzdHJ5TWFuYWdlclxuICogQHRlbXBsYXRlIE0gVHlwZSBvZiBtb2RlbCB0aGF0IGNhbiBiZSByZWdpc3RlcmVkLCBtdXN0IGV4dGVuZCBNb2RlbFxuICogQGltcGxlbWVudHMgTW9kZWxSZWdpc3RyeTxNPlxuICogQGNhdGVnb3J5IE1vZGVsXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIENyZWF0ZSBhIG1vZGVsIHJlZ2lzdHJ5XG4gKiBjb25zdCByZWdpc3RyeSA9IG5ldyBNb2RlbFJlZ2lzdHJ5TWFuYWdlcigpO1xuICpcbiAqIC8vIFJlZ2lzdGVyIGEgbW9kZWwgY2xhc3NcbiAqIHJlZ2lzdHJ5LnJlZ2lzdGVyKFVzZXIpO1xuICpcbiAqIC8vIFJldHJpZXZlIGEgbW9kZWwgY29uc3RydWN0b3IgYnkgbmFtZVxuICogY29uc3QgVXNlckNsYXNzID0gcmVnaXN0cnkuZ2V0KFwiVXNlclwiKTtcbiAqXG4gKiAvLyBCdWlsZCBhIG1vZGVsIGluc3RhbmNlIGZyb20gYSBwbGFpbiBvYmplY3RcbiAqIGNvbnN0IHVzZXJEYXRhID0geyBuYW1lOiBcIkpvaG5cIiwgYWdlOiAzMCB9O1xuICogY29uc3QgdXNlciA9IHJlZ2lzdHJ5LmJ1aWxkKHVzZXJEYXRhLCBcIlVzZXJcIik7XG4gKiBgYGBcbiAqXG4gKiBAbWVybWFpZFxuICogc2VxdWVuY2VEaWFncmFtXG4gKiAgIHBhcnRpY2lwYW50IEMgYXMgQ2xpZW50XG4gKiAgIHBhcnRpY2lwYW50IFIgYXMgTW9kZWxSZWdpc3RyeU1hbmFnZXJcbiAqICAgcGFydGljaXBhbnQgTSBhcyBNb2RlbCBDbGFzc1xuICpcbiAqICAgQy0+PlI6IG5ldyBNb2RlbFJlZ2lzdHJ5TWFuYWdlcih0ZXN0RnVuY3Rpb24pXG4gKiAgIEMtPj5SOiByZWdpc3RlcihNb2RlbENsYXNzKVxuICogICBSLT4+UjogU3RvcmUgaW4gY2FjaGVcbiAqICAgQy0+PlI6IGdldChcIk1vZGVsTmFtZVwiKVxuICogICBSLS0+PkM6IE1vZGVsQ2xhc3MgY29uc3RydWN0b3JcbiAqICAgQy0+PlI6IGJ1aWxkKGRhdGEsIFwiTW9kZWxOYW1lXCIpXG4gKiAgIFItPj5SOiBHZXQgY29uc3RydWN0b3IgZnJvbSBjYWNoZVxuICogICBSLT4+TTogbmV3IE1vZGVsQ2xhc3MoZGF0YSlcbiAqICAgTS0tPj5SOiBNb2RlbCBpbnN0YW5jZVxuICogICBSLS0+PkM6IE1vZGVsIGluc3RhbmNlXG4gKi9cbmV4cG9ydCBjbGFzcyBNb2RlbFJlZ2lzdHJ5TWFuYWdlcjxNIGV4dGVuZHMgTW9kZWw8dHJ1ZSB8IGZhbHNlPj5cbiAgaW1wbGVtZW50cyBNb2RlbFJlZ2lzdHJ5PE0+XG57XG4gIHByaXZhdGUgY2FjaGU6IFJlY29yZDxzdHJpbmcsIE1vZGVsQ29uc3RydWN0b3I8TT4+ID0ge307XG4gIHByaXZhdGUgcmVhZG9ubHkgdGVzdEZ1bmN0aW9uOiAob2JqOiBvYmplY3QpID0+IGJvb2xlYW47XG5cbiAgY29uc3RydWN0b3IoXG4gICAgdGVzdEZ1bmN0aW9uOiAob2JqOiBSZWNvcmQ8c3RyaW5nLCBhbnk+KSA9PiBib29sZWFuID0gTW9kZWwuaXNNb2RlbFxuICApIHtcbiAgICB0aGlzLnRlc3RGdW5jdGlvbiA9IHRlc3RGdW5jdGlvbjtcbiAgfVxuXG4gIC8qKlxuICAgKiBAZGVzY3JpcHRpb24gUmVnaXN0ZXJzIGEgbW9kZWwgY29uc3RydWN0b3Igd2l0aCB0aGUgcmVnaXN0cnlcbiAgICogQHN1bW1hcnkgQWRkcyBhIG1vZGVsIGNvbnN0cnVjdG9yIHRvIHRoZSByZWdpc3RyeSBjYWNoZSwgbWFraW5nIGl0IGF2YWlsYWJsZSBmb3JcbiAgICogbGF0ZXIgcmV0cmlldmFsIGFuZCBpbnN0YW50aWF0aW9uLiBJZiBubyBuYW1lIGlzIHByb3ZpZGVkLCB0aGUgY29uc3RydWN0b3IncyBuYW1lXG4gICAqIHByb3BlcnR5IGlzIHVzZWQgYXMgdGhlIGtleSBpbiB0aGUgcmVnaXN0cnkuXG4gICAqXG4gICAqIEBwYXJhbSB7TW9kZWxDb25zdHJ1Y3RvcjxNPn0gY29uc3RydWN0b3IgLSBUaGUgbW9kZWwgY29uc3RydWN0b3IgdG8gcmVnaXN0ZXJcbiAgICogQHBhcmFtIHtzdHJpbmd9IFtuYW1lXSAtIE9wdGlvbmFsIG5hbWUgdG8gcmVnaXN0ZXIgdGhlIGNvbnN0cnVjdG9yIHVuZGVyLCBkZWZhdWx0cyB0byBjb25zdHJ1Y3Rvci5uYW1lXG4gICAqIEByZXR1cm4ge3ZvaWR9XG4gICAqIEB0aHJvd3Mge0Vycm9yfSBJZiB0aGUgY29uc3RydWN0b3IgaXMgbm90IGEgZnVuY3Rpb25cbiAgICovXG4gIHJlZ2lzdGVyKGNvbnN0cnVjdG9yOiBNb2RlbENvbnN0cnVjdG9yPE0+LCBuYW1lPzogc3RyaW5nKTogdm9pZCB7XG4gICAgaWYgKHR5cGVvZiBjb25zdHJ1Y3RvciAhPT0gXCJmdW5jdGlvblwiKVxuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBcIk1vZGVsIHJlZ2lzdGVyaW5nIGZhaWxlZC4gTWlzc2luZyBDbGFzcyBuYW1lIG9yIGNvbnN0cnVjdG9yXCJcbiAgICAgICk7XG4gICAgbmFtZSA9IG5hbWUgfHwgY29uc3RydWN0b3IubmFtZTtcbiAgICB0aGlzLmNhY2hlW25hbWVdID0gY29uc3RydWN0b3I7XG4gIH1cblxuICAvKipcbiAgICogQHN1bW1hcnkgR2V0cyBhIHJlZ2lzdGVyZWQgTW9kZWwge0BsaW5rIE1vZGVsQ29uc3RydWN0b3J9XG4gICAqIEBwYXJhbSB7c3RyaW5nfSBuYW1lXG4gICAqL1xuICBnZXQobmFtZTogc3RyaW5nKTogTW9kZWxDb25zdHJ1Y3RvcjxNPiB8IHVuZGVmaW5lZCB7XG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiB0aGlzLmNhY2hlW25hbWVdO1xuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby11bnVzZWQtdmFyc1xuICAgIH0gY2F0Y2ggKGU6IGFueSkge1xuICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCBhbnk+fSBvYmpcbiAgICogQHBhcmFtIHtzdHJpbmd9IFtjbGF6el0gd2hlbiBwcm92aWRlZCwgaXQgd2lsbCBhdHRlbXB0IHRvIGZpbmQgdGhlIG1hdGNoaW5nIGNvbnN0cnVjdG9yXG4gICAqXG4gICAqIEB0aHJvd3MgRXJyb3IgSWYgY2xhenogaXMgbm90IGZvdW5kLCBvciBvYmogaXMgbm90IGEge0BsaW5rIE1vZGVsfSBtZWFuaW5nIGl0IGhhcyBubyB7QGxpbmsgTW9kZWxLZXlzLkFOQ0hPUn0gcHJvcGVydHlcbiAgICovXG4gIGJ1aWxkKG9iajogUmVjb3JkPHN0cmluZywgYW55PiA9IHt9LCBjbGF6ej86IHN0cmluZyk6IE0ge1xuICAgIGlmICghY2xhenogJiYgIXRoaXMudGVzdEZ1bmN0aW9uKG9iaikpXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJQcm92aWRlZCBvYmogaXMgbm90IGEgTW9kZWwgb2JqZWN0XCIpO1xuICAgIGNvbnN0IG5hbWUgPSBjbGF6eiB8fCBNb2RlbC5nZXRNZXRhZGF0YShvYmogYXMgYW55KTtcbiAgICBpZiAoIShuYW1lIGluIHRoaXMuY2FjaGUpKVxuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgUHJvdmlkZWQgY2xhc3MgJHtuYW1lfSBpcyBub3QgYSByZWdpc3RlcmVkIE1vZGVsIG9iamVjdGBcbiAgICAgICk7XG4gICAgcmV0dXJuIG5ldyB0aGlzLmNhY2hlW25hbWVdKG9iaik7XG4gIH1cbn1cblxuLyoqXG4gKiBAc3VtbWFyeSBCdWxrIFJlZ2lzdGVycyBNb2RlbHNcbiAqIEBkZXNjcmlwdGlvbiBVc2VmdWwgd2hlbiB1c2luZyBidW5kbGVycyB0aGF0IG1pZ2h0IG5vdCBldmFsdWF0ZSBhbGwgdGhlIGNvZGUgYXQgb25jZVxuICpcbiAqIEB0ZW1wbGF0ZSBNIGV4dGVuZHMgTW9kZWxcbiAqIEBwYXJhbSB7QXJyYXk8Q29uc3RydWN0b3I8TT4+IHwgQXJyYXk8e25hbWU6IHN0cmluZywgY29uc3RydWN0b3I6IENvbnN0cnVjdG9yPE0+fT59IFttb2RlbHNdXG4gKlxuICogQG1lbWJlck9mIG1vZHVsZTpkZWNvcmF0b3ItdmFsaWRhdGlvblxuICogQGNhdGVnb3J5IE1vZGVsXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBidWxrTW9kZWxSZWdpc3RlcjxNIGV4dGVuZHMgTW9kZWw+KFxuICAuLi5tb2RlbHM6IChDb25zdHJ1Y3RvcjxNPiB8IHsgbmFtZTogc3RyaW5nOyBjb25zdHJ1Y3RvcjogQ29uc3RydWN0b3I8TT4gfSlbXVxuKSB7XG4gIG1vZGVscy5mb3JFYWNoKFxuICAgIChtOiBDb25zdHJ1Y3RvcjxNPiB8IHsgbmFtZTogc3RyaW5nOyBjb25zdHJ1Y3RvcjogQ29uc3RydWN0b3I8TT4gfSkgPT4ge1xuICAgICAgY29uc3QgY29uc3RydWN0b3I6IENvbnN0cnVjdG9yPE0+ID0gKFxuICAgICAgICBtLmNvbnN0cnVjdG9yID8gbS5jb25zdHJ1Y3RvciA6IG1cbiAgICAgICkgYXMgQ29uc3RydWN0b3I8TT47XG4gICAgICBNb2RlbC5yZWdpc3Rlcihjb25zdHJ1Y3RvciwgKG0gYXMgQ29uc3RydWN0b3I8TT4pLm5hbWUpO1xuICAgIH1cbiAgKTtcbn1cblxuLyoqXG4gKiBAc3VtbWFyeSBBYnN0cmFjdCBjbGFzcyByZXByZXNlbnRpbmcgYSBWYWxpZGF0YWJsZSBNb2RlbCBvYmplY3RcbiAqIEBkZXNjcmlwdGlvbiBNZWFudCB0byBiZSB1c2VkIGFzIGEgYmFzZSBjbGFzcyBmb3IgYWxsIE1vZGVsIGNsYXNzZXNcbiAqXG4gKiBNb2RlbCBvYmplY3RzIG11c3Q6XG4gKiAgLSBIYXZlIGFsbCB0aGVpciByZXF1aXJlZCBwcm9wZXJ0aWVzIG1hcmtlZCB3aXRoICchJztcbiAqICAtIEhhdmUgYWxsIHRoZWlyIG9wdGlvbmFsIHByb3BlcnRpZXMgbWFya2VkIGFzICc/JzpcbiAqXG4gKiBAcGFyYW0ge01vZGVsQXJnPE1vZGVsPn0gbW9kZWwgYmFzZSBvYmplY3QgZnJvbSB3aGljaCB0byBwb3B1bGF0ZSBwcm9wZXJ0aWVzIGZyb21cbiAqXG4gKiBAY2xhc3MgTW9kZWxcbiAqIEBjYXRlZ29yeSBNb2RlbFxuICogQGFic3RyYWN0XG4gKiBAaW1wbGVtZW50cyBWYWxpZGF0YWJsZVxuICogQGltcGxlbWVudHMgU2VyaWFsaXphYmxlXG4gKlxuICogQGV4YW1wbGVcbiAqICAgICAgY2xhc3MgQ2xhc3NOYW1lIHtcbiAqICAgICAgICAgIEByZXF1aXJlZCgpXG4gKiAgICAgICAgICByZXF1aXJlZFByb3BlcnR5TmFtZSE6IFByb3BlcnR5VHlwZTtcbiAqXG4gKiAgICAgICAgICBvcHRpb25hbFByb3BlcnR5TmFtZT86IFByb3BlcnR5VHlwZTtcbiAqICAgICAgfVxuICovXG5leHBvcnQgYWJzdHJhY3QgY2xhc3MgTW9kZWw8QXN5bmMgZXh0ZW5kcyBib29sZWFuID0gZmFsc2U+XG4gIGltcGxlbWVudHNcbiAgICBWYWxpZGF0YWJsZTxBc3luYz4sXG4gICAgU2VyaWFsaXphYmxlLFxuICAgIEhhc2hhYmxlLFxuICAgIENvbXBhcmFibGU8TW9kZWw8QXN5bmM+Plxue1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLXVudXNlZC12YXJzXG4gIHByb3RlY3RlZCBjb25zdHJ1Y3Rvcihhcmc6IE1vZGVsQXJnPE1vZGVsPiB8IHVuZGVmaW5lZCA9IHVuZGVmaW5lZCkge31cblxuICBwdWJsaWMgaXNBc3luYygpOiBib29sZWFuIHtcbiAgICBjb25zdCBzZWxmID0gdGhpcyBhcyBhbnk7XG4gICAgcmV0dXJuICEhKHNlbGZbQVNZTkNfTUVUQV9LRVldID8/IHNlbGY/LmNvbnN0cnVjdG9yW0FTWU5DX01FVEFfS0VZXSk7XG4gIH1cblxuICAvKipcbiAgICogQGRlc2NyaXB0aW9uIFZhbGlkYXRlcyB0aGUgbW9kZWwgb2JqZWN0IGFnYWluc3QgaXRzIGRlZmluZWQgdmFsaWRhdGlvbiBydWxlc1xuICAgKiBAc3VtbWFyeSBWYWxpZGF0ZXMgdGhlIG9iamVjdCBhY2NvcmRpbmcgdG8gaXRzIGRlY29yYXRlZCBwcm9wZXJ0aWVzLCByZXR1cm5pbmcgYW55IHZhbGlkYXRpb24gZXJyb3JzXG4gICAqXG4gICAqIEBwYXJhbSB7YW55W119IFtleGNlcHRpb25zXSAtIFByb3BlcnRpZXMgaW4gdGhlIG9iamVjdCB0byBiZSBpZ25vcmVkIGZvciB0aGUgdmFsaWRhdGlvbi4gTWFya2VkIGFzICdhbnknIHRvIGFsbG93IGZvciBleHRlbnNpb24gYnV0IGV4cGVjdHMgc3RyaW5nc1xuICAgKiBAcmV0dXJuIHtNb2RlbEVycm9yRGVmaW5pdGlvbiB8IHVuZGVmaW5lZH0gLSBSZXR1cm5zIGEgTW9kZWxFcnJvckRlZmluaXRpb24gb2JqZWN0IGlmIHZhbGlkYXRpb24gZXJyb3JzIGV4aXN0LCBvdGhlcndpc2UgdW5kZWZpbmVkXG4gICAqL1xuICBwdWJsaWMgaGFzRXJyb3JzKFxuICAgIC4uLmV4Y2VwdGlvbnM6IGFueVtdXG4gICk6IENvbmRpdGlvbmFsQXN5bmM8QXN5bmMsIE1vZGVsRXJyb3JEZWZpbml0aW9uIHwgdW5kZWZpbmVkPiB7XG4gICAgcmV0dXJuIHZhbGlkYXRlPGFueSwgQXN5bmM+KFxuICAgICAgdGhpcyxcbiAgICAgIHRoaXMuaXNBc3luYygpIGFzIGFueSxcbiAgICAgIC4uLmV4Y2VwdGlvbnNcbiAgICApIGFzIGFueTtcbiAgfVxuXG4gIC8qKlxuICAgKiBAZGVzY3JpcHRpb24gRGV0ZXJtaW5lcyBpZiB0aGlzIG1vZGVsIGlzIGVxdWFsIHRvIGFub3RoZXIgb2JqZWN0XG4gICAqIEBzdW1tYXJ5IENvbXBhcmUgb2JqZWN0IGVxdWFsaXR5IHJlY3Vyc2l2ZWx5LCBjaGVja2luZyBhbGwgcHJvcGVydGllcyB1bmxlc3MgZXhjbHVkZWRcbiAgICpcbiAgICogQHBhcmFtIHthbnl9IG9iaiAtIE9iamVjdCB0byBjb21wYXJlIHRvXG4gICAqIEBwYXJhbSB7c3RyaW5nW119IFtleGNlcHRpb25zXSAtIFByb3BlcnR5IG5hbWVzIHRvIGJlIGV4Y2x1Z