2018-05-23 13:34:53 -07:00
|
|
|
import { typeOf } from '@ember/utils';
|
|
|
|
|
import { DatasetClassifiers } from 'wherehows-web/constants';
|
2018-05-25 16:28:46 -07:00
|
|
|
import { arrayEvery, arrayMap, arrayReduce } from 'wherehows-web/utils/array';
|
2018-05-23 13:34:53 -07:00
|
|
|
import { IObject } from 'wherehows-web/typings/generic';
|
2018-05-25 16:28:46 -07:00
|
|
|
import { isObject } from 'wherehows-web/utils/object';
|
2018-06-17 03:51:10 -07:00
|
|
|
import { difference } from 'lodash';
|
2018-05-23 13:34:53 -07:00
|
|
|
|
|
|
|
|
/**
|
2018-05-25 16:28:46 -07:00
|
|
|
* Defines the interface for an IDL that specifies the data types for properties on
|
|
|
|
|
* a compliance metadata object
|
|
|
|
|
* @interface IMetadataType
|
2018-05-23 13:34:53 -07:00
|
|
|
*/
|
2018-05-25 16:28:46 -07:00
|
|
|
interface IMetadataType {
|
|
|
|
|
// the expected type or types for the property with @name
|
|
|
|
|
'@type': string | Array<string>;
|
|
|
|
|
// the name of the property that should be on the metadata object
|
|
|
|
|
'@name': string;
|
|
|
|
|
// optional list of properties that are expected on the metadata object
|
|
|
|
|
'@props'?: Array<IMetadataType>;
|
|
|
|
|
// optional list of expected string values for an enum type (not implemented)
|
|
|
|
|
'@symbols'?: Array<string>;
|
2018-05-23 13:34:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-05-25 16:28:46 -07:00
|
|
|
* Maps a datasetClassification property to the expected type of boolean
|
|
|
|
|
* @param {string} prop
|
|
|
|
|
* @returns {IMetadataType}
|
2018-05-23 13:34:53 -07:00
|
|
|
*/
|
2018-05-25 16:28:46 -07:00
|
|
|
const datasetClassificationPropType = (prop: string): IMetadataType => ({
|
|
|
|
|
'@type': 'boolean',
|
|
|
|
|
'@name': prop
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
2018-06-13 15:12:23 -07:00
|
|
|
* Lists the types for objects or instances in the the compliance metadata entities list
|
|
|
|
|
* @type Array<IMetadataType>
|
2018-05-25 16:28:46 -07:00
|
|
|
*/
|
2018-06-13 15:12:23 -07:00
|
|
|
const complianceEntitiesTaxonomy: Array<IMetadataType> = [
|
2018-05-23 13:34:53 -07:00
|
|
|
{
|
2018-05-25 16:28:46 -07:00
|
|
|
'@type': 'array',
|
|
|
|
|
'@name': 'complianceEntities',
|
2018-05-23 13:34:53 -07:00
|
|
|
'@props': [
|
2018-05-25 16:28:46 -07:00
|
|
|
{
|
|
|
|
|
'@name': 'identifierField',
|
|
|
|
|
'@type': 'string'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'@name': 'identifierType',
|
|
|
|
|
'@type': ['string', 'null']
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'@type': ['string', 'null'],
|
|
|
|
|
'@name': 'logicalType'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'@name': 'nonOwner',
|
|
|
|
|
'@type': ['boolean', 'null']
|
|
|
|
|
},
|
2018-06-13 20:35:19 -07:00
|
|
|
{
|
|
|
|
|
'@name': 'readonly',
|
|
|
|
|
'@type': 'boolean'
|
|
|
|
|
},
|
2018-05-25 16:28:46 -07:00
|
|
|
{
|
|
|
|
|
'@name': 'valuePattern',
|
|
|
|
|
'@type': ['string', 'null']
|
|
|
|
|
}
|
|
|
|
|
]
|
2018-06-13 15:12:23 -07:00
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Defines the shape of the dataset compliance metadata json object using the IMetadataType interface
|
|
|
|
|
* @type {Array<IMetadataType>}
|
|
|
|
|
*/
|
|
|
|
|
const complianceMetadataTaxonomy: Array<IMetadataType> = [
|
|
|
|
|
{
|
|
|
|
|
'@type': 'object',
|
|
|
|
|
'@name': 'datasetClassification',
|
|
|
|
|
'@props': arrayMap(datasetClassificationPropType)(Object.keys(DatasetClassifiers))
|
2018-05-31 11:27:33 -07:00
|
|
|
},
|
2018-06-13 15:12:23 -07:00
|
|
|
...complianceEntitiesTaxonomy,
|
2018-05-31 11:27:33 -07:00
|
|
|
{
|
|
|
|
|
'@type': ['string', 'null'],
|
|
|
|
|
'@name': 'compliancePurgeNote'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'@type': 'string',
|
|
|
|
|
'@name': 'complianceType'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'@type': ['string', 'null'],
|
|
|
|
|
'@name': 'confidentiality'
|
2018-05-23 13:34:53 -07:00
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks that a value type matches an expected pattern string
|
|
|
|
|
* @param {*} value the value to check
|
2018-05-25 16:28:46 -07:00
|
|
|
* @param {string | Array<string>} expectedType the pattern string to match against
|
2018-05-23 13:34:53 -07:00
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
2018-05-25 16:28:46 -07:00
|
|
|
const valueEquiv = (value: any, expectedType: string | Array<string>): boolean => expectedType.includes(typeOf(value));
|
2018-05-23 13:34:53 -07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extracts the type key and the pattern string from the string mapping into a tuple pair
|
2018-05-25 16:28:46 -07:00
|
|
|
* @param {IMetadataType} metadataType
|
|
|
|
|
* @return {[string , (string | Array<string>)]}
|
2018-05-23 13:34:53 -07:00
|
|
|
*/
|
2018-05-25 16:28:46 -07:00
|
|
|
const typePatternMap = (metadataType: IMetadataType): [string, string | Array<string>] => [
|
|
|
|
|
metadataType['@name'],
|
|
|
|
|
metadataType['@type']
|
|
|
|
|
];
|
2018-05-23 13:34:53 -07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a iteratee bound to an object that checks that a key matches the expected value in the typeMap
|
|
|
|
|
* @param {IObject<any>} object the object with keys to check
|
2018-05-25 16:28:46 -07:00
|
|
|
* @return {(metadataType: IMetadataType) => boolean}
|
2018-05-23 13:34:53 -07:00
|
|
|
*/
|
2018-05-25 16:28:46 -07:00
|
|
|
const keyValueHasMatch = (object: IObject<any>) => (metadataType: IMetadataType): boolean => {
|
|
|
|
|
const [name, type] = typePatternMap(metadataType);
|
|
|
|
|
const value = object[name];
|
|
|
|
|
const rootValueEquiv = object.hasOwnProperty(name) && valueEquiv(value, type);
|
|
|
|
|
const innerType = metadataType['@props'];
|
|
|
|
|
|
|
|
|
|
if (type.includes('object') && isObject(value)) {
|
2018-05-31 11:27:33 -07:00
|
|
|
// recurse on object properties
|
2018-05-25 16:28:46 -07:00
|
|
|
return rootValueEquiv && keysEquiv(value, innerType!);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (type.includes('array') && Array.isArray(value)) {
|
2018-06-13 15:38:13 -07:00
|
|
|
const { length } = value;
|
|
|
|
|
|
2018-05-25 16:28:46 -07:00
|
|
|
return (
|
2018-05-31 11:27:33 -07:00
|
|
|
// recursively reduce on array elements
|
2018-06-13 15:38:13 -07:00
|
|
|
// ensure the array contains at least on element
|
2018-05-25 16:28:46 -07:00
|
|
|
rootValueEquiv &&
|
2018-06-13 15:38:13 -07:00
|
|
|
length > 0 &&
|
2018-05-25 16:28:46 -07:00
|
|
|
arrayReduce((isEquiv: boolean, value: any) => isEquiv && keysEquiv(value, innerType!), rootValueEquiv)(value)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rootValueEquiv;
|
2018-05-23 13:34:53 -07:00
|
|
|
};
|
|
|
|
|
|
2018-06-13 15:12:23 -07:00
|
|
|
/**
|
|
|
|
|
* Ensures that the keys on the supplied object are equivalent to the names in the type definition list
|
|
|
|
|
* @param {IObject<any>} object
|
|
|
|
|
* @param {Array<IMetadataType>} typeMaps
|
|
|
|
|
* @return {boolean}
|
2018-06-17 03:51:10 -07:00
|
|
|
* @throws {Error} if object keys do not match type @names
|
2018-06-13 15:12:23 -07:00
|
|
|
*/
|
2018-06-17 03:51:10 -07:00
|
|
|
const keysMatchNames = (object: IObject<any>, typeMaps: Array<IMetadataType>): boolean => {
|
|
|
|
|
const objectKeys = Object.keys(object).sort();
|
|
|
|
|
const typeKeys = arrayMap((typeMap: IMetadataType) => typeMap['@name'])(typeMaps).sort();
|
|
|
|
|
const objectKeysSerialized = objectKeys.toString();
|
|
|
|
|
const typeKeysSerialized = typeKeys.toString();
|
|
|
|
|
const match = objectKeysSerialized === typeKeysSerialized;
|
|
|
|
|
|
|
|
|
|
if (!match) {
|
|
|
|
|
throw new Error(`Extra attributes found: ${difference(objectKeys, typeKeys).join(', ')}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return match;
|
|
|
|
|
};
|
2018-06-13 15:12:23 -07:00
|
|
|
|
2018-05-23 13:34:53 -07:00
|
|
|
/**
|
|
|
|
|
* Checks each key on an object matches the expected types in the typeMap
|
|
|
|
|
* @param {IObject<any>} object the object with keys to check
|
2018-05-25 16:28:46 -07:00
|
|
|
* @param {Array<IMetadataType>} typeMaps the colon delimited type string
|
2018-05-23 13:34:53 -07:00
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
2018-05-25 16:28:46 -07:00
|
|
|
const keysEquiv = (object: IObject<any>, typeMaps: Array<IMetadataType>): boolean =>
|
2018-06-13 15:12:23 -07:00
|
|
|
arrayEvery(keyValueHasMatch(object))(typeMaps) && keysMatchNames(object, typeMaps);
|
2018-05-23 13:34:53 -07:00
|
|
|
|
2018-06-13 15:12:23 -07:00
|
|
|
export default keysEquiv;
|
2018-05-23 13:34:53 -07:00
|
|
|
|
2018-06-13 15:12:23 -07:00
|
|
|
export { complianceMetadataTaxonomy, complianceEntitiesTaxonomy };
|