fix oauth code
[ccsdk/features.git] / sdnr / wt-odlux / odlux / apps / configurationApp / src / utilities / viewEngineHelper.ts
1 import { storeService } from '../../../../framework/src/services/storeService';
2 import { WhenAST, WhenTokenType } from '../yang/whenParser';
3
4 import {
5   ViewSpecification,
6   ViewElement,
7   isViewElementReference,
8   isViewElementList,
9   isViewElementObjectOrList,
10   isViewElementRpc,
11   isViewElementChoice,
12   ViewElementChoiceCase,
13 } from '../models/uiModels';
14
15 import { Module } from '../models/yang';
16
17 import { restService } from '../services/restServices';
18
19 export type HttpResult = {
20   status: number;
21   message?: string | undefined;
22   data: {
23     [key: string]: any;
24   } | null | undefined;
25 };
26
27 export const checkResponseCode = (restResult: HttpResult) =>{
28   //403 gets handled by the framework from now on
29   return restResult.status !== 403 && ( restResult.status < 200 || restResult.status > 299);
30 };
31
32 export const resolveVPath = (current: string, vPath: string): string => {
33   if (vPath.startsWith('/')) {
34     return vPath;
35   }
36   const parts = current.split('/');
37   const vPathParts = vPath.split('/');
38   for (const part of vPathParts) {
39     if (part === '.') {
40       continue;
41     } else if (part === '..') {
42       parts.pop();
43     } else {
44       parts.push(part);
45     }
46   }
47   return parts.join('/');
48 };
49
50 export const splitVPath = (vPath: string, vPathParser : RegExp): [string, string?][] => {
51   const pathParts: [string, string?][] = [];
52   let partMatch: RegExpExecArray | null;
53   if (vPath) do {
54     partMatch = vPathParser.exec(vPath);
55     if (partMatch) {
56       pathParts.push([partMatch[1], partMatch[2] || undefined]);
57     }
58   } while (partMatch);
59   return pathParts;
60 };
61
62 const derivedFrom = (vPath: string, when: WhenAST, viewData: any, includeSelf = false) => {
63   if (when.args?.length !== 2) {
64     throw new Error('derived-from or derived-from-or-self requires 2 arguments.');
65   }
66   const [arg1, arg2] = when.args;
67   if (arg1.type !== WhenTokenType.IDENTIFIER || arg2.type !== WhenTokenType.STRING) {
68     throw new Error('derived-from or derived-from-or-self requires first argument IDENTIFIER and second argument STRING.');
69   }
70
71   if (!storeService.applicationStore) {
72     throw new Error('storeService.applicationStore is not defined.');
73   }
74
75   const pathParts = splitVPath(arg1.value as string || '', /(?:(?:([^\/\:]+):)?([^\/]+))/g); 
76   const referenceValueParts = /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(arg2.value as string || ''); 
77
78   if (!pathParts || !referenceValueParts || pathParts.length === 0 || referenceValueParts.length === 0) {
79     throw new Error('derived-from or derived-from-or-self requires first argument PATH and second argument IDENTITY.');
80   }
81
82   if (pathParts[0][1]?.startsWith('..') || pathParts[0][1]?.startsWith('/')) {
83     throw new Error('derived-from or derived-from-or-self currently only supports relative paths.');
84   }
85
86   const { configuration: { deviceDescription: { modules } } } = storeService.applicationStore.state;
87   const dataValue = pathParts.reduce((acc, [ns, prop]) => {
88     if (prop === '.') {
89       return acc;
90     } 
91     if (acc && prop) {
92       const moduleName = ns && (Object.values(modules).find((m: Module) => m.prefix === ns) || Object.values(modules).find((m: Module) => m.name === ns))?.name;
93       return (moduleName) ? acc[`${moduleName}:${prop}`] ||  acc[prop] : acc[prop];
94     }
95     return undefined;
96   }, viewData);
97
98   let dataValueParts = dataValue && /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(dataValue);
99   if (!dataValueParts || dataValueParts.length < 2) {
100     throw new Error(`derived-from or derived-from-or-self value referenced by first argument [${arg1.value}] not found.`);
101   }
102   let [, dataValueNs, dataValueProp] = dataValueParts;
103   let dataValueModule: Module = dataValueNs && (Object.values(modules).find((m: Module) => m.name === dataValueNs));
104   let dataValueIdentity = dataValueModule && dataValueModule.identities && (Object.values(dataValueModule.identities).find((i) => i.label === dataValueProp));
105
106   if (!dataValueIdentity) {
107     throw new Error(`derived-from or derived-from-or-self identity [${dataValue}] referenced by first argument [${arg1.value}] not found.`);
108   }
109
110   const [, referenceValueNs, referenceValueProp] = referenceValueParts;
111   const referenceValueModule = referenceValueNs && (Object.values(modules).find((m: Module) => m.prefix === referenceValueNs));
112   const referenceValueIdentity = referenceValueModule && referenceValueModule.identities && (Object.values(referenceValueModule.identities).find((i) => i.label === referenceValueProp));
113
114   if (!referenceValueIdentity) {
115     throw new Error(`derived-from or derived-from-or-self identity [${arg2.value}] referenced by second argument not found.`);
116   }
117
118   let result = includeSelf && (referenceValueIdentity === dataValueIdentity);
119   while (dataValueIdentity && dataValueIdentity.base && !result) {
120     dataValueParts = dataValue && /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(dataValueIdentity.base);
121     const [, innerDataValueNs, innerDataValueProp] = dataValueParts;
122     dataValueModule = innerDataValueNs && (Object.values(modules).find((m: Module) => m.prefix === innerDataValueNs)) || dataValueModule;
123     dataValueIdentity = dataValueModule && dataValueModule.identities && (Object.values(dataValueModule.identities).find((i) => i.label === innerDataValueProp)) ;
124     result = (referenceValueIdentity === dataValueIdentity);
125   }
126
127   return result;
128 };
129
130 const evaluateWhen = async (vPath: string, when: WhenAST, viewData: any): Promise<boolean> => {
131   switch (when.type) {
132     case WhenTokenType.FUNCTION:
133       switch (when.name) {
134         case 'derived-from-or-self':
135           return derivedFrom(vPath, when, viewData, true);
136         case 'derived-from':
137           return derivedFrom(vPath, when, viewData, false);
138         default:
139           throw new Error(`Unknown function ${when.name}`);
140       }
141     case WhenTokenType.AND:
142       return !when.left || !when.right || (await evaluateWhen(vPath, when.left, viewData) && await evaluateWhen(vPath, when.right, viewData));
143     case WhenTokenType.OR:
144       return !when.left || !when.right || (await evaluateWhen(vPath, when.left, viewData) || await evaluateWhen(vPath, when.right, viewData));
145     case WhenTokenType.NOT:
146       return !when.right || ! await evaluateWhen(vPath, when.right, viewData);
147     case WhenTokenType.EXPRESSION:
148       return !(when.value && typeof when.value !== 'string') || await evaluateWhen(vPath, when.value, viewData);
149   }   
150   return true;
151 };
152
153 export const getReferencedDataList = async (refPath: string, dataPath: string, modules: { [name: string]: Module }, views: ViewSpecification[]) => {
154   const pathParts = splitVPath(refPath, /(?:(?:([^\/\:]+):)?([^\/]+))/g);  // 1 = opt: namespace / 2 = property
155   const defaultNS = pathParts[0][0];
156   let referencedModule = modules[defaultNS];
157
158   let dataMember: string;
159   let view: ViewSpecification;
160   let currentNS: string | null = null;
161   let dataUrls = [dataPath];
162   let data: any;
163
164   for (let i = 0; i < pathParts.length; ++i) {
165     const [pathPartNS, pathPart] = pathParts[i];
166     const namespace = pathPartNS != null ? (currentNS = pathPartNS) : currentNS;
167
168     const viewElement = i === 0
169       ? views[0].elements[`${referencedModule.name}:${pathPart}`]
170       : view!.elements[`${pathPart}`] || view!.elements[`${namespace}:${pathPart}`];
171
172     if (!viewElement) throw new Error(`Could not find ${pathPart} in ${refPath}`);
173     if (i < pathParts.length - 1) {
174       if (!isViewElementObjectOrList(viewElement)) {
175         throw Error(`Module: [${referencedModule.name}].[${viewElement.label}]. View element is not list or object.`);
176       }
177       view = views[+viewElement.viewId];
178       const resultingDataUrls : string[] = [];
179       if (isViewElementList(viewElement)) {
180         for (let j = 0; j < dataUrls.length; ++j) {
181           const dataUrl = dataUrls[j];
182           const restResult = (await restService.getConfigData(dataUrl));
183           if (restResult.data == null || checkResponseCode(restResult)) {
184             const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || '';
185             throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
186           }
187
188           let dataRaw = restResult.data[`${defaultNS}:${dataMember!}`];
189           if (dataRaw === undefined) {
190             dataRaw = restResult.data[dataMember!];
191           }
192           dataRaw = dataRaw instanceof Array
193             ? dataRaw[0]
194             : dataRaw;
195
196           data = dataRaw && dataRaw[viewElement.label] || [];
197           const keys: string[] = data.map((entry: { [key: string]: any } )=> entry[viewElement.key!]);
198           resultingDataUrls.push(...keys.map(key => `${dataUrl}/${viewElement.label.replace(/\//ig, '%2F')}=${key.replace(/\//ig, '%2F')}`));
199         }
200         dataMember = viewElement.label;
201       } else {
202         // just a member, not a list
203         const pathSegment = (i === 0
204           ? `/${referencedModule.name}:${viewElement.label.replace(/\//ig, '%2F')}`
205           : `/${viewElement.label.replace(/\//ig, '%2F')}`);
206         resultingDataUrls.push(...dataUrls.map(dataUrl => dataUrl + pathSegment));
207         dataMember = viewElement.label;
208       }
209       dataUrls = resultingDataUrls;
210     } else {
211       data = [];
212       for (let j = 0; j < dataUrls.length; ++j) {
213         const dataUrl = dataUrls[j];
214         const restResult = (await restService.getConfigData(dataUrl));
215         if (restResult.data == null || checkResponseCode(restResult)) {
216           const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || '';
217           throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
218         }
219         let dataRaw = restResult.data[`${defaultNS}:${dataMember!}`];
220         if (dataRaw === undefined) {
221           dataRaw = restResult.data[dataMember!];
222         }
223         dataRaw = dataRaw instanceof Array
224           ? dataRaw[0]
225           : dataRaw;
226         data.push(dataRaw);
227       }
228       // BUG UUID ist nicht in den elements enthalten !!!!!!
229       const key = viewElement && viewElement.label || pathPart;
230       return {
231         view: view!,
232         data: data,
233         key: key,
234       };
235     }
236   }
237   return null;
238 };
239
240 export const resolveViewDescription = (defaultNS: string | null, vPath: string, view: ViewSpecification): ViewSpecification =>{
241
242   // resolve all references.
243   view = { ...view };
244   view.elements = Object.keys(view.elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => {
245     const resolveHistory : ViewElement[] = [];  
246     let elm = view.elements[cur];
247     const key = defaultNS && cur.replace(new RegExp(`^${defaultNS}:`, 'i'), '') || cur;
248     while (isViewElementReference(elm)) {
249       const result = (elm.ref(vPath));  
250       if (result) {
251         const [referencedElement, referencedPath] = result;
252         if (resolveHistory.some(hist => hist === referencedElement)) {
253           console.error(`Circle reference found at: ${vPath}`, resolveHistory);
254           break;
255         }
256         elm = referencedElement;
257         vPath = referencedPath;
258         resolveHistory.push(elm);
259       }
260     } 
261     
262     acc[key] = { ...elm, id: key };
263     
264     return acc;
265   }, {});
266   return view;
267 };
268
269 export const flattenViewElements = (defaultNS: string | null, parentPath: string, elements: { [name: string]: ViewElement }, views: ViewSpecification[], currentPath: string ): { [name: string]: ViewElement } => {
270   if (!elements) return {};
271   return Object.keys(elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => {
272     const elm = elements[cur];
273
274     // remove the default namespace, and only the default namespace, sine it seems that this is also not in the restconf response
275     const elmKey = defaultNS && elm.id.replace(new RegExp(`^${defaultNS}:`, 'i'), '') || elm.id;
276     const key = parentPath ? `${parentPath}.${elmKey}` : elmKey;
277
278     if (isViewElementRpc(elm)) {
279       console.warn(`Flatten of RFC not supported ! [${currentPath}][${elm.label}]`);
280       return acc;
281     } else if (isViewElementObjectOrList(elm)) {
282       const view = views[+elm.viewId];
283       const inner = view && flattenViewElements(defaultNS, key, view.elements, views, `${currentPath}/${view.name}`);
284       if (inner) {
285         Object.keys(inner).forEach(k => (acc[k] = inner[k]));
286       }
287     } else if (isViewElementChoice(elm)) {
288       acc[key] = {
289         ...elm,
290         id: key,
291         cases: Object.keys(elm.cases).reduce<{ [name: string]: ViewElementChoiceCase }>((accCases, curCases) => {
292           const caseElement = elm.cases[curCases];
293           accCases[curCases] = {
294             ...caseElement,
295             // Hint: do not use key it contains elmKey, which shell be omitted for cases.
296             elements: flattenViewElements(defaultNS, /*key*/ parentPath, caseElement.elements, views, `${currentPath}/${elm.label}`),
297           };
298           return accCases;
299         }, {}),
300       };
301     } else {
302       acc[key] = {
303         ...elm,
304         id: key,
305       };
306     }
307     return acc;
308   }, {});
309 };
310
311 export const filterViewElements = async (vPath: string, viewData: any, viewSpecification: ViewSpecification) => {
312   // filter elements of viewSpecification by evaluating when property
313   return Object.keys(viewSpecification.elements).reduce(async (accPromise, cur) => {
314     const acc = await accPromise;
315     const elm = viewSpecification.elements[cur];
316     if (!elm.when || await evaluateWhen(vPath || '', elm.when, viewData).catch((ex) => {
317       console.warn(`Error evaluating when clause at: ${viewSpecification.name} for element: ${cur}`, ex);
318       return true;
319     })) {
320       acc.elements[cur] = elm;
321     }
322     return acc;
323   }, Promise.resolve({ ...viewSpecification, elements: {} as { [key: string]: ViewElement } }));
324 };