aboutsummaryrefslogtreecommitdiffstats
path: root/sdnr/wt/odlux/apps/configurationApp/src/utilities
diff options
context:
space:
mode:
Diffstat (limited to 'sdnr/wt/odlux/apps/configurationApp/src/utilities')
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/utilities/verifyer.ts261
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts324
2 files changed, 585 insertions, 0 deletions
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/utilities/verifyer.ts b/sdnr/wt/odlux/apps/configurationApp/src/utilities/verifyer.ts
new file mode 100644
index 000000000..9dd12031f
--- /dev/null
+++ b/sdnr/wt/odlux/apps/configurationApp/src/utilities/verifyer.ts
@@ -0,0 +1,261 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import { YangRange, Operator, ViewElementNumber, ViewElementString, isViewElementNumber, isViewElementString } from '../models/uiModels';
+
+export type validated = { isValid: boolean; error?: string };
+
+export type validatedRange = { isValid: boolean; error?: string };
+
+const rangeErrorStartNumber = 'The entered number must be';
+const rangeErrorInnerMinTextNumber = 'greater or equals than';
+const rangeErrorInnerMaxTextNumber = 'less or equals than';
+const rangeErrorEndTextNumber = '.';
+
+const rangeErrorStartString = 'The entered text must have';
+const rangeErrorInnerMinTextString = 'no more than';
+const rangeErrorInnerMaxTextString = 'less than';
+const rangeErrorEndTextString = ' characters.';
+
+let errorMessageStart = '';
+let errorMessageMiddleMinPart = '';
+let errorMessageMiddleMaxPart = '';
+let errorMessageEnd = '';
+
+const isYangRange = (val: YangRange | Operator<YangRange>): val is YangRange => (val as YangRange).min !== undefined;
+
+const isYangOperator = (val: YangRange | Operator<YangRange>): val is Operator<YangRange> => (val as Operator<YangRange>).operation !== undefined;
+
+const isRegExp = (val: RegExp | Operator<RegExp>): val is RegExp => (val as RegExp).source !== undefined;
+
+const isRegExpOperator = (val: RegExp | Operator<RegExp>): val is Operator<RegExp> => (val as Operator<RegExp>).operation !== undefined;
+
+const getRangeErrorMessagesRecursively = (value: Operator<YangRange>, data: number): string[] => {
+ let currentIteration: string[] = [];
+
+ // iterate over all elements
+ for (let i = 0; i < value.arguments.length; i++) {
+ const element = value.arguments[i];
+
+ let min = undefined;
+ let max = undefined;
+
+ let isNumberCorrect = false;
+
+ if (isYangRange(element)) {
+
+ //check found min values
+ if (!isNaN(element.min)) {
+ if (data < element.min) {
+ min = element.min;
+ } else {
+ isNumberCorrect = true;
+ }
+ }
+
+ // check found max values
+ if (!isNaN(element.max)) {
+ if (data > element.max) {
+ max = element.max;
+ } else {
+ isNumberCorrect = true;
+ }
+ }
+
+ // construct error messages
+ if (min != undefined) {
+ currentIteration.push(`${value.operation.toLocaleLowerCase()} ${errorMessageMiddleMinPart} ${min}`);
+ } else if (max != undefined) {
+ currentIteration.push(`${value.operation.toLocaleLowerCase()} ${errorMessageMiddleMaxPart} ${max}`);
+
+ }
+
+ } else if (isYangOperator(element)) {
+
+ //get error_message from expression
+ const result = getRangeErrorMessagesRecursively(element, data);
+ if (result.length === 0) {
+ isNumberCorrect = true;
+ }
+ currentIteration = currentIteration.concat(result);
+ }
+
+ // if its an OR operation, the number has been checked and min/max are empty (thus not violated)
+ // delete everything found (because at least one found is correct, therefore all are correct) and break from loop
+ if (min === undefined && max === undefined && isNumberCorrect && value.operation === 'OR') {
+
+ currentIteration.splice(0, currentIteration.length);
+ break;
+ }
+ }
+
+ return currentIteration;
+};
+
+const createStartMessage = (element: string) => {
+ //remove leading or or and from text
+ if (element.startsWith('and')) {
+ element = element.replace('and', '');
+ } else if (element.startsWith('or')) {
+ element = element.replace('or', '');
+ }
+ return `${errorMessageStart} ${element}`;
+};
+
+const getRangeErrorMessages = (value: Operator<YangRange>, data: number): string => {
+
+ const currentIteration = getRangeErrorMessagesRecursively(value, data);
+
+ // build complete error message from found parts
+ let errorMessage = '';
+ if (currentIteration.length > 1) {
+
+ currentIteration.forEach((element, index) => {
+ if (index === 0) {
+ errorMessage = createStartMessage(element);
+ } else if (index === currentIteration.length - 1) {
+ errorMessage += ` ${element}${errorMessageEnd}`;
+ } else {
+ errorMessage += `, ${element}`;
+ }
+ });
+ } else if (currentIteration.length == 1) {
+ errorMessage = `${createStartMessage(currentIteration[0])}${errorMessageEnd}`;
+ }
+
+ return errorMessage;
+};
+
+export const checkRange = (element: ViewElementNumber | ViewElementString, data: number): string => {
+ const number = data;
+
+ let expression = undefined;
+
+ if (isViewElementString(element)) {
+ expression = element.length;
+
+ errorMessageStart = rangeErrorStartString;
+ errorMessageMiddleMaxPart = rangeErrorInnerMaxTextString;
+ errorMessageMiddleMinPart = rangeErrorInnerMinTextString;
+ errorMessageEnd = rangeErrorEndTextString;
+
+ } else if (isViewElementNumber(element)) {
+ expression = element.range;
+
+ errorMessageStart = rangeErrorStartNumber;
+ errorMessageMiddleMaxPart = rangeErrorInnerMaxTextNumber;
+ errorMessageMiddleMinPart = rangeErrorInnerMinTextNumber;
+ errorMessageEnd = rangeErrorEndTextNumber;
+ }
+
+ if (expression) {
+ if (isYangOperator(expression)) {
+
+ const errorMessage = getRangeErrorMessages(expression, data);
+ return errorMessage;
+
+ } else
+ if (isYangRange(expression)) {
+
+ if (!isNaN(expression.min)) {
+ if (number < expression.min) {
+ return `${errorMessageStart} ${errorMessageMiddleMinPart} ${expression.min}${errorMessageEnd}`;
+ }
+ }
+
+ if (!isNaN(expression.max)) {
+ if (number > expression.max) {
+ return `${errorMessageStart} ${errorMessageMiddleMaxPart} ${expression.max}${errorMessageEnd}`;
+ }
+ }
+ }
+ }
+
+ return '';
+};
+
+const getRegexRecursively = (value: Operator<RegExp>, data: string): boolean[] => {
+ let currentItteration: boolean[] = [];
+ for (let i = 0; i < value.arguments.length; i++) {
+ const element = value.arguments[i];
+ if (isRegExp(element)) {
+ // if regex is found, add it to list
+ currentItteration.push(element.test(data));
+ } else if (isRegExpOperator(element)) {
+ //if RegexExpression is found, try to get regex from it
+ currentItteration = currentItteration.concat(getRegexRecursively(element, data));
+ }
+ }
+
+ if (value.operation === 'OR') {
+ // if one is true, all are true, all found items can be discarded
+ let result = currentItteration.find(element => element);
+ if (result) {
+ return [];
+ }
+ }
+ return currentItteration;
+};
+
+const isPatternValid = (value: Operator<RegExp>, data: string): boolean => {
+ // get all regex
+ const result = getRegexRecursively(value, data);
+
+ if (value.operation === 'AND') {
+ // if AND operation is executed...
+ // no element can be false
+ const check = result.find(element => element !== true);
+ if (check)
+ return false;
+ else
+ return true;
+ } else {
+ // if OR operation is executed...
+ // ... just one element must be true
+ const check = result.find(element => element === true);
+ if (check)
+ return true;
+ else
+ return false;
+
+ }
+};
+
+export const checkPattern = (expression: RegExp | Operator<RegExp> | undefined, data: string): validated => {
+
+ if (expression) {
+ if (isRegExp(expression)) {
+ const isValid = expression.test(data);
+ if (!isValid)
+ return { isValid: isValid, error: 'The input is in a wrong format.' };
+
+ } else if (isRegExpOperator(expression)) {
+ const result = isPatternValid(expression, data);
+
+ if (!result) {
+ return { isValid: false, error: 'The input is in a wrong format.' };
+ }
+ }
+ }
+
+ return { isValid: true };
+};
+
+
+
+
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts b/sdnr/wt/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts
new file mode 100644
index 000000000..ad34c83b9
--- /dev/null
+++ b/sdnr/wt/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts
@@ -0,0 +1,324 @@
+import { storeService } from '../../../../framework/src/services/storeService';
+import { WhenAST, WhenTokenType } from '../yang/whenParser';
+
+import {
+ ViewSpecification,
+ ViewElement,
+ isViewElementReference,
+ isViewElementList,
+ isViewElementObjectOrList,
+ isViewElementRpc,
+ isViewElementChoice,
+ ViewElementChoiceCase,
+} from '../models/uiModels';
+
+import { Module } from '../models/yang';
+
+import { restService } from '../services/restServices';
+
+export type HttpResult = {
+ status: number;
+ message?: string | undefined;
+ data: {
+ [key: string]: any;
+ } | null | undefined;
+};
+
+export const checkResponseCode = (restResult: HttpResult) =>{
+ //403 gets handled by the framework from now on
+ return restResult.status !== 403 && ( restResult.status < 200 || restResult.status > 299);
+};
+
+export const resolveVPath = (current: string, vPath: string): string => {
+ if (vPath.startsWith('/')) {
+ return vPath;
+ }
+ const parts = current.split('/');
+ const vPathParts = vPath.split('/');
+ for (const part of vPathParts) {
+ if (part === '.') {
+ continue;
+ } else if (part === '..') {
+ parts.pop();
+ } else {
+ parts.push(part);
+ }
+ }
+ return parts.join('/');
+};
+
+export const splitVPath = (vPath: string, vPathParser : RegExp): [string, string?][] => {
+ const pathParts: [string, string?][] = [];
+ let partMatch: RegExpExecArray | null;
+ if (vPath) do {
+ partMatch = vPathParser.exec(vPath);
+ if (partMatch) {
+ pathParts.push([partMatch[1], partMatch[2] || undefined]);
+ }
+ } while (partMatch);
+ return pathParts;
+};
+
+const derivedFrom = (vPath: string, when: WhenAST, viewData: any, includeSelf = false) => {
+ if (when.args?.length !== 2) {
+ throw new Error('derived-from or derived-from-or-self requires 2 arguments.');
+ }
+ const [arg1, arg2] = when.args;
+ if (arg1.type !== WhenTokenType.IDENTIFIER || arg2.type !== WhenTokenType.STRING) {
+ throw new Error('derived-from or derived-from-or-self requires first argument IDENTIFIER and second argument STRING.');
+ }
+
+ if (!storeService.applicationStore) {
+ throw new Error('storeService.applicationStore is not defined.');
+ }
+
+ const pathParts = splitVPath(arg1.value as string || '', /(?:(?:([^\/\:]+):)?([^\/]+))/g);
+ const referenceValueParts = /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(arg2.value as string || '');
+
+ if (!pathParts || !referenceValueParts || pathParts.length === 0 || referenceValueParts.length === 0) {
+ throw new Error('derived-from or derived-from-or-self requires first argument PATH and second argument IDENTITY.');
+ }
+
+ if (pathParts[0][1]?.startsWith('..') || pathParts[0][1]?.startsWith('/')) {
+ throw new Error('derived-from or derived-from-or-self currently only supports relative paths.');
+ }
+
+ const { configuration: { deviceDescription: { modules } } } = storeService.applicationStore.state;
+ const dataValue = pathParts.reduce((acc, [ns, prop]) => {
+ if (prop === '.') {
+ return acc;
+ }
+ if (acc && prop) {
+ const moduleName = ns && (Object.values(modules).find((m: Module) => m.prefix === ns) || Object.values(modules).find((m: Module) => m.name === ns))?.name;
+ return (moduleName) ? acc[`${moduleName}:${prop}`] || acc[prop] : acc[prop];
+ }
+ return undefined;
+ }, viewData);
+
+ let dataValueParts = dataValue && /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(dataValue);
+ if (!dataValueParts || dataValueParts.length < 2) {
+ throw new Error(`derived-from or derived-from-or-self value referenced by first argument [${arg1.value}] not found.`);
+ }
+ let [, dataValueNs, dataValueProp] = dataValueParts;
+ let dataValueModule: Module = dataValueNs && (Object.values(modules).find((m: Module) => m.name === dataValueNs));
+ let dataValueIdentity = dataValueModule && dataValueModule.identities && (Object.values(dataValueModule.identities).find((i) => i.label === dataValueProp));
+
+ if (!dataValueIdentity) {
+ throw new Error(`derived-from or derived-from-or-self identity [${dataValue}] referenced by first argument [${arg1.value}] not found.`);
+ }
+
+ const [, referenceValueNs, referenceValueProp] = referenceValueParts;
+ const referenceValueModule = referenceValueNs && (Object.values(modules).find((m: Module) => m.prefix === referenceValueNs));
+ const referenceValueIdentity = referenceValueModule && referenceValueModule.identities && (Object.values(referenceValueModule.identities).find((i) => i.label === referenceValueProp));
+
+ if (!referenceValueIdentity) {
+ throw new Error(`derived-from or derived-from-or-self identity [${arg2.value}] referenced by second argument not found.`);
+ }
+
+ let result = includeSelf && (referenceValueIdentity === dataValueIdentity);
+ while (dataValueIdentity && dataValueIdentity.base && !result) {
+ dataValueParts = dataValue && /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(dataValueIdentity.base);
+ const [, innerDataValueNs, innerDataValueProp] = dataValueParts;
+ dataValueModule = innerDataValueNs && (Object.values(modules).find((m: Module) => m.prefix === innerDataValueNs)) || dataValueModule;
+ dataValueIdentity = dataValueModule && dataValueModule.identities && (Object.values(dataValueModule.identities).find((i) => i.label === innerDataValueProp)) ;
+ result = (referenceValueIdentity === dataValueIdentity);
+ }
+
+ return result;
+};
+
+const evaluateWhen = async (vPath: string, when: WhenAST, viewData: any): Promise<boolean> => {
+ switch (when.type) {
+ case WhenTokenType.FUNCTION:
+ switch (when.name) {
+ case 'derived-from-or-self':
+ return derivedFrom(vPath, when, viewData, true);
+ case 'derived-from':
+ return derivedFrom(vPath, when, viewData, false);
+ default:
+ throw new Error(`Unknown function ${when.name}`);
+ }
+ case WhenTokenType.AND:
+ return !when.left || !when.right || (await evaluateWhen(vPath, when.left, viewData) && await evaluateWhen(vPath, when.right, viewData));
+ case WhenTokenType.OR:
+ return !when.left || !when.right || (await evaluateWhen(vPath, when.left, viewData) || await evaluateWhen(vPath, when.right, viewData));
+ case WhenTokenType.NOT:
+ return !when.right || ! await evaluateWhen(vPath, when.right, viewData);
+ case WhenTokenType.EXPRESSION:
+ return !(when.value && typeof when.value !== 'string') || await evaluateWhen(vPath, when.value, viewData);
+ }
+ return true;
+};
+
+export const getReferencedDataList = async (refPath: string, dataPath: string, modules: { [name: string]: Module }, views: ViewSpecification[]) => {
+ const pathParts = splitVPath(refPath, /(?:(?:([^\/\:]+):)?([^\/]+))/g); // 1 = opt: namespace / 2 = property
+ const defaultNS = pathParts[0][0];
+ let referencedModule = modules[defaultNS];
+
+ let dataMember: string;
+ let view: ViewSpecification;
+ let currentNS: string | null = null;
+ let dataUrls = [dataPath];
+ let data: any;
+
+ for (let i = 0; i < pathParts.length; ++i) {
+ const [pathPartNS, pathPart] = pathParts[i];
+ const namespace = pathPartNS != null ? (currentNS = pathPartNS) : currentNS;
+
+ const viewElement = i === 0
+ ? views[0].elements[`${referencedModule.name}:${pathPart}`]
+ : view!.elements[`${pathPart}`] || view!.elements[`${namespace}:${pathPart}`];
+
+ if (!viewElement) throw new Error(`Could not find ${pathPart} in ${refPath}`);
+ if (i < pathParts.length - 1) {
+ if (!isViewElementObjectOrList(viewElement)) {
+ throw Error(`Module: [${referencedModule.name}].[${viewElement.label}]. View element is not list or object.`);
+ }
+ view = views[+viewElement.viewId];
+ const resultingDataUrls : string[] = [];
+ if (isViewElementList(viewElement)) {
+ for (let j = 0; j < dataUrls.length; ++j) {
+ const dataUrl = dataUrls[j];
+ const restResult = (await restService.getConfigData(dataUrl));
+ if (restResult.data == null || checkResponseCode(restResult)) {
+ const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || '';
+ throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
+ }
+
+ let dataRaw = restResult.data[`${defaultNS}:${dataMember!}`];
+ if (dataRaw === undefined) {
+ dataRaw = restResult.data[dataMember!];
+ }
+ dataRaw = dataRaw instanceof Array
+ ? dataRaw[0]
+ : dataRaw;
+
+ data = dataRaw && dataRaw[viewElement.label] || [];
+ const keys: string[] = data.map((entry: { [key: string]: any } )=> entry[viewElement.key!]);
+ resultingDataUrls.push(...keys.map(key => `${dataUrl}/${viewElement.label.replace(/\//ig, '%2F')}=${key.replace(/\//ig, '%2F')}`));
+ }
+ dataMember = viewElement.label;
+ } else {
+ // just a member, not a list
+ const pathSegment = (i === 0
+ ? `/${referencedModule.name}:${viewElement.label.replace(/\//ig, '%2F')}`
+ : `/${viewElement.label.replace(/\//ig, '%2F')}`);
+ resultingDataUrls.push(...dataUrls.map(dataUrl => dataUrl + pathSegment));
+ dataMember = viewElement.label;
+ }
+ dataUrls = resultingDataUrls;
+ } else {
+ data = [];
+ for (let j = 0; j < dataUrls.length; ++j) {
+ const dataUrl = dataUrls[j];
+ const restResult = (await restService.getConfigData(dataUrl));
+ if (restResult.data == null || checkResponseCode(restResult)) {
+ const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || '';
+ throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
+ }
+ let dataRaw = restResult.data[`${defaultNS}:${dataMember!}`];
+ if (dataRaw === undefined) {
+ dataRaw = restResult.data[dataMember!];
+ }
+ dataRaw = dataRaw instanceof Array
+ ? dataRaw[0]
+ : dataRaw;
+ data.push(dataRaw);
+ }
+ // BUG UUID ist nicht in den elements enthalten !!!!!!
+ const key = viewElement && viewElement.label || pathPart;
+ return {
+ view: view!,
+ data: data,
+ key: key,
+ };
+ }
+ }
+ return null;
+};
+
+export const resolveViewDescription = (defaultNS: string | null, vPath: string, view: ViewSpecification): ViewSpecification =>{
+
+ // resolve all references.
+ view = { ...view };
+ view.elements = Object.keys(view.elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => {
+ const resolveHistory : ViewElement[] = [];
+ let elm = view.elements[cur];
+ const key = defaultNS && cur.replace(new RegExp(`^${defaultNS}:`, 'i'), '') || cur;
+ while (isViewElementReference(elm)) {
+ const result = (elm.ref(vPath));
+ if (result) {
+ const [referencedElement, referencedPath] = result;
+ if (resolveHistory.some(hist => hist === referencedElement)) {
+ console.error(`Circle reference found at: ${vPath}`, resolveHistory);
+ break;
+ }
+ elm = referencedElement;
+ vPath = referencedPath;
+ resolveHistory.push(elm);
+ }
+ }
+
+ acc[key] = { ...elm, id: key };
+
+ return acc;
+ }, {});
+ return view;
+};
+
+export const flattenViewElements = (defaultNS: string | null, parentPath: string, elements: { [name: string]: ViewElement }, views: ViewSpecification[], currentPath: string ): { [name: string]: ViewElement } => {
+ if (!elements) return {};
+ return Object.keys(elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => {
+ const elm = elements[cur];
+
+ // remove the default namespace, and only the default namespace, sine it seems that this is also not in the restconf response
+ const elmKey = defaultNS && elm.id.replace(new RegExp(`^${defaultNS}:`, 'i'), '') || elm.id;
+ const key = parentPath ? `${parentPath}.${elmKey}` : elmKey;
+
+ if (isViewElementRpc(elm)) {
+ console.warn(`Flatten of RFC not supported ! [${currentPath}][${elm.label}]`);
+ return acc;
+ } else if (isViewElementObjectOrList(elm)) {
+ const view = views[+elm.viewId];
+ const inner = view && flattenViewElements(defaultNS, key, view.elements, views, `${currentPath}/${view.name}`);
+ if (inner) {
+ Object.keys(inner).forEach(k => (acc[k] = inner[k]));
+ }
+ } else if (isViewElementChoice(elm)) {
+ acc[key] = {
+ ...elm,
+ id: key,
+ cases: Object.keys(elm.cases).reduce<{ [name: string]: ViewElementChoiceCase }>((accCases, curCases) => {
+ const caseElement = elm.cases[curCases];
+ accCases[curCases] = {
+ ...caseElement,
+ // Hint: do not use key it contains elmKey, which shell be omitted for cases.
+ elements: flattenViewElements(defaultNS, /*key*/ parentPath, caseElement.elements, views, `${currentPath}/${elm.label}`),
+ };
+ return accCases;
+ }, {}),
+ };
+ } else {
+ acc[key] = {
+ ...elm,
+ id: key,
+ };
+ }
+ return acc;
+ }, {});
+};
+
+export const filterViewElements = async (vPath: string, viewData: any, viewSpecification: ViewSpecification) => {
+ // filter elements of viewSpecification by evaluating when property
+ return Object.keys(viewSpecification.elements).reduce(async (accPromise, cur) => {
+ const acc = await accPromise;
+ const elm = viewSpecification.elements[cur];
+ if (!elm.when || await evaluateWhen(vPath || '', elm.when, viewData).catch((ex) => {
+ console.warn(`Error evaluating when clause at: ${viewSpecification.name} for element: ${cur}`, ex);
+ return true;
+ })) {
+ acc.elements[cur] = elm;
+ }
+ return acc;
+ }, Promise.resolve({ ...viewSpecification, elements: {} as { [key: string]: ViewElement } }));
+}; \ No newline at end of file