@eclipse-emfcloud/modelserver-client
Version:
Typescript rest client to interact with an EMF.cloud modelserver
209 lines (188 loc) • 7.19 kB
text/typescript
/********************************************************************************
* Copyright (c) 2021-2022 CS GROUP - France and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0, or the MIT License which is
* available at https://opensource.org/licenses/MIT.
*
* SPDX-License-Identifier: EPL-2.0 OR MIT
*******************************************************************************/
import { AnyObject, isArray, isNumber, isString } from '../utils/type-util';
/** The bit mask value for okay severity */
export const OK = 0x0;
/** The bit mask value for informational message severity */
export const INFO = 0x1;
/** The bit mask value for warning message severity */
export const WARNING = 0x2;
/** The bit mask value for error message severity */
export const ERROR = 0x4;
/** The bit mask value for canceled diagnosis severity */
export const CANCEL = 0x8;
/**
* Information about the result of a model validation.
* @see org.eclipse.emf.common.util.Diagnostic
*/
export interface Diagnostic {
/** indicator of the severity of the problem */
severity: number;
/** message describing the situation */
message: string;
/** unique identifier of the source */
source: string;
/** source-specific identity code */
code: number;
/** the relevant low-level exception */
exception?: Error | any;
/**
* The arbitrary associated list of data.
* The first element is typically the object that is the primary source of the problem;
* the second element is typically some object describing the problematic feature or aspect of the primary source,
* and the remaining elements are additional objects associated with or describing the problem.
*/
data: any[];
/** list of child Diagnostic */
children: Diagnostic[];
/** The id or URI fragment of the validated model element */
id: string;
}
export namespace Diagnostic {
/**
* Typeguard function to check wether the given object is of type {@link Diagnostic}.
* @param object the object to check
* @returns the given object as {@link Diagnostic} or `false`
*/
export function is(object: unknown): object is Diagnostic {
return (
AnyObject.is(object) &&
isNumber(object, 'severity') &&
isString(object, 'message') &&
isString(object, 'source') &&
isNumber(object, 'code') &&
isArray(object, 'data') &&
isArray(object, 'children') &&
isString(object, 'id')
);
}
/**
* Recompute the severity of a composed diagnostic by looking at its children.
* Contrary to the org.eclipse.emf.common.util.BasicDiagnostic.recomputeSeverity() implementation,
* we combine the severity values and do not just keep the highest number value.
* This means the severity number will keep trace of all non-OK severities found in children.
* If you need a simple severity value, just use getSeverityLabel method afterward.
* @param diagnostic diagnostic to analyze
* @returns the updated severity value
*/
export function recomputeSeverity(diagnostic: Diagnostic): number {
if (diagnostic.children.length > 0) {
// reset this diagnostic's severity
diagnostic.severity = OK;
// then use children to update with bit OR
diagnostic.children.forEach(child => {
const childSeverity = recomputeSeverity(child);
diagnostic.severity = diagnostic.severity | childSeverity;
});
}
// return updated severity
return diagnostic.severity;
}
/**
* Get the label severity value of the diagnostic using masks.
* @param diagnostic diagnostic to analyze
* @returns severity label which the mask matches the severity number
*/
export function getSeverityLabel(diagnostic: Diagnostic): 'OK' | 'INFO' | 'WARNING' | 'ERROR' | 'CANCEL' {
const severityCode = diagnostic.severity;
if ((severityCode & CANCEL) > 0) {
return 'CANCEL';
} else if ((severityCode & ERROR) > 0) {
return 'ERROR';
} else if ((severityCode & WARNING) > 0) {
return 'WARNING';
} else if ((severityCode & INFO) > 0) {
return 'INFO';
} else {
return 'OK';
}
}
/**
* Collect leaf diagnostics, which are children diagnostics without children.
* An OK diagnostic without children is not returned.
* @param diagnostic diagnostic to analyze
* @returns diagnostics without children (eventually including this one or empty)
*/
export function collectLeaves(diagnostic: Diagnostic): Diagnostic[] {
if (diagnostic.children.length > 0) {
const leavesByChild = diagnostic.children.map(collectLeaves);
return leavesByChild.reduce((accumulator, values) => accumulator.concat(values), []);
} else if (diagnostic.severity > 0) {
return [diagnostic];
} else {
return [];
}
}
/**
* Get an OK diagnostic.
*
* @returns an OK diagnostic
*/
export function ok(): Diagnostic {
return {
severity: OK,
code: 0,
message: 'OK',
source: '',
data: [],
children: [],
id: ''
};
}
/**
* Merge any number of `diagnostics` (even zero) into a single result.
* Any OK diagnostics are elided and the result may itself just be OK.
*
* @param diagnostics diagnostics to merge
* @returns the merged diagnostic
*/
export function merge(...diagnostics: Diagnostic[]): Diagnostic {
const nonOK = diagnostics.filter(d => d && d.severity > OK);
if (nonOK.length === 0) {
return ok();
}
if (nonOK.length === 1) {
return nonOK[0];
}
const max = worstOf(diagnostics);
return {
severity: max.severity,
message: `Diagnosis of ${nonOK.length} problems.`,
source: max.source,
code: max.code,
data: [],
children: nonOK,
id: '/'
};
}
/**
* Find the worst severity in a list of `diagnostics`.
*
* @param diagnostics an array of diagnostics, possibly empty
* @returns the first diagnostic of the great severity amongst the `diagnostics`
* or else an OK diagnostic if none
*/
export function worstOf(diagnostics: Diagnostic[]): Diagnostic {
if (!diagnostics || diagnostics.length === 0) {
return ok();
}
let result = diagnostics[0];
for (const d of diagnostics) {
if (d.severity > result.severity) {
result = d;
}
if (d.severity === CANCEL) {
break; // Cannot get worse than this
}
}
return result;
}
}