@decaf-ts/decorator-validation
Version:
simple decorator based validation engine
600 lines • 75.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Model = exports.ModelRegistryManager = void 0;
exports.bulkModelRegister = bulkModelRegister;
const serialization_1 = require("./../utils/serialization.cjs");
const reflection_1 = require("@decaf-ts/reflection");
const validation_1 = require("./validation.cjs");
const hashing_1 = require("./../utils/hashing.cjs");
const constants_1 = require("./../utils/constants.cjs");
const constants_2 = require("./../validation/Validators/constants.cjs");
const constants_3 = require("./constants.cjs");
const utils_1 = require("./utils.cjs");
const constants_4 = require("./../constants/index.cjs");
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
*/
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);
}
}
exports.ModelRegistryManager = ModelRegistryManager;
/**
* @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
*/
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;
* }
*/
class Model {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
constructor(arg = undefined) { }
isAsync() {
const self = this;
return !!(self[constants_4.ASYNC_META_KEY] ?? self?.constructor[constants_4.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 (0, validation_1.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 (0, reflection_1.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(constants_1.ModelKeys.SERIALIZATION), this.constructor);
if (metadata && metadata.serializer)
return serialization_1.Serialization.deserialize(str, metadata.serializer, ...(metadata.args || []));
return serialization_1.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_1.Reflection.getPropertyDecorators(constants_2.ValidationKeys.REFLECT, self, prop).decorators;
decorators = allDecorators.filter((d) => [constants_1.ModelKeys.TYPE, constants_2.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(constants_3.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 === constants_2.ValidationKeys.LIST);
if (listDec) {
const clazzName = listDec.props.clazz.find((t) => !constants_3.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 (0, utils_1.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[constants_1.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 (0, reflection_1.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 (0, validation_1.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(constants_1.ModelKeys.SERIALIZATION), model.constructor);
if (metadata && metadata.serializer)
return serialization_1.Serialization.serialize(this, metadata.serializer, ...(metadata.args || []));
return serialization_1.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(constants_1.ModelKeys.HASHING), model.constructor);
if (metadata && metadata.algorithm)
return hashing_1.Hashing.hash(model, metadata.algorithm, ...(metadata.args || []));
return hashing_1.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 (0, utils_1.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(constants_1.ModelKeys.TYPE, target, attribute);
return Model.get(metadata.name) ? metadata.name : undefined;
}
static describe(model, key) {
const descKey = Model.key(constants_1.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(constants_1.ModelKeys.DESCRIPTION), model instanceof Model ? model.constructor : model) || model.toString());
}
}
exports.Model = Model;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTW9kZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9kZWwvTW9kZWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBZ0tBLDhDQVdDO0FBM0tELGdFQUF1RDtBQWF2RCxxREFBOEU7QUFDOUUsaURBQXdDO0FBQ3hDLG9EQUEyQztBQUMzQyx3REFBK0M7QUFDL0Msd0VBQW9FO0FBQ3BFLCtDQUFzRDtBQUN0RCx1Q0FBbUQ7QUFFbkQsd0RBQThDO0FBRTlDLElBQUksb0JBQXNELENBQUM7QUFDM0QsSUFBSSxtQkFBeUMsQ0FBQztBQWdCOUM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E4Q0c7QUFDSCxNQUFhLG9CQUFvQjtJQU0vQixZQUNFLGVBQXNELEtBQUssQ0FBQyxPQUFPO1FBSjdELFVBQUssR0FBd0MsRUFBRSxDQUFDO1FBTXRELElBQUksQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDO0lBQ25DLENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBQ0gsUUFBUSxDQUFDLFdBQWdDLEVBQUUsSUFBYTtRQUN0RCxJQUFJLE9BQU8sV0FBVyxLQUFLLFVBQVU7WUFDbkMsTUFBTSxJQUFJLEtBQUssQ0FDYiw2REFBNkQsQ0FDOUQsQ0FBQztRQUNKLElBQUksR0FBRyxJQUFJLElBQUksV0FBVyxDQUFDLElBQUksQ0FBQztRQUNoQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLFdBQVcsQ0FBQztJQUNqQyxDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsR0FBRyxDQUFDLElBQVk7UUFDZCxJQUFJLENBQUM7WUFDSCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDeEIsNkRBQTZEO1FBQy9ELENBQUM7UUFBQyxPQUFPLENBQU0sRUFBRSxDQUFDO1lBQ2hCLE9BQU8sU0FBUyxDQUFDO1FBQ25CLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7O09BS0c7SUFD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QUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FDbkQsSUFBSSxLQUFLLENBQUMsUUFBUSxFQUFFLENBQ3RCLENBQUM7SUFDSixDQUFDO0NBQ0Y7QUFyaUJELHNCQXFpQkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBTZXJpYWxpemF0aW9uIH0gZnJvbSBcIi4uL3V0aWxzL3NlcmlhbGl6YXRpb25cIjtcbmltcG9ydCB7IEJ1aWxkZXJSZWdpc3RyeSB9IGZyb20gXCIuLi91dGlscy9yZWdpc3RyeVwiO1xuaW1wb3J0IHsgTW9kZWxFcnJvckRlZmluaXRpb24gfSBmcm9tIFwiLi9Nb2RlbEVycm9yRGVmaW5pdGlvblwiO1xuaW1wb3J0IHtcbiAgQ29tcGFyYWJsZSxcbiAgQ29uc3RydWN0b3IsXG4gIEhhc2hhYmxlLFxuICBNb2RlbEFyZyxcbiAgTW9kZWxCdWlsZGVyRnVuY3Rpb24sXG4gIE1vZGVsQ29uc3RydWN0b3IsXG4gIFNlcmlhbGl6YWJsZSxcbiAgVmFsaWRhdGFibGUsXG59IGZyb20gXCIuL3R5cGVzXCI7XG5pbXBvcnQgeyBEZWNvcmF0b3JNZXRhZGF0YSwgaXNFcXVhbCwgUmVmbGVjdGlvbiB9IGZyb20gXCJAZGVjYWYtdHMvcmVmbGVjdGlvblwiO1xuaW1wb3J0IHsgdmFsaWRhdGUgfSBmcm9tIFwiLi92YWxpZGF0aW9uXCI7XG5pbXBvcnQgeyBIYXNoaW5nIH0gZnJvbSBcIi4uL3V0aWxzL2hhc2hpbmdcIjtcbmltcG9ydCB7IE1vZGVsS2V5cyB9IGZyb20gXCIuLi91dGlscy9jb25zdGFudHNcIjtcbmltcG9ydCB7IFZhbGlkYXRpb25LZXlzIH0gZnJvbSBcIi4uL3ZhbGlkYXRpb24vVmFsaWRhdG9ycy9jb25zdGFudHNcIjtcbmltcG9ydCB7IGpzVHlwZXMsIFJlc2VydmVkTW9kZWxzIH0gZnJvbSBcIi4vY29uc3RhbnRzXCI7XG5pbXBvcnQgeyBnZXRNZXRhZGF0YSwgZ2V0TW9kZWxLZXkgfSBmcm9tIFwiLi91dGlsc1wiO1xuaW1wb3J0IHsgQ29uZGl0aW9uYWxBc3luYyB9IGZyb20gXCIuLi90eXBlc1wiO1xuaW1wb3J0IHsgQVNZTkNfTUVUQV9LRVkgfSBmcm9tIFwiLi4vY29uc3RhbnRzXCI7XG5cbmxldCBtb2RlbEJ1aWxkZXJGdW5jdGlvbjogTW9kZWxCdWlsZGVyRnVuY3Rpb24gfCB1bmRlZmluZWQ7XG5sZXQgYWN0aW5nTW9kZWxSZWdpc3RyeTogQnVpbGRlclJlZ2lzdHJ5PGFueT47XG5cbi8qKlxuICogQGRlc2NyaXB0aW9uIFJlZ2lzdHJ5IHR5cGUgZm9yIHN0b3JpbmcgYW5kIHJldHJpZXZpbmcgbW9kZWwgY29uc3RydWN0b3JzXG4gKiBAc3VtbWFyeSBUaGUgTW9kZWxSZWdpc3RyeSB0eXBlIGRlZmluZXMgYSByZWdpc3RyeSBmb3IgbW9kZWwgY29uc3RydWN0b3JzIHRoYXQgZXh0ZW5kc1xuICogdGhlIEJ1aWxkZXJSZWdpc3RyeSBpbnRlcmZhY2UuIEl0IHByb3ZpZGVzIGEgc3RhbmRhcmRpemVkIHdheSB0byByZWdpc3RlciwgcmV0cmlldmUsXG4gKiBhbmQgYnVpbGQgbW9kZWwgaW5zdGFuY2VzLCBlbmFibGluZyB0aGUgbW9kZWwgc3lzdGVtIHRvIHdvcmsgd2l0aCBkaWZmZXJlbnQgdHlwZXMgb2YgbW9kZWxzLlxuICpcbiAqIEBpbnRlcmZhY2UgTW9kZWxSZWdpc3RyeVxuICogQHRlbXBsYXRlIFQgVHlwZSBvZiBtb2RlbCB0aGF0IGNhbiBiZSByZWdpc3RlcmVkLCBtdXN0IGV4dGVuZCBNb2RlbFxuICogQGV4dGVuZHMgQnVpbGRlclJlZ2lzdHJ5PFQ+XG4gKiBAbWVtYmVyT2YgbW9kdWxlOmRlY29yYXRvci12YWxpZGF0aW9uXG4gKiBAY2F0ZWdvcnkgTW9kZWxcbiAqL1xuZXhwb3J0IHR5cGUgTW9kZWxSZWdpc3RyeTxUIGV4dGVuZHMgTW9kZWw+ID0gQnVpbGRlclJlZ2lzdHJ5PFQ+O1xuXG4vKipcbiAqIEBkZXNjcmlwdGlvbiBSZWdpc3RyeSBtYW5hZ2VyIGZvciBtb2RlbCBjb25zdHJ1Y3RvcnMgdGhhdCBlbmFibGVzIHNlcmlhbGl6YXRpb24gYW5kIHJlYnVpbGRpbmdcbiAqIEBzdW1tYXJ5IFRoZSBNb2RlbFJlZ2lzdHJ5TWFuYWdlciBpbXBsZW1lbnRzIHRoZSBNb2RlbFJlZ2lzdHJ5IGludGVyZmFjZSBhbmQgcHJvdmlkZXNcbiAqIGZ1bmN0aW9uYWxpdHkgZm9yIHJlZ2lzdGVyaW5nLCByZXRyaWV2aW5nLCBhbmQgYnVpbGRpbmcgbW9kZWwgaW5zdGFuY2VzLiBJdCBtYWludGFpbnNcbiAqIGEgY2FjaGUgb2YgbW9kZWwgY29uc3RydWN0b3JzIGluZGV4ZWQgYnkgbmFtZSwgYWxsb3dpbmcgZm9yIGVmZmljaWVudCBsb29rdXAgYW5kIGluc3RhbnRpYXRpb24uXG4gKiBUaGlzIGNsYXNzIGlzIGVzc2VudGlhbCBmb3IgdGhlIHNlcmlhbGl6YXRpb24gYW5kIGRlc2VyaWFsaXphdGlvbiBvZiBtb2RlbCBvYmplY3RzLlxuICpcbiAqIEBwYXJhbSB7ZnVuY3Rpb24oUmVjb3JkPHN0cmluZywgYW55Pik6IGJvb2xlYW59IFt0ZXN0RnVuY3Rpb25dIC0gRnVuY3Rpb24gdG8gdGVzdCBpZiBhbiBvYmplY3QgaXMgYSBtb2RlbCwgZGVmYXVsdHMgdG8ge0BsaW5rIE1vZGVsI2lzTW9kZWx9XG4gKlxuICogQGNsYXNzIE1vZGVsUmVnaXN0cnlNYW5hZ2VyXG4gKiBAdGVtcGxhdGUgTSBUeXBlIG9mIG1vZGVsIHRoYXQgY2FuIGJlIHJlZ2lzdGVyZWQsIG11c3QgZXh0ZW5kIE1vZGVsXG4gKiBAaW1wbGVtZW50cyBNb2RlbFJlZ2lzdHJ5PE0+XG4gKiBAY2F0ZWdvcnkgTW9kZWxcbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogLy8gQ3JlYXRlIGEgbW9kZWwgcmVnaXN0cnlcbiAqIGNvbnN0IHJlZ2lzdHJ5ID0gbmV3IE1vZGVsUmVnaXN0cnlNYW5hZ2VyKCk7XG4gKlxuICogLy8gUmVnaXN0ZXIgYSBtb2RlbCBjbGFzc1xuICogcmVnaXN0cnkucmVnaXN0ZXIoVXNlcik7XG4gKlxuICogLy8gUmV0cmlldmUgYSBtb2RlbCBjb25zdHJ1Y3RvciBieSBuYW1lXG4gKiBjb25zdCBVc2VyQ2xhc3MgPSByZWdpc3RyeS5nZXQoXCJVc2VyXCIpO1xuICpcbiAqIC8vIEJ1aWxkIGEgbW9kZWwgaW5zdGFuY2UgZnJvbSBhIHBsYWluIG9iamVjdFxuICogY29uc3QgdXNlckRhdGEgPSB7IG5hbWU6IFwiSm9oblwiLCBhZ2U6IDMwIH07XG4gKiBjb25zdCB1c2VyID0gcmVnaXN0cnkuYnVpbGQodXNlckRhdGEsIFwiVXNlclwiKTtcbiAqIGBgYFxuICpcbiAqIEBtZXJtYWlkXG4gKiBzZXF1ZW5jZURpYWdyYW1cbiAqICAgcGFydGljaXBhbnQgQyBhcyBDbGllbnRcbiAqICAgcGFydGljaXBhbnQgUiBhcyBNb2RlbFJlZ2lzdHJ5TWFuYWdlclxuICogICBwYXJ0aWNpcGFudCBNIGFzIE1vZGVsIENsYXNzXG4gKlxuICogICBDLT4+UjogbmV3IE1vZGVsUmVnaXN0cnlNYW5hZ2VyKHRlc3RGdW5jdGlvbilcbiAqICAgQy0+PlI6IHJlZ2lzdGVyKE1vZGVsQ2xhc3MpXG4gKiAgIFItPj5SOiBTdG9yZSBpbiBjYWNoZVxuICogICBDLT4+UjogZ2V0KFwiTW9kZWxOYW1lXCIpXG4gKiAgIFItLT4+QzogTW9kZWxDbGFzcyBjb25zdHJ1Y3RvclxuICogICBDLT4+UjogYnVpbGQoZGF0YSwgXCJNb2RlbE5hbWVcIilcbiAqICAgUi0+PlI6IEdldCBjb25zdHJ1Y3RvciBmcm9tIGNhY2hlXG4gKiAgIFItPj5NOiBuZXcgTW9kZWxDbGFzcyhkYXRhKVxuICogICBNLS0+PlI6IE1vZGVsIGluc3RhbmNlXG4gKiAgIFItLT4+QzogTW9kZWwgaW5zdGFuY2VcbiAqL1xuZXhwb3J0IGNsYXNzIE1vZGVsUmVnaXN0cnlNYW5hZ2VyPE0gZXh0ZW5kcyBNb2RlbDx0cnVlIHwgZmFsc2U+PlxuICBpbXBsZW1lbnRzIE1vZGVsUmVnaXN0cnk8TT5cbntcbiAgcHJpdmF0ZSBjYWNoZTogUmVjb3JkPHN0cmluZywgTW9kZWxDb25zdHJ1Y3RvcjxNPj4gPSB7fTtcbiAgcHJpdmF0ZSByZWFkb25seSB0ZXN0RnVuY3Rpb246IChvYmo6IG9iamVjdCkgPT4gYm9vbGVhbjtcblxuICBjb25zdHJ1Y3RvcihcbiAgICB0ZXN0RnVuY3Rpb246IChvYmo6IFJlY29yZDxzdHJpbmcsIGFueT4pID0+IGJvb2xlYW4gPSBNb2RlbC5pc01vZGVsXG4gICkge1xuICAgIHRoaXMudGVzdEZ1bmN0aW9uID0gdGVzdEZ1bmN0aW9uO1xuICB9XG5cbiAgLyoqXG4gICAqIEBkZXNjcmlwdGlvbiBSZWdpc3RlcnMgYSBtb2RlbCBjb25zdHJ1Y3RvciB3aXRoIHRoZSByZWdpc3RyeVxuICAgKiBAc3VtbWFyeSBBZGRzIGEgbW9kZWwgY29uc3RydWN0b3IgdG8gdGhlIHJlZ2lzdHJ5IGNhY2hlLCBtYWtpbmcgaXQgYXZhaWxhYmxlIGZvclxuICAgKiBsYXRlciByZXRyaWV2YWwgYW5kIGluc3RhbnRpYXRpb24uIElmIG5vIG5hbWUgaXMgcHJvdmlkZWQsIHRoZSBjb25zdHJ1Y3RvcidzIG5hbWVcbiAgICogcHJvcGVydHkgaXMgdXNlZCBhcyB0aGUga2V5IGluIHRoZSByZWdpc3RyeS5cbiAgICpcbiAgICogQHBhcmFtIHtNb2RlbENvbnN0cnVjdG9yPE0+fSBjb25zdHJ1Y3RvciAtIFRoZSBtb2RlbCBjb25zdHJ1Y3RvciB0byByZWdpc3RlclxuICAgKiBAcGFyYW0ge3N0cmluZ30gW25hbWVdIC0gT3B0aW9uYWwgbmFtZSB0byByZWdpc3RlciB0aGUgY29uc3RydWN0b3IgdW5kZXIsIGRlZmF1bHRzIHRvIGNvbnN0cnVjdG9yLm5hbWVcbiAgICogQHJldHVybiB7dm9pZH1cbiAgICogQHRocm93cyB7RXJyb3J9IElmIHRoZSBjb25zdHJ1Y3RvciBpcyBub3QgYSBmdW5jdGlvblxuICAgKi9cbiAgcmVnaXN0ZXIoY29uc3RydWN0b3I6IE1vZGVsQ29uc3RydWN0b3I8TT4sIG5hbWU/OiBzdHJpbmcpOiB2b2lkIHtcbiAgICBpZiAodHlwZW9mIGNvbnN0cnVjdG9yICE9PSBcImZ1bmN0aW9uXCIpXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIFwiTW9kZWwgcmVnaXN0ZXJpbmcgZmFpbGVkLiBNaXNzaW5nIENsYXNzIG5hbWUgb3IgY29uc3RydWN0b3JcIlxuICAgICAgKTtcbiAgICBuYW1lID0gbmFtZSB8fCBjb25zdHJ1Y3Rvci5uYW1lO1xuICAgIHRoaXMuY2FjaGVbbmFtZV0gPSBjb25zdHJ1Y3RvcjtcbiAgfVxuXG4gIC8qKlxuICAgKiBAc3VtbWFyeSBHZXRzIGEgcmVnaXN0ZXJlZCBNb2RlbCB7QGxpbmsgTW9kZWxDb25zdHJ1Y3Rvcn1cbiAgICogQHBhcmFtIHtzdHJpbmd9IG5hbWVcbiAgICovXG4gIGdldChuYW1lOiBzdHJpbmcpOiBNb2RlbENvbnN0cnVjdG9yPE0+IHwgdW5kZWZpbmVkIHtcbiAgICB0cnkge1xuICAgICAgcmV0dXJuIHRoaXMuY2FjaGVbbmFtZV07XG4gICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLXVudXNlZC12YXJzXG4gICAgfSBjYXRjaCAoZTogYW55KSB7XG4gICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIGFueT59IG9ialxuICAgKiBAcGFyYW0ge3N0cmluZ30gW2NsYXp6XSB3aGVuIHByb3ZpZGVkLCBpdCB3aWxsIGF0dGVtcHQgdG8gZmluZCB0aGUgbWF0Y2hpbmcgY29uc3RydWN0b3JcbiAgICpcbiAgICogQHRocm93cyBFcnJvciBJZiBjbGF6eiBpcyBub3QgZm91bmQsIG9yIG9iaiBpcyBub3QgYSB7QGxpbmsgTW9kZWx9IG1lYW5pbmcgaXQgaGFzIG5vIHtAbGluayBNb2RlbEtleXMuQU5DSE9SfSBwcm9wZXJ0eVxuICAgKi9cbiAgYnVpbGQob2JqOiBSZWNvcmQ8c3RyaW5nLCBhbnk+ID0ge30sIGNsYXp6Pzogc3RyaW5nKTogTSB7XG4gICAgaWYgKCFjbGF6eiAmJiAhdGhpcy50ZXN0RnVuY3Rpb24ob2JqKSlcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIlByb3ZpZGVkIG9iaiBpcyBub3QgYSBNb2RlbCBvYmplY3RcIik7XG4gICAgY29uc3QgbmFtZSA9IGNsYXp6IHx8IE1vZGVsLmdldE1ldGFkYXRhKG9iaiBhcyBhbnkpO1xuICAgIGlmICghKG5hbWUgaW4gdGhpcy5jYWNoZSkpXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIGBQcm92aWRlZCBjbGFzcyAke25hbWV9IGlzIG5vdCBhIHJlZ2lzdGVyZWQgTW9kZWwgb2JqZWN0YFxuICAgICAgKTtcbiAgICByZXR1cm4gbmV3IHRoaXMuY2FjaGVbbmFtZV0ob2JqKTtcbiAgfVxufVxuXG4vKipcbiAqIEBzdW1tYXJ5IEJ1bGsgUmVnaXN0ZXJzIE1vZGVsc1xuICogQGRlc2NyaXB0aW9uIFVzZWZ1bCB3aGVuIHVzaW5nIGJ1bmRsZXJzIHRoYXQgbWlnaHQgbm90IGV2YWx1YXRlIGFsbCB0aGUgY29kZSBhdCBvbmNlXG4gKlxuICogQHRlbXBsYXRlIE0gZXh0ZW5kcyBNb2RlbFxuICogQHBhcmFtIHtBcnJheTxDb25zdHJ1Y3RvcjxNPj4gfCBBcnJheTx7bmFtZTogc3RyaW5nLCBjb25zdHJ1Y3RvcjogQ29uc3RydWN0b3I8TT59Pn0gW21vZGVsc11cbiAqXG4gKiBAbWVtYmVyT2YgbW9kdWxlOmRlY29yYXRvci12YWxpZGF0aW9uXG4gKiBAY2F0ZWdvcnkgTW9kZWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJ1bGtNb2RlbFJlZ2lzdGVyPE0gZXh0ZW5kcyBNb2RlbD4oXG4gIC4uLm1vZGVsczogKENvbnN0cnVjdG9yPE0+IHwgeyBuYW1lOiBzdHJpbmc7IGNvbnN0cnVjdG9yOiBDb25zdHJ1Y3RvcjxNPiB9KVtdXG4pIHtcbiAgbW9kZWxzLmZvckVhY2goXG4gICAgKG06IENvbnN0cnVjdG9yPE0+IHwgeyBuYW1lOiBzdHJpbmc7IGNvbnN0cnVjdG9yOiBDb25zdHJ1Y3RvcjxNPiB9KSA9PiB7XG4gICAgICBjb25zdCBjb25zdHJ1Y3RvcjogQ29uc3RydWN0b3I8TT4gPSAoXG4gICAgICAgIG0uY29uc3RydWN0b3IgPyBtLmNvbnN0cnVjdG9yIDogbVxuICAgICAgKSBhcyBDb25zdHJ1Y3RvcjxNPjtcbiAgICAgIE1vZGVsLnJlZ2lzdGVyKGNvbnN0cnVjdG9yLCAobSBhcyBDb25zdHJ1Y3RvcjxNPikubmFtZSk7XG4gICAgfVxuICApO1xufVxuXG4vKipcbiAqIEBzdW1tYXJ5IEFic3RyYWN0IGNsYXNzIHJlcHJlc2VudGluZyBhIFZhbGlkYXRhYmxlIE1vZGVsIG9iamVjdFxuICogQGRlc2NyaXB0aW9uIE1lYW50IHRvIGJlIHVzZWQgYXMgYSBiYXNlIGNsYXNzIGZvciBhbGwgTW9kZWwgY2xhc3Nlc1xuICpcbiAqIE1vZGVsIG9iamVjdHMgbXVzdDpcbiAqICAtIEhhdmUgYWxsIHRoZWlyIHJlcXVpcmVkIHByb3BlcnRpZXMgbWFya2VkIHdpdGggJyEnO1xuICogIC0gSGF2ZSBhbGwgdGhlaXIgb3B0aW9uYWwgcHJvcGVydGllcyBtYXJrZWQgYXMgJz8nOlxuICpcbiAqIEBwYXJhbSB7TW9kZWxBcmc8TW9kZWw+fSBtb2RlbCBiYXNlIG9iamVjdCBmcm9tIHdoaWNoIHRvIHBvcHVsYXRlIHByb3BlcnRpZXMgZnJvbVxuICpcbiAqIEBjbGFzcyBNb2RlbFxuICogQGNhdGVnb3J5IE1vZGVsXG4gKiBAYWJzdHJhY3RcbiAqIEBpbXBsZW1lbnRzIFZhbGlkYXRhYmxlXG4gKiBAaW1wbGVtZW50cyBTZXJpYWxpemFibGVcbiAqXG4gKiBAZXhhbXBsZVxuICogICAgICBjbGFzcyBDbGFzc05hbWUge1xuICogICAgICAgICAgQHJlcXVpcmVkKClcbiAqICAgICAgICAgIHJlcXVpcmVkUHJvcGVydHlOYW1lITogUHJvcGVydHlUeXBlO1xuICpcbiAqICAgICAgICAgIG9wdGlvbmFsUHJvcGVydHlOYW1lPzogUHJvcGVydHlUeXBlO1xuICogICAgICB9XG4gKi9cbmV4cG9ydCBhYnN0cmFjdCBjbGFzcyBNb2RlbDxBc3luYyBleHRlbmRzIGJvb2xlYW4gPSBmYWxzZT5cbiAgaW1wbGVtZW50c1xuICAgIFZhbGlkYXRhYmxlPEFzeW5jPixcbiAgICBTZXJpYWxpemFibGUsXG4gICAgSGFzaGFibGUsXG4gICAgQ29tcGFyYWJsZTxNb2RlbDxBc3luYz4+XG57XG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tdW51c2VkLXZhcnNcbiAgcHJvdGVjdGVkIGNvbnN0cnVjdG9yKGFyZzogTW9kZWxBcmc8TW9kZWw+IHwgdW5kZWZpbmVkID0gdW5kZWZpbmVkKSB7fVxuXG4gIHB1YmxpYyBpc0FzeW5jKCk6IGJvb2xlYW4ge1xuICAgIGNvbnN0IHNlbGYgPSB0aGlzIGFzIGFueTtcbiAgICByZXR1cm4gISEoc2VsZltBU1lOQ19NRVRBX0tFWV0gPz8gc2VsZj8uY29uc3RydWN0b3JbQVNZTkNfTUVUQV9LRVldKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBAZGVzY3JpcHRpb24gVmFsaWRhdGVzIHRoZSBtb2RlbCBvYmplY3QgYWdhaW5zdCBpdHMgZGVmaW5lZCB2YWxpZGF0aW9uIHJ1bGVzXG4gICAqIEBzdW1tYXJ5IFZhbGlkYXRlcyB0aGUgb2JqZWN0IGFjY29yZGluZyB0byBpdHMgZGVjb3JhdGVkIHByb3BlcnRpZXMsIHJldHVybmluZyBhbnkgdmFsaWRhdGlvbiBlcnJvcnNcbiAgICpcbiAgICogQHBhcmFtIHthbnlbXX0gW2V4Y2VwdGlvbnNdIC0gUHJvcGVydGllcyBpbiB0aGUgb2JqZWN0IHRvIGJlIGlnbm9yZWQgZm9yIHRoZSB2YWxpZGF0aW9uLiBNYXJrZWQgYXMgJ2FueScgdG8gYWxsb3cgZm9yIGV4dGVuc2lvbiBidXQgZXhwZWN0cyBzdHJpbmdzXG4gICAqIEByZXR1cm4ge01vZGVsRXJyb3JEZWZpbml0aW9uIHwgdW5kZWZpbmVkfSAtIFJldHVybnMgYSBNb2RlbEVycm9yRGVmaW5pdGlvbiBvYmplY3QgaWYgdmFsaWRhdGlvbiBlcnJvcnMgZXhpc3QsIG90aGVyd2lzZSB1bmRlZmluZWRcbiAgICovXG4gIHB1YmxpYyBoYXNFcnJvcnMoXG4gICAgLi4uZXhjZXB0aW9uczogYW55W11cbiAgKTogQ29uZGl0aW9uYWxBc3luYzxBc3luYywgTW9kZWxFcnJvckRlZmluaXRpb24gfCB1bmRlZmluZWQ+IHtcbiAgICByZXR1cm4gdmFsaWRhdGU8YW55LCBBc3luYz4oXG4gICAgICB0aGlzLFxuICAgICAgdGhpcy5pc0FzeW5jKCkgYXMgYW55LFxuICAgICAgLi4uZXhjZXB0aW9uc1x